[New PowerToy] Add Screen Ruler module for measuring screen contents (#19701)
* [MeasureTool] initial commit * [chore] clean up needless WindowsTargetPlatformVersion overrides from projects * [MeasureTool] initial implementation * Fix build errors * Update vsconfig for needed Windows 10 SDK versions * fix spellchecker * another spellcheck fix * more spellcheck errors * Fix measurement being off by 1 on both ends * UI fixes * Add feet to crosses * Remove anti-aliasing, as it's creating artifacts * Use pixel tolerance from settings * Tooltip updates * Restore antialiasing to draw the tooltip * remove comment for spell check * Updated icons * Icon updates * Improve measurement accuracy and display * Fix spellchecker * Add less precise drawing on continuous warning * Add setting for turning cross feet on * Swap LMB/RMB for interaction * Uncheck active tool's RadioButton when it exits * activation hotkey toggles UI instead of just launching it * track runner process and exit when it exits * add proj ref * toolbar is interactive during measurements * always open toolbar on the main display * refactor colors * refactor edge detection & overlay ui * refactor overlay ui even more * simplify state structs * multimonitor preparation: eliminate global state * prepare for merge * spelling * proper thread termination + minor fixes * multimonitor: launch tools on all monitors * multimonitor support: track cursor position * spell * fix powertoys! * ScreenSize -> Box * add shadow effect for textbox * spell * fix debug mode * dynamic text box size based on text layout metrics * add mouse wheel to adjust pixel tolerance + per channel detection algorithm setting * spelling * fix per channel distance calculations * update installer deps + spelling * tool activation telemetry * update assets and try to fix build * use × instead of x * allow multiple measurements with bounds tool with shift-click * move #define DEBUG_OVERLAY in an appropriate space * spell-checked * update issue template + refactor text box drawing * implement custom renderer and make × semiopaque * spelling * pass dpiScale to x renderer * add sse2neon license * update OOBE * move license to NOTICE * appropriate module preview image * localization for AutomationPeer * increase default pixel tolerance from 5 to 30 * add PowerToys.MeasureToolUI.exe to bugreport * explicitly set texture dims * clarify continuous capture description * fix a real spelling error! * cleanup * clean up x2 * debug texture * fix texture access * fix saveasbitmap * improve sum of all channel diffs method score calc * optimize * ContinuousCapture is enabled by default to avoid confusion * build fix * draw captured screen in a non continuous mode * cast a spell... * merge fix * disable stroboscopic effect * split global/perScreen measure state and minor improvements * spelling * fix comment * primary monitor debug also active for the bounds tool * dpi from rt for custom renderer * add comment * fix off by 1 * make backround convertion success for non continuous mode non-essential * fix spelling * overlay window covers taskbar * fix CI * revert taskbar covering * fix CI * fix ci again * fix 2 * fix ci * CI fix * fix arm ci * cleanup cursor convertion between coordinate spaces * fix spelling * Fix signing * Fix MeasureToolUI version * Fix core version * fix race condition in system internals which happens during concurrent d3d/d2d resource creation Co-authored-by: Jaime Bernardo <jaime@janeasystems.com> Co-authored-by: Niels Laute <niels.laute@live.nl>
73
src/modules/MeasureTool/MeasureToolCore/BGRATextureView.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "BGRATextureView.h"
|
||||
|
||||
#if defined(DEBUG_TEXTURE)
|
||||
void BGRATextureView::SaveAsBitmap(const char* filename) const
|
||||
{
|
||||
wil::unique_hbitmap bitmap{ CreateBitmap(static_cast<int>(pitch), static_cast<int>(height), 1, 32, pixels) };
|
||||
const HBITMAP hBitmap = bitmap.get();
|
||||
DWORD dwPaletteSize = 0, dwBmBitsSize = 0, dwDIBSize = 0, dwWritten = 0;
|
||||
LPBITMAPINFOHEADER lpBitmapInfo;
|
||||
HANDLE hDib, hPal, hOldPal2 = NULL;
|
||||
HDC hDC = CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL);
|
||||
const int iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES);
|
||||
DeleteDC(hDC);
|
||||
WORD wBitCount = 24;
|
||||
if (iBits <= 1)
|
||||
wBitCount = 1;
|
||||
else if (iBits <= 4)
|
||||
wBitCount = 4;
|
||||
else if (iBits <= 8)
|
||||
wBitCount = 8;
|
||||
|
||||
BITMAP Bitmap0;
|
||||
GetObject(hBitmap, sizeof(Bitmap0), (LPSTR)&Bitmap0);
|
||||
BITMAPINFOHEADER bi = {};
|
||||
bi.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bi.biWidth = Bitmap0.bmWidth;
|
||||
bi.biHeight = -Bitmap0.bmHeight;
|
||||
bi.biPlanes = 1;
|
||||
bi.biBitCount = wBitCount;
|
||||
bi.biCompression = BI_RGB;
|
||||
bi.biClrUsed = 256;
|
||||
dwBmBitsSize = ((Bitmap0.bmWidth * wBitCount + 31) & ~31) / 8 * Bitmap0.bmHeight;
|
||||
hDib = GlobalAlloc(GHND, dwBmBitsSize + dwPaletteSize + sizeof(BITMAPINFOHEADER));
|
||||
lpBitmapInfo = (LPBITMAPINFOHEADER)GlobalLock(hDib);
|
||||
*lpBitmapInfo = bi;
|
||||
|
||||
hPal = GetStockObject(DEFAULT_PALETTE);
|
||||
if (hPal)
|
||||
{
|
||||
hDC = GetDC(NULL);
|
||||
hOldPal2 = SelectPalette(hDC, (HPALETTE)hPal, FALSE);
|
||||
RealizePalette(hDC);
|
||||
}
|
||||
|
||||
GetDIBits(hDC, hBitmap, 0, (UINT)Bitmap0.bmHeight, (LPSTR)lpBitmapInfo + sizeof(BITMAPINFOHEADER) + dwPaletteSize, (BITMAPINFO*)lpBitmapInfo, DIB_RGB_COLORS);
|
||||
|
||||
if (hOldPal2)
|
||||
{
|
||||
SelectPalette(hDC, (HPALETTE)hOldPal2, TRUE);
|
||||
RealizePalette(hDC);
|
||||
ReleaseDC(NULL, hDC);
|
||||
}
|
||||
|
||||
wil::unique_handle fh{ CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL) };
|
||||
|
||||
if (!fh)
|
||||
return;
|
||||
|
||||
BITMAPFILEHEADER bitmapFileHeader = {};
|
||||
bitmapFileHeader.bfType = 0x4D42; // "BM"
|
||||
dwDIBSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwPaletteSize + dwBmBitsSize;
|
||||
bitmapFileHeader.bfSize = dwDIBSize;
|
||||
bitmapFileHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + dwPaletteSize;
|
||||
|
||||
WriteFile(fh.get(), (LPSTR)&bitmapFileHeader, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
|
||||
|
||||
WriteFile(fh.get(), (LPSTR)lpBitmapInfo, dwDIBSize, &dwWritten, NULL);
|
||||
GlobalUnlock(hDib);
|
||||
GlobalFree(hDib);
|
||||
}
|
||||
#endif
|
||||
127
src/modules/MeasureTool/MeasureToolCore/BGRATextureView.h
Normal file
@@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
|
||||
#include <cinttypes>
|
||||
#include <wil/resource.h>
|
||||
#ifdef _M_ARM64
|
||||
#include <arm64_neon.h.>
|
||||
#else
|
||||
#include <emmintrin.h>
|
||||
#endif
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
|
||||
//#define DEBUG_TEXTURE
|
||||
|
||||
#if defined(_M_ARM64)
|
||||
|
||||
// Adopted from https://github.com/DLTcollab/sse2neon/blob/master/sse2neon.h
|
||||
|
||||
using __m128i = int64x2_t;
|
||||
|
||||
inline __m128i _mm_cvtsi32_si128(int a)
|
||||
{
|
||||
return vreinterpretq_s64_s32(vsetq_lane_s32(a, vdupq_n_s32(0), 0));
|
||||
}
|
||||
|
||||
inline __m128i _mm_or_si128(__m128i a, __m128i b)
|
||||
{
|
||||
return vreinterpretq_s64_s32(
|
||||
vorrq_s32(vreinterpretq_s32_s64(a), vreinterpretq_s32_s64(b)));
|
||||
}
|
||||
|
||||
inline __m128i _mm_subs_epu8(__m128i a, __m128i b)
|
||||
{
|
||||
return vreinterpretq_s64_u8(
|
||||
vqsubq_u8(vreinterpretq_u8_s64(a), vreinterpretq_u8_s64(b)));
|
||||
}
|
||||
|
||||
inline __m128i _mm_sad_epu8(__m128i a, __m128i b)
|
||||
{
|
||||
uint16x8_t t = vpaddlq_u8(vabdq_u8((uint8x16_t)a, (uint8x16_t)b));
|
||||
return vreinterpretq_s64_u64(vpaddlq_u32(vpaddlq_u16(t)));
|
||||
}
|
||||
|
||||
inline __m128i _mm_setzero_si128(void)
|
||||
{
|
||||
return vreinterpretq_s64_s32(vdupq_n_s32(0));
|
||||
}
|
||||
|
||||
inline int _mm_cvtsi128_si32(__m128i a)
|
||||
{
|
||||
return vgetq_lane_s32(vreinterpretq_s32_s64(a), 0);
|
||||
}
|
||||
|
||||
inline __m128i _mm_set1_epi16(short w)
|
||||
{
|
||||
return vreinterpretq_s64_s16(vdupq_n_s16(w));
|
||||
}
|
||||
|
||||
inline __m128i _mm_cmpgt_epi16(__m128i a, __m128i b)
|
||||
{
|
||||
return vreinterpretq_s64_u16(
|
||||
vcgtq_s16(vreinterpretq_s16_s64(a), vreinterpretq_s16_s64(b)));
|
||||
}
|
||||
|
||||
inline __m128i _mm_cvtepu8_epi16(__m128i a)
|
||||
{
|
||||
uint8x16_t u8x16 = vreinterpretq_u8_s64(a); /* xxxx xxxx HGFE DCBA */
|
||||
uint16x8_t u16x8 = vmovl_u8(vget_low_u8(u8x16)); /* 0H0G 0F0E 0D0C 0B0A */
|
||||
return vreinterpretq_s64_u16(u16x8);
|
||||
}
|
||||
|
||||
inline int64_t _mm_cvtsi128_si64(__m128i a)
|
||||
{
|
||||
return vgetq_lane_s64(a, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
inline __m128i distance_epu8(const __m128i a, __m128i b)
|
||||
{
|
||||
return _mm_or_si128(_mm_subs_epu8(a, b),
|
||||
_mm_subs_epu8(b, a));
|
||||
}
|
||||
|
||||
struct BGRATextureView
|
||||
{
|
||||
const uint32_t* pixels = nullptr;
|
||||
size_t pitch = {};
|
||||
size_t width = {};
|
||||
size_t height = {};
|
||||
|
||||
BGRATextureView() = default;
|
||||
|
||||
BGRATextureView(BGRATextureView&& rhs) = default;
|
||||
|
||||
inline uint32_t GetPixel(const size_t x, const size_t y) const
|
||||
{
|
||||
assert(x < width && x >= 0);
|
||||
assert(y < height && y >= 0);
|
||||
return pixels[x + pitch * y];
|
||||
}
|
||||
|
||||
template<bool perChannel>
|
||||
static inline bool PixelsClose(const uint32_t pixel1, const uint32_t pixel2, uint8_t tolerance)
|
||||
{
|
||||
const __m128i rgba1 = _mm_cvtsi32_si128(pixel1);
|
||||
const __m128i rgba2 = _mm_cvtsi32_si128(pixel2);
|
||||
const __m128i distances = distance_epu8(rgba1, rgba2);
|
||||
|
||||
// Method 1: Test whether each channel distance is not greater than tolerance
|
||||
if constexpr (perChannel)
|
||||
{
|
||||
const __m128i tolerances = _mm_set1_epi16(tolerance);
|
||||
const auto gtResults128 = _mm_cmpgt_epi16(_mm_cvtepu8_epi16(distances), tolerances);
|
||||
return _mm_cvtsi128_si64(gtResults128) == 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Method 2: Test whether sum of all channel differences is smaller than tolerance
|
||||
const int32_t score = _mm_cvtsi128_si32(_mm_sad_epu8(distances, _mm_setzero_si128())) & std::numeric_limits<uint8_t>::max();
|
||||
return score <= tolerance;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(DEBUG_TEXTURE)
|
||||
void SaveAsBitmap(const char* filename) const;
|
||||
#endif
|
||||
};
|
||||
161
src/modules/MeasureTool/MeasureToolCore/BoundsToolOverlayUI.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
#include "pch.h"
|
||||
#include "BoundsToolOverlayUI.h"
|
||||
#include "CoordinateSystemConversion.h"
|
||||
#include "Clipboard.h"
|
||||
|
||||
#include <common/utils/window.h>
|
||||
|
||||
LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case WM_CREATE:
|
||||
{
|
||||
auto toolState = GetWindowCreateParam<BoundsToolState*>(lparam);
|
||||
StoreWindowParam(window, toolState);
|
||||
break;
|
||||
}
|
||||
case WM_ERASEBKGND:
|
||||
return 1;
|
||||
case WM_KEYUP:
|
||||
if (wparam == VK_ESCAPE)
|
||||
{
|
||||
PostMessageW(window, WM_CLOSE, {}, {});
|
||||
}
|
||||
break;
|
||||
case WM_LBUTTONDOWN:
|
||||
{
|
||||
auto toolState = GetWindowParam<BoundsToolState*>(window);
|
||||
if (!toolState)
|
||||
break;
|
||||
const POINT cursorPos = convert::FromSystemToRelativeForDirect2D(window, toolState->commonState->cursorPosSystemSpace);
|
||||
|
||||
D2D_POINT_2F newRegionStart = { .x = static_cast<float>(cursorPos.x), .y = static_cast<float>(cursorPos.y) };
|
||||
toolState->perScreen[window].currentRegionStart = newRegionStart;
|
||||
break;
|
||||
}
|
||||
case WM_CURSOR_LEFT_MONITOR:
|
||||
{
|
||||
auto toolState = GetWindowParam<BoundsToolState*>(window);
|
||||
if (!toolState)
|
||||
break;
|
||||
toolState->perScreen[window].currentRegionStart = std::nullopt;
|
||||
break;
|
||||
}
|
||||
case WM_LBUTTONUP:
|
||||
{
|
||||
auto toolState = GetWindowParam<BoundsToolState*>(window);
|
||||
if (!toolState || !toolState->perScreen[window].currentRegionStart)
|
||||
break;
|
||||
|
||||
toolState->commonState->overlayBoxText.Read([](const OverlayBoxText& text) {
|
||||
SetClipBoardToText(text.buffer);
|
||||
});
|
||||
|
||||
if (const bool shiftPress = GetKeyState(VK_SHIFT) & 0x8000; shiftPress)
|
||||
{
|
||||
const auto cursorPos = convert::FromSystemToRelativeForDirect2D(window, toolState->commonState->cursorPosSystemSpace);
|
||||
|
||||
D2D1_RECT_F rect;
|
||||
std::tie(rect.left, rect.right) = std::minmax(static_cast<float>(cursorPos.x), toolState->perScreen[window].currentRegionStart->x);
|
||||
std::tie(rect.top, rect.bottom) = std::minmax(static_cast<float>(cursorPos.y), toolState->perScreen[window].currentRegionStart->y);
|
||||
toolState->perScreen[window].measurements.push_back(rect);
|
||||
}
|
||||
|
||||
toolState->perScreen[window].currentRegionStart = std::nullopt;
|
||||
break;
|
||||
}
|
||||
case WM_RBUTTONUP:
|
||||
{
|
||||
auto toolState = GetWindowParam<BoundsToolState*>(window);
|
||||
if (!toolState)
|
||||
break;
|
||||
|
||||
if (toolState->perScreen[window].currentRegionStart)
|
||||
toolState->perScreen[window].currentRegionStart = std::nullopt;
|
||||
else
|
||||
{
|
||||
if (toolState->perScreen[window].measurements.empty())
|
||||
PostMessageW(window, WM_CLOSE, {}, {});
|
||||
else
|
||||
toolState->perScreen[window].measurements.clear();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return DefWindowProcW(window, message, wparam, lparam);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
void DrawMeasurement(const D2D1_RECT_F rect,
|
||||
const bool alignTextBoxToCenter,
|
||||
const CommonState& commonState,
|
||||
HWND window,
|
||||
const D2DState& d2dState)
|
||||
{
|
||||
const bool screenQuadrantAware = !alignTextBoxToCenter;
|
||||
const auto prevMode = d2dState.rt->GetAntialiasMode();
|
||||
d2dState.rt->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
d2dState.rt->DrawRectangle(rect, d2dState.solidBrushes[Brush::line].get());
|
||||
d2dState.rt->SetAntialiasMode(prevMode);
|
||||
|
||||
OverlayBoxText text;
|
||||
const auto width = std::abs(rect.right - rect.left + 1);
|
||||
const auto height = std::abs(rect.top - rect.bottom + 1);
|
||||
const uint32_t textLen = swprintf_s(text.buffer.data(),
|
||||
text.buffer.size(),
|
||||
L"%.0f × %.0f",
|
||||
width,
|
||||
height);
|
||||
std::optional<size_t> crossSymbolPos = wcschr(text.buffer.data(), L' ') - text.buffer.data() + 1;
|
||||
|
||||
commonState.overlayBoxText.Access([&](OverlayBoxText& v) {
|
||||
v = text;
|
||||
});
|
||||
|
||||
float cornerX = rect.right;
|
||||
float cornerY = rect.bottom;
|
||||
if (alignTextBoxToCenter)
|
||||
{
|
||||
cornerX = rect.left + width / 2;
|
||||
cornerY = rect.top + height / 2;
|
||||
}
|
||||
|
||||
d2dState.DrawTextBox(text.buffer.data(),
|
||||
textLen,
|
||||
crossSymbolPos,
|
||||
cornerX,
|
||||
cornerY,
|
||||
screenQuadrantAware,
|
||||
window);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawBoundsToolTick(const CommonState& commonState,
|
||||
const BoundsToolState& toolState,
|
||||
const HWND window,
|
||||
const D2DState& d2dState)
|
||||
{
|
||||
const auto it = toolState.perScreen.find(window);
|
||||
if (it == end(toolState.perScreen))
|
||||
return;
|
||||
|
||||
d2dState.rt->Clear();
|
||||
|
||||
const auto& perScreen = it->second;
|
||||
for (const auto& measure : perScreen.measurements)
|
||||
DrawMeasurement(measure, true, commonState, window, d2dState);
|
||||
|
||||
if (!perScreen.currentRegionStart.has_value())
|
||||
return;
|
||||
|
||||
const auto cursorPos = convert::FromSystemToRelativeForDirect2D(window, commonState.cursorPosSystemSpace);
|
||||
|
||||
const D2D1_RECT_F rect{ .left = perScreen.currentRegionStart->x,
|
||||
.top = perScreen.currentRegionStart->y,
|
||||
.right = static_cast<float>(cursorPos.x),
|
||||
.bottom = static_cast<float>(cursorPos.y) };
|
||||
DrawMeasurement(rect, false, commonState, window, d2dState);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "D2DState.h"
|
||||
#include "ToolState.h"
|
||||
|
||||
void DrawBoundsToolTick(const CommonState& commonState,
|
||||
const BoundsToolState& toolState,
|
||||
const HWND overlayWindow,
|
||||
const D2DState& d2dState);
|
||||
|
||||
LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept;
|
||||
28
src/modules/MeasureTool/MeasureToolCore/Clipboard.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "Clipboard.h"
|
||||
|
||||
void SetClipBoardToText(const std::wstring_view text)
|
||||
{
|
||||
if (!OpenClipboard(nullptr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const wil::unique_hglobal handle{ GlobalAlloc(GMEM_MOVEABLE, static_cast<size_t>((text.length() + 1) * sizeof(wchar_t))) };
|
||||
if (!handle)
|
||||
{
|
||||
CloseClipboard();
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto* bufPtr = static_cast<wchar_t*>(GlobalLock(handle.get())); bufPtr != nullptr)
|
||||
{
|
||||
text.copy(bufPtr, text.length());
|
||||
GlobalUnlock(handle.get());
|
||||
}
|
||||
|
||||
EmptyClipboard();
|
||||
SetClipboardData(CF_UNICODETEXT, handle.get());
|
||||
CloseClipboard();
|
||||
}
|
||||
5
src/modules/MeasureTool/MeasureToolCore/Clipboard.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
void SetClipBoardToText(const std::wstring_view text);
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
|
||||
namespace convert
|
||||
{
|
||||
// Converts a given point from multi-monitor coordinate system to the one relative to HWND
|
||||
inline POINT FromSystemToRelative(HWND window, POINT p)
|
||||
{
|
||||
ScreenToClient(window, &p);
|
||||
return p;
|
||||
}
|
||||
|
||||
// Converts a given point from multi-monitor coordinate system to the one relative to HWND and also ready
|
||||
// to be used in Direct2D calls with AA mode set to aliased
|
||||
inline POINT FromSystemToRelativeForDirect2D(HWND window, POINT p)
|
||||
{
|
||||
ScreenToClient(window, &p);
|
||||
// Submitting DrawLine calls to Direct2D with thickness == 1.f and AA mode set to aliased causes
|
||||
// them to be drawn offset by [1,1] toward upper-left corner, so we must to compensate for that.
|
||||
++p.x;
|
||||
++p.y;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
170
src/modules/MeasureTool/MeasureToolCore/D2DState.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "constants.h"
|
||||
#include "D2DState.h"
|
||||
|
||||
#include <common/Display/dpi_aware.h>
|
||||
#include <ToolState.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
void DetermineScreenQuadrant(const HWND window, long x, long y, bool& inLeftHalf, bool& inTopHalf)
|
||||
{
|
||||
RECT windowRect{};
|
||||
GetWindowRect(window, &windowRect);
|
||||
const long w = windowRect.right - windowRect.left;
|
||||
const long h = windowRect.bottom - windowRect.top;
|
||||
inLeftHalf = x < w / 2;
|
||||
inTopHalf = y < h / 2;
|
||||
}
|
||||
}
|
||||
|
||||
D2DState::D2DState(const HWND overlayWindow, std::vector<D2D1::ColorF> solidBrushesColors)
|
||||
{
|
||||
std::lock_guard guard{ gpuAccessLock };
|
||||
|
||||
RECT clientRect = {};
|
||||
|
||||
winrt::check_bool(GetClientRect(overlayWindow, &clientRect));
|
||||
winrt::check_hresult(D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, &d2dFactory));
|
||||
|
||||
winrt::check_hresult(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), writeFactory.put_unknown()));
|
||||
|
||||
// We should always use DPIAware::DEFAULT_DPI, since it's the correct thing to do in DPI-Aware mode
|
||||
auto renderTargetProperties = D2D1::RenderTargetProperties(
|
||||
D2D1_RENDER_TARGET_TYPE_DEFAULT,
|
||||
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
|
||||
DPIAware::DEFAULT_DPI,
|
||||
DPIAware::DEFAULT_DPI,
|
||||
D2D1_RENDER_TARGET_USAGE_NONE,
|
||||
D2D1_FEATURE_LEVEL_DEFAULT);
|
||||
|
||||
auto renderTargetSize = D2D1::SizeU(clientRect.right - clientRect.left, clientRect.bottom - clientRect.top);
|
||||
auto hwndRenderTargetProperties = D2D1::HwndRenderTargetProperties(overlayWindow, renderTargetSize);
|
||||
|
||||
winrt::check_hresult(d2dFactory->CreateHwndRenderTarget(renderTargetProperties, hwndRenderTargetProperties, &rt));
|
||||
winrt::check_hresult(rt->CreateCompatibleRenderTarget(&bitmapRt));
|
||||
|
||||
unsigned dpi = DPIAware::DEFAULT_DPI;
|
||||
DPIAware::GetScreenDPIForWindow(overlayWindow, dpi);
|
||||
dpiScale = dpi / static_cast<float>(DPIAware::DEFAULT_DPI);
|
||||
|
||||
winrt::check_hresult(writeFactory->CreateTextFormat(L"Segoe UI Variable Text",
|
||||
nullptr,
|
||||
DWRITE_FONT_WEIGHT_NORMAL,
|
||||
DWRITE_FONT_STYLE_NORMAL,
|
||||
DWRITE_FONT_STRETCH_NORMAL,
|
||||
consts::FONT_SIZE * dpiScale,
|
||||
L"en-US",
|
||||
&textFormat));
|
||||
winrt::check_hresult(textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER));
|
||||
winrt::check_hresult(textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER));
|
||||
winrt::check_hresult(textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP));
|
||||
|
||||
solidBrushes.resize(solidBrushesColors.size());
|
||||
for (size_t i = 0; i < solidBrushes.size(); ++i)
|
||||
{
|
||||
winrt::check_hresult(rt->CreateSolidColorBrush(solidBrushesColors[i], &solidBrushes[i]));
|
||||
}
|
||||
|
||||
const auto deviceContext = rt.query<ID2D1DeviceContext>();
|
||||
winrt::check_hresult(deviceContext->CreateEffect(CLSID_D2D1Shadow, &shadowEffect));
|
||||
winrt::check_hresult(shadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, consts::SHADOW_RADIUS));
|
||||
winrt::check_hresult(shadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR, D2D1::ColorF(0.f, 0.f, 0.f, consts::SHADOW_OPACITY)));
|
||||
|
||||
winrt::check_hresult(deviceContext->CreateEffect(CLSID_D2D12DAffineTransform, &affineTransformEffect));
|
||||
affineTransformEffect->SetInputEffect(0, shadowEffect.get());
|
||||
|
||||
textRenderer = winrt::make_self<PerGlyphOpacityTextRender>(d2dFactory, rt, solidBrushes[Brush::foreground]);
|
||||
}
|
||||
|
||||
void D2DState::DrawTextBox(const wchar_t* text,
|
||||
const uint32_t textLen,
|
||||
const std::optional<size_t> halfOpaqueSymbolPos,
|
||||
const float centerX,
|
||||
const float centerY,
|
||||
const bool screenQuadrantAware,
|
||||
const HWND window) const
|
||||
{
|
||||
wil::com_ptr<IDWriteTextLayout> textLayout;
|
||||
winrt::check_hresult(writeFactory->CreateTextLayout(text,
|
||||
textLen,
|
||||
textFormat.get(),
|
||||
std::numeric_limits<float>::max(),
|
||||
std::numeric_limits<float>::max(),
|
||||
&textLayout));
|
||||
DWRITE_TEXT_METRICS textMetrics = {};
|
||||
winrt::check_hresult(textLayout->GetMetrics(&textMetrics));
|
||||
textMetrics.width *= consts::TEXT_BOX_MARGIN_COEFF;
|
||||
textMetrics.height *= consts::TEXT_BOX_MARGIN_COEFF;
|
||||
winrt::check_hresult(textLayout->SetMaxWidth(textMetrics.width));
|
||||
winrt::check_hresult(textLayout->SetMaxHeight(textMetrics.height));
|
||||
|
||||
D2D1_RECT_F textRect{ .left = centerX - textMetrics.width / 2.f,
|
||||
.top = centerY - textMetrics.height / 2.f,
|
||||
.right = centerX + textMetrics.width / 2.f,
|
||||
.bottom = centerY + textMetrics.height / 2.f };
|
||||
if (screenQuadrantAware)
|
||||
{
|
||||
bool cursorInLeftScreenHalf = false;
|
||||
bool cursorInTopScreenHalf = false;
|
||||
DetermineScreenQuadrant(window,
|
||||
static_cast<long>(centerX),
|
||||
static_cast<long>(centerY),
|
||||
cursorInLeftScreenHalf,
|
||||
cursorInTopScreenHalf);
|
||||
float textQuadrantOffsetX = textMetrics.width * dpiScale;
|
||||
float textQuadrantOffsetY = textMetrics.height * dpiScale;
|
||||
if (!cursorInLeftScreenHalf)
|
||||
textQuadrantOffsetX *= -1.f;
|
||||
if (!cursorInTopScreenHalf)
|
||||
textQuadrantOffsetY *= -1.f;
|
||||
textRect.left += textQuadrantOffsetX;
|
||||
textRect.right += textQuadrantOffsetX;
|
||||
textRect.top += textQuadrantOffsetY;
|
||||
textRect.bottom += textQuadrantOffsetY;
|
||||
}
|
||||
|
||||
// Draw shadow
|
||||
bitmapRt->BeginDraw();
|
||||
bitmapRt->Clear(D2D1::ColorF(0.f, 0.f, 0.f, 0.f));
|
||||
D2D1_ROUNDED_RECT textBoxRect;
|
||||
textBoxRect.radiusX = textBoxRect.radiusY = consts::TEXT_BOX_CORNER_RADIUS * dpiScale;
|
||||
textBoxRect.rect.bottom = textRect.bottom;
|
||||
textBoxRect.rect.top = textRect.top;
|
||||
textBoxRect.rect.left = textRect.left;
|
||||
textBoxRect.rect.right = textRect.right;
|
||||
bitmapRt->FillRoundedRectangle(textBoxRect, solidBrushes[Brush::border].get());
|
||||
bitmapRt->EndDraw();
|
||||
|
||||
wil::com_ptr<ID2D1Bitmap> rtBitmap;
|
||||
bitmapRt->GetBitmap(&rtBitmap);
|
||||
|
||||
shadowEffect->SetInput(0, rtBitmap.get());
|
||||
const auto shadowMatrix = D2D1::Matrix3x2F::Translation(consts::SHADOW_OFFSET * dpiScale,
|
||||
consts::SHADOW_OFFSET * dpiScale);
|
||||
winrt::check_hresult(affineTransformEffect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_TRANSFORM_MATRIX,
|
||||
shadowMatrix));
|
||||
auto deviceContext = rt.query<ID2D1DeviceContext>();
|
||||
deviceContext->DrawImage(affineTransformEffect.get(), D2D1_INTERPOLATION_MODE_LINEAR);
|
||||
|
||||
// Draw text box border rectangle
|
||||
rt->DrawRoundedRectangle(textBoxRect, solidBrushes[Brush::border].get());
|
||||
const float TEXT_BOX_PADDING = 1.f * dpiScale;
|
||||
textBoxRect.rect.bottom -= TEXT_BOX_PADDING;
|
||||
textBoxRect.rect.top += TEXT_BOX_PADDING;
|
||||
textBoxRect.rect.left += TEXT_BOX_PADDING;
|
||||
textBoxRect.rect.right -= TEXT_BOX_PADDING;
|
||||
|
||||
// Draw text & its box
|
||||
rt->FillRoundedRectangle(textBoxRect, solidBrushes[Brush::background].get());
|
||||
|
||||
if (halfOpaqueSymbolPos.has_value())
|
||||
{
|
||||
DWRITE_TEXT_RANGE textRange = { static_cast<uint32_t>(*halfOpaqueSymbolPos), 2 };
|
||||
auto opacityEffect = winrt::make_self<OpacityEffect>();
|
||||
opacityEffect->alpha = consts::CROSS_OPACITY;
|
||||
winrt::check_hresult(textLayout->SetDrawingEffect(opacityEffect.get(), textRange));
|
||||
}
|
||||
winrt::check_hresult(textLayout->Draw(nullptr, textRenderer.get(), textRect.left, textRect.top));
|
||||
}
|
||||
42
src/modules/MeasureTool/MeasureToolCore/D2DState.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <d2d1_3.h>
|
||||
#include <wil/com.h>
|
||||
#include <windef.h>
|
||||
|
||||
#include "PerGlyphOpacityTextRender.h"
|
||||
|
||||
enum Brush : size_t
|
||||
{
|
||||
line,
|
||||
foreground,
|
||||
background,
|
||||
border
|
||||
};
|
||||
|
||||
struct D2DState
|
||||
{
|
||||
wil::com_ptr<ID2D1Factory> d2dFactory;
|
||||
wil::com_ptr<IDWriteFactory> writeFactory;
|
||||
wil::com_ptr<ID2D1HwndRenderTarget> rt;
|
||||
wil::com_ptr<ID2D1BitmapRenderTarget> bitmapRt;
|
||||
wil::com_ptr<IDWriteTextFormat> textFormat;
|
||||
winrt::com_ptr<PerGlyphOpacityTextRender> textRenderer;
|
||||
std::vector<wil::com_ptr<ID2D1SolidColorBrush>> solidBrushes;
|
||||
wil::com_ptr<ID2D1Effect> shadowEffect;
|
||||
wil::com_ptr<ID2D1Effect> affineTransformEffect;
|
||||
|
||||
float dpiScale = 1.f;
|
||||
|
||||
D2DState(const HWND window, std::vector<D2D1::ColorF> solidBrushesColors);
|
||||
void DrawTextBox(const wchar_t* text,
|
||||
const uint32_t textLen,
|
||||
const std::optional<size_t> halfOpaqueSymbolPos,
|
||||
const float centerX,
|
||||
const float centerY,
|
||||
const bool screenQuadrantAware,
|
||||
const HWND window) const;
|
||||
};
|
||||
112
src/modules/MeasureTool/MeasureToolCore/EdgeDetection.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "constants.h"
|
||||
#include "EdgeDetection.h"
|
||||
template<bool PerChannel,
|
||||
bool ContinuousCapture,
|
||||
bool IsX,
|
||||
bool Increment>
|
||||
inline long FindEdge(const BGRATextureView& texture, const POINT centerPoint, const uint8_t tolerance)
|
||||
{
|
||||
using namespace consts;
|
||||
|
||||
long xOffset = 0;
|
||||
long yOffset = 0;
|
||||
|
||||
// For continuous capture, we'll be a bit off center from the cursor so the cross we draw won't interfere with the measurement.
|
||||
if constexpr (ContinuousCapture)
|
||||
{
|
||||
if constexpr (IsX)
|
||||
{
|
||||
xOffset = Increment ? CURSOR_OFFSET_AMOUNT_X : -CURSOR_OFFSET_AMOUNT_X;
|
||||
yOffset = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
xOffset = 1;
|
||||
yOffset = Increment ? CURSOR_OFFSET_AMOUNT_Y : -CURSOR_OFFSET_AMOUNT_Y;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t maxDim = IsX ? texture.width : texture.height;
|
||||
|
||||
long x = std::clamp<long>(centerPoint.x + xOffset, 1, static_cast<long>(texture.width - 2));
|
||||
long y = std::clamp<long>(centerPoint.y + yOffset, 1, static_cast<long>(texture.height - 2));
|
||||
|
||||
const uint32_t startPixel = texture.GetPixel(x, y);
|
||||
while (true)
|
||||
{
|
||||
long oldX = x;
|
||||
long oldY = y;
|
||||
if constexpr (IsX)
|
||||
{
|
||||
if constexpr (Increment)
|
||||
{
|
||||
if (++x == maxDim)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (--x == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if constexpr (Increment)
|
||||
{
|
||||
if (++y == maxDim)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (--y == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t nextPixel = texture.GetPixel(x, y);
|
||||
if (!texture.PixelsClose<PerChannel>(startPixel, nextPixel, tolerance))
|
||||
{
|
||||
return IsX ? oldX : oldY;
|
||||
}
|
||||
}
|
||||
|
||||
return Increment ? static_cast<long>(IsX ? texture.width : texture.height) - 1 : 0;
|
||||
}
|
||||
|
||||
template<bool PerChannel, bool ContinuousCapture>
|
||||
inline RECT DetectEdgesInternal(const BGRATextureView& texture,
|
||||
const POINT centerPoint,
|
||||
const uint8_t tolerance)
|
||||
{
|
||||
return RECT{ .left = FindEdge<PerChannel,
|
||||
ContinuousCapture,
|
||||
true,
|
||||
false>(texture, centerPoint, tolerance),
|
||||
.top = FindEdge<PerChannel,
|
||||
ContinuousCapture,
|
||||
false,
|
||||
false>(texture, centerPoint, tolerance),
|
||||
.right = FindEdge<PerChannel,
|
||||
ContinuousCapture,
|
||||
true,
|
||||
true>(texture, centerPoint, tolerance),
|
||||
.bottom = FindEdge<PerChannel,
|
||||
ContinuousCapture,
|
||||
false,
|
||||
true>(texture, centerPoint, tolerance) };
|
||||
}
|
||||
|
||||
RECT DetectEdges(const BGRATextureView& texture,
|
||||
const POINT centerPoint,
|
||||
const bool perChannel,
|
||||
const uint8_t tolerance,
|
||||
const bool continuousCapture)
|
||||
{
|
||||
auto function = perChannel ? &DetectEdgesInternal<true, false> : DetectEdgesInternal<false, false>;
|
||||
if (continuousCapture)
|
||||
function = perChannel ? &DetectEdgesInternal<true, true> : &DetectEdgesInternal<false, true>;
|
||||
|
||||
return function(texture, centerPoint, tolerance);
|
||||
}
|
||||
9
src/modules/MeasureTool/MeasureToolCore/EdgeDetection.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "BGRATextureView.h"
|
||||
|
||||
RECT DetectEdges(const BGRATextureView& texture,
|
||||
const POINT centerPoint,
|
||||
const bool perChannel,
|
||||
const uint8_t tolerance,
|
||||
const bool continuousCapture);
|
||||
3
src/modules/MeasureTool/MeasureToolCore/MeasureTool.def
Normal file
@@ -0,0 +1,3 @@
|
||||
EXPORTS
|
||||
DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE
|
||||
DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE
|
||||
300
src/modules/MeasureTool/MeasureToolCore/MeasureToolOverlayUI.cpp
Normal file
@@ -0,0 +1,300 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "BGRATextureView.h"
|
||||
#include "Clipboard.h"
|
||||
#include "CoordinateSystemConversion.h"
|
||||
#include "constants.h"
|
||||
#include "MeasureToolOverlayUI.h"
|
||||
|
||||
#include <common/utils/window.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
inline std::pair<D2D_POINT_2F, D2D_POINT_2F> ComputeCrossFeetLine(D2D_POINT_2F center, const bool horizontal)
|
||||
{
|
||||
D2D_POINT_2F start = center, end = center;
|
||||
// Computing in this way to achieve pixel-perfect axial symmetry of aliased D2D lines
|
||||
if (horizontal)
|
||||
{
|
||||
start.x -= consts::FEET_HALF_LENGTH + 1.f;
|
||||
end.x += consts::FEET_HALF_LENGTH;
|
||||
|
||||
start.y += 1.f;
|
||||
end.y += 1.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
start.y -= consts::FEET_HALF_LENGTH + 1.f;
|
||||
end.y += consts::FEET_HALF_LENGTH;
|
||||
|
||||
start.x += 1.f;
|
||||
end.x += 1.f;
|
||||
}
|
||||
|
||||
return { start, end };
|
||||
}
|
||||
}
|
||||
|
||||
winrt::com_ptr<ID2D1Bitmap> ConvertID3D11Texture2DToD2D1Bitmap(wil::com_ptr<ID2D1HwndRenderTarget> rt,
|
||||
winrt::com_ptr<ID3D11Texture2D> texture)
|
||||
{
|
||||
std::lock_guard guard{ gpuAccessLock };
|
||||
|
||||
auto dxgiSurface = texture.try_as<IDXGISurface>();
|
||||
if (!dxgiSurface)
|
||||
return nullptr;
|
||||
|
||||
DXGI_MAPPED_RECT bitmap2Dmap = {};
|
||||
HRESULT hr = dxgiSurface->Map(&bitmap2Dmap, DXGI_MAP_READ);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
D2D1_BITMAP_PROPERTIES props = { .pixelFormat = rt->GetPixelFormat() };
|
||||
rt->GetDpi(&props.dpiX, &props.dpiY);
|
||||
const auto sizeF = rt->GetSize();
|
||||
winrt::com_ptr<ID2D1Bitmap> bitmap;
|
||||
if (FAILED(rt->CreateBitmap(D2D1::SizeU(static_cast<uint32_t>(sizeF.width),
|
||||
static_cast<uint32_t>(sizeF.height)),
|
||||
bitmap2Dmap.pBits,
|
||||
bitmap2Dmap.Pitch,
|
||||
props,
|
||||
bitmap.put())))
|
||||
return nullptr;
|
||||
if (FAILED(dxgiSurface->Unmap()))
|
||||
return nullptr;
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
LRESULT CALLBACK MeasureToolWndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case WM_CURSOR_LEFT_MONITOR:
|
||||
{
|
||||
if (auto state = GetWindowParam<Serialized<MeasureToolState>*>(window))
|
||||
{
|
||||
state->Access([&](MeasureToolState& s) {
|
||||
s.perScreen[window].measuredEdges = {};
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_NCHITTEST:
|
||||
return HTCLIENT;
|
||||
case WM_CREATE:
|
||||
{
|
||||
auto state = GetWindowCreateParam<Serialized<MeasureToolState>*>(lparam);
|
||||
StoreWindowParam(window, state);
|
||||
|
||||
#if !defined(DEBUG_OVERLAY)
|
||||
for (; ShowCursor(false) > 0;)
|
||||
;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case WM_ERASEBKGND:
|
||||
return 1;
|
||||
case WM_KEYUP:
|
||||
if (wparam == VK_ESCAPE)
|
||||
{
|
||||
PostMessageW(window, WM_CLOSE, {}, {});
|
||||
}
|
||||
break;
|
||||
case WM_RBUTTONUP:
|
||||
PostMessageW(window, WM_CLOSE, {}, {});
|
||||
break;
|
||||
case WM_LBUTTONUP:
|
||||
if (auto state = GetWindowParam<Serialized<MeasureToolState>*>(window))
|
||||
{
|
||||
state->Read([](const MeasureToolState& s) { s.commonState->overlayBoxText.Read([](const OverlayBoxText& text) {
|
||||
SetClipBoardToText(text.buffer);
|
||||
}); });
|
||||
}
|
||||
break;
|
||||
case WM_MOUSEWHEEL:
|
||||
if (auto state = GetWindowParam<Serialized<MeasureToolState>*>(window))
|
||||
{
|
||||
const int8_t step = static_cast<short>(HIWORD(wparam)) < 0 ? -consts::MOUSE_WHEEL_TOLERANCE_STEP : consts::MOUSE_WHEEL_TOLERANCE_STEP;
|
||||
state->Access([step](MeasureToolState& s) {
|
||||
int wideVal = s.global.pixelTolerance;
|
||||
wideVal += step;
|
||||
s.global.pixelTolerance = static_cast<uint8_t>(std::clamp(wideVal, 0, 255));
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return DefWindowProcW(window, message, wparam, lparam);
|
||||
}
|
||||
|
||||
void DrawMeasureToolTick(const CommonState& commonState,
|
||||
Serialized<MeasureToolState>& toolState,
|
||||
HWND window,
|
||||
D2DState& d2dState)
|
||||
{
|
||||
bool continuousCapture = {};
|
||||
bool drawFeetOnCross = {};
|
||||
bool drawHorizontalCrossLine = true;
|
||||
bool drawVerticalCrossLine = true;
|
||||
RECT measuredEdges{};
|
||||
MeasureToolState::Mode mode = {};
|
||||
winrt::com_ptr<ID2D1Bitmap> backgroundBitmap;
|
||||
winrt::com_ptr<ID3D11Texture2D> backgroundTextureToConvert;
|
||||
|
||||
toolState.Read([&](const MeasureToolState& state) {
|
||||
continuousCapture = state.global.continuousCapture;
|
||||
drawFeetOnCross = state.global.drawFeetOnCross;
|
||||
mode = state.global.mode;
|
||||
|
||||
if (auto it = state.perScreen.find(window); it != end(state.perScreen))
|
||||
{
|
||||
const auto& perScreen = it->second;
|
||||
measuredEdges = perScreen.measuredEdges;
|
||||
|
||||
if (continuousCapture)
|
||||
return;
|
||||
|
||||
if (perScreen.capturedScreenBitmap)
|
||||
{
|
||||
backgroundBitmap = perScreen.capturedScreenBitmap;
|
||||
}
|
||||
else if (perScreen.capturedScreenTexture)
|
||||
{
|
||||
backgroundTextureToConvert = perScreen.capturedScreenTexture;
|
||||
}
|
||||
}
|
||||
});
|
||||
switch (mode)
|
||||
{
|
||||
case MeasureToolState::Mode::Cross:
|
||||
drawHorizontalCrossLine = true;
|
||||
drawVerticalCrossLine = true;
|
||||
break;
|
||||
case MeasureToolState::Mode::Vertical:
|
||||
drawHorizontalCrossLine = false;
|
||||
drawVerticalCrossLine = true;
|
||||
break;
|
||||
case MeasureToolState::Mode::Horizontal:
|
||||
drawHorizontalCrossLine = true;
|
||||
drawVerticalCrossLine = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!continuousCapture && !backgroundBitmap && backgroundTextureToConvert)
|
||||
{
|
||||
backgroundBitmap = ConvertID3D11Texture2DToD2D1Bitmap(d2dState.rt, backgroundTextureToConvert);
|
||||
if (backgroundBitmap)
|
||||
{
|
||||
toolState.Access([&](MeasureToolState& state) {
|
||||
state.perScreen[window].capturedScreenTexture = {};
|
||||
state.perScreen[window].capturedScreenBitmap = backgroundBitmap;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (continuousCapture || !backgroundBitmap)
|
||||
d2dState.rt->Clear();
|
||||
|
||||
// Add 1px to each dim, since the range we obtain from measuredEdges is inclusive.
|
||||
const float hMeasure = static_cast<float>(measuredEdges.right - measuredEdges.left + 1);
|
||||
const float vMeasure = static_cast<float>(measuredEdges.bottom - measuredEdges.top + 1);
|
||||
|
||||
// Prevent drawing until we get the first capture
|
||||
const bool hasMeasure = (measuredEdges.right != measuredEdges.left) && (measuredEdges.bottom != measuredEdges.top);
|
||||
if (!hasMeasure)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!continuousCapture && backgroundBitmap)
|
||||
{
|
||||
d2dState.rt->DrawBitmap(backgroundBitmap.get());
|
||||
}
|
||||
|
||||
const auto previousAliasingMode = d2dState.rt->GetAntialiasMode();
|
||||
// Anti-aliasing is creating artifacts. Aliasing is for drawing straight lines.
|
||||
d2dState.rt->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
|
||||
const auto cursorPos = convert::FromSystemToRelativeForDirect2D(window, commonState.cursorPosSystemSpace);
|
||||
|
||||
if (drawHorizontalCrossLine)
|
||||
{
|
||||
const D2D_POINT_2F hLineStart{ .x = static_cast<float>(measuredEdges.left), .y = static_cast<float>(cursorPos.y) };
|
||||
D2D_POINT_2F hLineEnd{ .x = hLineStart.x + hMeasure, .y = hLineStart.y };
|
||||
d2dState.rt->DrawLine(hLineStart, hLineEnd, d2dState.solidBrushes[Brush::line].get());
|
||||
|
||||
if (drawFeetOnCross && !continuousCapture)
|
||||
{
|
||||
// To fill all pixels which are close, we call DrawLine with end point one pixel too far, since
|
||||
// it doesn't get filled, i.e. end point of the range is excluded. However, we want to draw cross
|
||||
// feet *on* the last pixel row, so we must subtract 1px from the corresponding axis.
|
||||
hLineEnd.x -= 1.f;
|
||||
auto [left_start, left_end] = ComputeCrossFeetLine(hLineStart, false);
|
||||
auto [right_start, right_end] = ComputeCrossFeetLine(hLineEnd, false);
|
||||
d2dState.rt->DrawLine(left_start, left_end, d2dState.solidBrushes[Brush::line].get());
|
||||
d2dState.rt->DrawLine(right_start, right_end, d2dState.solidBrushes[Brush::line].get());
|
||||
}
|
||||
}
|
||||
|
||||
if (drawVerticalCrossLine)
|
||||
{
|
||||
const D2D_POINT_2F vLineStart{ .x = static_cast<float>(cursorPos.x), .y = static_cast<float>(measuredEdges.top) };
|
||||
D2D_POINT_2F vLineEnd{ .x = vLineStart.x, .y = vLineStart.y + vMeasure };
|
||||
d2dState.rt->DrawLine(vLineStart, vLineEnd, d2dState.solidBrushes[Brush::line].get());
|
||||
|
||||
if (drawFeetOnCross && !continuousCapture)
|
||||
{
|
||||
vLineEnd.y -= 1.f;
|
||||
auto [top_start, top_end] = ComputeCrossFeetLine(vLineStart, true);
|
||||
auto [bottom_start, bottom_end] = ComputeCrossFeetLine(vLineEnd, true);
|
||||
d2dState.rt->DrawLine(top_start, top_end, d2dState.solidBrushes[Brush::line].get());
|
||||
d2dState.rt->DrawLine(bottom_start, bottom_end, d2dState.solidBrushes[Brush::line].get());
|
||||
}
|
||||
}
|
||||
|
||||
// After drawing the lines, restore anti aliasing to draw the measurement tooltip.
|
||||
d2dState.rt->SetAntialiasMode(previousAliasingMode);
|
||||
|
||||
uint32_t measureStringBufLen = 0;
|
||||
|
||||
OverlayBoxText text;
|
||||
std::optional<size_t> crossSymbolPos;
|
||||
switch (mode)
|
||||
{
|
||||
case MeasureToolState::Mode::Cross:
|
||||
measureStringBufLen = swprintf_s(text.buffer.data(),
|
||||
text.buffer.size(),
|
||||
L"%.0f × %.0f",
|
||||
hMeasure,
|
||||
vMeasure);
|
||||
crossSymbolPos = wcschr(text.buffer.data(), L' ') - text.buffer.data() + 1;
|
||||
break;
|
||||
case MeasureToolState::Mode::Vertical:
|
||||
measureStringBufLen = swprintf_s(text.buffer.data(),
|
||||
text.buffer.size(),
|
||||
L"%.0f",
|
||||
vMeasure);
|
||||
break;
|
||||
case MeasureToolState::Mode::Horizontal:
|
||||
measureStringBufLen = swprintf_s(text.buffer.data(),
|
||||
text.buffer.size(),
|
||||
L"%.0f",
|
||||
hMeasure);
|
||||
break;
|
||||
}
|
||||
|
||||
commonState.overlayBoxText.Access([&](OverlayBoxText& v) {
|
||||
v = text;
|
||||
});
|
||||
|
||||
d2dState.DrawTextBox(text.buffer.data(),
|
||||
measureStringBufLen,
|
||||
crossSymbolPos,
|
||||
static_cast<float>(cursorPos.x),
|
||||
static_cast<float>(cursorPos.y),
|
||||
true,
|
||||
window);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include "D2DState.h"
|
||||
#include "ToolState.h"
|
||||
|
||||
#include <common/utils/serialized.h>
|
||||
|
||||
void DrawMeasureToolTick(const CommonState& commonState,
|
||||
Serialized<MeasureToolState>& toolState,
|
||||
HWND overlayWindow,
|
||||
D2DState& d2dState);
|
||||
|
||||
LRESULT CALLBACK MeasureToolWndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept;
|
||||
231
src/modules/MeasureTool/MeasureToolCore/OverlayUI.cpp
Normal file
@@ -0,0 +1,231 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "BoundsToolOverlayUI.h"
|
||||
#include "MeasureToolOverlayUI.h"
|
||||
#include "OverlayUI.h"
|
||||
|
||||
#include <common/Display/dpi_aware.h>
|
||||
#include <common/Display/monitors.h>
|
||||
#include <common/logger/logger.h>
|
||||
#include <common/Themes/windows_colors.h>
|
||||
#include <common/utils/window.h>
|
||||
|
||||
namespace NonLocalizable
|
||||
{
|
||||
const wchar_t MeasureToolOverlayWindowName[] = L"PowerToys.MeasureToolOverlayWindow";
|
||||
const wchar_t BoundsToolOverlayWindowName[] = L"PowerToys.BoundsToolOverlayWindow";
|
||||
}
|
||||
|
||||
void CreateOverlayWindowClasses()
|
||||
{
|
||||
WNDCLASSEXW wcex{ .cbSize = sizeof(WNDCLASSEX), .hInstance = GetModuleHandleW(nullptr) };
|
||||
|
||||
wcex.lpfnWndProc = MeasureToolWndProc;
|
||||
wcex.lpszClassName = NonLocalizable::MeasureToolOverlayWindowName;
|
||||
RegisterClassExW(&wcex);
|
||||
|
||||
wcex.lpfnWndProc = BoundsToolWndProc;
|
||||
wcex.lpszClassName = NonLocalizable::BoundsToolOverlayWindowName;
|
||||
wcex.hCursor = LoadCursorW(nullptr, IDC_CROSS);
|
||||
RegisterClassExW(&wcex);
|
||||
}
|
||||
|
||||
HWND CreateOverlayUIWindow(const CommonState& commonState,
|
||||
const MonitorInfo& monitor,
|
||||
const wchar_t* windowClass,
|
||||
void* extraParam)
|
||||
{
|
||||
static std::once_flag windowClassesCreatedFlag;
|
||||
std::call_once(windowClassesCreatedFlag, CreateOverlayWindowClasses);
|
||||
|
||||
const auto screenArea = monitor.GetScreenSize(true);
|
||||
HWND window{ CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
|
||||
windowClass,
|
||||
L"PowerToys.MeasureToolOverlay",
|
||||
WS_POPUP,
|
||||
screenArea.left(),
|
||||
screenArea.top(),
|
||||
screenArea.width(),
|
||||
screenArea.height(),
|
||||
HWND_DESKTOP,
|
||||
nullptr,
|
||||
GetModuleHandleW(nullptr),
|
||||
extraParam) };
|
||||
winrt::check_bool(window);
|
||||
ShowWindow(window, SW_SHOWNORMAL);
|
||||
#if !defined(DEBUG_OVERLAY)
|
||||
SetWindowPos(window, HWND_TOPMOST, {}, {}, {}, {}, SWP_NOMOVE | SWP_NOSIZE);
|
||||
#else
|
||||
(void)window;
|
||||
#endif
|
||||
|
||||
const int pos = -GetSystemMetrics(SM_CXVIRTUALSCREEN) - 8;
|
||||
if (wil::unique_hrgn hrgn{ CreateRectRgn(pos, 0, (pos + 1), 1) })
|
||||
{
|
||||
DWM_BLURBEHIND bh = { DWM_BB_ENABLE | DWM_BB_BLURREGION, TRUE, hrgn.get(), FALSE };
|
||||
DwmEnableBlurBehindWindow(window, &bh);
|
||||
}
|
||||
|
||||
RECT windowRect = {};
|
||||
// Exclude toolbar from the window's region to be able to use toolbar during tool usage.
|
||||
if (monitor.IsPrimary() && GetWindowRect(window, &windowRect))
|
||||
{
|
||||
// will be freed during SetWindowRgn call
|
||||
const HRGN windowRegion{ CreateRectRgn(windowRect.left, windowRect.top, windowRect.right, windowRect.bottom) };
|
||||
wil::unique_hrgn toolbarRegion{ CreateRectRgn(commonState.toolbarBoundingBox.left(),
|
||||
commonState.toolbarBoundingBox.top(),
|
||||
commonState.toolbarBoundingBox.right(),
|
||||
commonState.toolbarBoundingBox.bottom()) };
|
||||
const auto res = CombineRgn(windowRegion, windowRegion, toolbarRegion.get(), RGN_DIFF);
|
||||
if (res != ERROR)
|
||||
SetWindowRgn(window, windowRegion, true);
|
||||
}
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
std::vector<D2D1::ColorF> AppendCommonOverlayUIColors(const D2D1::ColorF& lineColor)
|
||||
{
|
||||
D2D1::ColorF foreground = D2D1::ColorF::Black;
|
||||
D2D1::ColorF background = D2D1::ColorF(0.96f, 0.96f, 0.96f, 1.0f);
|
||||
D2D1::ColorF border = D2D1::ColorF(0.44f, 0.44f, 0.44f, 0.4f);
|
||||
|
||||
if (WindowsColors::is_dark_mode())
|
||||
{
|
||||
foreground = D2D1::ColorF::White;
|
||||
background = D2D1::ColorF(0.17f, 0.17f, 0.17f, 1.0f);
|
||||
border = D2D1::ColorF(0.44f, 0.44f, 0.44f, 0.4f);
|
||||
}
|
||||
|
||||
return { lineColor, foreground, background, border };
|
||||
}
|
||||
|
||||
void OverlayUIState::RunUILoop()
|
||||
{
|
||||
while (IsWindow(_window) && !_commonState.closeOnOtherMonitors)
|
||||
{
|
||||
const auto cursor = _commonState.cursorPosSystemSpace;
|
||||
const bool cursorOnScreen = _monitorArea.inside(cursor);
|
||||
const bool cursorOverToolbar = _commonState.toolbarBoundingBox.inside(cursor);
|
||||
|
||||
if (cursorOnScreen != _cursorOnScreen)
|
||||
{
|
||||
_cursorOnScreen = cursorOnScreen;
|
||||
if (!cursorOnScreen)
|
||||
{
|
||||
if (_clearOnCursorLeavingScreen)
|
||||
{
|
||||
_d2dState.rt->BeginDraw();
|
||||
_d2dState.rt->Clear();
|
||||
_d2dState.rt->EndDraw();
|
||||
}
|
||||
PostMessageW(_window, WM_CURSOR_LEFT_MONITOR, {}, {});
|
||||
}
|
||||
}
|
||||
if (cursorOnScreen)
|
||||
{
|
||||
_d2dState.rt->BeginDraw();
|
||||
if (!cursorOverToolbar)
|
||||
_tickFunc();
|
||||
else
|
||||
_d2dState.rt->Clear();
|
||||
|
||||
{
|
||||
// TODO: use latch to wait until all threads are created their corresponding d2d textures
|
||||
// in the non-continuous mode
|
||||
// std::lock_guard guard{ gpuAccessLock };
|
||||
_d2dState.rt->EndDraw();
|
||||
}
|
||||
}
|
||||
|
||||
run_message_loop(true, 1);
|
||||
}
|
||||
|
||||
DestroyWindow(_window);
|
||||
}
|
||||
|
||||
template<typename StateT, typename TickFuncT>
|
||||
OverlayUIState::OverlayUIState(StateT& toolState,
|
||||
TickFuncT tickFunc,
|
||||
const CommonState& commonState,
|
||||
HWND window) :
|
||||
_window{ window },
|
||||
_commonState{ commonState },
|
||||
_d2dState{ window, AppendCommonOverlayUIColors(commonState.lineColor) },
|
||||
_tickFunc{ [this, tickFunc, &toolState] {
|
||||
tickFunc(_commonState, toolState, _window, _d2dState);
|
||||
} }
|
||||
{
|
||||
}
|
||||
|
||||
OverlayUIState::~OverlayUIState()
|
||||
{
|
||||
PostMessageW(_window, WM_CLOSE, {}, {});
|
||||
try
|
||||
{
|
||||
if (_uiThread.joinable())
|
||||
_uiThread.join();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// Returning unique_ptr, since we need to pin ui state in memory
|
||||
template<typename ToolT, typename TickFuncT>
|
||||
inline std::unique_ptr<OverlayUIState> OverlayUIState::CreateInternal(ToolT& toolState,
|
||||
TickFuncT tickFunc,
|
||||
CommonState& commonState,
|
||||
const wchar_t* toolWindowClassName,
|
||||
void* windowParam,
|
||||
const MonitorInfo& monitor,
|
||||
const bool clearOnCursorLeavingScreen)
|
||||
{
|
||||
wil::shared_event uiCreatedEvent(wil::EventOptions::ManualReset);
|
||||
std::unique_ptr<OverlayUIState> uiState;
|
||||
auto threadHandle = SpawnLoggedThread(L"OverlayUI thread", [&] {
|
||||
const HWND window = CreateOverlayUIWindow(commonState, monitor, toolWindowClassName, windowParam);
|
||||
uiState = std::unique_ptr<OverlayUIState>{ new OverlayUIState{ toolState, tickFunc, commonState, window } };
|
||||
uiState->_monitorArea = monitor.GetScreenSize(true);
|
||||
uiState->_clearOnCursorLeavingScreen = clearOnCursorLeavingScreen;
|
||||
// we must create window + d2d state in the same thread, then store thread handle in uiState, thus
|
||||
// lifetime is ok here, since we join the thread in destructor
|
||||
auto* state = uiState.get();
|
||||
uiCreatedEvent.SetEvent();
|
||||
|
||||
state->RunUILoop();
|
||||
|
||||
commonState.closeOnOtherMonitors = true;
|
||||
commonState.sessionCompletedCallback();
|
||||
});
|
||||
|
||||
uiCreatedEvent.wait();
|
||||
uiState->_uiThread = std::move(threadHandle);
|
||||
return uiState;
|
||||
}
|
||||
|
||||
std::unique_ptr<OverlayUIState> OverlayUIState::Create(Serialized<MeasureToolState>& toolState,
|
||||
CommonState& commonState,
|
||||
const MonitorInfo& monitor)
|
||||
{
|
||||
return OverlayUIState::CreateInternal(toolState,
|
||||
DrawMeasureToolTick,
|
||||
commonState,
|
||||
NonLocalizable::MeasureToolOverlayWindowName,
|
||||
&toolState,
|
||||
monitor,
|
||||
true);
|
||||
}
|
||||
|
||||
std::unique_ptr<OverlayUIState> OverlayUIState::Create(BoundsToolState& toolState,
|
||||
CommonState& commonState,
|
||||
const MonitorInfo& monitor)
|
||||
{
|
||||
return OverlayUIState::CreateInternal(toolState,
|
||||
DrawBoundsToolTick,
|
||||
commonState,
|
||||
NonLocalizable::BoundsToolOverlayWindowName,
|
||||
&toolState,
|
||||
monitor,
|
||||
false);
|
||||
}
|
||||
51
src/modules/MeasureTool/MeasureToolCore/OverlayUI.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "D2DState.h"
|
||||
#include "ToolState.h"
|
||||
|
||||
#include <common/display/monitors.h>
|
||||
#include <common/utils/serialized.h>
|
||||
|
||||
class OverlayUIState final
|
||||
{
|
||||
template<typename StateT, typename TickFuncT>
|
||||
OverlayUIState(StateT& toolState,
|
||||
TickFuncT tickFunc,
|
||||
const CommonState& commonState,
|
||||
HWND window);
|
||||
|
||||
Box _monitorArea;
|
||||
HWND _window = {};
|
||||
const CommonState& _commonState;
|
||||
D2DState _d2dState;
|
||||
std::function<void()> _tickFunc;
|
||||
std::thread _uiThread;
|
||||
bool _cursorOnScreen = true;
|
||||
bool _clearOnCursorLeavingScreen = false;
|
||||
|
||||
template<typename ToolT, typename TickFuncT>
|
||||
static std::unique_ptr<OverlayUIState> CreateInternal(ToolT& toolState,
|
||||
TickFuncT tickFunc,
|
||||
CommonState& commonState,
|
||||
const wchar_t* toolWindowClassName,
|
||||
void* windowParam,
|
||||
const MonitorInfo& monitor,
|
||||
const bool clearOnCursorLeavingScreen);
|
||||
|
||||
public:
|
||||
OverlayUIState(OverlayUIState&&) noexcept = default;
|
||||
~OverlayUIState();
|
||||
|
||||
static std::unique_ptr<OverlayUIState> Create(BoundsToolState& toolState,
|
||||
CommonState& commonState,
|
||||
const MonitorInfo& monitor);
|
||||
static std::unique_ptr<OverlayUIState> Create(Serialized<MeasureToolState>& toolState,
|
||||
CommonState& commonState,
|
||||
const MonitorInfo& monitor);
|
||||
inline HWND overlayWindowHandle() const
|
||||
{
|
||||
return _window;
|
||||
}
|
||||
|
||||
void RunUILoop();
|
||||
};
|
||||
@@ -0,0 +1,141 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "PerGlyphOpacityTextRender.h"
|
||||
|
||||
PerGlyphOpacityTextRender::PerGlyphOpacityTextRender(
|
||||
wil::com_ptr<ID2D1Factory> pD2DFactory,
|
||||
wil::com_ptr<ID2D1HwndRenderTarget> rt,
|
||||
wil::com_ptr<ID2D1SolidColorBrush> baseBrush) :
|
||||
_pD2DFactory{ pD2DFactory },
|
||||
_rt{ rt },
|
||||
_baseBrush{ baseBrush }
|
||||
{
|
||||
}
|
||||
|
||||
HRESULT __stdcall PerGlyphOpacityTextRender::DrawGlyphRun(void* /*clientDrawingContext*/,
|
||||
FLOAT baselineOriginX,
|
||||
FLOAT baselineOriginY,
|
||||
DWRITE_MEASURING_MODE measuringMode,
|
||||
_In_ const DWRITE_GLYPH_RUN* glyphRun,
|
||||
_In_ const DWRITE_GLYPH_RUN_DESCRIPTION* /*glyphRunDescription*/,
|
||||
IUnknown* clientDrawingEffect) noexcept
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
if (!clientDrawingEffect)
|
||||
{
|
||||
_rt->DrawGlyphRun(D2D1_POINT_2F{ .x = baselineOriginX, .y = baselineOriginY }, glyphRun, _baseBrush.get(), measuringMode);
|
||||
return hr;
|
||||
}
|
||||
// Create the path geometry.
|
||||
wil::com_ptr<ID2D1PathGeometry> pathGeometry;
|
||||
hr = _pD2DFactory->CreatePathGeometry(&pathGeometry);
|
||||
|
||||
// Write to the path geometry using the geometry sink.
|
||||
ID2D1GeometrySink* pSink = nullptr;
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = pathGeometry->Open(&pSink);
|
||||
}
|
||||
|
||||
// Get the glyph run outline geometries back from DirectWrite and place them within the
|
||||
// geometry sink.
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = glyphRun->fontFace->GetGlyphRunOutline(
|
||||
glyphRun->fontEmSize,
|
||||
glyphRun->glyphIndices,
|
||||
glyphRun->glyphAdvances,
|
||||
glyphRun->glyphOffsets,
|
||||
glyphRun->glyphCount,
|
||||
glyphRun->isSideways,
|
||||
glyphRun->bidiLevel % 2,
|
||||
pSink);
|
||||
}
|
||||
|
||||
// Close the geometry sink
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = pSink->Close();
|
||||
}
|
||||
|
||||
// Initialize a matrix to translate the origin of the glyph run.
|
||||
D2D1::Matrix3x2F const matrix = D2D1::Matrix3x2F(
|
||||
1.0f, 0.0f, 0.0f, 1.0f, baselineOriginX, baselineOriginY);
|
||||
|
||||
// Create the transformed geometry
|
||||
wil::com_ptr<ID2D1TransformedGeometry> pTransformedGeometry;
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = _pD2DFactory->CreateTransformedGeometry(pathGeometry.get(), &matrix, &pTransformedGeometry);
|
||||
}
|
||||
|
||||
float prevOpacity = _baseBrush->GetOpacity();
|
||||
OpacityEffect* opacityEffect = nullptr;
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = clientDrawingEffect->QueryInterface(__uuidof(IDrawingEffect), reinterpret_cast<void**>(&opacityEffect));
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
_baseBrush->SetOpacity(opacityEffect->alpha);
|
||||
}
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
_rt->DrawGeometry(pTransformedGeometry.get(), _baseBrush.get());
|
||||
_rt->FillGeometry(pTransformedGeometry.get(), _baseBrush.get());
|
||||
_baseBrush->SetOpacity(prevOpacity);
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT __stdcall PerGlyphOpacityTextRender::DrawUnderline(void* /*clientDrawingContext*/,
|
||||
FLOAT /*baselineOriginX*/,
|
||||
FLOAT /*baselineOriginY*/,
|
||||
_In_ const DWRITE_UNDERLINE* /*underline*/,
|
||||
IUnknown* /*clientDrawingEffect*/) noexcept
|
||||
{
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT __stdcall PerGlyphOpacityTextRender::DrawStrikethrough(void* /*clientDrawingContext*/,
|
||||
FLOAT /*baselineOriginX*/,
|
||||
FLOAT /*baselineOriginY*/,
|
||||
_In_ const DWRITE_STRIKETHROUGH* /*strikethrough*/,
|
||||
IUnknown* /*clientDrawingEffect*/) noexcept
|
||||
{
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT __stdcall PerGlyphOpacityTextRender::DrawInlineObject(void* /*clientDrawingContext*/,
|
||||
FLOAT /*originX*/,
|
||||
FLOAT /*originY*/,
|
||||
IDWriteInlineObject* /*inlineObject*/,
|
||||
BOOL /*isSideways*/,
|
||||
BOOL /*isRightToLeft*/,
|
||||
IUnknown* /*clientDrawingEffect*/) noexcept
|
||||
{
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT __stdcall PerGlyphOpacityTextRender::IsPixelSnappingDisabled(void* /*clientDrawingContext*/, BOOL* isDisabled) noexcept
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, isDisabled);
|
||||
|
||||
*isDisabled = false;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT __stdcall PerGlyphOpacityTextRender::GetCurrentTransform(void* /*clientDrawingContext*/, DWRITE_MATRIX* transform) noexcept
|
||||
{
|
||||
_rt->GetTransform(reinterpret_cast<D2D1_MATRIX_3X2_F*>(transform));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT __stdcall PerGlyphOpacityTextRender::GetPixelsPerDip(void* /*clientDrawingContext*/, FLOAT* pixelsPerDip) noexcept
|
||||
{
|
||||
_rt->GetDpi(pixelsPerDip, pixelsPerDip);
|
||||
return S_OK;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <winrt/base.h>
|
||||
#include <wil/resource.h>
|
||||
#include <Windows.h>
|
||||
#include <dwrite.h>
|
||||
|
||||
struct __declspec(uuid("{01557C9F-E3DD-4C28-AE64-E731EAB479CC}")) IDrawingEffect : IUnknown
|
||||
{
|
||||
};
|
||||
|
||||
struct OpacityEffect : winrt::implements<OpacityEffect, IDrawingEffect>
|
||||
{
|
||||
float alpha = 1.f;
|
||||
};
|
||||
|
||||
struct PerGlyphOpacityTextRender : winrt::implements<PerGlyphOpacityTextRender, IDWriteTextRenderer>
|
||||
{
|
||||
wil::com_ptr<ID2D1Factory> _pD2DFactory;
|
||||
wil::com_ptr<ID2D1HwndRenderTarget> _rt;
|
||||
wil::com_ptr<ID2D1SolidColorBrush> _baseBrush;
|
||||
|
||||
PerGlyphOpacityTextRender(
|
||||
wil::com_ptr<ID2D1Factory> pD2DFactory,
|
||||
wil::com_ptr<ID2D1HwndRenderTarget> rt,
|
||||
wil::com_ptr<ID2D1SolidColorBrush> baseBrush);
|
||||
|
||||
HRESULT __stdcall DrawGlyphRun(void* clientDrawingContext,
|
||||
FLOAT baselineOriginX,
|
||||
FLOAT baselineOriginY,
|
||||
DWRITE_MEASURING_MODE measuringMode,
|
||||
_In_ const DWRITE_GLYPH_RUN* glyphRun,
|
||||
_In_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription,
|
||||
IUnknown* clientDrawingEffect) noexcept override;
|
||||
HRESULT __stdcall DrawUnderline(void* clientDrawingContext,
|
||||
FLOAT baselineOriginX,
|
||||
FLOAT baselineOriginY,
|
||||
_In_ const DWRITE_UNDERLINE* underline,
|
||||
IUnknown* clientDrawingEffect) noexcept override;
|
||||
HRESULT __stdcall DrawStrikethrough(void* clientDrawingContext,
|
||||
FLOAT baselineOriginX,
|
||||
FLOAT baselineOriginY,
|
||||
_In_ const DWRITE_STRIKETHROUGH* strikethrough,
|
||||
IUnknown* clientDrawingEffect) noexcept override;
|
||||
HRESULT __stdcall DrawInlineObject(void* clientDrawingContext,
|
||||
FLOAT originX,
|
||||
FLOAT originY,
|
||||
IDWriteInlineObject* inlineObject,
|
||||
BOOL isSideways,
|
||||
BOOL isRightToLeft,
|
||||
IUnknown* clientDrawingEffect) noexcept override;
|
||||
HRESULT __stdcall IsPixelSnappingDisabled(void* clientDrawingContext, BOOL* isDisabled) noexcept override;
|
||||
HRESULT __stdcall GetCurrentTransform(void* clientDrawingContext, DWRITE_MATRIX* transform) noexcept override;
|
||||
HRESULT __stdcall GetPixelsPerDip(void* clientDrawingContext, FLOAT* pixelsPerDip) noexcept override;
|
||||
};
|
||||
@@ -0,0 +1,156 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <common/display/dpi_aware.h>
|
||||
#include <common/display/monitors.h>
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include <common/logger/logger.h>
|
||||
|
||||
#include "../MeasureToolModuleInterface/trace.h"
|
||||
#include "constants.h"
|
||||
#include "PowerToys.MeasureToolCore.h"
|
||||
#include "Core.g.cpp"
|
||||
#include "OverlayUI.h"
|
||||
#include "ScreenCapturing.h"
|
||||
|
||||
//#define DEBUG_PRIMARY_MONITOR_ONLY
|
||||
|
||||
std::recursive_mutex gpuAccessLock;
|
||||
|
||||
namespace winrt::PowerToys::MeasureToolCore::implementation
|
||||
{
|
||||
void Core::MouseCaptureThread()
|
||||
{
|
||||
while (!_stopMouseCaptureThreadSignal.is_signaled())
|
||||
{
|
||||
static_assert(sizeof(_commonState.cursorPosSystemSpace) == sizeof(LONG64));
|
||||
POINT cursorPos = {};
|
||||
GetCursorPos(&cursorPos);
|
||||
InterlockedExchange64(reinterpret_cast<LONG64*>(&_commonState.cursorPosSystemSpace), std::bit_cast<LONG64>(cursorPos));
|
||||
std::this_thread::sleep_for(consts::TARGET_FRAME_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
Core::Core() :
|
||||
_mouseCaptureThread{ [this] { MouseCaptureThread(); } },
|
||||
_stopMouseCaptureThreadSignal{ wil::EventOptions::ManualReset }
|
||||
{
|
||||
Trace::RegisterProvider();
|
||||
LoggerHelpers::init_logger(L"Measure Tool", L"Core", "Measure Tool");
|
||||
}
|
||||
|
||||
Core::~Core()
|
||||
{
|
||||
_stopMouseCaptureThreadSignal.SetEvent();
|
||||
_mouseCaptureThread.join();
|
||||
|
||||
ResetState();
|
||||
Trace::UnregisterProvider();
|
||||
}
|
||||
|
||||
void Core::ResetState()
|
||||
{
|
||||
_commonState.closeOnOtherMonitors = true;
|
||||
_overlayUIStates.clear();
|
||||
_boundsToolState = { .commonState = &_commonState };
|
||||
for (auto& thread : _screenCaptureThreads)
|
||||
{
|
||||
if (thread.joinable())
|
||||
{
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
_screenCaptureThreads.clear();
|
||||
_measureToolState.Reset();
|
||||
_measureToolState.Access([&](MeasureToolState& s) {
|
||||
s.commonState = &_commonState;
|
||||
});
|
||||
|
||||
_settings = Settings::LoadFromFile();
|
||||
|
||||
_commonState.lineColor.r = _settings.lineColor[0] / 255.f;
|
||||
_commonState.lineColor.g = _settings.lineColor[1] / 255.f;
|
||||
_commonState.lineColor.b = _settings.lineColor[2] / 255.f;
|
||||
_commonState.closeOnOtherMonitors = false;
|
||||
}
|
||||
|
||||
void Core::StartBoundsTool()
|
||||
{
|
||||
ResetState();
|
||||
|
||||
#if defined(DEBUG_PRIMARY_MONITOR_ONLY)
|
||||
const auto& monitorInfo = MonitorInfo::GetPrimaryMonitor();
|
||||
#else
|
||||
for (const auto& monitorInfo : MonitorInfo::GetMonitors(true))
|
||||
#endif
|
||||
{
|
||||
auto overlayUI = OverlayUIState::Create(_boundsToolState, _commonState, monitorInfo);
|
||||
#if !defined(DEBUG_PRIMARY_MONITOR_ONLY)
|
||||
if (!overlayUI)
|
||||
continue;
|
||||
#endif
|
||||
_overlayUIStates.push_back(std::move(overlayUI));
|
||||
}
|
||||
Trace::BoundsToolActivated();
|
||||
}
|
||||
|
||||
void Core::StartMeasureTool(const bool horizontal, const bool vertical)
|
||||
{
|
||||
ResetState();
|
||||
|
||||
_measureToolState.Access([horizontal, vertical, this](MeasureToolState& state) {
|
||||
if (horizontal)
|
||||
state.global.mode = vertical ? MeasureToolState::Mode::Cross : MeasureToolState::Mode::Horizontal;
|
||||
else
|
||||
state.global.mode = MeasureToolState::Mode::Vertical;
|
||||
|
||||
state.global.continuousCapture = _settings.continuousCapture;
|
||||
state.global.drawFeetOnCross = _settings.drawFeetOnCross;
|
||||
state.global.pixelTolerance = _settings.pixelTolerance;
|
||||
state.global.perColorChannelEdgeDetection = _settings.perColorChannelEdgeDetection;
|
||||
});
|
||||
|
||||
#if defined(DEBUG_PRIMARY_MONITOR_ONLY)
|
||||
const auto& monitorInfo = MonitorInfo::GetPrimaryMonitor();
|
||||
#else
|
||||
for (const auto& monitorInfo : MonitorInfo::GetMonitors(true))
|
||||
#endif
|
||||
{
|
||||
auto overlayUI = OverlayUIState::Create(_measureToolState, _commonState, monitorInfo);
|
||||
#if !defined(DEBUG_PRIMARY_MONITOR_ONLY)
|
||||
if (!overlayUI)
|
||||
continue;
|
||||
#endif
|
||||
_screenCaptureThreads.emplace_back(StartCapturingThread(_commonState,
|
||||
_measureToolState,
|
||||
overlayUI->overlayWindowHandle(),
|
||||
monitorInfo));
|
||||
_overlayUIStates.push_back(std::move(overlayUI));
|
||||
}
|
||||
Trace::MeasureToolActivated();
|
||||
}
|
||||
|
||||
void MeasureToolCore::implementation::Core::SetToolCompletionEvent(ToolSessionCompleted sessionCompletedTrigger)
|
||||
{
|
||||
_commonState.sessionCompletedCallback = [trigger = std::move(sessionCompletedTrigger)] {
|
||||
trigger();
|
||||
};
|
||||
}
|
||||
|
||||
void MeasureToolCore::implementation::Core::SetToolbarBoundingBox(const uint32_t fromX,
|
||||
const uint32_t fromY,
|
||||
const uint32_t toX,
|
||||
const uint32_t toY)
|
||||
{
|
||||
_commonState.toolbarBoundingBox = Box{ RECT{ .left = static_cast<long>(fromX),
|
||||
.top = static_cast<long>(fromY),
|
||||
.right = static_cast<long>(toX),
|
||||
.bottom = static_cast<long>(toY) } };
|
||||
}
|
||||
|
||||
float MeasureToolCore::implementation::Core::GetDPIScaleForWindow(uint64_t windowHandle)
|
||||
{
|
||||
UINT dpi = DPIAware::DEFAULT_DPI;
|
||||
DPIAware::GetScreenDPIForWindow(std::bit_cast<HWND>(windowHandle), dpi);
|
||||
return static_cast<float>(dpi) / DPIAware::DEFAULT_DPI;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "Core.g.h"
|
||||
#include "ToolState.h"
|
||||
#include "OverlayUI.h"
|
||||
#include "Settings.h"
|
||||
|
||||
#include <common/utils/serialized.h>
|
||||
|
||||
namespace winrt::PowerToys::MeasureToolCore::implementation
|
||||
{
|
||||
struct Core : CoreT<Core>
|
||||
{
|
||||
Core();
|
||||
~Core();
|
||||
void StartBoundsTool();
|
||||
void StartMeasureTool(const bool horizontal, const bool vertical);
|
||||
void SetToolCompletionEvent(ToolSessionCompleted sessionCompletedTrigger);
|
||||
void SetToolbarBoundingBox(const uint32_t fromX, const uint32_t fromY, const uint32_t toX, const uint32_t toY);
|
||||
void ResetState();
|
||||
float GetDPIScaleForWindow(uint64_t windowHandle);
|
||||
void MouseCaptureThread();
|
||||
|
||||
std::thread _mouseCaptureThread;
|
||||
std::vector<std::thread> _screenCaptureThreads;
|
||||
wil::shared_event _stopMouseCaptureThreadSignal;
|
||||
|
||||
std::vector<std::unique_ptr<OverlayUIState>> _overlayUIStates;
|
||||
Serialized<MeasureToolState> _measureToolState;
|
||||
BoundsToolState _boundsToolState;
|
||||
CommonState _commonState;
|
||||
Settings _settings;
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::PowerToys::MeasureToolCore::factory_implementation
|
||||
{
|
||||
struct Core : CoreT<Core, implementation::Core>
|
||||
{
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace PowerToys
|
||||
{
|
||||
namespace MeasureToolCore
|
||||
{
|
||||
struct Point
|
||||
{
|
||||
Int32 X;
|
||||
Int32 Y;
|
||||
};
|
||||
|
||||
delegate void ToolSessionCompleted();
|
||||
|
||||
[default_interface]
|
||||
runtimeclass Core
|
||||
{
|
||||
Core();
|
||||
void SetToolCompletionEvent(event ToolSessionCompleted completionTrigger);
|
||||
void StartMeasureTool(Boolean horizontal, Boolean vertical);
|
||||
void StartBoundsTool();
|
||||
void ResetState();
|
||||
|
||||
void SetToolbarBoundingBox(Int32 fromX, Int32 fromY, Int32 toX, Int32 toY);
|
||||
Single GetDPIScaleForWindow(Int64 windowHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#include <windows.h>
|
||||
#include "resource.h"
|
||||
#include "../../../common/version/version.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
#include "winres.h"
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION FILE_VERSION
|
||||
PRODUCTVERSION PRODUCT_VERSION
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS VS_FF_DEBUG
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_DLL
|
||||
FILESUBTYPE VFT2_UNKNOWN
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
|
||||
BEGIN
|
||||
VALUE "CompanyName", COMPANY_NAME
|
||||
VALUE "FileDescription", FILE_DESCRIPTION
|
||||
VALUE "FileVersion", FILE_VERSION_STRING
|
||||
VALUE "InternalName", INTERNAL_NAME
|
||||
VALUE "LegalCopyright", COPYRIGHT_NOTE
|
||||
VALUE "OriginalFilename", ORIGINAL_FILENAME
|
||||
VALUE "ProductName", PRODUCT_NAME
|
||||
VALUE "ProductVersion", PRODUCT_VERSION_STRING
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
|
||||
END
|
||||
END
|
||||
@@ -0,0 +1,158 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.1.4\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.1.4\build\native\Microsoft.WindowsAppSDK.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22000.197\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22000.197\build\Microsoft.Windows.SDK.BuildTools.props')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<CppWinRTOptimized>true</CppWinRTOptimized>
|
||||
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
|
||||
<MinimalCoreWin>true</MinimalCoreWin>
|
||||
<ProjectGuid>{54a93af7-60c7-4f6c-99d2-fbb1f75f853a}</ProjectGuid>
|
||||
<ProjectName>PowerToys.MeasureToolCore</ProjectName>
|
||||
<RootNamespace>PowerToys.MeasureToolCore</RootNamespace>
|
||||
<!--
|
||||
$(TargetName) should be same as $(RootNamespace) so that the produced binaries (.exe/.pri/etc.)
|
||||
have a name that matches the .winmd
|
||||
-->
|
||||
<TargetName>PowerToys.MeasureToolCore</TargetName>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<MinimumVisualStudioVersion>16.0</MinimumVisualStudioVersion>
|
||||
<AppContainerApplication>false</AppContainerApplication>
|
||||
<AppxPackage>false</AppxPackage>
|
||||
<ApplicationType>Windows Store</ApplicationType>
|
||||
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>
|
||||
<OverrideWindowsTargetPlatformVersion>true</OverrideWindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.20348.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformMinVersion>10.0.19041.0</WindowsTargetPlatformMinVersion>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<GenerateManifest>false</GenerateManifest>
|
||||
<DesktopCompatible>true</DesktopCompatible>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<Import Project="..\..\..\..\deps\spdlog.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\MeasureTool\</OutDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)src\;..\..\..\common\Telemetry;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
|
||||
<ModuleDefinitionFile>MeasureTool.def</ModuleDefinitionFile>
|
||||
<AdditionalDependencies>Shell32.lib;Shcore.lib;Dwmapi.lib;Gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<Manifest Include="app.manifest" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="MeasureToolCore.def" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\MeasureToolModuleInterface\trace.h" />
|
||||
<ClInclude Include="BoundsToolOverlayUI.h" />
|
||||
<ClInclude Include="Clipboard.h" />
|
||||
<ClInclude Include="constants.h" />
|
||||
<ClInclude Include="D2DState.h" />
|
||||
<ClInclude Include="MeasureToolOverlayUI.h" />
|
||||
<ClInclude Include="PerGlyphOpacityTextRender.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="Settings.h" />
|
||||
<ClInclude Include="PowerToys.MeasureToolCore.h">
|
||||
<DependentUpon>PowerToys.MeasureToolCore.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="BGRATextureView.h" />
|
||||
<ClInclude Include="EdgeDetection.h" />
|
||||
<ClInclude Include="ToolState.h" />
|
||||
<ClInclude Include="OverlayUI.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="ScreenCapturing.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\MeasureToolModuleInterface\trace.cpp" />
|
||||
<ClCompile Include="BGRATextureView.cpp" />
|
||||
<ClCompile Include="BoundsToolOverlayUI.cpp" />
|
||||
<ClCompile Include="Clipboard.cpp" />
|
||||
<ClCompile Include="D2DState.cpp" />
|
||||
<ClCompile Include="EdgeDetection.cpp" />
|
||||
<ClCompile Include="MeasureToolOverlayUI.cpp" />
|
||||
<ClCompile Include="OverlayUI.cpp" />
|
||||
<ClCompile Include="PerGlyphOpacityTextRender.cpp" />
|
||||
<ClCompile Include="PowerToys.MeasureToolCore.cpp">
|
||||
<DependentUpon>PowerToys.MeasureToolCore.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
|
||||
<ClCompile Include="ScreenCapturing.cpp" />
|
||||
<ClCompile Include="Settings.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="PowerToys.MeasureToolCore.idl">
|
||||
<WarningLevel>2</WarningLevel>
|
||||
<WarnAsError>true</WarnAsError>
|
||||
</Midl>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\Display\Display.vcxproj">
|
||||
<Project>{caba8dfb-823b-4bf2-93ac-3f31984150d9}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
|
||||
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
|
||||
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\common\Themes\Themes.vcxproj">
|
||||
<Project>{98537082-0fdb-40de-abd8-0dc5a4269bab}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="PowerToys.MeasureToolCore.rc" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22000.197\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22000.197\build\Microsoft.Windows.SDK.BuildTools.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.1.4\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.1.4\build\native\Microsoft.WindowsAppSDK.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220201.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220201.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22000.197\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22000.197\build\Microsoft.Windows.SDK.BuildTools.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22000.197\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22000.197\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.1.4\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.1.4\build\native\Microsoft.WindowsAppSDK.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.1.4\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.1.4\build\native\Microsoft.WindowsAppSDK.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220201.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220201.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Midl Include="PowerToys.MeasureToolCore.idl" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="pch.cpp" />
|
||||
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
|
||||
<ClCompile Include="ScreenCapturing.cpp" />
|
||||
<ClCompile Include="OverlayUI.cpp" />
|
||||
<ClCompile Include="BGRATextureView.cpp" />
|
||||
<ClCompile Include="EdgeDetection.cpp" />
|
||||
<ClCompile Include="Settings.cpp" />
|
||||
<ClCompile Include="D2DState.cpp" />
|
||||
<ClCompile Include="BoundsToolOverlayUI.cpp" />
|
||||
<ClCompile Include="MeasureToolOverlayUI.cpp" />
|
||||
<ClCompile Include="..\MeasureToolModuleInterface\trace.cpp" />
|
||||
<ClCompile Include="Clipboard.cpp" />
|
||||
<ClCompile Include="PerGlyphOpacityTextRender.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="ScreenCapturing.h" />
|
||||
<ClInclude Include="OverlayUI.h" />
|
||||
<ClInclude Include="BGRATextureView.h" />
|
||||
<ClInclude Include="EdgeDetection.h" />
|
||||
<ClInclude Include="ToolState.h" />
|
||||
<ClInclude Include="Settings.h" />
|
||||
<ClInclude Include="BoundsToolOverlayUI.h" />
|
||||
<ClInclude Include="D2DState.h" />
|
||||
<ClInclude Include="MeasureToolOverlayUI.h" />
|
||||
<ClInclude Include="constants.h" />
|
||||
<ClInclude Include="..\MeasureToolModuleInterface\trace.h" />
|
||||
<ClInclude Include="Clipboard.h" />
|
||||
<ClInclude Include="PerGlyphOpacityTextRender.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Assets">
|
||||
<UniqueIdentifier>{e48dc53e-40b1-40cb-970a-f89935452892}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Manifest Include="app.manifest" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
<None Include="MeasureToolCore.def" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="PowerToys.MeasureToolCore.rc" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
443
src/modules/MeasureTool/MeasureToolCore/ScreenCapturing.cpp
Normal file
@@ -0,0 +1,443 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include "BGRATextureView.h"
|
||||
#include "constants.h"
|
||||
#include "CoordinateSystemConversion.h"
|
||||
#include "EdgeDetection.h"
|
||||
#include "ScreenCapturing.h"
|
||||
|
||||
#include <common/Display/monitors.h>
|
||||
|
||||
//#define DEBUG_EDGES
|
||||
|
||||
class MappedTextureView
|
||||
{
|
||||
winrt::com_ptr<ID3D11DeviceContext> context;
|
||||
winrt::com_ptr<ID3D11Texture2D> texture;
|
||||
|
||||
public:
|
||||
BGRATextureView view;
|
||||
MappedTextureView(winrt::com_ptr<ID3D11Texture2D> _texture,
|
||||
winrt::com_ptr<ID3D11DeviceContext> _context,
|
||||
const size_t textureWidth,
|
||||
const size_t textureHeight) :
|
||||
texture{ std::move(_texture) }, context{ std::move(_context) }
|
||||
{
|
||||
D3D11_TEXTURE2D_DESC desc;
|
||||
texture->GetDesc(&desc);
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE resource = {};
|
||||
winrt::check_hresult(context->Map(texture.get(), D3D11CalcSubresource(0, 0, 0), D3D11_MAP_READ, 0, &resource));
|
||||
|
||||
view.pixels = static_cast<const uint32_t*>(resource.pData);
|
||||
view.pitch = resource.RowPitch / 4;
|
||||
view.width = textureWidth;
|
||||
view.height = textureHeight;
|
||||
}
|
||||
|
||||
MappedTextureView(MappedTextureView&&) = default;
|
||||
MappedTextureView& operator=(MappedTextureView&&) = default;
|
||||
|
||||
inline winrt::com_ptr<ID3D11Texture2D> GetTexture() const
|
||||
{
|
||||
return texture;
|
||||
}
|
||||
|
||||
~MappedTextureView()
|
||||
{
|
||||
if (context && texture)
|
||||
context->Unmap(texture.get(), D3D11CalcSubresource(0, 0, 0));
|
||||
}
|
||||
};
|
||||
|
||||
class D3DCaptureState final
|
||||
{
|
||||
winrt::com_ptr<ID3D11Device> d3dDevice;
|
||||
winrt::IDirect3DDevice device;
|
||||
winrt::com_ptr<IDXGISwapChain1> swapChain;
|
||||
winrt::com_ptr<ID3D11DeviceContext> context;
|
||||
winrt::SizeInt32 frameSize;
|
||||
|
||||
winrt::DirectXPixelFormat pixelFormat;
|
||||
winrt::Direct3D11CaptureFramePool framePool;
|
||||
winrt::GraphicsCaptureSession session;
|
||||
|
||||
std::function<void(MappedTextureView)> frameCallback;
|
||||
Box monitorArea;
|
||||
bool captureOutsideOfMonitor = false;
|
||||
|
||||
D3DCaptureState(winrt::com_ptr<ID3D11Device> d3dDevice,
|
||||
winrt::IDirect3DDevice _device,
|
||||
winrt::com_ptr<IDXGISwapChain1> _swapChain,
|
||||
winrt::com_ptr<ID3D11DeviceContext> _context,
|
||||
const winrt::GraphicsCaptureItem& item,
|
||||
winrt::DirectXPixelFormat _pixelFormat,
|
||||
Box monitorArea,
|
||||
const bool captureOutsideOfMonitor);
|
||||
|
||||
winrt::com_ptr<ID3D11Texture2D> CopyFrameToCPU(const winrt::com_ptr<ID3D11Texture2D>& texture);
|
||||
|
||||
void OnFrameArrived(const winrt::Direct3D11CaptureFramePool& sender, const winrt::IInspectable&);
|
||||
|
||||
void StartSessionInPreferredMode();
|
||||
|
||||
std::mutex destructorMutex;
|
||||
|
||||
public:
|
||||
static std::unique_ptr<D3DCaptureState> Create(winrt::GraphicsCaptureItem item,
|
||||
const winrt::DirectXPixelFormat pixelFormat,
|
||||
Box monitorSize,
|
||||
const bool captureOutsideOfMonitor);
|
||||
|
||||
~D3DCaptureState();
|
||||
|
||||
void StartCapture(std::function<void(MappedTextureView)> _frameCallback);
|
||||
MappedTextureView CaptureSingleFrame();
|
||||
|
||||
void StopCapture();
|
||||
};
|
||||
|
||||
D3DCaptureState::D3DCaptureState(winrt::com_ptr<ID3D11Device> _d3dDevice,
|
||||
winrt::IDirect3DDevice _device,
|
||||
winrt::com_ptr<IDXGISwapChain1> _swapChain,
|
||||
winrt::com_ptr<ID3D11DeviceContext> _context,
|
||||
const winrt::GraphicsCaptureItem& item,
|
||||
winrt::DirectXPixelFormat _pixelFormat,
|
||||
Box _monitorArea,
|
||||
const bool _captureOutsideOfMonitor) :
|
||||
d3dDevice{ std::move(_d3dDevice) },
|
||||
device{ std::move(_device) },
|
||||
swapChain{ std::move(_swapChain) },
|
||||
context{ std::move(_context) },
|
||||
frameSize{ item.Size() },
|
||||
pixelFormat{ std::move(_pixelFormat) },
|
||||
framePool{ winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(device, pixelFormat, 2, item.Size()) },
|
||||
session{ framePool.CreateCaptureSession(item) },
|
||||
monitorArea{ _monitorArea },
|
||||
captureOutsideOfMonitor{ _captureOutsideOfMonitor }
|
||||
{
|
||||
framePool.FrameArrived({ this, &D3DCaptureState::OnFrameArrived });
|
||||
}
|
||||
|
||||
winrt::com_ptr<ID3D11Texture2D> D3DCaptureState::CopyFrameToCPU(const winrt::com_ptr<ID3D11Texture2D>& frameTexture)
|
||||
{
|
||||
D3D11_TEXTURE2D_DESC desc = {};
|
||||
frameTexture->GetDesc(&desc);
|
||||
desc.Usage = D3D11_USAGE_STAGING;
|
||||
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
desc.MiscFlags = 0;
|
||||
desc.BindFlags = 0;
|
||||
|
||||
winrt::com_ptr<ID3D11Texture2D> cpuTexture;
|
||||
winrt::check_hresult(d3dDevice->CreateTexture2D(&desc, nullptr, cpuTexture.put()));
|
||||
context->CopyResource(cpuTexture.get(), frameTexture.get());
|
||||
|
||||
return cpuTexture;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
auto GetDXGIInterfaceFromObject(winrt::IInspectable const& object)
|
||||
{
|
||||
auto access = object.as<Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>();
|
||||
winrt::com_ptr<T> result;
|
||||
winrt::check_hresult(access->GetInterface(winrt::guid_of<T>(), result.put_void()));
|
||||
return result;
|
||||
}
|
||||
|
||||
void D3DCaptureState::OnFrameArrived(const winrt::Direct3D11CaptureFramePool& sender, const winrt::IInspectable&)
|
||||
{
|
||||
// Prevent calling a callback on a partially destroyed state
|
||||
std::unique_lock callbackLock{ destructorMutex };
|
||||
|
||||
bool resized = false;
|
||||
POINT cursorPos = {};
|
||||
GetCursorPos(&cursorPos);
|
||||
|
||||
auto frame = sender.TryGetNextFrame();
|
||||
winrt::check_bool(frame);
|
||||
if (monitorArea.inside(cursorPos) || captureOutsideOfMonitor)
|
||||
{
|
||||
winrt::com_ptr<ID3D11Texture2D> texture;
|
||||
{
|
||||
if (auto newFrameSize = frame.ContentSize(); newFrameSize != frameSize)
|
||||
{
|
||||
winrt::check_hresult(swapChain->ResizeBuffers(2,
|
||||
static_cast<uint32_t>(newFrameSize.Height),
|
||||
static_cast<uint32_t>(newFrameSize.Width),
|
||||
static_cast<DXGI_FORMAT>(pixelFormat),
|
||||
0));
|
||||
frameSize = newFrameSize;
|
||||
resized = true;
|
||||
}
|
||||
|
||||
winrt::check_hresult(swapChain->GetBuffer(0, winrt::guid_of<ID3D11Texture2D>(), texture.put_void()));
|
||||
auto gpuTexture = GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
|
||||
texture = CopyFrameToCPU(gpuTexture);
|
||||
MappedTextureView textureView{ texture, context, static_cast<size_t>(frameSize.Width), static_cast<size_t>(frameSize.Height) };
|
||||
|
||||
frameCallback(std::move(textureView));
|
||||
}
|
||||
}
|
||||
|
||||
frame.Close();
|
||||
|
||||
DXGI_PRESENT_PARAMETERS presentParameters = {};
|
||||
swapChain->Present1(1, 0, &presentParameters);
|
||||
|
||||
if (resized)
|
||||
{
|
||||
framePool.Recreate(device, pixelFormat, 2, frameSize);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<D3DCaptureState> D3DCaptureState::Create(winrt::GraphicsCaptureItem item,
|
||||
const winrt::DirectXPixelFormat pixelFormat,
|
||||
Box monitorArea,
|
||||
const bool captureOutsideOfMonitor)
|
||||
{
|
||||
std::lock_guard guard{ gpuAccessLock };
|
||||
|
||||
winrt::com_ptr<ID3D11Device> d3dDevice;
|
||||
UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
||||
#ifndef NDEBUG
|
||||
flags |= D3D11_CREATE_DEVICE_DEBUG;
|
||||
#endif
|
||||
HRESULT hr =
|
||||
D3D11CreateDevice(nullptr,
|
||||
D3D_DRIVER_TYPE_HARDWARE,
|
||||
nullptr,
|
||||
flags,
|
||||
nullptr,
|
||||
0,
|
||||
D3D11_SDK_VERSION,
|
||||
d3dDevice.put(),
|
||||
nullptr,
|
||||
nullptr);
|
||||
if (hr == DXGI_ERROR_UNSUPPORTED)
|
||||
{
|
||||
hr = D3D11CreateDevice(nullptr,
|
||||
D3D_DRIVER_TYPE_WARP,
|
||||
nullptr,
|
||||
flags,
|
||||
nullptr,
|
||||
0,
|
||||
D3D11_SDK_VERSION,
|
||||
d3dDevice.put(),
|
||||
nullptr,
|
||||
nullptr);
|
||||
}
|
||||
winrt::check_hresult(hr);
|
||||
|
||||
auto dxgiDevice = d3dDevice.as<IDXGIDevice>();
|
||||
winrt::com_ptr<IInspectable> d3dDeviceInspectable;
|
||||
winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.get(), d3dDeviceInspectable.put()));
|
||||
|
||||
const DXGI_SWAP_CHAIN_DESC1 desc = {
|
||||
.Width = static_cast<uint32_t>(item.Size().Width),
|
||||
.Height = static_cast<uint32_t>(item.Size().Height),
|
||||
.Format = static_cast<DXGI_FORMAT>(pixelFormat),
|
||||
.SampleDesc = { .Count = 1, .Quality = 0 },
|
||||
.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
|
||||
.BufferCount = 2,
|
||||
.Scaling = DXGI_SCALING_STRETCH,
|
||||
.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
|
||||
.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED,
|
||||
};
|
||||
winrt::com_ptr<IDXGIAdapter> adapter;
|
||||
winrt::check_hresult(dxgiDevice->GetParent(winrt::guid_of<IDXGIAdapter>(), adapter.put_void()));
|
||||
winrt::com_ptr<IDXGIFactory2> factory;
|
||||
winrt::check_hresult(adapter->GetParent(winrt::guid_of<IDXGIFactory2>(), factory.put_void()));
|
||||
|
||||
winrt::com_ptr<IDXGISwapChain1> swapChain;
|
||||
winrt::check_hresult(factory->CreateSwapChainForComposition(d3dDevice.get(), &desc, nullptr, swapChain.put()));
|
||||
|
||||
winrt::com_ptr<ID3D11DeviceContext> context;
|
||||
d3dDevice->GetImmediateContext(context.put());
|
||||
winrt::check_bool(context);
|
||||
|
||||
// We must create the object in a heap, since we need to pin it in memory to receive callbacks
|
||||
auto statePtr = new D3DCaptureState{ d3dDevice,
|
||||
d3dDeviceInspectable.as<winrt::IDirect3DDevice>(),
|
||||
std::move(swapChain),
|
||||
std::move(context),
|
||||
item,
|
||||
pixelFormat,
|
||||
monitorArea,
|
||||
captureOutsideOfMonitor };
|
||||
|
||||
return std::unique_ptr<D3DCaptureState>{ statePtr };
|
||||
}
|
||||
|
||||
D3DCaptureState::~D3DCaptureState()
|
||||
{
|
||||
std::unique_lock callbackLock{ destructorMutex };
|
||||
StopCapture();
|
||||
framePool.Close();
|
||||
device.Close();
|
||||
}
|
||||
|
||||
void D3DCaptureState::StartSessionInPreferredMode()
|
||||
{
|
||||
// Try disable border if possible (available on Windows ver >= 20348)
|
||||
if (auto session3 = session.try_as<winrt::IGraphicsCaptureSession3>())
|
||||
{
|
||||
session3.IsBorderRequired(false);
|
||||
}
|
||||
|
||||
session.IsCursorCaptureEnabled(false);
|
||||
session.StartCapture();
|
||||
}
|
||||
|
||||
void D3DCaptureState::StartCapture(std::function<void(MappedTextureView)> _frameCallback)
|
||||
{
|
||||
frameCallback = std::move(_frameCallback);
|
||||
StartSessionInPreferredMode();
|
||||
}
|
||||
|
||||
MappedTextureView D3DCaptureState::CaptureSingleFrame()
|
||||
{
|
||||
std::optional<MappedTextureView> result;
|
||||
wil::shared_event frameArrivedEvent(wil::EventOptions::ManualReset);
|
||||
|
||||
frameCallback = [frameArrivedEvent, &result, this](MappedTextureView tex) {
|
||||
if (result)
|
||||
return;
|
||||
|
||||
StopCapture();
|
||||
result.emplace(std::move(tex));
|
||||
frameArrivedEvent.SetEvent();
|
||||
};
|
||||
std::lock_guard guard{ gpuAccessLock };
|
||||
StartSessionInPreferredMode();
|
||||
|
||||
frameArrivedEvent.wait();
|
||||
|
||||
assert(result.has_value());
|
||||
return std::move(*result);
|
||||
}
|
||||
|
||||
void D3DCaptureState::StopCapture()
|
||||
{
|
||||
session.Close();
|
||||
}
|
||||
|
||||
void UpdateCaptureState(const CommonState& commonState,
|
||||
Serialized<MeasureToolState>& state,
|
||||
HWND window,
|
||||
const MappedTextureView& textureView,
|
||||
const bool continuousCapture)
|
||||
{
|
||||
const auto cursorPos = convert::FromSystemToRelative(window, commonState.cursorPosSystemSpace);
|
||||
const bool cursorInLeftScreenHalf = cursorPos.x < textureView.view.width / 2;
|
||||
const bool cursorInTopScreenHalf = cursorPos.y < textureView.view.height / 2;
|
||||
uint8_t pixelTolerance = {};
|
||||
bool perColorChannelEdgeDetection = {};
|
||||
state.Access([&](MeasureToolState& state) {
|
||||
state.perScreen[window].cursorInLeftScreenHalf = cursorInLeftScreenHalf;
|
||||
state.perScreen[window].cursorInTopScreenHalf = cursorInTopScreenHalf;
|
||||
pixelTolerance = state.global.pixelTolerance;
|
||||
perColorChannelEdgeDetection = state.global.perColorChannelEdgeDetection;
|
||||
});
|
||||
|
||||
// Every one of 4 edges is a coordinate of the last similar pixel in a row
|
||||
// Example: given a 5x5 green square on a blue background with its top-left pixel
|
||||
// at 20x100, bounds should be [20,100]-[24,104]. We don't include [25,105] or
|
||||
// [19,99], since those pixels are blue. Thus, square dims are equal to
|
||||
// [24-20+1,104-100+1]=[5,5].
|
||||
const RECT bounds = DetectEdges(textureView.view,
|
||||
cursorPos,
|
||||
perColorChannelEdgeDetection,
|
||||
pixelTolerance,
|
||||
continuousCapture);
|
||||
|
||||
#if defined(DEBUG_EDGES)
|
||||
char buffer[256];
|
||||
sprintf_s(buffer,
|
||||
"Cursor: [%ld,%ld] Bounds: [%ld,%ld]-[%ld,%ld] Screen size: [%zu, %zu]\n",
|
||||
cursorPos.x,
|
||||
cursorPos.y,
|
||||
bounds.left,
|
||||
bounds.top,
|
||||
bounds.right,
|
||||
bounds.bottom,
|
||||
textureView.view.width,
|
||||
textureView.view.height);
|
||||
OutputDebugStringA(buffer);
|
||||
#endif
|
||||
state.Access([&](MeasureToolState& state) {
|
||||
state.perScreen[window].measuredEdges = bounds;
|
||||
});
|
||||
}
|
||||
|
||||
std::thread StartCapturingThread(const CommonState& commonState,
|
||||
Serialized<MeasureToolState>& state,
|
||||
HWND window,
|
||||
MonitorInfo targetMonitor)
|
||||
{
|
||||
return SpawnLoggedThread(L"Screen Capture thread", [&state, &commonState, targetMonitor, window] {
|
||||
auto captureInterop = winrt::get_activation_factory<
|
||||
winrt::GraphicsCaptureItem,
|
||||
IGraphicsCaptureItemInterop>();
|
||||
|
||||
winrt::GraphicsCaptureItem item = nullptr;
|
||||
|
||||
winrt::check_hresult(captureInterop->CreateForMonitor(
|
||||
targetMonitor.GetHandle(),
|
||||
winrt::guid_of<winrt::GraphicsCaptureItem>(),
|
||||
winrt::put_abi(item)));
|
||||
|
||||
bool continuousCapture = {};
|
||||
state.Read([&](const MeasureToolState& state) {
|
||||
continuousCapture = state.global.continuousCapture;
|
||||
});
|
||||
|
||||
const auto monitorArea = targetMonitor.GetScreenSize(true);
|
||||
auto captureState = D3DCaptureState::Create(item,
|
||||
winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized,
|
||||
monitorArea,
|
||||
!continuousCapture);
|
||||
if (continuousCapture)
|
||||
{
|
||||
captureState->StartCapture([&, window](MappedTextureView textureView) {
|
||||
UpdateCaptureState(commonState, state, window, textureView, continuousCapture);
|
||||
});
|
||||
|
||||
while (IsWindow(window) && !commonState.closeOnOtherMonitors)
|
||||
{
|
||||
std::this_thread::sleep_for(consts::TARGET_FRAME_DURATION);
|
||||
}
|
||||
captureState->StopCapture();
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto textureView = captureState->CaptureSingleFrame();
|
||||
|
||||
state.Access([&](MeasureToolState& s) {
|
||||
s.perScreen[window].capturedScreenTexture = textureView.GetTexture();
|
||||
});
|
||||
|
||||
while (IsWindow(window) && !commonState.closeOnOtherMonitors)
|
||||
{
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
if (monitorArea.inside(commonState.cursorPosSystemSpace))
|
||||
{
|
||||
#if defined(DEBUG_TEXTURE)
|
||||
SYSTEMTIME lt{};
|
||||
GetLocalTime(<);
|
||||
char buf[256];
|
||||
sprintf_s(buf, "frame-%02d-%02d-Monitor-%zu.bmp", lt.wHour, lt.wMinute, (uint64_t)window);
|
||||
auto path = std::filesystem::temp_directory_path() / buf;
|
||||
textureView.view.SaveAsBitmap(path.string().c_str());
|
||||
#endif
|
||||
UpdateCaptureState(commonState, state, window, textureView, continuousCapture);
|
||||
}
|
||||
|
||||
const auto frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - now);
|
||||
if (frameTime < consts::TARGET_FRAME_DURATION)
|
||||
{
|
||||
std::this_thread::sleep_for(consts::TARGET_FRAME_DURATION - frameTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
10
src/modules/MeasureTool/MeasureToolCore/ScreenCapturing.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "ToolState.h"
|
||||
|
||||
#include <common/utils/serialized.h>
|
||||
|
||||
std::thread StartCapturingThread(const CommonState& commonState,
|
||||
Serialized<MeasureToolState>& state,
|
||||
HWND targetWindow,
|
||||
MonitorInfo targetMonitor);
|
||||
74
src/modules/MeasureTool/MeasureToolCore/Settings.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
#include <common/utils/color.h>
|
||||
|
||||
#include "Settings.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
|
||||
const wchar_t JSON_KEY_VALUE[] = L"value";
|
||||
|
||||
const wchar_t JSON_KEY_CONTINUOUS_CAPTURE[] = L"ContinuousCapture";
|
||||
const wchar_t JSON_KEY_DRAW_FEET_ON_CROSS[] = L"DrawFeetOnCross";
|
||||
const wchar_t JSON_KEY_PIXEL_TOLERANCE[] = L"PixelTolerance";
|
||||
const wchar_t JSON_KEY_PER_COLOR_CHANNEL_EDGE_DETECTION[] = L"PerColorChannelEdgeDetection";
|
||||
const wchar_t JSON_KEY_MEASURE_CROSS_COLOR[] = L"MeasureCrossColor";
|
||||
}
|
||||
|
||||
Settings Settings::LoadFromFile()
|
||||
{
|
||||
Settings result;
|
||||
|
||||
try
|
||||
{
|
||||
auto props = PTSettingsHelper::load_module_settings(L"Measure Tool").GetNamedObject(JSON_KEY_PROPERTIES);
|
||||
|
||||
try
|
||||
{
|
||||
result.continuousCapture = props.GetNamedObject(JSON_KEY_CONTINUOUS_CAPTURE).GetNamedBoolean(JSON_KEY_VALUE);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
result.drawFeetOnCross = props.GetNamedObject(JSON_KEY_DRAW_FEET_ON_CROSS).GetNamedBoolean(JSON_KEY_VALUE);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
result.pixelTolerance = static_cast<uint8_t>(props.GetNamedObject(JSON_KEY_PIXEL_TOLERANCE).GetNamedNumber(JSON_KEY_VALUE));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const auto colorString = props.GetNamedObject(JSON_KEY_MEASURE_CROSS_COLOR).GetNamedString(JSON_KEY_VALUE);
|
||||
checkValidRGB(colorString, &result.lineColor[0], &result.lineColor[1], &result.lineColor[2]);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
result.perColorChannelEdgeDetection = props.GetNamedObject(JSON_KEY_PER_COLOR_CHANNEL_EDGE_DETECTION).GetNamedBoolean(JSON_KEY_VALUE);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
15
src/modules/MeasureTool/MeasureToolCore/Settings.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
|
||||
struct Settings
|
||||
{
|
||||
uint8_t pixelTolerance = 30;
|
||||
bool continuousCapture = true;
|
||||
bool drawFeetOnCross = true;
|
||||
bool perColorChannelEdgeDetection = false;
|
||||
std::array<uint8_t, 3> lineColor = {255, 69, 0};
|
||||
|
||||
static Settings LoadFromFile();
|
||||
};
|
||||
83
src/modules/MeasureTool/MeasureToolCore/ToolState.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <windef.h>
|
||||
#include <d2d1helper.h>
|
||||
#include <dCommon.h>
|
||||
|
||||
#include <common/Display/monitors.h>
|
||||
#include <common/utils/serialized.h>
|
||||
|
||||
//#define DEBUG_OVERLAY
|
||||
|
||||
struct OverlayBoxText
|
||||
{
|
||||
std::array<wchar_t, 32> buffer = {};
|
||||
};
|
||||
|
||||
struct CommonState
|
||||
{
|
||||
std::function<void()> sessionCompletedCallback;
|
||||
D2D1::ColorF lineColor = D2D1::ColorF::OrangeRed;
|
||||
Box toolbarBoundingBox;
|
||||
|
||||
mutable Serialized<OverlayBoxText> overlayBoxText;
|
||||
POINT cursorPosSystemSpace = {}; // updated atomically
|
||||
std::atomic_bool closeOnOtherMonitors = false;
|
||||
};
|
||||
|
||||
struct BoundsToolState
|
||||
{
|
||||
struct PerScreen
|
||||
{
|
||||
std::optional<D2D_POINT_2F> currentRegionStart;
|
||||
std::vector<D2D1_RECT_F> measurements;
|
||||
};
|
||||
std::unordered_map<HWND, PerScreen> perScreen;
|
||||
|
||||
CommonState* commonState = nullptr; // required for WndProc
|
||||
};
|
||||
|
||||
struct MeasureToolState
|
||||
{
|
||||
enum class Mode
|
||||
{
|
||||
Horizontal,
|
||||
Vertical,
|
||||
Cross
|
||||
};
|
||||
|
||||
struct Global
|
||||
{
|
||||
uint8_t pixelTolerance = 30;
|
||||
bool continuousCapture = true;
|
||||
bool drawFeetOnCross = true;
|
||||
bool perColorChannelEdgeDetection = false;
|
||||
Mode mode = Mode::Cross;
|
||||
} global;
|
||||
|
||||
struct PerScreen
|
||||
{
|
||||
bool cursorInLeftScreenHalf = false;
|
||||
bool cursorInTopScreenHalf = false;
|
||||
RECT measuredEdges = {};
|
||||
// While not in a continuous capturing mode, we need to draw captured backgrounds. These are passed
|
||||
// directly from a capturing thread.
|
||||
winrt::com_ptr<ID3D11Texture2D> capturedScreenTexture;
|
||||
// After the drawing thread finds its capturedScreenTexture, it converts it to
|
||||
// a Direct2D compatible bitmap and caches it here
|
||||
winrt::com_ptr<ID2D1Bitmap> capturedScreenBitmap;
|
||||
};
|
||||
std::unordered_map<HWND, PerScreen> perScreen;
|
||||
|
||||
CommonState* commonState = nullptr; // required for WndProc
|
||||
};
|
||||
|
||||
// Concurrently accessing Direct2D and Direct3D APIs make the driver go boom
|
||||
extern std::recursive_mutex gpuAccessLock;
|
||||
15
src/modules/MeasureTool/MeasureToolCore/app.manifest
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="MeasureTool.app"/>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<!-- The combination of below two tags have the following effect:
|
||||
1) Per-Monitor for >= Windows 10 Anniversary Update
|
||||
2) System < Windows 10 Anniversary Update
|
||||
-->
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
22
src/modules/MeasureTool/MeasureToolCore/constants.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace consts
|
||||
{
|
||||
constexpr inline size_t TARGET_FRAME_RATE = 120;
|
||||
constexpr inline auto TARGET_FRAME_DURATION = std::chrono::milliseconds{ 1000 } / TARGET_FRAME_RATE;
|
||||
|
||||
constexpr inline float FONT_SIZE = 14.f;
|
||||
constexpr inline float TEXT_BOX_CORNER_RADIUS = 4.f;
|
||||
constexpr inline float TEXT_BOX_MARGIN_COEFF = 1.25f;
|
||||
constexpr inline float FEET_HALF_LENGTH = 2.f;
|
||||
constexpr inline float SHADOW_OPACITY = .4f;
|
||||
constexpr inline float SHADOW_RADIUS = 6.f;
|
||||
constexpr inline float SHADOW_OFFSET = 5.f;
|
||||
constexpr inline float CROSS_OPACITY = .25f;
|
||||
constexpr inline int8_t MOUSE_WHEEL_TOLERANCE_STEP = 15;
|
||||
/* Offset to not try not to use the cursor immediate pixels in measuring, but it seems only necessary for continuous mode. */
|
||||
constexpr inline long CURSOR_OFFSET_AMOUNT_X = 4;
|
||||
constexpr inline long CURSOR_OFFSET_AMOUNT_Y = 4;
|
||||
}
|
||||
6
src/modules/MeasureTool/MeasureToolCore/packages.config
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.220418.1" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.220201.1" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22000.197" targetFramework="native" />
|
||||
</packages>
|
||||
1
src/modules/MeasureTool/MeasureToolCore/pch.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "pch.h"
|
||||
103
src/modules/MeasureTool/MeasureToolCore/pch.h
Normal file
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
#define NOMINMAX
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
|
||||
#include <initguid.h>
|
||||
#include <windows.h>
|
||||
#include <unknwn.h>
|
||||
#include <restrictederrorinfo.h>
|
||||
#include <hstring.h>
|
||||
#include <dxgi.h>
|
||||
#include <d3d11.h>
|
||||
#include <d3d11_4.h>
|
||||
#include <dxgi1_6.h>
|
||||
#include <d2d1_3.h>
|
||||
#include <dwrite.h>
|
||||
#include <dwmapi.h>
|
||||
#include <windows.graphics.directX.direct3d11.interop.h>
|
||||
#include <windows.graphics.capture.interop.h>
|
||||
#include <windows.graphics.capture.h>
|
||||
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
#include <cassert>
|
||||
#include <cinttypes>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
#include <chrono>
|
||||
#include <stdio.h>
|
||||
#include <ProjectTelemetry.h>
|
||||
|
||||
// Undefine GetCurrentTime macro to prevent
|
||||
// conflict with Storyboard::GetCurrentTime
|
||||
#undef GetCurrentTime
|
||||
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <winrt/Windows.ApplicationModel.Activation.h>
|
||||
#include <winrt/Microsoft.UI.Composition.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.Controls.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.Data.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.Interop.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.Markup.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.Media.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.Navigation.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.Shapes.h>
|
||||
#include <winrt/Microsoft.UI.Dispatching.h>
|
||||
#include <winrt/Windows.Graphics.DirectX.h>
|
||||
#include <winrt/Windows.Graphics.DirectX.Direct3d11.h>
|
||||
#include <winrt/Windows.Graphics.Capture.h>
|
||||
|
||||
#include <inspectable.h>
|
||||
|
||||
#include <wil/cppwinrt_helpers.h>
|
||||
#include <wil/resource.h>
|
||||
#include <wil/com.h>
|
||||
|
||||
#include <common/logger/logger.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
|
||||
namespace winrt
|
||||
{
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::Foundation::Numerics;
|
||||
using namespace Windows::Graphics;
|
||||
using namespace Windows::Graphics::Capture;
|
||||
using namespace Windows::Graphics::DirectX;
|
||||
using namespace Windows::Graphics::DirectX::Direct3D11;
|
||||
using namespace Microsoft::UI::Xaml;
|
||||
using namespace Microsoft::UI::Xaml::Controls;
|
||||
using namespace Microsoft::UI::Xaml::Navigation;
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
[[nodiscard]] std::thread SpawnLoggedThread(const wchar_t* description, Func&& f)
|
||||
{
|
||||
return std::thread{ [f = std::move(f), description = std::wstring{ description }] {
|
||||
try
|
||||
{
|
||||
SetThreadDescription(GetCurrentThread(), description.c_str());
|
||||
f();
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
Logger::error(L"{}:", description);
|
||||
Logger::error("{}", ex.what());
|
||||
}
|
||||
catch (winrt::hresult_error const& ex)
|
||||
{
|
||||
Logger::error(L"{}: {}", description, ex.message().c_str());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::error(L"{} unknown error: {}", description, get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
} };
|
||||
}
|
||||
|
||||
#define WM_CURSOR_LEFT_MONITOR (WM_USER + 1)
|
||||
13
src/modules/MeasureTool/MeasureToolCore/resource.h
Normal file
@@ -0,0 +1,13 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by PowerToys.MeasureToolCore.rc
|
||||
|
||||
//////////////////////////////
|
||||
// Non-localizable
|
||||
|
||||
#define FILE_DESCRIPTION "PowerToys MeasureToolCore"
|
||||
#define INTERNAL_NAME "PowerToys.MeasureToolCore"
|
||||
#define ORIGINAL_FILENAME "PowerToys.MeasureToolCore.dll"
|
||||
|
||||
// Non-localizable
|
||||
//////////////////////////////
|
||||
@@ -0,0 +1,40 @@
|
||||
#include <windows.h>
|
||||
#include "resource.h"
|
||||
#include "../../../common/version/version.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
#include "winres.h"
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION FILE_VERSION
|
||||
PRODUCTVERSION PRODUCT_VERSION
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS VS_FF_DEBUG
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_DLL
|
||||
FILESUBTYPE VFT2_UNKNOWN
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
|
||||
BEGIN
|
||||
VALUE "CompanyName", COMPANY_NAME
|
||||
VALUE "FileDescription", FILE_DESCRIPTION
|
||||
VALUE "FileVersion", FILE_VERSION_STRING
|
||||
VALUE "InternalName", INTERNAL_NAME
|
||||
VALUE "LegalCopyright", COPYRIGHT_NOTE
|
||||
VALUE "OriginalFilename", ORIGINAL_FILENAME
|
||||
VALUE "ProductName", PRODUCT_NAME
|
||||
VALUE "ProductVersion", PRODUCT_VERSION_STRING
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
|
||||
END
|
||||
END
|
||||
@@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<ProjectGuid>{92C39820-9F84-4529-BC7D-22AAE514D63B}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>MeasureToolModuleInterface</RootNamespace>
|
||||
<ProjectName>MeasureToolModuleInterface</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\MeasureTool\</OutDir>
|
||||
<TargetName>PowerToys.MeasureToolModuleInterface</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)src\;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(CIBuild)'!='true'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="MeasureTool.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="trace.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="trace.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
|
||||
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
|
||||
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="MeasureToolModuleInterface.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="..\..\..\..\deps\spdlog.props" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Generated Files">
|
||||
<UniqueIdentifier>{875a08c6-f610-4667-bd0f-80171ed96072}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="trace.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dllmain.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="trace.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MeasureTool.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="resource.h">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="MeasureToolModuleInterface.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
283
src/modules/MeasureTool/MeasureToolModuleInterface/dllmain.cpp
Normal file
@@ -0,0 +1,283 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <interface/powertoy_module_interface.h>
|
||||
#include <common/SettingsAPI/settings_objects.h>
|
||||
#include "trace.h"
|
||||
#include <common/utils/string_utils.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
#include <common/utils/logger_helper.h>
|
||||
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
|
||||
HMODULE m_hModule;
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||
{
|
||||
m_hModule = hModule;
|
||||
switch (ul_reason_for_call)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
Trace::RegisterProvider();
|
||||
break;
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
Trace::UnregisterProvider();
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
const static wchar_t* MODULE_NAME = L"Measure Tool";
|
||||
const static wchar_t* MODULE_DESC = L"Measure your screen contents";
|
||||
|
||||
namespace
|
||||
{
|
||||
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
|
||||
const wchar_t JSON_KEY_WIN[] = L"win";
|
||||
const wchar_t JSON_KEY_ALT[] = L"alt";
|
||||
const wchar_t JSON_KEY_CTRL[] = L"ctrl";
|
||||
const wchar_t JSON_KEY_SHIFT[] = L"shift";
|
||||
const wchar_t JSON_KEY_CODE[] = L"code";
|
||||
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"ActivationShortcut";
|
||||
}
|
||||
|
||||
class MeasureTool : public PowertoyModuleIface
|
||||
{
|
||||
private:
|
||||
// The PowerToy state.
|
||||
bool m_enabled = false;
|
||||
|
||||
Hotkey m_hotkey;
|
||||
HANDLE m_hProcess;
|
||||
|
||||
void parse_hotkey(PowerToysSettings::PowerToyValues& settings)
|
||||
{
|
||||
auto settingsObject = settings.get_raw_json();
|
||||
if (settingsObject.GetView().Size())
|
||||
{
|
||||
try
|
||||
{
|
||||
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT);
|
||||
m_hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
|
||||
m_hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
|
||||
m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
|
||||
m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
|
||||
m_hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::error("Failed to initialize Measure Tool start shortcut");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::info("MeasureTool settings are empty");
|
||||
}
|
||||
|
||||
if (!m_hotkey.key)
|
||||
{
|
||||
Logger::info("MeasureTool is going to use default shortcut");
|
||||
m_hotkey.win = true;
|
||||
m_hotkey.alt = false;
|
||||
m_hotkey.shift = true;
|
||||
m_hotkey.ctrl = false;
|
||||
m_hotkey.key = 'M';
|
||||
}
|
||||
}
|
||||
|
||||
bool is_process_running()
|
||||
{
|
||||
return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
|
||||
}
|
||||
|
||||
void launch_process()
|
||||
{
|
||||
Logger::trace(L"Starting MeasureTool process");
|
||||
unsigned long powertoys_pid = GetCurrentProcessId();
|
||||
|
||||
std::wstring executable_args = L"";
|
||||
executable_args.append(std::to_wstring(powertoys_pid));
|
||||
|
||||
SHELLEXECUTEINFOW sei{ sizeof(sei) };
|
||||
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
|
||||
sei.lpFile = L"modules\\MeasureTool\\PowerToys.MeasureToolUI.exe";
|
||||
sei.nShow = SW_SHOWNORMAL;
|
||||
sei.lpParameters = executable_args.data();
|
||||
if (ShellExecuteExW(&sei))
|
||||
{
|
||||
Logger::trace("Successfully started the Measure Tool process");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"MeasureTool failed to start. {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
|
||||
m_hProcess = sei.hProcess;
|
||||
}
|
||||
|
||||
// Load the settings file.
|
||||
void init_settings()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Load and parse the settings file for this PowerToy.
|
||||
PowerToysSettings::PowerToyValues settings =
|
||||
PowerToysSettings::PowerToyValues::load_from_settings_file(get_key());
|
||||
|
||||
parse_hotkey(settings);
|
||||
}
|
||||
catch (std::exception ex)
|
||||
{
|
||||
Logger::warn(L"An exception occurred while loading the settings file");
|
||||
// Error while loading from the settings file. Let default values stay as they are.
|
||||
}
|
||||
}
|
||||
|
||||
void terminate_process()
|
||||
{
|
||||
TerminateProcess(m_hProcess, 1);
|
||||
}
|
||||
|
||||
public:
|
||||
MeasureTool()
|
||||
{
|
||||
LoggerHelpers::init_logger(L"Measure Tool", L"ModuleInterface", "Measure Tool");
|
||||
init_settings();
|
||||
}
|
||||
|
||||
~MeasureTool()
|
||||
{
|
||||
if (m_enabled)
|
||||
{
|
||||
terminate_process();
|
||||
}
|
||||
m_enabled = false;
|
||||
}
|
||||
|
||||
// Destroy the powertoy and free memory
|
||||
virtual void destroy() override
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
// Return the localized display name of the powertoy
|
||||
virtual const wchar_t* get_name() override
|
||||
{
|
||||
return MODULE_NAME;
|
||||
}
|
||||
|
||||
// Return the non localized key of the powertoy, this will be cached by the runner
|
||||
virtual const wchar_t* get_key() override
|
||||
{
|
||||
return MODULE_NAME;
|
||||
}
|
||||
|
||||
// Return JSON with the configuration options.
|
||||
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
|
||||
{
|
||||
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
|
||||
|
||||
// Create a Settings object.
|
||||
PowerToysSettings::Settings settings(hinstance, get_name());
|
||||
settings.set_description(MODULE_DESC);
|
||||
|
||||
return settings.serialize_to_buffer(buffer, buffer_size);
|
||||
}
|
||||
|
||||
// Signal from the Settings editor to call a custom action.
|
||||
// This can be used to spawn more complex editors.
|
||||
virtual void call_custom_action(const wchar_t*) override
|
||||
{
|
||||
}
|
||||
|
||||
// Called by the runner to pass the updated settings values as a serialized JSON.
|
||||
virtual void set_config(const wchar_t* config) override
|
||||
{
|
||||
try
|
||||
{
|
||||
// Parse the input JSON string.
|
||||
PowerToysSettings::PowerToyValues values =
|
||||
PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
|
||||
|
||||
parse_hotkey(values);
|
||||
// If you don't need to do any custom processing of the settings, proceed
|
||||
// to persists the values calling:
|
||||
values.save_to_settings_file();
|
||||
// Otherwise call a custom function to process the settings before saving them to disk:
|
||||
// save_settings();
|
||||
}
|
||||
catch (std::exception ex)
|
||||
{
|
||||
// Improper JSON.
|
||||
}
|
||||
}
|
||||
|
||||
// Enable the powertoy
|
||||
virtual void enable()
|
||||
{
|
||||
m_enabled = true;
|
||||
Trace::EnableMeasureTool(true);
|
||||
}
|
||||
|
||||
// Disable the powertoy
|
||||
virtual void disable()
|
||||
{
|
||||
if (m_enabled)
|
||||
{
|
||||
terminate_process();
|
||||
}
|
||||
|
||||
m_enabled = false;
|
||||
Trace::EnableMeasureTool(false);
|
||||
}
|
||||
|
||||
// Returns if the powertoys is enabled
|
||||
virtual bool is_enabled() override
|
||||
{
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
virtual bool on_hotkey(size_t hotkeyId) override
|
||||
{
|
||||
if (m_enabled)
|
||||
{
|
||||
Logger::trace(L"MeasureTool hotkey pressed");
|
||||
if (is_process_running())
|
||||
{
|
||||
terminate_process();
|
||||
}
|
||||
else
|
||||
{
|
||||
launch_process();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
|
||||
{
|
||||
if (m_hotkey.key)
|
||||
{
|
||||
if (hotkeys && buffer_size >= 1)
|
||||
{
|
||||
hotkeys[0] = m_hotkey;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
|
||||
{
|
||||
return new MeasureTool();
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.220418.1" targetFramework="native" />
|
||||
</packages>
|
||||
@@ -0,0 +1 @@
|
||||
#include "pch.h"
|
||||
14
src/modules/MeasureTool/MeasureToolModuleInterface/pch.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <strsafe.h>
|
||||
#include <hIdUsage.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <ProjectTelemetry.h>
|
||||
#include <common/SettingsAPI/settings_helpers.h>
|
||||
#include <common/logger/logger.h>
|
||||
@@ -0,0 +1,13 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by MeasureTool.rc
|
||||
|
||||
//////////////////////////////
|
||||
// Non-localizable
|
||||
|
||||
#define FILE_DESCRIPTION "PowerToys MeasureTool"
|
||||
#define INTERNAL_NAME "PowerToys.MeasureTool"
|
||||
#define ORIGINAL_FILENAME "PowerToys.MeasureTool.dll"
|
||||
|
||||
// Non-localizable
|
||||
//////////////////////////////
|
||||
47
src/modules/MeasureTool/MeasureToolModuleInterface/trace.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "pch.h"
|
||||
#include "trace.h"
|
||||
|
||||
TRACELOGGING_DEFINE_PROVIDER(
|
||||
g_hProvider,
|
||||
"Microsoft.PowerToys",
|
||||
// {38e8889b-9731-53f5-e901-e8a7c1753074}
|
||||
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
|
||||
TraceLoggingOptionProjectTelemetry());
|
||||
|
||||
void Trace::RegisterProvider() noexcept
|
||||
{
|
||||
TraceLoggingRegister(g_hProvider);
|
||||
}
|
||||
|
||||
void Trace::UnregisterProvider() noexcept
|
||||
{
|
||||
TraceLoggingUnregister(g_hProvider);
|
||||
}
|
||||
|
||||
void Trace::EnableMeasureTool(const bool enabled) noexcept
|
||||
{
|
||||
TraceLoggingWrite(
|
||||
g_hProvider,
|
||||
"MeasureTool_EnableMeasureTool",
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
|
||||
TraceLoggingBoolean(enabled, "Enabled"));
|
||||
}
|
||||
|
||||
void Trace::BoundsToolActivated() noexcept
|
||||
{
|
||||
TraceLoggingWrite(
|
||||
g_hProvider,
|
||||
"MeasureTool_BoundsToolActivated",
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
|
||||
}
|
||||
|
||||
void Trace::MeasureToolActivated() noexcept
|
||||
{
|
||||
TraceLoggingWrite(
|
||||
g_hProvider,
|
||||
"MeasureTool_MeasureToolActivated",
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
|
||||
}
|
||||
13
src/modules/MeasureTool/MeasureToolModuleInterface/trace.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
class Trace
|
||||
{
|
||||
public:
|
||||
static void RegisterProvider() noexcept;
|
||||
static void UnregisterProvider() noexcept;
|
||||
|
||||
static void EnableMeasureTool(const bool enabled) noexcept;
|
||||
|
||||
static void BoundsToolActivated() noexcept;
|
||||
static void MeasureToolActivated() noexcept;
|
||||
};
|
||||
15
src/modules/MeasureTool/MeasureToolUI/App.xaml
Normal file
@@ -0,0 +1,15 @@
|
||||
<Application
|
||||
x:Class="MeasureToolUI.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:MeasureToolUI">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<!-- Other merged dictionaries here -->
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!-- Other app resources here -->
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
51
src/modules/MeasureTool/MeasureToolUI/App.xaml.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace MeasureToolUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="App"/> class.
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the application is launched normally by the end user. Other entry points
|
||||
/// will be used such as when the application is launched to open a specific file.
|
||||
/// </summary>
|
||||
/// <param name="args">Details about the launch request and process.</param>
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
var cmdArgs = Environment.GetCommandLineArgs();
|
||||
if (cmdArgs?.Length > 1)
|
||||
{
|
||||
if (int.TryParse(cmdArgs[cmdArgs.Length - 1], out int powerToysRunnerPid))
|
||||
{
|
||||
RunnerHelper.WaitForPowerToysRunner(powerToysRunnerPid, () =>
|
||||
{
|
||||
Environment.Exit(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_window = new MainWindow();
|
||||
_window.Activate();
|
||||
}
|
||||
|
||||
private Window _window;
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 432 B |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 637 B |
|
After Width: | Height: | Size: 283 B |
BIN
src/modules/MeasureTool/MeasureToolUI/Assets/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 456 B |
|
After Width: | Height: | Size: 2.0 KiB |
298
src/modules/MeasureTool/MeasureToolUI/MainWindow.xaml
Normal file
@@ -0,0 +1,298 @@
|
||||
<winuiex:WindowEx
|
||||
x:Class="MeasureToolUI.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:contract7NotPresent="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractNotPresent(Windows.Foundation.UniversalApiContract,7)"
|
||||
xmlns:contract7Present="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract,7)"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:p="using:PowerToys.MeasureToolUI.Properties"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:winuiex="using:WinUIEx"
|
||||
IsAlwaysOnTop="True"
|
||||
IsMaximizable="False"
|
||||
IsMinimizable="False"
|
||||
IsResizable="False"
|
||||
IsShownInSwitchers="False"
|
||||
IsTitleBarVisible="False"
|
||||
mc:Ignorable="d">
|
||||
<winuiex:WindowEx.Backdrop>
|
||||
<winuiex:AcrylicSystemBackdrop
|
||||
DarkFallbackColor="#1c1c1c"
|
||||
DarkLuminosityOpacity="0.96"
|
||||
DarkTintColor="#202020"
|
||||
DarkTintOpacity="0.5"
|
||||
LightFallbackColor="#EEEEEE"
|
||||
LightLuminosityOpacity="0.85"
|
||||
LightTintColor="#FCFCFC"
|
||||
LightTintOpacity="0" />
|
||||
</winuiex:WindowEx.Backdrop>
|
||||
<Grid>
|
||||
<Grid.Resources>
|
||||
<SolidColorBrush x:Key="ToggleButtonBackground" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="ButtonBackground" Color="Transparent" />
|
||||
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="ControlSolidFillColorDefaultBrush" />
|
||||
|
||||
<Style BasedOn="{StaticResource DefaultButtonStyle}" TargetType="Button">
|
||||
<Setter Property="MinHeight" Value="30" />
|
||||
<Setter Property="MinWidth" Value="30" />
|
||||
<Setter Property="Width" Value="30" />
|
||||
<Setter Property="Height" Value="30" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource SymbolThemeFontFamily}" />
|
||||
<Setter Property="FontSize" Value="10" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
</Style>
|
||||
<Style x:Key="ToggleButtonRadioButtonStyle" TargetType="ToggleButton">
|
||||
<Setter Property="MinHeight" Value="30" />
|
||||
<Setter Property="MinWidth" Value="30" />
|
||||
<Setter Property="Width" Value="30" />
|
||||
<Setter Property="Height" Value="30" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource SymbolThemeFontFamily}" />
|
||||
<Setter Property="FontSize" Value="16" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Background" Value="{ThemeResource ToggleButtonBackground}" />
|
||||
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource ToggleButtonForeground}" />
|
||||
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="FocusVisualMargin" Value="-3" />
|
||||
<Setter Property="CornerRadius" Value="{StaticResource ControlCornerRadius}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
contract7NotPresent:CornerRadius="{ThemeResource ControlCornerRadius}"
|
||||
contract7Present:BackgroundSizing="{TemplateBinding BackgroundSizing}"
|
||||
contract7Present:CornerRadius="{TemplateBinding CornerRadius}"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}">
|
||||
<contract7Present:ContentPresenter.BackgroundTransition>
|
||||
<contract7Present:BrushTransition Duration="0:0:0.083" />
|
||||
</contract7Present:ContentPresenter.BackgroundTransition>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ControlSolidFillColorDefaultBrush}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Disabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Checked">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundChecked}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundChecked}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushChecked}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<contract7Present:ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BackgroundSizing">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonCheckedStateBackgroundSizing}" />
|
||||
</contract7Present:ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CheckedPointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundCheckedPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushCheckedPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundCheckedPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<contract7Present:ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BackgroundSizing">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonCheckedStateBackgroundSizing}" />
|
||||
</contract7Present:ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CheckedPressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundCheckedPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundCheckedPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushCheckedPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<contract7Present:ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BackgroundSizing">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonCheckedStateBackgroundSizing}" />
|
||||
</contract7Present:ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CheckedDisabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundCheckedDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundCheckedDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushCheckedDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Indeterminate">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundIndeterminate}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundIndeterminate}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushIndeterminate}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="IndeterminatePointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundIndeterminatePointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushIndeterminatePointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundIndeterminatePointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="IndeterminatePressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundIndeterminatePressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushIndeterminatePressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundIndeterminatePressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="IndeterminateDisabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBackgroundIndeterminateDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonForegroundIndeterminateDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonBorderBrushIndeterminateDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</ContentPresenter>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
|
||||
</Grid.Resources>
|
||||
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<ToggleButton
|
||||
AutomationProperties.Name="{x:Bind p:Resources.Bounds}"
|
||||
Click="BoundsTool_Click"
|
||||
Content=""
|
||||
Style="{StaticResource ToggleButtonRadioButtonStyle}"
|
||||
ToolTipService.ToolTip="{x:Bind p:Resources.Bounds}" />
|
||||
<ToggleButton
|
||||
AutomationProperties.Name="{x:Bind p:Resources.Spacing}"
|
||||
Click="MeasureTool_Click"
|
||||
Style="{StaticResource ToggleButtonRadioButtonStyle}"
|
||||
ToolTipService.ToolTip="{x:Bind p:Resources.Spacing}">
|
||||
<FontIcon Margin="1,0,0,0" Glyph="" />
|
||||
</ToggleButton>
|
||||
|
||||
<ToggleButton
|
||||
AutomationProperties.Name="{x:Bind p:Resources.HorizontalSpacing}"
|
||||
Click="HorizontalMeasureTool_Click"
|
||||
Style="{StaticResource ToggleButtonRadioButtonStyle}"
|
||||
ToolTipService.ToolTip="{x:Bind p:Resources.HorizontalSpacing}">
|
||||
<FontIcon Margin="1,0,0,0" Glyph="" />
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
AutomationProperties.Name="{x:Bind p:Resources.VerticalSpacing}"
|
||||
Click="VerticalMeasureTool_Click"
|
||||
Style="{StaticResource ToggleButtonRadioButtonStyle}"
|
||||
ToolTipService.ToolTip="{x:Bind p:Resources.VerticalSpacing}">
|
||||
<FontIcon Glyph="" RenderTransformOrigin="0.5,0.5">
|
||||
<FontIcon.RenderTransform>
|
||||
<RotateTransform Angle="90" />
|
||||
</FontIcon.RenderTransform>
|
||||
</FontIcon>
|
||||
</ToggleButton>
|
||||
<AppBarSeparator Height="36" />
|
||||
<Button
|
||||
Click="ClosePanelTool_Click"
|
||||
Content=""
|
||||
ToolTipService.ToolTip="Close" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</winuiex:WindowEx>
|
||||
141
src/modules/MeasureTool/MeasureToolUI/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Windows.Graphics;
|
||||
using WinUIEx;
|
||||
|
||||
namespace MeasureToolUI
|
||||
{
|
||||
using static NativeMethods;
|
||||
|
||||
/// <summary>
|
||||
/// An empty window that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MainWindow : WindowEx
|
||||
{
|
||||
private const int WindowWidth = 216;
|
||||
private const int WindowHeight = 50;
|
||||
|
||||
private PowerToys.MeasureToolCore.Core _coreLogic = new PowerToys.MeasureToolCore.Core();
|
||||
|
||||
private AppWindow _appWindow;
|
||||
private PointInt32 _initialPosition;
|
||||
|
||||
protected override void OnPositionChanged(PointInt32 position)
|
||||
{
|
||||
_appWindow.Move(_initialPosition);
|
||||
this.SetWindowSize(WindowWidth, WindowHeight);
|
||||
}
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
|
||||
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
|
||||
WindowId windowId = Win32Interop.GetWindowIdFromWindow(hwnd);
|
||||
_appWindow = AppWindow.GetFromWindowId(windowId);
|
||||
var presenter = _appWindow.Presenter as OverlappedPresenter;
|
||||
presenter.IsAlwaysOnTop = true;
|
||||
this.SetIsAlwaysOnTop(true);
|
||||
this.SetIsShownInSwitchers(false);
|
||||
this.SetIsResizable(false);
|
||||
this.SetIsMinimizable(false);
|
||||
this.SetIsMaximizable(false);
|
||||
IsTitleBarVisible = false;
|
||||
|
||||
DisplayArea displayArea = DisplayArea.GetFromWindowId(windowId, DisplayAreaFallback.Nearest);
|
||||
float dpiScale = _coreLogic.GetDPIScaleForWindow((int)hwnd);
|
||||
|
||||
_initialPosition = new PointInt32(displayArea.WorkArea.X + (displayArea.WorkArea.Width / 2) - (int)(dpiScale * WindowWidth / 2), displayArea.WorkArea.Y + (int)(dpiScale * 12));
|
||||
|
||||
_coreLogic.SetToolbarBoundingBox(
|
||||
_initialPosition.X,
|
||||
_initialPosition.Y,
|
||||
_initialPosition.X + (int)(dpiScale * WindowWidth),
|
||||
_initialPosition.Y + (int)(dpiScale * WindowHeight));
|
||||
|
||||
OnPositionChanged(_initialPosition);
|
||||
}
|
||||
|
||||
private void UpdateToolUsageCompletionEvent(object sender)
|
||||
{
|
||||
_coreLogic.SetToolCompletionEvent(new PowerToys.MeasureToolCore.ToolSessionCompleted(() =>
|
||||
{
|
||||
DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
((ToggleButton)sender).IsChecked = false;
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private void UncheckOtherButtons(ToggleButton button)
|
||||
{
|
||||
var panel = button.Parent as Panel;
|
||||
foreach (var elem in panel.Children)
|
||||
{
|
||||
if (elem is ToggleButton otherButton)
|
||||
{
|
||||
if (!button.Equals(otherButton))
|
||||
{
|
||||
otherButton.IsChecked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleToolClick(object toolButton, Action startToolAction)
|
||||
{
|
||||
ToggleButton button = toolButton as ToggleButton;
|
||||
if (button == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (button.IsChecked.GetValueOrDefault())
|
||||
{
|
||||
UncheckOtherButtons(button);
|
||||
_coreLogic.ResetState();
|
||||
UpdateToolUsageCompletionEvent(toolButton);
|
||||
startToolAction();
|
||||
}
|
||||
else
|
||||
{
|
||||
_coreLogic.ResetState();
|
||||
}
|
||||
}
|
||||
|
||||
private void BoundsTool_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
HandleToolClick(sender, () => _coreLogic.StartBoundsTool());
|
||||
}
|
||||
|
||||
private void MeasureTool_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
HandleToolClick(sender, () => _coreLogic.StartMeasureTool(true, true));
|
||||
}
|
||||
|
||||
private void HorizontalMeasureTool_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
HandleToolClick(sender, () => _coreLogic.StartMeasureTool(true, false));
|
||||
}
|
||||
|
||||
private void VerticalMeasureTool_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
HandleToolClick(sender, () => _coreLogic.StartMeasureTool(false, true));
|
||||
}
|
||||
|
||||
private void ClosePanelTool_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_coreLogic.ResetState();
|
||||
this.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
85
src/modules/MeasureTool/MeasureToolUI/MeasureToolUI.csproj
Normal file
@@ -0,0 +1,85 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\Version.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyTitle>PowerToys.MeasureTool</AssemblyTitle>
|
||||
<AssemblyDescription>PowerToys MeasureTool</AssemblyDescription>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0-windows10.0.20348.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\modules\MeasureTool</OutputPath>
|
||||
<RootNamespace>PowerToys.MeasureToolUI</RootNamespace>
|
||||
<AssemblyName>PowerToys.MeasureToolUI</AssemblyName>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<Platforms>x86;x64;arm64</Platforms>
|
||||
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
|
||||
<PublishProfile>win10-$(Platform).pubxml</PublishProfile>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- See https://docs.microsoft.com/en-us/windows/apps/develop/platform/csharp-winrt/net-projection-from-cppwinrt-component for more info -->
|
||||
<PropertyGroup>
|
||||
<CsWinRTIncludes>PowerToys.MeasureToolCore</CsWinRTIncludes>
|
||||
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
|
||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<NoWarn>0436</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\SplashScreen.scale-200.png" />
|
||||
<Content Include="Assets\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Assets\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||
<Content Include="Assets\StoreLogo.png" />
|
||||
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="1.6.4" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.4" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22000.194" />
|
||||
<PackageReference Include="WinUIEx" Version="1.6.0" />
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
||||
Tools extension to be activated for this project even if the Windows App SDK Nuget
|
||||
package has not yet been restored -->
|
||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Assets\Icons\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\MeasureToolCore\PowerToys.MeasureToolCore.vcxproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
17
src/modules/MeasureTool/MeasureToolUI/NativeMethods.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
internal static class NativeMethods
|
||||
{
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
|
||||
|
||||
internal static readonly IntPtr HWND_TOPMOST = new System.IntPtr(-1);
|
||||
internal const uint SWP_NOSIZE = 0x0001;
|
||||
internal const uint SWP_NOMOVE = 0x0002;
|
||||
internal const uint SWP_SHOWWINDOW = 0x0040;
|
||||
}
|
||||
48
src/modules/MeasureTool/MeasureToolUI/Package.appxmanifest
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
|
||||
<Identity
|
||||
Name="a3e178b8-eac3-48e9-8ae0-1756eb4ff1ec"
|
||||
Publisher="CN=Microsoft Corporation"
|
||||
Version="1.0.0.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>MeasureToolUI</DisplayName>
|
||||
<PublisherDisplayName>nielslaute</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17134.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17134.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="MeasureToolUI"
|
||||
Description="MeasureToolUI"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
99
src/modules/MeasureTool/MeasureToolUI/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,99 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace PowerToys.MeasureToolUI.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PowerToys.MeasureToolUI.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Bounds.
|
||||
/// </summary>
|
||||
public static string Bounds {
|
||||
get {
|
||||
return ResourceManager.GetString("Bounds", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Horizontal spacing.
|
||||
/// </summary>
|
||||
public static string HorizontalSpacing {
|
||||
get {
|
||||
return ResourceManager.GetString("HorizontalSpacing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Spacing.
|
||||
/// </summary>
|
||||
public static string Spacing {
|
||||
get {
|
||||
return ResourceManager.GetString("Spacing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Vertical spacing.
|
||||
/// </summary>
|
||||
public static string VerticalSpacing {
|
||||
get {
|
||||
return ResourceManager.GetString("VerticalSpacing", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
132
src/modules/MeasureTool/MeasureToolUI/Properties/Resources.resx
Normal file
@@ -0,0 +1,132 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Bounds" xml:space="preserve">
|
||||
<value>Bounds</value>
|
||||
</data>
|
||||
<data name="Spacing" xml:space="preserve">
|
||||
<value>Spacing</value>
|
||||
</data>
|
||||
<data name="HorizontalSpacing" xml:space="preserve">
|
||||
<value>Horizontal spacing</value>
|
||||
</data>
|
||||
<data name="VerticalSpacing" xml:space="preserve">
|
||||
<value>Vertical spacing</value>
|
||||
</data>
|
||||
</root>
|
||||
15
src/modules/MeasureTool/MeasureToolUI/app.manifest
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="MeasureToolUI.app"/>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<!-- The combination of below two tags have the following effect:
|
||||
1) Per-Monitor for >= Windows 10 Anniversary Update
|
||||
2) System < Windows 10 Anniversary Update
|
||||
-->
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -6,8 +6,6 @@
|
||||
<ProjectGuid>{e94fd11c-0591-456f-899f-efc0ca548336}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>FindMyMouse</RootNamespace>
|
||||
<OverrideWindowsTargetPlatformVersion>true</OverrideWindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>FindMyMouse</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
|
||||
@@ -334,13 +334,14 @@ void D2DOverlayWindow::show(HWND active_window, bool snappable)
|
||||
}
|
||||
monitors = MonitorInfo::GetMonitors(true);
|
||||
// calculate the rect covering all the screens
|
||||
total_screen = ScreenSize(monitors[0].rect);
|
||||
total_screen = monitors[0].GetScreenSize(true);
|
||||
for (auto& monitor : monitors)
|
||||
{
|
||||
total_screen.rect.left = std::min(total_screen.rect.left, monitor.rect.left);
|
||||
total_screen.rect.top = std::min(total_screen.rect.top, monitor.rect.top);
|
||||
total_screen.rect.right = std::max(total_screen.rect.right, monitor.rect.right);
|
||||
total_screen.rect.bottom = std::max(total_screen.rect.bottom, monitor.rect.bottom);
|
||||
const auto monitorSize = monitor.GetScreenSize(true);
|
||||
total_screen.rect.left = std::min(total_screen.left(), monitorSize.left());
|
||||
total_screen.rect.top = std::min(total_screen.top(), monitorSize.top());
|
||||
total_screen.rect.right = std::max(total_screen.right(), monitorSize.right());
|
||||
total_screen.rect.bottom = std::max(total_screen.bottom(), monitorSize.bottom());
|
||||
}
|
||||
// make sure top-right corner of all the monitor rects is (0,0)
|
||||
monitor_dx = -total_screen.left();
|
||||
@@ -356,10 +357,10 @@ void D2DOverlayWindow::show(HWND active_window, bool snappable)
|
||||
DwmRegisterThumbnail(hwnd, active_window, &thumbnail);
|
||||
}
|
||||
animation.reset();
|
||||
auto primary_screen = MonitorInfo::GetPrimaryMonitor();
|
||||
auto primary_size = MonitorInfo::GetPrimaryMonitor().GetScreenSize(false);
|
||||
shown_start_time = std::chrono::steady_clock::now();
|
||||
lock.unlock();
|
||||
D2DWindow::show(primary_screen.left(), primary_screen.top(), primary_screen.width(), primary_screen.height());
|
||||
D2DWindow::show(primary_size.left(), primary_size.top(), primary_size.width(), primary_size.height());
|
||||
// Check if taskbar is auto-hidden. If so, don't display the number arrows
|
||||
APPBARDATA param = {};
|
||||
param.cbSize = sizeof(APPBARDATA);
|
||||
@@ -733,10 +734,11 @@ void D2DOverlayWindow::render(ID2D1DeviceContext5* d2d_dc)
|
||||
for (auto& monitor : monitors)
|
||||
{
|
||||
D2D1_RECT_F monitor_rect;
|
||||
monitor_rect.left = (float)((monitor.rect.left + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left);
|
||||
monitor_rect.top = (float)((monitor.rect.top + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top);
|
||||
monitor_rect.right = (float)((monitor.rect.right + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left);
|
||||
monitor_rect.bottom = (float)((monitor.rect.bottom + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top);
|
||||
const auto monitor_size = monitor.GetScreenSize(true);
|
||||
monitor_rect.left = (float)((monitor_size.left() + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left);
|
||||
monitor_rect.top = (float)((monitor_size.top() + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top);
|
||||
monitor_rect.right = (float)((monitor_size.right() + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left);
|
||||
monitor_rect.bottom = (float)((monitor_size.bottom() + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top);
|
||||
d2d_dc->SetTransform(D2D1::Matrix3x2F::Identity());
|
||||
d2d_dc->FillRectangle(monitor_rect, brush.get());
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ private:
|
||||
bool running = true;
|
||||
std::vector<AnimateKeys> key_animations;
|
||||
std::vector<MonitorInfo> monitors;
|
||||
ScreenSize total_screen;
|
||||
Box total_screen;
|
||||
int monitor_dx = 0, monitor_dy = 0;
|
||||
D2DText text;
|
||||
WindowsColors colors;
|
||||
|
||||
@@ -55,8 +55,7 @@
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<!-- Global props -->
|
||||
<PropertyGroup Label="Globals" Condition="'$(OverrideWindowsTargetPlatformVersion)'!='True'">
|
||||
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{1DC3BE92-CE89-43FB-8110-9C043A2FE7A2}</ProjectGuid>
|
||||
|
||||
@@ -55,8 +55,7 @@
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<!-- Global props -->
|
||||
<PropertyGroup Label="Globals" Condition="'$(OverrideWindowsTargetPlatformVersion)'!='True'">
|
||||
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{ff1d7936-842a-4bbb-8bea-e9fe796de700}</ProjectGuid>
|
||||
|
||||
@@ -63,9 +63,6 @@
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{8df78b53-200e-451f-9328-01eb907193ae}</ProjectGuid>
|
||||
<RootNamespace>KeyboardManagerEditor</RootNamespace>
|
||||
<OverrideWindowsTargetPlatformVersion>true</OverrideWindowsTargetPlatformVersion>
|
||||
<TargetPlatformVersion>10.0.19041.0</TargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<!-- Props that are constant for both Debug and Release configurations -->
|
||||
<PropertyGroup Label="Configuration">
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{23d2070d-e4ad-4add-85a7-083d9c76ad49}</ProjectGuid>
|
||||
<RootNamespace>KeyboardManagerEditorLibrary</RootNamespace>
|
||||
<OverrideWindowsTargetPlatformVersion>true</OverrideWindowsTargetPlatformVersion>
|
||||
<TargetPlatformVersion>10.0.19041.0</TargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
<ProjectGuid>{62173D9A-6724-4C00-A1C8-FB646480A9EC}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>KeyboardManagerEditorTest</RootNamespace>
|
||||
<OverrideWindowsTargetPlatformVersion>true</OverrideWindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
|
||||
<ProjectSubType>NativeUnitTestProject</ProjectSubType>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
|
||||
@@ -10,9 +10,6 @@
|
||||
<ProjectGuid>{ba661f5b-1d5a-4ffc-9bf1-fc39df280bdd}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>KeyboardManagerEngine</RootNamespace>
|
||||
<OverrideWindowsTargetPlatformVersion>true</OverrideWindowsTargetPlatformVersion>
|
||||
<TargetPlatformVersion>10.0.19041.0</TargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{e496b7fc-1e99-4bab-849b-0e8367040b02}</ProjectGuid>
|
||||
<RootNamespace>KeyboardManagerEngineLibrary</RootNamespace>
|
||||
<OverrideWindowsTargetPlatformVersion>true</OverrideWindowsTargetPlatformVersion>
|
||||
<TargetPlatformVersion>10.0.19041.0</TargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
<ProjectGuid>{7f4b3a60-bc27-45a7-8000-68b0b6ea7466}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>KeyboardManagerEngineTest</RootNamespace>
|
||||
<OverrideWindowsTargetPlatformVersion>true</OverrideWindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
|
||||
<ProjectSubType>NativeUnitTestProject</ProjectSubType>
|
||||
<ProjectName>KeyboardManagerEngineTest</ProjectName>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<ProjectGuid>{8AFFA899-0B73-49EC-8C50-0FADDA57B2FC}</ProjectGuid>
|
||||
<RootNamespace>KeyboardManagerCommon</RootNamespace>
|
||||
<OverrideWindowsTargetPlatformVersion>true</OverrideWindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
<ProjectGuid>{89f34af7-1c34-4a72-aa6e-534bcf972bd9}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>KeyboardManager</RootNamespace>
|
||||
<OverrideWindowsTargetPlatformVersion>true</OverrideWindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>KeyboardManager</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
|
||||
@@ -232,38 +232,39 @@ void Toolbar::show(std::wstring position, std::wstring monitorString)
|
||||
|
||||
for (auto& monitorInfo : monitorInfos)
|
||||
{
|
||||
const auto screenSize = monitorInfo.GetScreenSize(false);
|
||||
int positionX = 0;
|
||||
int positionY = 0;
|
||||
|
||||
if (position == L"Top left corner")
|
||||
{
|
||||
positionX = monitorInfo.left() + BORDER_OFFSET;
|
||||
positionY = monitorInfo.top() + BORDER_OFFSET;
|
||||
positionX = screenSize.left() + BORDER_OFFSET;
|
||||
positionY = screenSize.top() + BORDER_OFFSET;
|
||||
}
|
||||
else if (position == L"Top center")
|
||||
{
|
||||
positionX = monitorInfo.middle().x - overlayWidth / 2;
|
||||
positionY = monitorInfo.top() + BORDER_OFFSET;
|
||||
positionX = screenSize.middle().x - overlayWidth / 2;
|
||||
positionY = screenSize.top() + BORDER_OFFSET;
|
||||
}
|
||||
else if (position == L"Bottom left corner")
|
||||
{
|
||||
positionX = monitorInfo.left() + BORDER_OFFSET;
|
||||
positionY = monitorInfo.bottom() - overlayHeight - BORDER_OFFSET;
|
||||
positionX = screenSize.left() + BORDER_OFFSET;
|
||||
positionY = screenSize.bottom() - overlayHeight - BORDER_OFFSET;
|
||||
}
|
||||
else if (position == L"Bottom center")
|
||||
{
|
||||
positionX = monitorInfo.middle().x - overlayWidth / 2;
|
||||
positionY = monitorInfo.bottom() - overlayHeight - BORDER_OFFSET;
|
||||
positionX = screenSize.middle().x - overlayWidth / 2;
|
||||
positionY = screenSize.bottom() - overlayHeight - BORDER_OFFSET;
|
||||
}
|
||||
else if (position == L"Bottom right corner")
|
||||
{
|
||||
positionX = monitorInfo.right() - overlayWidth - BORDER_OFFSET;
|
||||
positionY = monitorInfo.bottom() - overlayHeight - BORDER_OFFSET;
|
||||
positionX = screenSize.right() - overlayWidth - BORDER_OFFSET;
|
||||
positionY = screenSize.bottom() - overlayHeight - BORDER_OFFSET;
|
||||
}
|
||||
else //"Top right corner" or non-present
|
||||
{
|
||||
positionX = monitorInfo.right() - overlayWidth - BORDER_OFFSET;
|
||||
positionY = monitorInfo.top() + TOP_RIGHT_BORDER_OFFSET;
|
||||
positionX = screenSize.right() - overlayWidth - BORDER_OFFSET;
|
||||
positionY = screenSize.top() + TOP_RIGHT_BORDER_OFFSET;
|
||||
}
|
||||
|
||||
HWND hwnd;
|
||||
|
||||
@@ -32,8 +32,6 @@
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>VideoConferenceProxyFilter</RootNamespace>
|
||||
<ProjectName>VideoConferenceProxyFilter</ProjectName>
|
||||
<OverrideWindowsTargetPlatformVersion>true</OverrideWindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
|
||||
@@ -37,8 +37,6 @@
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{459e0768-7ebd-4c41-bba1-6db3b3815e0a}</ProjectGuid>
|
||||
<RootNamespace>VideoConferenceShared</RootNamespace>
|
||||
<OverrideWindowsTargetPlatformVersion>true</OverrideWindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup>
|
||||
|
||||