mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-15 11:17:53 +01:00
[Screen Ruler] Better error handling and reuse D3D device (#20223)
* [Screen Ruler] simplify lines calculation * [Screen Ruler] Add inches and centimeters support * [Chore] prefer x64 toolset to avoid hitting C1076 * [Screen Ruler] Allow making screenshots in non-continuous mode * [Screen Ruler] Use single d3d device for all ops * [Screen Ruler] remove gpu mutex and clean up screen capturing * [Screen Ruler] handle and log DXGI initialization failure * [Screen Ruler] Add unhandled exception handler * [Screen Ruler] comment out Units of Measure setting * [Screen Ruler] introduce a separate device dedicated for capturing
This commit is contained in:
11
.github/actions/spell-check/expect.txt
vendored
11
.github/actions/spell-check/expect.txt
vendored
@@ -346,7 +346,6 @@ CSIDL
|
|||||||
csignal
|
csignal
|
||||||
cso
|
cso
|
||||||
CSRW
|
CSRW
|
||||||
cstddef
|
|
||||||
cstdint
|
cstdint
|
||||||
cstdlib
|
cstdlib
|
||||||
cstring
|
cstring
|
||||||
@@ -402,6 +401,7 @@ DBLEPSILON
|
|||||||
DCapture
|
DCapture
|
||||||
DCBA
|
DCBA
|
||||||
DCOM
|
DCOM
|
||||||
|
dcommon
|
||||||
dcomp
|
dcomp
|
||||||
dcompi
|
dcompi
|
||||||
DComposition
|
DComposition
|
||||||
@@ -490,7 +490,6 @@ dreamsofameaningfullife
|
|||||||
drivedetectionwarning
|
drivedetectionwarning
|
||||||
dshow
|
dshow
|
||||||
dst
|
dst
|
||||||
DState
|
|
||||||
DTo
|
DTo
|
||||||
dutil
|
dutil
|
||||||
DVASPECT
|
DVASPECT
|
||||||
@@ -522,6 +521,7 @@ DWORDLONG
|
|||||||
dworigin
|
dworigin
|
||||||
dwrite
|
dwrite
|
||||||
dxgi
|
dxgi
|
||||||
|
dxgidebug
|
||||||
dxgiformat
|
dxgiformat
|
||||||
dxguid
|
dxguid
|
||||||
ecount
|
ecount
|
||||||
@@ -811,6 +811,7 @@ ICapture
|
|||||||
icase
|
icase
|
||||||
ICEBLUE
|
ICEBLUE
|
||||||
IClass
|
IClass
|
||||||
|
IClosable
|
||||||
ICollection
|
ICollection
|
||||||
IColor
|
IColor
|
||||||
ICommand
|
ICommand
|
||||||
@@ -881,7 +882,6 @@ IMAGERESIZERCONTEXTMENU
|
|||||||
IMAGERESIZEREXT
|
IMAGERESIZEREXT
|
||||||
imageresizerinput
|
imageresizerinput
|
||||||
imageresizersettings
|
imageresizersettings
|
||||||
TEXTEXTRACTOR
|
|
||||||
imagingdevices
|
imagingdevices
|
||||||
IMain
|
IMain
|
||||||
IMarkdown
|
IMarkdown
|
||||||
@@ -1278,6 +1278,7 @@ Moq
|
|||||||
MOUSEACTIVATE
|
MOUSEACTIVATE
|
||||||
MOUSEHWHEEL
|
MOUSEHWHEEL
|
||||||
MOUSEINPUT
|
MOUSEINPUT
|
||||||
|
MOUSELEAVE
|
||||||
MOUSEMOVE
|
MOUSEMOVE
|
||||||
MOUSEWHEEL
|
MOUSEWHEEL
|
||||||
MOVESIZEEND
|
MOVESIZEEND
|
||||||
@@ -1632,7 +1633,6 @@ ptd
|
|||||||
PTOKEN
|
PTOKEN
|
||||||
PToy
|
PToy
|
||||||
ptr
|
ptr
|
||||||
ptrdiff
|
|
||||||
ptstr
|
ptstr
|
||||||
PVOID
|
PVOID
|
||||||
pwa
|
pwa
|
||||||
@@ -1746,6 +1746,7 @@ RIGHTSCROLLBAR
|
|||||||
riid
|
riid
|
||||||
riverar
|
riverar
|
||||||
RKey
|
RKey
|
||||||
|
RLO
|
||||||
RMENU
|
RMENU
|
||||||
RNumber
|
RNumber
|
||||||
roadmap
|
roadmap
|
||||||
@@ -2063,6 +2064,7 @@ testhost
|
|||||||
testprocess
|
testprocess
|
||||||
TEXCOORD
|
TEXCOORD
|
||||||
textblock
|
textblock
|
||||||
|
TEXTEXTRACTOR
|
||||||
TEXTINCLUDE
|
TEXTINCLUDE
|
||||||
THH
|
THH
|
||||||
THICKFRAME
|
THICKFRAME
|
||||||
@@ -2272,7 +2274,6 @@ wchar
|
|||||||
WClass
|
WClass
|
||||||
wcout
|
wcout
|
||||||
wcscat
|
wcscat
|
||||||
wcschr
|
|
||||||
wcscmp
|
wcscmp
|
||||||
wcscpy
|
wcscpy
|
||||||
wcslen
|
wcslen
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
|
|
||||||
<!-- C++ source compile-specific things for all configurations -->
|
<!-- C++ source compile-specific things for all configurations -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
|
||||||
<VcpkgEnabled>false</VcpkgEnabled>
|
<VcpkgEnabled>false</VcpkgEnabled>
|
||||||
<ExternalIncludePath>$(MSBuildThisFileFullPath)\..\deps\;$(ExternalIncludePath)</ExternalIncludePath>
|
<ExternalIncludePath>$(MSBuildThisFileFullPath)\..\deps\;$(ExternalIncludePath)</ExternalIncludePath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -119,7 +119,7 @@
|
|||||||
|
|
||||||
<?define ImageResizerSparsePackageAssets=LargeTile.png;SmallTile.png;SplashScreen.png;Square150x150Logo.png;Square44x44Logo.png;storelogo.png;Wide310x150Logo.png?>
|
<?define ImageResizerSparsePackageAssets=LargeTile.png;SmallTile.png;SplashScreen.png;Square150x150Logo.png;Square44x44Logo.png;storelogo.png;Wide310x150Logo.png?>
|
||||||
|
|
||||||
<?define MeasureToolFiles=CoreMessagingXP.dll;dcompi.dll;dwmcorei.dll;DwmSceneI.dll;DWriteCore.dll;marshal.dll;Microsoft.DirectManipulation.dll;Microsoft.InputStateManager.dll;Microsoft.InteractiveExperiences.Projection.dll;Microsoft.Internal.FrameworkUdk.dll;Microsoft.UI.Composition.OSSupport.dll;Microsoft.UI.Input.dll;Microsoft.UI.Windowing.Core.dll;Microsoft.UI.Xaml.Controls.dll;Microsoft.UI.Xaml.Controls.pri;Microsoft.ui.xaml.dll;Microsoft.UI.Xaml.Internal.dll;Microsoft.UI.Xaml.Phone.dll;Microsoft.ui.xaml.resources.19h1.dll;Microsoft.ui.xaml.resources.common.dll;Microsoft.Web.WebView2.Core.dll;Microsoft.Windows.ApplicationModel.Resources.dll;Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.dll;Microsoft.Windows.AppLifecycle.Projection.dll;Microsoft.Windows.AppNotifications.Projection.dll;Microsoft.Windows.PushNotifications.Projection.dll;Microsoft.Windows.SDK.NET.dll;Microsoft.Windows.System.Projection.dll;Microsoft.WindowsAppRuntime.Bootstrap.dll;Microsoft.WindowsAppRuntime.Bootstrap.Net.dll;Microsoft.WindowsAppRuntime.dll;Microsoft.WindowsAppRuntime.Insights.Resource.dll;Microsoft.WindowsAppRuntime.Release.Net.dll;Microsoft.WinUI.dll;MRM.dll;PowerToys.ManagedCommon.dll;PowerToys.ManagedTelemetry.dll;PowerToys.MeasureToolCore.dll;PowerToys.MeasureToolUI.deps.json;PowerToys.MeasureToolUI.dll;PowerToys.MeasureToolUI.exe;PowerToys.MeasureToolUI.runtimeconfig.json;PushNotificationsLongRunningTask.ProxyStub.dll;resources.pri;System.CodeDom.dll;System.Management.dll;WindowsAppRuntime.png;WindowsAppSdk.AppxDeploymentExtensions.Desktop.dll;WinRT.Runtime.dll;WinUIEdit.dll;WinUIEx.dll;wuceffectsi.dll?>
|
<?define MeasureToolFiles=CoreMessagingXP.dll;dcompi.dll;dwmcorei.dll;DwmSceneI.dll;DWriteCore.dll;marshal.dll;Microsoft.DirectManipulation.dll;Microsoft.InputStateManager.dll;Microsoft.InteractiveExperiences.Projection.dll;Microsoft.Internal.FrameworkUdk.dll;Microsoft.UI.Composition.OSSupport.dll;Microsoft.UI.Input.dll;Microsoft.UI.Windowing.Core.dll;Microsoft.UI.Xaml.Controls.dll;Microsoft.UI.Xaml.Controls.pri;Microsoft.ui.xaml.dll;Microsoft.UI.Xaml.Internal.dll;Microsoft.UI.Xaml.Phone.dll;Microsoft.ui.xaml.resources.19h1.dll;Microsoft.ui.xaml.resources.common.dll;Microsoft.Web.WebView2.Core.dll;Microsoft.Windows.ApplicationModel.Resources.dll;Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.dll;Microsoft.Windows.AppLifecycle.Projection.dll;Microsoft.Windows.AppNotifications.Projection.dll;Microsoft.Windows.PushNotifications.Projection.dll;Microsoft.Windows.SDK.NET.dll;Microsoft.Windows.System.Projection.dll;Microsoft.WindowsAppRuntime.Bootstrap.dll;Microsoft.WindowsAppRuntime.Bootstrap.Net.dll;Microsoft.WindowsAppRuntime.dll;Microsoft.WindowsAppRuntime.Insights.Resource.dll;Microsoft.WindowsAppRuntime.Release.Net.dll;Microsoft.WinUI.dll;MRM.dll;PowerToys.ManagedCommon.dll;PowerToys.Interop.dll;PowerToys.ManagedTelemetry.dll;PowerToys.MeasureToolCore.dll;PowerToys.MeasureToolUI.deps.json;PowerToys.MeasureToolUI.dll;PowerToys.MeasureToolUI.exe;PowerToys.MeasureToolUI.runtimeconfig.json;PushNotificationsLongRunningTask.ProxyStub.dll;resources.pri;System.CodeDom.dll;System.Management.dll;WindowsAppRuntime.png;WindowsAppSdk.AppxDeploymentExtensions.Desktop.dll;WinRT.Runtime.dll;WinUIEdit.dll;WinUIEx.dll;wuceffectsi.dll?>
|
||||||
|
|
||||||
<?define PowerRenameMicrosoftUIXamlAssetsInstallFiles=NoiseAsset_256x256_PNG.png?>
|
<?define PowerRenameMicrosoftUIXamlAssetsInstallFiles=NoiseAsset_256x256_PNG.png?>
|
||||||
|
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ inline LONG WINAPI UnhandledExceptionHandler(PEXCEPTION_POINTERS info)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Handler to trap abort() calls */
|
/* Handler to trap abort() calls */
|
||||||
inline void AbortHandler(int signal_number)
|
inline void AbortHandler(int /*signal_number*/)
|
||||||
{
|
{
|
||||||
Logger::error("--- ABORT");
|
Logger::error("--- ABORT");
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
|
|||||||
auto toolState = GetWindowParam<BoundsToolState*>(window);
|
auto toolState = GetWindowParam<BoundsToolState*>(window);
|
||||||
if (!toolState)
|
if (!toolState)
|
||||||
break;
|
break;
|
||||||
const POINT cursorPos = convert::FromSystemToRelativeForDirect2D(window, toolState->commonState->cursorPosSystemSpace);
|
const POINT cursorPos = convert::FromSystemToWindow(window, toolState->commonState->cursorPosSystemSpace);
|
||||||
|
|
||||||
D2D_POINT_2F newRegionStart = { .x = static_cast<float>(cursorPos.x), .y = static_cast<float>(cursorPos.y) };
|
D2D_POINT_2F newRegionStart = { .x = static_cast<float>(cursorPos.x), .y = static_cast<float>(cursorPos.y) };
|
||||||
toolState->perScreen[window].currentRegionStart = newRegionStart;
|
toolState->perScreen[window].currentRegionStart = newRegionStart;
|
||||||
@@ -38,6 +38,8 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
|
|||||||
}
|
}
|
||||||
case WM_CURSOR_LEFT_MONITOR:
|
case WM_CURSOR_LEFT_MONITOR:
|
||||||
{
|
{
|
||||||
|
for (; ShowCursor(true) < 0;)
|
||||||
|
;
|
||||||
auto toolState = GetWindowParam<BoundsToolState*>(window);
|
auto toolState = GetWindowParam<BoundsToolState*>(window);
|
||||||
if (!toolState)
|
if (!toolState)
|
||||||
break;
|
break;
|
||||||
@@ -59,12 +61,12 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
|
|||||||
|
|
||||||
if (const bool shiftPress = GetKeyState(VK_SHIFT) & 0x8000; shiftPress)
|
if (const bool shiftPress = GetKeyState(VK_SHIFT) & 0x8000; shiftPress)
|
||||||
{
|
{
|
||||||
const auto cursorPos = convert::FromSystemToRelativeForDirect2D(window, toolState->commonState->cursorPosSystemSpace);
|
const auto cursorPos = convert::FromSystemToWindow(window, toolState->commonState->cursorPosSystemSpace);
|
||||||
|
|
||||||
D2D1_RECT_F rect;
|
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.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);
|
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].measurements.push_back(Measurement{ rect });
|
||||||
}
|
}
|
||||||
|
|
||||||
toolState->perScreen[window].currentRegionStart = std::nullopt;
|
toolState->perScreen[window].currentRegionStart = std::nullopt;
|
||||||
@@ -97,45 +99,42 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
void DrawMeasurement(const D2D1_RECT_F rect,
|
void DrawMeasurement(const Measurement& measurement,
|
||||||
const bool alignTextBoxToCenter,
|
const bool alignTextBoxToCenter,
|
||||||
const CommonState& commonState,
|
const CommonState& commonState,
|
||||||
HWND window,
|
HWND window,
|
||||||
const D2DState& d2dState)
|
const D2DState& d2dState,
|
||||||
|
float mouseX,
|
||||||
|
float mouseY)
|
||||||
{
|
{
|
||||||
const bool screenQuadrantAware = !alignTextBoxToCenter;
|
const bool screenQuadrantAware = !alignTextBoxToCenter;
|
||||||
const auto prevMode = d2dState.rt->GetAntialiasMode();
|
d2dState.ToggleAliasedLinesMode(true);
|
||||||
d2dState.rt->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
|
d2dState.dxgiWindowState.rt->DrawRectangle(measurement.rect, d2dState.solidBrushes[Brush::line].get());
|
||||||
d2dState.rt->DrawRectangle(rect, d2dState.solidBrushes[Brush::line].get());
|
d2dState.ToggleAliasedLinesMode(false);
|
||||||
d2dState.rt->SetAntialiasMode(prevMode);
|
|
||||||
|
|
||||||
OverlayBoxText text;
|
OverlayBoxText text;
|
||||||
const auto width = std::abs(rect.right - rect.left + 1);
|
const auto [crossSymbolPos, measureStringBufLen] =
|
||||||
const auto height = std::abs(rect.top - rect.bottom + 1);
|
measurement.Print(text.buffer.data(),
|
||||||
const uint32_t textLen = swprintf_s(text.buffer.data(),
|
|
||||||
text.buffer.size(),
|
text.buffer.size(),
|
||||||
L"%.0f × %.0f",
|
true,
|
||||||
width,
|
true,
|
||||||
height);
|
commonState.units);
|
||||||
std::optional<size_t> crossSymbolPos = wcschr(text.buffer.data(), L' ') - text.buffer.data() + 1;
|
|
||||||
|
|
||||||
commonState.overlayBoxText.Access([&](OverlayBoxText& v) {
|
commonState.overlayBoxText.Access([&](OverlayBoxText& v) {
|
||||||
v = text;
|
v = text;
|
||||||
});
|
});
|
||||||
|
|
||||||
float cornerX = rect.right;
|
|
||||||
float cornerY = rect.bottom;
|
|
||||||
if (alignTextBoxToCenter)
|
if (alignTextBoxToCenter)
|
||||||
{
|
{
|
||||||
cornerX = rect.left + width / 2;
|
mouseX = measurement.rect.left + measurement.Width(Measurement::Unit::Pixel) / 2;
|
||||||
cornerY = rect.top + height / 2;
|
mouseY = measurement.rect.top + measurement.Height(Measurement::Unit::Pixel) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
d2dState.DrawTextBox(text.buffer.data(),
|
d2dState.DrawTextBox(text.buffer.data(),
|
||||||
textLen,
|
measureStringBufLen,
|
||||||
crossSymbolPos,
|
crossSymbolPos,
|
||||||
cornerX,
|
mouseX,
|
||||||
cornerY,
|
mouseY,
|
||||||
screenQuadrantAware,
|
screenQuadrantAware,
|
||||||
window);
|
window);
|
||||||
}
|
}
|
||||||
@@ -150,20 +149,21 @@ void DrawBoundsToolTick(const CommonState& commonState,
|
|||||||
if (it == end(toolState.perScreen))
|
if (it == end(toolState.perScreen))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
d2dState.rt->Clear();
|
d2dState.dxgiWindowState.rt->Clear();
|
||||||
|
|
||||||
const auto& perScreen = it->second;
|
const auto& perScreen = it->second;
|
||||||
for (const auto& measure : perScreen.measurements)
|
for (const auto& measure : perScreen.measurements)
|
||||||
DrawMeasurement(measure, true, commonState, window, d2dState);
|
DrawMeasurement(measure, true, commonState, window, d2dState, measure.rect.right, measure.rect.bottom);
|
||||||
|
|
||||||
if (!perScreen.currentRegionStart.has_value())
|
if (!perScreen.currentRegionStart.has_value())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const auto cursorPos = convert::FromSystemToRelativeForDirect2D(window, commonState.cursorPosSystemSpace);
|
const auto cursorPos = convert::FromSystemToWindow(window, commonState.cursorPosSystemSpace);
|
||||||
|
|
||||||
const D2D1_RECT_F rect{ .left = perScreen.currentRegionStart->x,
|
D2D1_RECT_F rect;
|
||||||
.top = perScreen.currentRegionStart->y,
|
const float cursorX = static_cast<float>(cursorPos.x);
|
||||||
.right = static_cast<float>(cursorPos.x),
|
const float cursorY = static_cast<float>(cursorPos.y);
|
||||||
.bottom = static_cast<float>(cursorPos.y) };
|
std::tie(rect.left, rect.right) = std::minmax(cursorX, perScreen.currentRegionStart->x);
|
||||||
DrawMeasurement(rect, false, commonState, window, d2dState);
|
std::tie(rect.top, rect.bottom) = std::minmax(cursorY, perScreen.currentRegionStart->y);
|
||||||
|
DrawMeasurement(Measurement{ rect }, false, commonState, window, d2dState, cursorX, cursorY);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,21 +6,9 @@
|
|||||||
namespace convert
|
namespace convert
|
||||||
{
|
{
|
||||||
// Converts a given point from multi-monitor coordinate system to the one relative to HWND
|
// Converts a given point from multi-monitor coordinate system to the one relative to HWND
|
||||||
inline POINT FromSystemToRelative(HWND window, POINT p)
|
inline POINT FromSystemToWindow(HWND window, POINT p)
|
||||||
{
|
{
|
||||||
ScreenToClient(window, &p);
|
ScreenToClient(window, &p);
|
||||||
return 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "constants.h"
|
#include "constants.h"
|
||||||
#include "D2DState.h"
|
#include "D2DState.h"
|
||||||
|
#include "DxgiAPI.h"
|
||||||
|
|
||||||
#include <common/Display/dpi_aware.h>
|
#include <common/Display/dpi_aware.h>
|
||||||
#include <ToolState.h>
|
#include <ToolState.h>
|
||||||
@@ -19,43 +20,28 @@ namespace
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
D2DState::D2DState(const HWND overlayWindow, std::vector<D2D1::ColorF> solidBrushesColors)
|
D2DState::D2DState(const DxgiAPI* dxgi,
|
||||||
|
HWND window,
|
||||||
|
std::vector<D2D1::ColorF> solidBrushesColors)
|
||||||
{
|
{
|
||||||
std::lock_guard guard{ gpuAccessLock };
|
dxgiAPI = dxgi;
|
||||||
|
|
||||||
RECT clientRect = {};
|
|
||||||
|
|
||||||
winrt::check_bool(GetClientRect(overlayWindow, &clientRect));
|
|
||||||
winrt::check_hresult(D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, &d2dFactory));
|
|
||||||
|
|
||||||
// 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;
|
unsigned dpi = DPIAware::DEFAULT_DPI;
|
||||||
DPIAware::GetScreenDPIForWindow(overlayWindow, dpi);
|
DPIAware::GetScreenDPIForWindow(window, dpi);
|
||||||
dpiScale = dpi / static_cast<float>(DPIAware::DEFAULT_DPI);
|
dpiScale = dpi / static_cast<float>(DPIAware::DEFAULT_DPI);
|
||||||
|
|
||||||
winrt::check_hresult(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), writeFactory.put_unknown()));
|
dxgiWindowState = dxgiAPI->CreateD2D1RenderTarget(window);
|
||||||
winrt::check_hresult(writeFactory->CreateTextFormat(L"Segoe UI Variable Text",
|
|
||||||
|
winrt::check_hresult(dxgiWindowState.rt->CreateCompatibleRenderTarget(bitmapRt.put()));
|
||||||
|
|
||||||
|
winrt::check_hresult(dxgiAPI->writeFactory->CreateTextFormat(L"Segoe UI Variable Text",
|
||||||
nullptr,
|
nullptr,
|
||||||
DWRITE_FONT_WEIGHT_NORMAL,
|
DWRITE_FONT_WEIGHT_NORMAL,
|
||||||
DWRITE_FONT_STYLE_NORMAL,
|
DWRITE_FONT_STYLE_NORMAL,
|
||||||
DWRITE_FONT_STRETCH_NORMAL,
|
DWRITE_FONT_STRETCH_NORMAL,
|
||||||
consts::FONT_SIZE * dpiScale,
|
consts::FONT_SIZE * dpiScale,
|
||||||
L"en-US",
|
L"en-US",
|
||||||
&textFormat));
|
textFormat.put()));
|
||||||
winrt::check_hresult(textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER));
|
winrt::check_hresult(textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER));
|
||||||
winrt::check_hresult(textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER));
|
winrt::check_hresult(textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER));
|
||||||
winrt::check_hresult(textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP));
|
winrt::check_hresult(textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP));
|
||||||
@@ -63,22 +49,22 @@ D2DState::D2DState(const HWND overlayWindow, std::vector<D2D1::ColorF> solidBrus
|
|||||||
solidBrushes.resize(solidBrushesColors.size());
|
solidBrushes.resize(solidBrushesColors.size());
|
||||||
for (size_t i = 0; i < solidBrushes.size(); ++i)
|
for (size_t i = 0; i < solidBrushes.size(); ++i)
|
||||||
{
|
{
|
||||||
winrt::check_hresult(rt->CreateSolidColorBrush(solidBrushesColors[i], &solidBrushes[i]));
|
winrt::check_hresult(dxgiWindowState.rt->CreateSolidColorBrush(solidBrushesColors[i], solidBrushes[i].put()));
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto deviceContext = rt.query<ID2D1DeviceContext>();
|
const auto deviceContext = dxgiWindowState.rt.as<ID2D1DeviceContext>();
|
||||||
winrt::check_hresult(deviceContext->CreateEffect(CLSID_D2D1Shadow, &shadowEffect));
|
winrt::check_hresult(deviceContext->CreateEffect(CLSID_D2D1Shadow, shadowEffect.put()));
|
||||||
winrt::check_hresult(shadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, consts::SHADOW_RADIUS));
|
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(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));
|
winrt::check_hresult(deviceContext->CreateEffect(CLSID_D2D12DAffineTransform, affineTransformEffect.put()));
|
||||||
affineTransformEffect->SetInputEffect(0, shadowEffect.get());
|
affineTransformEffect->SetInputEffect(0, shadowEffect.get());
|
||||||
|
|
||||||
textRenderer = winrt::make_self<PerGlyphOpacityTextRender>(d2dFactory, rt, solidBrushes[Brush::foreground]);
|
textRenderer = winrt::make_self<PerGlyphOpacityTextRender>(dxgi->d2dFactory2, dxgiWindowState.rt, solidBrushes[Brush::foreground]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void D2DState::DrawTextBox(const wchar_t* text,
|
void D2DState::DrawTextBox(const wchar_t* text,
|
||||||
const uint32_t textLen,
|
const size_t textLen,
|
||||||
const std::optional<size_t> halfOpaqueSymbolPos,
|
const std::optional<size_t> halfOpaqueSymbolPos,
|
||||||
const float centerX,
|
const float centerX,
|
||||||
const float centerY,
|
const float centerY,
|
||||||
@@ -86,16 +72,19 @@ void D2DState::DrawTextBox(const wchar_t* text,
|
|||||||
const HWND window) const
|
const HWND window) const
|
||||||
{
|
{
|
||||||
wil::com_ptr<IDWriteTextLayout> textLayout;
|
wil::com_ptr<IDWriteTextLayout> textLayout;
|
||||||
winrt::check_hresult(writeFactory->CreateTextLayout(text,
|
winrt::check_hresult(
|
||||||
textLen,
|
dxgiAPI->writeFactory->CreateTextLayout(text,
|
||||||
|
static_cast<uint32_t>(textLen),
|
||||||
textFormat.get(),
|
textFormat.get(),
|
||||||
std::numeric_limits<float>::max(),
|
std::numeric_limits<float>::max(),
|
||||||
std::numeric_limits<float>::max(),
|
std::numeric_limits<float>::max(),
|
||||||
&textLayout));
|
&textLayout));
|
||||||
DWRITE_TEXT_METRICS textMetrics = {};
|
DWRITE_TEXT_METRICS textMetrics = {};
|
||||||
winrt::check_hresult(textLayout->GetMetrics(&textMetrics));
|
winrt::check_hresult(textLayout->GetMetrics(&textMetrics));
|
||||||
textMetrics.width *= consts::TEXT_BOX_MARGIN_COEFF;
|
// Assumes text doesn't contain new lines
|
||||||
textMetrics.height *= consts::TEXT_BOX_MARGIN_COEFF;
|
const float lineHeight = textMetrics.height;
|
||||||
|
textMetrics.width += lineHeight;
|
||||||
|
textMetrics.height += lineHeight * .5f;
|
||||||
winrt::check_hresult(textLayout->SetMaxWidth(textMetrics.width));
|
winrt::check_hresult(textLayout->SetMaxWidth(textMetrics.width));
|
||||||
winrt::check_hresult(textLayout->SetMaxHeight(textMetrics.height));
|
winrt::check_hresult(textLayout->SetMaxHeight(textMetrics.height));
|
||||||
|
|
||||||
@@ -103,6 +92,8 @@ void D2DState::DrawTextBox(const wchar_t* text,
|
|||||||
.top = centerY - textMetrics.height / 2.f,
|
.top = centerY - textMetrics.height / 2.f,
|
||||||
.right = centerX + textMetrics.width / 2.f,
|
.right = centerX + textMetrics.width / 2.f,
|
||||||
.bottom = centerY + textMetrics.height / 2.f };
|
.bottom = centerY + textMetrics.height / 2.f };
|
||||||
|
|
||||||
|
const float SHADOW_OFFSET = consts::SHADOW_OFFSET * dpiScale;
|
||||||
if (screenQuadrantAware)
|
if (screenQuadrantAware)
|
||||||
{
|
{
|
||||||
bool cursorInLeftScreenHalf = false;
|
bool cursorInLeftScreenHalf = false;
|
||||||
@@ -112,8 +103,8 @@ void D2DState::DrawTextBox(const wchar_t* text,
|
|||||||
static_cast<long>(centerY),
|
static_cast<long>(centerY),
|
||||||
cursorInLeftScreenHalf,
|
cursorInLeftScreenHalf,
|
||||||
cursorInTopScreenHalf);
|
cursorInTopScreenHalf);
|
||||||
float textQuadrantOffsetX = textMetrics.width * dpiScale;
|
float textQuadrantOffsetX = textMetrics.width / 2.f + SHADOW_OFFSET;
|
||||||
float textQuadrantOffsetY = textMetrics.height * dpiScale;
|
float textQuadrantOffsetY = textMetrics.height / 2.f + SHADOW_OFFSET;
|
||||||
if (!cursorInLeftScreenHalf)
|
if (!cursorInLeftScreenHalf)
|
||||||
textQuadrantOffsetX *= -1.f;
|
textQuadrantOffsetX *= -1.f;
|
||||||
if (!cursorInTopScreenHalf)
|
if (!cursorInTopScreenHalf)
|
||||||
@@ -140,15 +131,14 @@ void D2DState::DrawTextBox(const wchar_t* text,
|
|||||||
bitmapRt->GetBitmap(&rtBitmap);
|
bitmapRt->GetBitmap(&rtBitmap);
|
||||||
|
|
||||||
shadowEffect->SetInput(0, rtBitmap.get());
|
shadowEffect->SetInput(0, rtBitmap.get());
|
||||||
const auto shadowMatrix = D2D1::Matrix3x2F::Translation(consts::SHADOW_OFFSET * dpiScale,
|
const auto shadowMatrix = D2D1::Matrix3x2F::Translation(SHADOW_OFFSET, SHADOW_OFFSET);
|
||||||
consts::SHADOW_OFFSET * dpiScale);
|
|
||||||
winrt::check_hresult(affineTransformEffect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_TRANSFORM_MATRIX,
|
winrt::check_hresult(affineTransformEffect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_TRANSFORM_MATRIX,
|
||||||
shadowMatrix));
|
shadowMatrix));
|
||||||
auto deviceContext = rt.query<ID2D1DeviceContext>();
|
auto deviceContext = dxgiWindowState.rt.as<ID2D1DeviceContext>();
|
||||||
deviceContext->DrawImage(affineTransformEffect.get(), D2D1_INTERPOLATION_MODE_LINEAR);
|
deviceContext->DrawImage(affineTransformEffect.get(), D2D1_INTERPOLATION_MODE_LINEAR);
|
||||||
|
|
||||||
// Draw text box border rectangle
|
// Draw text box border rectangle
|
||||||
rt->DrawRoundedRectangle(textBoxRect, solidBrushes[Brush::border].get());
|
dxgiWindowState.rt->DrawRoundedRectangle(textBoxRect, solidBrushes[Brush::border].get());
|
||||||
const float TEXT_BOX_PADDING = 1.f * dpiScale;
|
const float TEXT_BOX_PADDING = 1.f * dpiScale;
|
||||||
textBoxRect.rect.bottom -= TEXT_BOX_PADDING;
|
textBoxRect.rect.bottom -= TEXT_BOX_PADDING;
|
||||||
textBoxRect.rect.top += TEXT_BOX_PADDING;
|
textBoxRect.rect.top += TEXT_BOX_PADDING;
|
||||||
@@ -156,7 +146,7 @@ void D2DState::DrawTextBox(const wchar_t* text,
|
|||||||
textBoxRect.rect.right -= TEXT_BOX_PADDING;
|
textBoxRect.rect.right -= TEXT_BOX_PADDING;
|
||||||
|
|
||||||
// Draw text & its box
|
// Draw text & its box
|
||||||
rt->FillRoundedRectangle(textBoxRect, solidBrushes[Brush::background].get());
|
dxgiWindowState.rt->FillRoundedRectangle(textBoxRect, solidBrushes[Brush::background].get());
|
||||||
|
|
||||||
if (halfOpaqueSymbolPos.has_value())
|
if (halfOpaqueSymbolPos.has_value())
|
||||||
{
|
{
|
||||||
@@ -167,3 +157,19 @@ void D2DState::DrawTextBox(const wchar_t* text,
|
|||||||
}
|
}
|
||||||
winrt::check_hresult(textLayout->Draw(nullptr, textRenderer.get(), textRect.left, textRect.top));
|
winrt::check_hresult(textLayout->Draw(nullptr, textRenderer.get(), textRect.left, textRect.top));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void D2DState::ToggleAliasedLinesMode(const bool enabled) const
|
||||||
|
{
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
// Draw lines in the middle of a pixel to avoid bleeding, since [0,0] pixel is
|
||||||
|
// a rectangle filled from (0,0) to (1,1) and the lines use thickness = 1.
|
||||||
|
dxgiWindowState.rt->SetTransform(D2D1::Matrix3x2F::Translation(.5f, .5f));
|
||||||
|
dxgiWindowState.rt->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dxgiWindowState.rt->SetTransform(D2D1::Matrix3x2F::Identity());
|
||||||
|
dxgiWindowState.rt->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,10 +3,9 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <d2d1_3.h>
|
|
||||||
#include <wil/com.h>
|
|
||||||
#include <windef.h>
|
#include <windef.h>
|
||||||
|
|
||||||
|
#include "DxgiAPI.h"
|
||||||
#include "PerGlyphOpacityTextRender.h"
|
#include "PerGlyphOpacityTextRender.h"
|
||||||
|
|
||||||
enum Brush : size_t
|
enum Brush : size_t
|
||||||
@@ -19,24 +18,27 @@ enum Brush : size_t
|
|||||||
|
|
||||||
struct D2DState
|
struct D2DState
|
||||||
{
|
{
|
||||||
wil::com_ptr<ID2D1Factory> d2dFactory;
|
const DxgiAPI* dxgiAPI = nullptr;
|
||||||
wil::com_ptr<IDWriteFactory> writeFactory;
|
|
||||||
wil::com_ptr<ID2D1HwndRenderTarget> rt;
|
DxgiWindowState dxgiWindowState;
|
||||||
wil::com_ptr<ID2D1BitmapRenderTarget> bitmapRt;
|
winrt::com_ptr<ID2D1BitmapRenderTarget> bitmapRt;
|
||||||
wil::com_ptr<IDWriteTextFormat> textFormat;
|
winrt::com_ptr<IDWriteTextFormat> textFormat;
|
||||||
winrt::com_ptr<PerGlyphOpacityTextRender> textRenderer;
|
winrt::com_ptr<PerGlyphOpacityTextRender> textRenderer;
|
||||||
std::vector<wil::com_ptr<ID2D1SolidColorBrush>> solidBrushes;
|
std::vector<winrt::com_ptr<ID2D1SolidColorBrush>> solidBrushes;
|
||||||
wil::com_ptr<ID2D1Effect> shadowEffect;
|
winrt::com_ptr<ID2D1Effect> shadowEffect;
|
||||||
wil::com_ptr<ID2D1Effect> affineTransformEffect;
|
winrt::com_ptr<ID2D1Effect> affineTransformEffect;
|
||||||
|
|
||||||
float dpiScale = 1.f;
|
float dpiScale = 1.f;
|
||||||
|
|
||||||
D2DState(const HWND window, std::vector<D2D1::ColorF> solidBrushesColors);
|
D2DState(const DxgiAPI*,
|
||||||
|
HWND window,
|
||||||
|
std::vector<D2D1::ColorF> solidBrushesColors);
|
||||||
void DrawTextBox(const wchar_t* text,
|
void DrawTextBox(const wchar_t* text,
|
||||||
const uint32_t textLen,
|
const size_t textLen,
|
||||||
const std::optional<size_t> halfOpaqueSymbolPos,
|
const std::optional<size_t> halfOpaqueSymbolPos,
|
||||||
const float centerX,
|
const float centerX,
|
||||||
const float centerY,
|
const float centerY,
|
||||||
const bool screenQuadrantAware,
|
const bool screenQuadrantAware,
|
||||||
const HWND window) const;
|
const HWND window) const;
|
||||||
|
void ToggleAliasedLinesMode(const bool enabled) const;
|
||||||
};
|
};
|
||||||
|
|||||||
145
src/modules/MeasureTool/MeasureToolCore/DxgiAPI.cpp
Normal file
145
src/modules/MeasureTool/MeasureToolCore/DxgiAPI.cpp
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "DxgiAPI.h"
|
||||||
|
|
||||||
|
#include <common/Display/dpi_aware.h>
|
||||||
|
|
||||||
|
//#define DEBUG_DEVICES
|
||||||
|
#define SEPARATE_D3D_FOR_CAPTURE
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
DxgiAPI::D3D CreateD3D()
|
||||||
|
{
|
||||||
|
DxgiAPI::D3D d3d;
|
||||||
|
UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
||||||
|
#if defined(DEBUG_DEVICES)
|
||||||
|
flags |= D3D11_CREATE_DEVICE_DEBUG;
|
||||||
|
#endif
|
||||||
|
HRESULT hr =
|
||||||
|
D3D11CreateDevice(nullptr,
|
||||||
|
D3D_DRIVER_TYPE_HARDWARE,
|
||||||
|
nullptr,
|
||||||
|
flags,
|
||||||
|
nullptr,
|
||||||
|
0,
|
||||||
|
D3D11_SDK_VERSION,
|
||||||
|
d3d.d3dDevice.put(),
|
||||||
|
nullptr,
|
||||||
|
nullptr);
|
||||||
|
if (hr == DXGI_ERROR_UNSUPPORTED)
|
||||||
|
{
|
||||||
|
hr = D3D11CreateDevice(nullptr,
|
||||||
|
D3D_DRIVER_TYPE_WARP,
|
||||||
|
nullptr,
|
||||||
|
flags,
|
||||||
|
nullptr,
|
||||||
|
0,
|
||||||
|
D3D11_SDK_VERSION,
|
||||||
|
d3d.d3dDevice.put(),
|
||||||
|
nullptr,
|
||||||
|
nullptr);
|
||||||
|
}
|
||||||
|
winrt::check_hresult(hr);
|
||||||
|
|
||||||
|
d3d.dxgiDevice = d3d.d3dDevice.as<IDXGIDevice>();
|
||||||
|
winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice(d3d.dxgiDevice.get(), d3d.d3dDeviceInspectable.put()));
|
||||||
|
|
||||||
|
winrt::com_ptr<IDXGIAdapter> adapter;
|
||||||
|
winrt::check_hresult(d3d.dxgiDevice->GetParent(winrt::guid_of<IDXGIAdapter>(), adapter.put_void()));
|
||||||
|
winrt::check_hresult(adapter->GetParent(winrt::guid_of<IDXGIFactory2>(), d3d.dxgiFactory2.put_void()));
|
||||||
|
|
||||||
|
d3d.d3dDevice->GetImmediateContext(d3d.d3dContext.put());
|
||||||
|
winrt::check_bool(d3d.d3dContext);
|
||||||
|
auto contextMultithread = d3d.d3dContext.as<ID3D11Multithread>();
|
||||||
|
contextMultithread->SetMultithreadProtected(true);
|
||||||
|
|
||||||
|
return d3d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DxgiAPI::DxgiAPI()
|
||||||
|
{
|
||||||
|
const D2D1_FACTORY_OPTIONS d2dFactoryOptions = {
|
||||||
|
#if defined(DEBUG_DEVICES)
|
||||||
|
D2D1_DEBUG_LEVEL_INFORMATION
|
||||||
|
#else
|
||||||
|
D2D1_DEBUG_LEVEL_NONE
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
winrt::check_hresult(D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, d2dFactoryOptions, d2dFactory2.put()));
|
||||||
|
|
||||||
|
winrt::check_hresult(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED,
|
||||||
|
winrt::guid_of<IDWriteFactory>(),
|
||||||
|
reinterpret_cast<IUnknown**>(writeFactory.put())));
|
||||||
|
|
||||||
|
auto d3d = CreateD3D();
|
||||||
|
d3dDevice = d3d.d3dDevice;
|
||||||
|
dxgiDevice = d3d.dxgiDevice;
|
||||||
|
d3dDeviceInspectable = d3d.d3dDeviceInspectable;
|
||||||
|
dxgiFactory2 = d3d.dxgiFactory2;
|
||||||
|
d3dContext = d3d.d3dContext;
|
||||||
|
#if defined(SEPARATE_D3D_FOR_CAPTURE)
|
||||||
|
auto d3dFC = CreateD3D();
|
||||||
|
d3dForCapture = d3dFC;
|
||||||
|
#else
|
||||||
|
d3dForCapture = d3d;
|
||||||
|
#endif
|
||||||
|
winrt::check_hresult(d2dFactory2->CreateDevice(dxgiDevice.get(), d2dDevice1.put()));
|
||||||
|
winrt::check_hresult(DCompositionCreateDevice(
|
||||||
|
dxgiDevice.get(),
|
||||||
|
winrt::guid_of<IDCompositionDevice>(),
|
||||||
|
compositionDevice.put_void()));
|
||||||
|
}
|
||||||
|
|
||||||
|
DxgiWindowState DxgiAPI::CreateD2D1RenderTarget(HWND window) const
|
||||||
|
{
|
||||||
|
RECT rect = {};
|
||||||
|
winrt::check_bool(GetClientRect(window, &rect));
|
||||||
|
|
||||||
|
const DXGI_SWAP_CHAIN_DESC1 desc = {
|
||||||
|
.Width = static_cast<UINT>(rect.right - rect.left),
|
||||||
|
.Height = static_cast<UINT>(rect.bottom - rect.top),
|
||||||
|
.Format = static_cast<DXGI_FORMAT>(winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized),
|
||||||
|
.SampleDesc = { .Count = 1, .Quality = 0 },
|
||||||
|
.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
|
||||||
|
.BufferCount = 2,
|
||||||
|
.Scaling = DXGI_SCALING_STRETCH,
|
||||||
|
.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
|
||||||
|
.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED,
|
||||||
|
};
|
||||||
|
|
||||||
|
DxgiWindowState state;
|
||||||
|
winrt::com_ptr<ID2D1DeviceContext> rt;
|
||||||
|
d2dDevice1->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, rt.put());
|
||||||
|
state.rt = rt;
|
||||||
|
|
||||||
|
winrt::check_hresult(dxgiFactory2->CreateSwapChainForComposition(d3dDevice.get(),
|
||||||
|
&desc,
|
||||||
|
nullptr,
|
||||||
|
state.swapChain.put()));
|
||||||
|
winrt::com_ptr<IDXGISurface> surface;
|
||||||
|
winrt::check_hresult(state.swapChain->GetBuffer(0, winrt::guid_of<IDXGISurface>(), surface.put_void()));
|
||||||
|
|
||||||
|
const D2D1_BITMAP_PROPERTIES1 properties = {
|
||||||
|
.pixelFormat = { .format = DXGI_FORMAT_B8G8R8A8_UNORM, .alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED },
|
||||||
|
.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW
|
||||||
|
};
|
||||||
|
winrt::com_ptr<ID2D1Bitmap1> bitmap;
|
||||||
|
winrt::check_hresult(rt->CreateBitmapFromDxgiSurface(surface.get(),
|
||||||
|
properties,
|
||||||
|
bitmap.put()));
|
||||||
|
rt->SetTarget(bitmap.get());
|
||||||
|
winrt::check_hresult(compositionDevice->CreateTargetForHwnd(window,
|
||||||
|
true,
|
||||||
|
state.compositionTarget.put()));
|
||||||
|
|
||||||
|
winrt::com_ptr<IDCompositionVisual> visual;
|
||||||
|
winrt::check_hresult(compositionDevice->CreateVisual(visual.put()));
|
||||||
|
winrt::check_hresult(visual->SetContent(state.swapChain.get()));
|
||||||
|
winrt::check_hresult(state.compositionTarget->SetRoot(visual.get()));
|
||||||
|
winrt::check_hresult(compositionDevice->Commit());
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
49
src/modules/MeasureTool/MeasureToolCore/DxgiAPI.h
Normal file
49
src/modules/MeasureTool/MeasureToolCore/DxgiAPI.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <d2d1_3.h>
|
||||||
|
#include <d3d11_4.h>
|
||||||
|
#include <dcomp.h>
|
||||||
|
#include <dxgi1_3.h>
|
||||||
|
#include <inspectable.h>
|
||||||
|
#include <winrt/base.h>
|
||||||
|
|
||||||
|
struct DxgiWindowState
|
||||||
|
{
|
||||||
|
winrt::com_ptr<ID2D1RenderTarget> rt;
|
||||||
|
winrt::com_ptr<IDXGISwapChain1> swapChain;
|
||||||
|
winrt::com_ptr<IDCompositionTarget> compositionTarget;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DxgiAPI final
|
||||||
|
{
|
||||||
|
struct D3D
|
||||||
|
{
|
||||||
|
winrt::com_ptr<ID3D11Device> d3dDevice;
|
||||||
|
winrt::com_ptr<IDXGIDevice> dxgiDevice;
|
||||||
|
winrt::com_ptr<IInspectable> d3dDeviceInspectable;
|
||||||
|
winrt::com_ptr<IDXGIFactory2> dxgiFactory2;
|
||||||
|
winrt::com_ptr<ID3D11DeviceContext> d3dContext;
|
||||||
|
};
|
||||||
|
|
||||||
|
winrt::com_ptr<ID2D1Factory2> d2dFactory2;
|
||||||
|
winrt::com_ptr<IDWriteFactory> writeFactory;
|
||||||
|
|
||||||
|
winrt::com_ptr<ID3D11Device> d3dDevice;
|
||||||
|
winrt::com_ptr<IDXGIDevice> dxgiDevice;
|
||||||
|
winrt::com_ptr<IInspectable> d3dDeviceInspectable;
|
||||||
|
winrt::com_ptr<IDXGIFactory2> dxgiFactory2;
|
||||||
|
winrt::com_ptr<ID3D11DeviceContext> d3dContext;
|
||||||
|
|
||||||
|
D3D d3dForCapture;
|
||||||
|
|
||||||
|
winrt::com_ptr<ID2D1Device1> d2dDevice1;
|
||||||
|
winrt::com_ptr<IDCompositionDevice> compositionDevice;
|
||||||
|
|
||||||
|
DxgiAPI();
|
||||||
|
|
||||||
|
enum class Uninitialized
|
||||||
|
{
|
||||||
|
};
|
||||||
|
explicit inline DxgiAPI(Uninitialized) {}
|
||||||
|
|
||||||
|
DxgiWindowState CreateD2D1RenderTarget(HWND window) const;
|
||||||
|
};
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
#include "constants.h"
|
#include "constants.h"
|
||||||
#include "EdgeDetection.h"
|
#include "EdgeDetection.h"
|
||||||
|
|
||||||
template<bool PerChannel,
|
template<bool PerChannel,
|
||||||
bool ContinuousCapture,
|
|
||||||
bool IsX,
|
bool IsX,
|
||||||
bool Increment>
|
bool Increment>
|
||||||
inline long FindEdge(const BGRATextureView& texture, const POINT centerPoint, const uint8_t tolerance)
|
inline long FindEdge(const BGRATextureView& texture, const POINT centerPoint, const uint8_t tolerance)
|
||||||
@@ -57,25 +57,21 @@ inline long FindEdge(const BGRATextureView& texture, const POINT centerPoint, co
|
|||||||
return Increment ? static_cast<long>(IsX ? texture.width : texture.height) - 1 : 0;
|
return Increment ? static_cast<long>(IsX ? texture.width : texture.height) - 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<bool PerChannel, bool ContinuousCapture>
|
template<bool PerChannel>
|
||||||
inline RECT DetectEdgesInternal(const BGRATextureView& texture,
|
inline RECT DetectEdgesInternal(const BGRATextureView& texture,
|
||||||
const POINT centerPoint,
|
const POINT centerPoint,
|
||||||
const uint8_t tolerance)
|
const uint8_t tolerance)
|
||||||
{
|
{
|
||||||
return RECT{ .left = FindEdge<PerChannel,
|
return RECT{ .left = FindEdge<PerChannel,
|
||||||
ContinuousCapture,
|
|
||||||
true,
|
true,
|
||||||
false>(texture, centerPoint, tolerance),
|
false>(texture, centerPoint, tolerance),
|
||||||
.top = FindEdge<PerChannel,
|
.top = FindEdge<PerChannel,
|
||||||
ContinuousCapture,
|
|
||||||
false,
|
false,
|
||||||
false>(texture, centerPoint, tolerance),
|
false>(texture, centerPoint, tolerance),
|
||||||
.right = FindEdge<PerChannel,
|
.right = FindEdge<PerChannel,
|
||||||
ContinuousCapture,
|
|
||||||
true,
|
true,
|
||||||
true>(texture, centerPoint, tolerance),
|
true>(texture, centerPoint, tolerance),
|
||||||
.bottom = FindEdge<PerChannel,
|
.bottom = FindEdge<PerChannel,
|
||||||
ContinuousCapture,
|
|
||||||
false,
|
false,
|
||||||
true>(texture, centerPoint, tolerance) };
|
true>(texture, centerPoint, tolerance) };
|
||||||
}
|
}
|
||||||
@@ -83,12 +79,9 @@ inline RECT DetectEdgesInternal(const BGRATextureView& texture,
|
|||||||
RECT DetectEdges(const BGRATextureView& texture,
|
RECT DetectEdges(const BGRATextureView& texture,
|
||||||
const POINT centerPoint,
|
const POINT centerPoint,
|
||||||
const bool perChannel,
|
const bool perChannel,
|
||||||
const uint8_t tolerance,
|
const uint8_t tolerance)
|
||||||
const bool continuousCapture)
|
|
||||||
{
|
{
|
||||||
auto function = perChannel ? &DetectEdgesInternal<true, false> : DetectEdgesInternal<false, false>;
|
auto function = perChannel ? &DetectEdgesInternal<true> : DetectEdgesInternal<false>;
|
||||||
if (continuousCapture)
|
|
||||||
function = perChannel ? &DetectEdgesInternal<true, true> : &DetectEdgesInternal<false, true>;
|
|
||||||
|
|
||||||
return function(texture, centerPoint, tolerance);
|
return function(texture, centerPoint, tolerance);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,4 @@
|
|||||||
RECT DetectEdges(const BGRATextureView& texture,
|
RECT DetectEdges(const BGRATextureView& texture,
|
||||||
const POINT centerPoint,
|
const POINT centerPoint,
|
||||||
const bool perChannel,
|
const bool perChannel,
|
||||||
const uint8_t tolerance,
|
const uint8_t tolerance);
|
||||||
const bool continuousCapture);
|
|
||||||
@@ -16,30 +16,22 @@ namespace
|
|||||||
// Computing in this way to achieve pixel-perfect axial symmetry of aliased D2D lines
|
// Computing in this way to achieve pixel-perfect axial symmetry of aliased D2D lines
|
||||||
if (horizontal)
|
if (horizontal)
|
||||||
{
|
{
|
||||||
start.x -= consts::FEET_HALF_LENGTH + 1.f;
|
start.x -= consts::FEET_HALF_LENGTH;
|
||||||
end.x += consts::FEET_HALF_LENGTH;
|
end.x += consts::FEET_HALF_LENGTH + 1.f;
|
||||||
|
|
||||||
start.y += 1.f;
|
|
||||||
end.y += 1.f;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
start.y -= consts::FEET_HALF_LENGTH + 1.f;
|
start.y -= consts::FEET_HALF_LENGTH;
|
||||||
end.y += consts::FEET_HALF_LENGTH;
|
end.y += consts::FEET_HALF_LENGTH + 1.f;
|
||||||
|
|
||||||
start.x += 1.f;
|
|
||||||
end.x += 1.f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { start, end };
|
return { start, end };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
winrt::com_ptr<ID2D1Bitmap> ConvertID3D11Texture2DToD2D1Bitmap(wil::com_ptr<ID2D1HwndRenderTarget> rt,
|
winrt::com_ptr<ID2D1Bitmap> ConvertID3D11Texture2DToD2D1Bitmap(winrt::com_ptr<ID2D1RenderTarget> rt,
|
||||||
const MappedTextureView* capturedScreenTexture)
|
const MappedTextureView* capturedScreenTexture)
|
||||||
{
|
{
|
||||||
std::lock_guard guard{ gpuAccessLock };
|
|
||||||
|
|
||||||
capturedScreenTexture->view.pixels;
|
capturedScreenTexture->view.pixels;
|
||||||
|
|
||||||
D2D1_BITMAP_PROPERTIES props = { .pixelFormat = rt->GetPixelFormat() };
|
D2D1_BITMAP_PROPERTIES props = { .pixelFormat = rt->GetPixelFormat() };
|
||||||
@@ -62,6 +54,7 @@ LRESULT CALLBACK MeasureToolWndProc(HWND window, UINT message, WPARAM wparam, LP
|
|||||||
{
|
{
|
||||||
switch (message)
|
switch (message)
|
||||||
{
|
{
|
||||||
|
case WM_MOUSELEAVE:
|
||||||
case WM_CURSOR_LEFT_MONITOR:
|
case WM_CURSOR_LEFT_MONITOR:
|
||||||
{
|
{
|
||||||
if (auto state = GetWindowParam<Serialized<MeasureToolState>*>(window))
|
if (auto state = GetWindowParam<Serialized<MeasureToolState>*>(window))
|
||||||
@@ -130,20 +123,27 @@ void DrawMeasureToolTick(const CommonState& commonState,
|
|||||||
bool drawFeetOnCross = {};
|
bool drawFeetOnCross = {};
|
||||||
bool drawHorizontalCrossLine = true;
|
bool drawHorizontalCrossLine = true;
|
||||||
bool drawVerticalCrossLine = true;
|
bool drawVerticalCrossLine = true;
|
||||||
RECT measuredEdges{};
|
|
||||||
|
Measurement measuredEdges{};
|
||||||
MeasureToolState::Mode mode = {};
|
MeasureToolState::Mode mode = {};
|
||||||
winrt::com_ptr<ID2D1Bitmap> backgroundBitmap;
|
winrt::com_ptr<ID2D1Bitmap> backgroundBitmap;
|
||||||
const MappedTextureView* backgroundTextureToConvert = nullptr;
|
const MappedTextureView* backgroundTextureToConvert = nullptr;
|
||||||
|
|
||||||
|
bool gotMeasurement = false;
|
||||||
toolState.Read([&](const MeasureToolState& state) {
|
toolState.Read([&](const MeasureToolState& state) {
|
||||||
continuousCapture = state.global.continuousCapture;
|
continuousCapture = state.global.continuousCapture;
|
||||||
drawFeetOnCross = state.global.drawFeetOnCross;
|
drawFeetOnCross = state.global.drawFeetOnCross;
|
||||||
mode = state.global.mode;
|
mode = state.global.mode;
|
||||||
|
|
||||||
if (auto it = state.perScreen.find(window); it != end(state.perScreen))
|
if (auto it = state.perScreen.find(window); it != end(state.perScreen))
|
||||||
{
|
{
|
||||||
const auto& perScreen = it->second;
|
const auto& perScreen = it->second;
|
||||||
measuredEdges = perScreen.measuredEdges;
|
if (!perScreen.measuredEdges)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gotMeasurement = true;
|
||||||
|
measuredEdges = *perScreen.measuredEdges;
|
||||||
|
|
||||||
if (continuousCapture)
|
if (continuousCapture)
|
||||||
return;
|
return;
|
||||||
@@ -158,6 +158,10 @@ void DrawMeasureToolTick(const CommonState& commonState,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!gotMeasurement)
|
||||||
|
return;
|
||||||
|
|
||||||
switch (mode)
|
switch (mode)
|
||||||
{
|
{
|
||||||
case MeasureToolState::Mode::Cross:
|
case MeasureToolState::Mode::Cross:
|
||||||
@@ -176,7 +180,7 @@ void DrawMeasureToolTick(const CommonState& commonState,
|
|||||||
|
|
||||||
if (!continuousCapture && !backgroundBitmap && backgroundTextureToConvert)
|
if (!continuousCapture && !backgroundBitmap && backgroundTextureToConvert)
|
||||||
{
|
{
|
||||||
backgroundBitmap = ConvertID3D11Texture2DToD2D1Bitmap(d2dState.rt, backgroundTextureToConvert);
|
backgroundBitmap = ConvertID3D11Texture2DToD2D1Bitmap(d2dState.dxgiWindowState.rt, backgroundTextureToConvert);
|
||||||
if (backgroundBitmap)
|
if (backgroundBitmap)
|
||||||
{
|
{
|
||||||
toolState.Access([&](MeasureToolState& state) {
|
toolState.Access([&](MeasureToolState& state) {
|
||||||
@@ -187,28 +191,24 @@ void DrawMeasureToolTick(const CommonState& commonState,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (continuousCapture || !backgroundBitmap)
|
if (continuousCapture || !backgroundBitmap)
|
||||||
d2dState.rt->Clear();
|
d2dState.dxgiWindowState.rt->Clear();
|
||||||
|
|
||||||
// Add 1px to each dim, since the range we obtain from measuredEdges is inclusive.
|
const float hMeasure = measuredEdges.Width(Measurement::Unit::Pixel);
|
||||||
const float hMeasure = static_cast<float>(measuredEdges.right - measuredEdges.left + 1);
|
const float vMeasure = measuredEdges.Height(Measurement::Unit::Pixel);
|
||||||
const float vMeasure = static_cast<float>(measuredEdges.bottom - measuredEdges.top + 1);
|
|
||||||
|
|
||||||
if (!continuousCapture && backgroundBitmap)
|
if (!continuousCapture && backgroundBitmap)
|
||||||
{
|
{
|
||||||
d2dState.rt->DrawBitmap(backgroundBitmap.get());
|
d2dState.dxgiWindowState.rt->DrawBitmap(backgroundBitmap.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto previousAliasingMode = d2dState.rt->GetAntialiasMode();
|
const auto cursorPos = convert::FromSystemToWindow(window, commonState.cursorPosSystemSpace);
|
||||||
// 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);
|
|
||||||
|
|
||||||
|
d2dState.ToggleAliasedLinesMode(true);
|
||||||
if (drawHorizontalCrossLine)
|
if (drawHorizontalCrossLine)
|
||||||
{
|
{
|
||||||
const D2D_POINT_2F hLineStart{ .x = static_cast<float>(measuredEdges.left), .y = static_cast<float>(cursorPos.y) };
|
const D2D_POINT_2F hLineStart{ .x = measuredEdges.rect.left, .y = static_cast<float>(cursorPos.y) };
|
||||||
D2D_POINT_2F hLineEnd{ .x = hLineStart.x + hMeasure, .y = hLineStart.y };
|
D2D_POINT_2F hLineEnd{ .x = hLineStart.x + hMeasure, .y = hLineStart.y };
|
||||||
d2dState.rt->DrawLine(hLineStart, hLineEnd, d2dState.solidBrushes[Brush::line].get());
|
d2dState.dxgiWindowState.rt->DrawLine(hLineStart, hLineEnd, d2dState.solidBrushes[Brush::line].get());
|
||||||
|
|
||||||
if (drawFeetOnCross)
|
if (drawFeetOnCross)
|
||||||
{
|
{
|
||||||
@@ -218,57 +218,37 @@ void DrawMeasureToolTick(const CommonState& commonState,
|
|||||||
hLineEnd.x -= 1.f;
|
hLineEnd.x -= 1.f;
|
||||||
auto [left_start, left_end] = ComputeCrossFeetLine(hLineStart, false);
|
auto [left_start, left_end] = ComputeCrossFeetLine(hLineStart, false);
|
||||||
auto [right_start, right_end] = ComputeCrossFeetLine(hLineEnd, false);
|
auto [right_start, right_end] = ComputeCrossFeetLine(hLineEnd, false);
|
||||||
d2dState.rt->DrawLine(left_start, left_end, d2dState.solidBrushes[Brush::line].get());
|
d2dState.dxgiWindowState.rt->DrawLine(left_start, left_end, d2dState.solidBrushes[Brush::line].get());
|
||||||
d2dState.rt->DrawLine(right_start, right_end, d2dState.solidBrushes[Brush::line].get());
|
d2dState.dxgiWindowState.rt->DrawLine(right_start, right_end, d2dState.solidBrushes[Brush::line].get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (drawVerticalCrossLine)
|
if (drawVerticalCrossLine)
|
||||||
{
|
{
|
||||||
const D2D_POINT_2F vLineStart{ .x = static_cast<float>(cursorPos.x), .y = static_cast<float>(measuredEdges.top) };
|
const D2D_POINT_2F vLineStart{ .x = static_cast<float>(cursorPos.x), .y = measuredEdges.rect.top };
|
||||||
D2D_POINT_2F vLineEnd{ .x = vLineStart.x, .y = vLineStart.y + vMeasure };
|
D2D_POINT_2F vLineEnd{ .x = vLineStart.x, .y = vLineStart.y + vMeasure };
|
||||||
d2dState.rt->DrawLine(vLineStart, vLineEnd, d2dState.solidBrushes[Brush::line].get());
|
d2dState.dxgiWindowState.rt->DrawLine(vLineStart, vLineEnd, d2dState.solidBrushes[Brush::line].get());
|
||||||
|
|
||||||
if (drawFeetOnCross)
|
if (drawFeetOnCross)
|
||||||
{
|
{
|
||||||
vLineEnd.y -= 1.f;
|
vLineEnd.y -= 1.f;
|
||||||
auto [top_start, top_end] = ComputeCrossFeetLine(vLineStart, true);
|
auto [top_start, top_end] = ComputeCrossFeetLine(vLineStart, true);
|
||||||
auto [bottom_start, bottom_end] = ComputeCrossFeetLine(vLineEnd, true);
|
auto [bottom_start, bottom_end] = ComputeCrossFeetLine(vLineEnd, true);
|
||||||
d2dState.rt->DrawLine(top_start, top_end, d2dState.solidBrushes[Brush::line].get());
|
d2dState.dxgiWindowState.rt->DrawLine(top_start, top_end, d2dState.solidBrushes[Brush::line].get());
|
||||||
d2dState.rt->DrawLine(bottom_start, bottom_end, d2dState.solidBrushes[Brush::line].get());
|
d2dState.dxgiWindowState.rt->DrawLine(bottom_start, bottom_end, d2dState.solidBrushes[Brush::line].get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// After drawing the lines, restore anti aliasing to draw the measurement tooltip.
|
d2dState.ToggleAliasedLinesMode(false);
|
||||||
d2dState.rt->SetAntialiasMode(previousAliasingMode);
|
|
||||||
|
|
||||||
uint32_t measureStringBufLen = 0;
|
|
||||||
|
|
||||||
OverlayBoxText text;
|
OverlayBoxText text;
|
||||||
std::optional<size_t> crossSymbolPos;
|
|
||||||
switch (mode)
|
const auto [crossSymbolPos, measureStringBufLen] =
|
||||||
{
|
measuredEdges.Print(text.buffer.data(),
|
||||||
case MeasureToolState::Mode::Cross:
|
|
||||||
measureStringBufLen = swprintf_s(text.buffer.data(),
|
|
||||||
text.buffer.size(),
|
text.buffer.size(),
|
||||||
L"%.0f × %.0f",
|
drawHorizontalCrossLine,
|
||||||
hMeasure,
|
drawVerticalCrossLine,
|
||||||
vMeasure);
|
commonState.units);
|
||||||
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) {
|
commonState.overlayBoxText.Access([&](OverlayBoxText& v) {
|
||||||
v = text;
|
v = text;
|
||||||
|
|||||||
91
src/modules/MeasureTool/MeasureToolCore/Measurement.cpp
Normal file
91
src/modules/MeasureTool/MeasureToolCore/Measurement.cpp
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "Measurement.h"
|
||||||
|
|
||||||
|
Measurement::Measurement(RECT winRect)
|
||||||
|
{
|
||||||
|
rect.left = static_cast<float>(winRect.left);
|
||||||
|
rect.right = static_cast<float>(winRect.right);
|
||||||
|
rect.top = static_cast<float>(winRect.top);
|
||||||
|
rect.bottom = static_cast<float>(winRect.bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
Measurement::Measurement(D2D1_RECT_F d2dRect) :
|
||||||
|
rect{ d2dRect }
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
inline float Convert(const float pixels, const Measurement::Unit units)
|
||||||
|
{
|
||||||
|
switch (units)
|
||||||
|
{
|
||||||
|
case Measurement::Unit::Pixel:
|
||||||
|
return pixels;
|
||||||
|
case Measurement::Unit::Inch:
|
||||||
|
return pixels / 96.f;
|
||||||
|
case Measurement::Unit::Centimetre:
|
||||||
|
return pixels / 96.f * 2.54f;
|
||||||
|
default:
|
||||||
|
return pixels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float Measurement::Width(const Unit units) const
|
||||||
|
{
|
||||||
|
return Convert(rect.right - rect.left + 1.f, units);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float Measurement::Height(const Unit units) const
|
||||||
|
{
|
||||||
|
return Convert(rect.bottom - rect.top + 1.f, units);
|
||||||
|
}
|
||||||
|
|
||||||
|
Measurement::PrintResult Measurement::Print(wchar_t* buf,
|
||||||
|
const size_t bufSize,
|
||||||
|
const bool printWidth,
|
||||||
|
const bool printHeight,
|
||||||
|
const Unit units) const
|
||||||
|
{
|
||||||
|
PrintResult result;
|
||||||
|
if (printWidth)
|
||||||
|
{
|
||||||
|
result.strLen += swprintf_s(buf,
|
||||||
|
bufSize,
|
||||||
|
L"%g",
|
||||||
|
Width(units));
|
||||||
|
if (printHeight)
|
||||||
|
{
|
||||||
|
result.crossSymbolPos = result.strLen + 1;
|
||||||
|
result.strLen += swprintf_s(buf + result.strLen,
|
||||||
|
bufSize - result.strLen,
|
||||||
|
L" \x00D7 ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (printHeight)
|
||||||
|
{
|
||||||
|
result.strLen += swprintf_s(buf + result.strLen,
|
||||||
|
bufSize - result.strLen,
|
||||||
|
L"%g",
|
||||||
|
Height(units));
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (units)
|
||||||
|
{
|
||||||
|
case Measurement::Unit::Inch:
|
||||||
|
result.strLen += swprintf_s(buf + result.strLen,
|
||||||
|
bufSize - result.strLen,
|
||||||
|
L" in");
|
||||||
|
break;
|
||||||
|
case Measurement::Unit::Centimetre:
|
||||||
|
result.strLen += swprintf_s(buf + result.strLen,
|
||||||
|
bufSize - result.strLen,
|
||||||
|
L" cm");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
38
src/modules/MeasureTool/MeasureToolCore/Measurement.h
Normal file
38
src/modules/MeasureTool/MeasureToolCore/Measurement.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <dcommon.h>
|
||||||
|
#include <windef.h>
|
||||||
|
|
||||||
|
struct Measurement
|
||||||
|
{
|
||||||
|
enum Unit
|
||||||
|
{
|
||||||
|
Pixel,
|
||||||
|
Inch,
|
||||||
|
Centimetre
|
||||||
|
};
|
||||||
|
|
||||||
|
D2D1_RECT_F rect = {}; // corners are inclusive
|
||||||
|
|
||||||
|
Measurement() = default;
|
||||||
|
Measurement(const Measurement&) = default;
|
||||||
|
Measurement& operator=(const Measurement&) = default;
|
||||||
|
|
||||||
|
explicit Measurement(D2D1_RECT_F d2dRect);
|
||||||
|
explicit Measurement(RECT winRect);
|
||||||
|
|
||||||
|
float Width(const Unit units) const;
|
||||||
|
float Height(const Unit units) const;
|
||||||
|
|
||||||
|
struct PrintResult
|
||||||
|
{
|
||||||
|
std::optional<size_t> crossSymbolPos;
|
||||||
|
size_t strLen = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
PrintResult Print(wchar_t* buf,
|
||||||
|
const size_t bufSize,
|
||||||
|
const bool printWidth,
|
||||||
|
const bool printHeight,
|
||||||
|
const Unit units) const;
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
#include "BoundsToolOverlayUI.h"
|
#include "BoundsToolOverlayUI.h"
|
||||||
|
#include "constants.h"
|
||||||
#include "MeasureToolOverlayUI.h"
|
#include "MeasureToolOverlayUI.h"
|
||||||
#include "OverlayUI.h"
|
#include "OverlayUI.h"
|
||||||
|
|
||||||
@@ -22,16 +23,17 @@ void CreateOverlayWindowClasses()
|
|||||||
|
|
||||||
wcex.lpfnWndProc = MeasureToolWndProc;
|
wcex.lpfnWndProc = MeasureToolWndProc;
|
||||||
wcex.lpszClassName = NonLocalizable::MeasureToolOverlayWindowName;
|
wcex.lpszClassName = NonLocalizable::MeasureToolOverlayWindowName;
|
||||||
|
wcex.hCursor = LoadCursorW(nullptr, IDC_CROSS);
|
||||||
RegisterClassExW(&wcex);
|
RegisterClassExW(&wcex);
|
||||||
|
|
||||||
wcex.lpfnWndProc = BoundsToolWndProc;
|
wcex.lpfnWndProc = BoundsToolWndProc;
|
||||||
wcex.lpszClassName = NonLocalizable::BoundsToolOverlayWindowName;
|
wcex.lpszClassName = NonLocalizable::BoundsToolOverlayWindowName;
|
||||||
wcex.hCursor = LoadCursorW(nullptr, IDC_CROSS);
|
|
||||||
RegisterClassExW(&wcex);
|
RegisterClassExW(&wcex);
|
||||||
}
|
}
|
||||||
|
|
||||||
HWND CreateOverlayUIWindow(const CommonState& commonState,
|
HWND CreateOverlayUIWindow(const CommonState& commonState,
|
||||||
const MonitorInfo& monitor,
|
const MonitorInfo& monitor,
|
||||||
|
const bool excludeFromCapture,
|
||||||
const wchar_t* windowClass,
|
const wchar_t* windowClass,
|
||||||
void* extraParam)
|
void* extraParam)
|
||||||
{
|
{
|
||||||
@@ -39,7 +41,7 @@ HWND CreateOverlayUIWindow(const CommonState& commonState,
|
|||||||
std::call_once(windowClassesCreatedFlag, CreateOverlayWindowClasses);
|
std::call_once(windowClassesCreatedFlag, CreateOverlayWindowClasses);
|
||||||
|
|
||||||
const auto screenArea = monitor.GetScreenSize(true);
|
const auto screenArea = monitor.GetScreenSize(true);
|
||||||
DWORD windowStyle = WS_EX_TOOLWINDOW;
|
DWORD windowStyle = WS_EX_NOREDIRECTIONBITMAP | WS_EX_TOOLWINDOW;
|
||||||
#if !defined(DEBUG_OVERLAY)
|
#if !defined(DEBUG_OVERLAY)
|
||||||
windowStyle |= WS_EX_TOPMOST;
|
windowStyle |= WS_EX_TOPMOST;
|
||||||
#endif
|
#endif
|
||||||
@@ -47,7 +49,7 @@ HWND CreateOverlayUIWindow(const CommonState& commonState,
|
|||||||
CreateWindowExW(windowStyle,
|
CreateWindowExW(windowStyle,
|
||||||
windowClass,
|
windowClass,
|
||||||
L"PowerToys.MeasureToolOverlay",
|
L"PowerToys.MeasureToolOverlay",
|
||||||
WS_POPUP,
|
WS_POPUP | CS_HREDRAW | CS_VREDRAW,
|
||||||
screenArea.left(),
|
screenArea.left(),
|
||||||
screenArea.top(),
|
screenArea.top(),
|
||||||
screenArea.width(),
|
screenArea.width(),
|
||||||
@@ -59,7 +61,11 @@ HWND CreateOverlayUIWindow(const CommonState& commonState,
|
|||||||
};
|
};
|
||||||
winrt::check_bool(window);
|
winrt::check_bool(window);
|
||||||
ShowWindow(window, SW_SHOWNORMAL);
|
ShowWindow(window, SW_SHOWNORMAL);
|
||||||
|
UpdateWindow(window);
|
||||||
|
if (excludeFromCapture)
|
||||||
|
{
|
||||||
SetWindowDisplayAffinity(window, WDA_EXCLUDEFROMCAPTURE);
|
SetWindowDisplayAffinity(window, WDA_EXCLUDEFROMCAPTURE);
|
||||||
|
}
|
||||||
#if !defined(DEBUG_OVERLAY)
|
#if !defined(DEBUG_OVERLAY)
|
||||||
SetWindowPos(window, HWND_TOPMOST, {}, {}, {}, {}, SWP_NOMOVE | SWP_NOSIZE);
|
SetWindowPos(window, HWND_TOPMOST, {}, {}, {}, {}, SWP_NOMOVE | SWP_NOSIZE);
|
||||||
#else
|
#else
|
||||||
@@ -109,51 +115,60 @@ std::vector<D2D1::ColorF> AppendCommonOverlayUIColors(const D2D1::ColorF& lineCo
|
|||||||
|
|
||||||
void OverlayUIState::RunUILoop()
|
void OverlayUIState::RunUILoop()
|
||||||
{
|
{
|
||||||
|
bool cursorOnScreen = true;
|
||||||
|
|
||||||
while (IsWindow(_window) && !_commonState.closeOnOtherMonitors)
|
while (IsWindow(_window) && !_commonState.closeOnOtherMonitors)
|
||||||
{
|
{
|
||||||
|
const auto now = std::chrono::high_resolution_clock::now();
|
||||||
const auto cursor = _commonState.cursorPosSystemSpace;
|
const auto cursor = _commonState.cursorPosSystemSpace;
|
||||||
const bool cursorOnScreen = _monitorArea.inside(cursor);
|
|
||||||
const bool cursorOverToolbar = _commonState.toolbarBoundingBox.inside(cursor);
|
const bool cursorOverToolbar = _commonState.toolbarBoundingBox.inside(cursor);
|
||||||
|
auto& dxgi = _d2dState.dxgiWindowState;
|
||||||
if (cursorOnScreen != _cursorOnScreen)
|
if (_monitorArea.inside(cursor) != cursorOnScreen)
|
||||||
{
|
{
|
||||||
_cursorOnScreen = cursorOnScreen;
|
cursorOnScreen = !cursorOnScreen;
|
||||||
if (!cursorOnScreen)
|
if (!cursorOnScreen)
|
||||||
{
|
{
|
||||||
if (_clearOnCursorLeavingScreen)
|
|
||||||
{
|
|
||||||
_d2dState.rt->BeginDraw();
|
|
||||||
_d2dState.rt->Clear();
|
|
||||||
_d2dState.rt->EndDraw();
|
|
||||||
}
|
|
||||||
PostMessageW(_window, WM_CURSOR_LEFT_MONITOR, {}, {});
|
PostMessageW(_window, WM_CURSOR_LEFT_MONITOR, {}, {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cursorOnScreen)
|
run_message_loop(true, 1);
|
||||||
{
|
|
||||||
_d2dState.rt->BeginDraw();
|
dxgi.rt->BeginDraw();
|
||||||
|
dxgi.rt->Clear();
|
||||||
|
|
||||||
if (!cursorOverToolbar)
|
if (!cursorOverToolbar)
|
||||||
_tickFunc();
|
_tickFunc();
|
||||||
else
|
|
||||||
_d2dState.rt->Clear();
|
|
||||||
|
|
||||||
_d2dState.rt->EndDraw();
|
dxgi.rt->EndDraw();
|
||||||
|
dxgi.swapChain->Present(0, 0);
|
||||||
|
|
||||||
|
if (cursorOnScreen)
|
||||||
|
{
|
||||||
|
const auto frameTime = std::chrono::high_resolution_clock::now() - now;
|
||||||
|
if (frameTime < consts::TARGET_FRAME_DURATION)
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(consts::TARGET_FRAME_DURATION - frameTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Don't consume resources while nothing could be updated
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds{ 200 });
|
||||||
}
|
}
|
||||||
|
|
||||||
run_message_loop(true, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DestroyWindow(_window);
|
DestroyWindow(_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename StateT, typename TickFuncT>
|
template<typename StateT, typename TickFuncT>
|
||||||
OverlayUIState::OverlayUIState(StateT& toolState,
|
OverlayUIState::OverlayUIState(const DxgiAPI* dxgiAPI,
|
||||||
|
StateT& toolState,
|
||||||
TickFuncT tickFunc,
|
TickFuncT tickFunc,
|
||||||
const CommonState& commonState,
|
const CommonState& commonState,
|
||||||
HWND window) :
|
HWND window) :
|
||||||
_window{ window },
|
_window{ window },
|
||||||
_commonState{ commonState },
|
_commonState{ commonState },
|
||||||
_d2dState{ window, AppendCommonOverlayUIColors(commonState.lineColor) },
|
_d2dState{ dxgiAPI, window, AppendCommonOverlayUIColors(commonState.lineColor) },
|
||||||
_tickFunc{ [this, tickFunc, &toolState] {
|
_tickFunc{ [this, tickFunc, &toolState] {
|
||||||
tickFunc(_commonState, toolState, _window, _d2dState);
|
tickFunc(_commonState, toolState, _window, _d2dState);
|
||||||
} }
|
} }
|
||||||
@@ -175,25 +190,30 @@ OverlayUIState::~OverlayUIState()
|
|||||||
|
|
||||||
// Returning unique_ptr, since we need to pin ui state in memory
|
// Returning unique_ptr, since we need to pin ui state in memory
|
||||||
template<typename ToolT, typename TickFuncT>
|
template<typename ToolT, typename TickFuncT>
|
||||||
inline std::unique_ptr<OverlayUIState> OverlayUIState::CreateInternal(ToolT& toolState,
|
inline std::unique_ptr<OverlayUIState> OverlayUIState::CreateInternal(const DxgiAPI* dxgi,
|
||||||
|
ToolT& toolState,
|
||||||
TickFuncT tickFunc,
|
TickFuncT tickFunc,
|
||||||
CommonState& commonState,
|
CommonState& commonState,
|
||||||
const wchar_t* toolWindowClassName,
|
const wchar_t* toolWindowClassName,
|
||||||
void* windowParam,
|
void* windowParam,
|
||||||
const MonitorInfo& monitor,
|
const MonitorInfo& monitor,
|
||||||
const bool clearOnCursorLeavingScreen)
|
const bool excludeFromCapture)
|
||||||
{
|
{
|
||||||
wil::shared_event uiCreatedEvent(wil::EventOptions::ManualReset);
|
wil::shared_event uiCreatedEvent(wil::EventOptions::ManualReset);
|
||||||
std::unique_ptr<OverlayUIState> uiState;
|
std::unique_ptr<OverlayUIState> uiState;
|
||||||
auto threadHandle = SpawnLoggedThread(L"OverlayUI thread", [&] {
|
std::thread threadHandle = SpawnLoggedThread(L"OverlayUI thread", [&] {
|
||||||
const HWND window = CreateOverlayUIWindow(commonState, monitor, toolWindowClassName, windowParam);
|
OverlayUIState* state = nullptr;
|
||||||
uiState = std::unique_ptr<OverlayUIState>{ new OverlayUIState{ toolState, tickFunc, commonState, window } };
|
{
|
||||||
|
auto sinalUICreatedEvent = wil::scope_exit([&] { uiCreatedEvent.SetEvent(); });
|
||||||
|
|
||||||
|
const HWND window = CreateOverlayUIWindow(commonState, monitor, excludeFromCapture, toolWindowClassName, windowParam);
|
||||||
|
|
||||||
|
uiState = std::unique_ptr<OverlayUIState>{ new OverlayUIState{ dxgi, toolState, tickFunc, commonState, window } };
|
||||||
uiState->_monitorArea = monitor.GetScreenSize(true);
|
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
|
// 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
|
// lifetime is ok here, since we join the thread in destructor
|
||||||
auto* state = uiState.get();
|
state = uiState.get();
|
||||||
uiCreatedEvent.SetEvent();
|
}
|
||||||
|
|
||||||
state->RunUILoop();
|
state->RunUILoop();
|
||||||
|
|
||||||
@@ -202,28 +222,40 @@ inline std::unique_ptr<OverlayUIState> OverlayUIState::CreateInternal(ToolT& too
|
|||||||
});
|
});
|
||||||
|
|
||||||
uiCreatedEvent.wait();
|
uiCreatedEvent.wait();
|
||||||
|
if (uiState)
|
||||||
uiState->_uiThread = std::move(threadHandle);
|
uiState->_uiThread = std::move(threadHandle);
|
||||||
|
else if (threadHandle.joinable())
|
||||||
|
threadHandle.join();
|
||||||
|
|
||||||
return uiState;
|
return uiState;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<OverlayUIState> OverlayUIState::Create(Serialized<MeasureToolState>& toolState,
|
std::unique_ptr<OverlayUIState> OverlayUIState::Create(const DxgiAPI* dxgi,
|
||||||
|
Serialized<MeasureToolState>& toolState,
|
||||||
CommonState& commonState,
|
CommonState& commonState,
|
||||||
const MonitorInfo& monitor)
|
const MonitorInfo& monitor)
|
||||||
{
|
{
|
||||||
return OverlayUIState::CreateInternal(toolState,
|
bool excludeFromCapture = false;
|
||||||
|
toolState.Read([&](const MeasureToolState& s) {
|
||||||
|
excludeFromCapture = s.global.continuousCapture;
|
||||||
|
});
|
||||||
|
return OverlayUIState::CreateInternal(dxgi,
|
||||||
|
toolState,
|
||||||
DrawMeasureToolTick,
|
DrawMeasureToolTick,
|
||||||
commonState,
|
commonState,
|
||||||
NonLocalizable::MeasureToolOverlayWindowName,
|
NonLocalizable::MeasureToolOverlayWindowName,
|
||||||
&toolState,
|
&toolState,
|
||||||
monitor,
|
monitor,
|
||||||
true);
|
excludeFromCapture);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<OverlayUIState> OverlayUIState::Create(BoundsToolState& toolState,
|
std::unique_ptr<OverlayUIState> OverlayUIState::Create(const DxgiAPI* dxgi,
|
||||||
|
BoundsToolState& toolState,
|
||||||
CommonState& commonState,
|
CommonState& commonState,
|
||||||
const MonitorInfo& monitor)
|
const MonitorInfo& monitor)
|
||||||
{
|
{
|
||||||
return OverlayUIState::CreateInternal(toolState,
|
return OverlayUIState::CreateInternal(dxgi,
|
||||||
|
toolState,
|
||||||
DrawBoundsToolTick,
|
DrawBoundsToolTick,
|
||||||
commonState,
|
commonState,
|
||||||
NonLocalizable::BoundsToolOverlayWindowName,
|
NonLocalizable::BoundsToolOverlayWindowName,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "DxgiAPI.h"
|
||||||
#include "D2DState.h"
|
#include "D2DState.h"
|
||||||
|
|
||||||
#include "ToolState.h"
|
#include "ToolState.h"
|
||||||
|
|
||||||
#include <common/display/monitors.h>
|
#include <common/display/monitors.h>
|
||||||
@@ -9,7 +11,8 @@
|
|||||||
class OverlayUIState final
|
class OverlayUIState final
|
||||||
{
|
{
|
||||||
template<typename StateT, typename TickFuncT>
|
template<typename StateT, typename TickFuncT>
|
||||||
OverlayUIState(StateT& toolState,
|
OverlayUIState(const DxgiAPI* dxgiAPI,
|
||||||
|
StateT& toolState,
|
||||||
TickFuncT tickFunc,
|
TickFuncT tickFunc,
|
||||||
const CommonState& commonState,
|
const CommonState& commonState,
|
||||||
HWND window);
|
HWND window);
|
||||||
@@ -20,26 +23,27 @@ class OverlayUIState final
|
|||||||
D2DState _d2dState;
|
D2DState _d2dState;
|
||||||
std::function<void()> _tickFunc;
|
std::function<void()> _tickFunc;
|
||||||
std::thread _uiThread;
|
std::thread _uiThread;
|
||||||
bool _cursorOnScreen = true;
|
|
||||||
bool _clearOnCursorLeavingScreen = false;
|
|
||||||
|
|
||||||
template<typename ToolT, typename TickFuncT>
|
template<typename ToolT, typename TickFuncT>
|
||||||
static std::unique_ptr<OverlayUIState> CreateInternal(ToolT& toolState,
|
static std::unique_ptr<OverlayUIState> CreateInternal(const DxgiAPI* dxgi,
|
||||||
|
ToolT& toolState,
|
||||||
TickFuncT tickFunc,
|
TickFuncT tickFunc,
|
||||||
CommonState& commonState,
|
CommonState& commonState,
|
||||||
const wchar_t* toolWindowClassName,
|
const wchar_t* toolWindowClassName,
|
||||||
void* windowParam,
|
void* windowParam,
|
||||||
const MonitorInfo& monitor,
|
const MonitorInfo& monitor,
|
||||||
const bool clearOnCursorLeavingScreen);
|
const bool excludeFromCapture);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
OverlayUIState(OverlayUIState&&) noexcept = default;
|
OverlayUIState(OverlayUIState&&) noexcept = default;
|
||||||
~OverlayUIState();
|
~OverlayUIState();
|
||||||
|
|
||||||
static std::unique_ptr<OverlayUIState> Create(BoundsToolState& toolState,
|
static std::unique_ptr<OverlayUIState> Create(const DxgiAPI* dxgi,
|
||||||
|
BoundsToolState& toolState,
|
||||||
CommonState& commonState,
|
CommonState& commonState,
|
||||||
const MonitorInfo& monitor);
|
const MonitorInfo& monitor);
|
||||||
static std::unique_ptr<OverlayUIState> Create(Serialized<MeasureToolState>& toolState,
|
static std::unique_ptr<OverlayUIState> Create(const DxgiAPI* dxgi,
|
||||||
|
Serialized<MeasureToolState>& toolState,
|
||||||
CommonState& commonState,
|
CommonState& commonState,
|
||||||
const MonitorInfo& monitor);
|
const MonitorInfo& monitor);
|
||||||
inline HWND overlayWindowHandle() const
|
inline HWND overlayWindowHandle() const
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
#include "PerGlyphOpacityTextRender.h"
|
#include "PerGlyphOpacityTextRender.h"
|
||||||
|
|
||||||
PerGlyphOpacityTextRender::PerGlyphOpacityTextRender(
|
PerGlyphOpacityTextRender::PerGlyphOpacityTextRender(
|
||||||
wil::com_ptr<ID2D1Factory> pD2DFactory,
|
winrt::com_ptr<ID2D1Factory> pD2DFactory,
|
||||||
wil::com_ptr<ID2D1HwndRenderTarget> rt,
|
winrt::com_ptr<ID2D1RenderTarget> rt,
|
||||||
wil::com_ptr<ID2D1SolidColorBrush> baseBrush) :
|
winrt::com_ptr<ID2D1SolidColorBrush> baseBrush) :
|
||||||
_pD2DFactory{ pD2DFactory.get() },
|
_pD2DFactory{ pD2DFactory.get() },
|
||||||
_rt{ rt.get() },
|
_rt{ rt.get() },
|
||||||
_baseBrush{ baseBrush.get() }
|
_baseBrush{ baseBrush.get() }
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ struct OpacityEffect : winrt::implements<OpacityEffect, IDrawingEffect>
|
|||||||
struct PerGlyphOpacityTextRender : winrt::implements<PerGlyphOpacityTextRender, IDWriteTextRenderer>
|
struct PerGlyphOpacityTextRender : winrt::implements<PerGlyphOpacityTextRender, IDWriteTextRenderer>
|
||||||
{
|
{
|
||||||
ID2D1Factory* _pD2DFactory = nullptr;
|
ID2D1Factory* _pD2DFactory = nullptr;
|
||||||
ID2D1HwndRenderTarget* _rt = nullptr;
|
ID2D1RenderTarget* _rt = nullptr;
|
||||||
ID2D1SolidColorBrush* _baseBrush = nullptr;
|
ID2D1SolidColorBrush* _baseBrush = nullptr;
|
||||||
|
|
||||||
PerGlyphOpacityTextRender(
|
PerGlyphOpacityTextRender(
|
||||||
wil::com_ptr<ID2D1Factory> pD2DFactory,
|
winrt::com_ptr<ID2D1Factory> pD2DFactory,
|
||||||
wil::com_ptr<ID2D1HwndRenderTarget> rt,
|
winrt::com_ptr<ID2D1RenderTarget> rt,
|
||||||
wil::com_ptr<ID2D1SolidColorBrush> baseBrush);
|
winrt::com_ptr<ID2D1SolidColorBrush> baseBrush);
|
||||||
|
|
||||||
HRESULT __stdcall DrawGlyphRun(void* clientDrawingContext,
|
HRESULT __stdcall DrawGlyphRun(void* clientDrawingContext,
|
||||||
FLOAT baselineOriginX,
|
FLOAT baselineOriginX,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <common/display/dpi_aware.h>
|
#include <common/display/dpi_aware.h>
|
||||||
#include <common/display/monitors.h>
|
#include <common/display/monitors.h>
|
||||||
#include <common/utils/logger_helper.h>
|
#include <common/utils/logger_helper.h>
|
||||||
|
#include <common/utils/UnhandledExceptionHandler.h>
|
||||||
#include <common/logger/logger.h>
|
#include <common/logger/logger.h>
|
||||||
|
|
||||||
#include "../MeasureToolModuleInterface/trace.h"
|
#include "../MeasureToolModuleInterface/trace.h"
|
||||||
@@ -14,8 +15,6 @@
|
|||||||
|
|
||||||
//#define DEBUG_PRIMARY_MONITOR_ONLY
|
//#define DEBUG_PRIMARY_MONITOR_ONLY
|
||||||
|
|
||||||
std::recursive_mutex gpuAccessLock;
|
|
||||||
|
|
||||||
namespace winrt::PowerToys::MeasureToolCore::implementation
|
namespace winrt::PowerToys::MeasureToolCore::implementation
|
||||||
{
|
{
|
||||||
void Core::MouseCaptureThread()
|
void Core::MouseCaptureThread()
|
||||||
@@ -34,17 +33,33 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
|
|||||||
_stopMouseCaptureThreadSignal{ wil::EventOptions::ManualReset },
|
_stopMouseCaptureThreadSignal{ wil::EventOptions::ManualReset },
|
||||||
_mouseCaptureThread{ [this] { MouseCaptureThread(); } }
|
_mouseCaptureThread{ [this] { MouseCaptureThread(); } }
|
||||||
{
|
{
|
||||||
Trace::RegisterProvider();
|
|
||||||
LoggerHelpers::init_logger(L"Measure Tool", L"Core", "Measure Tool");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::~Core()
|
Core::~Core()
|
||||||
{
|
{
|
||||||
_stopMouseCaptureThreadSignal.SetEvent();
|
Close();
|
||||||
_mouseCaptureThread.join();
|
}
|
||||||
|
|
||||||
|
void Core::Close()
|
||||||
|
{
|
||||||
ResetState();
|
ResetState();
|
||||||
Trace::UnregisterProvider();
|
|
||||||
|
// avoid triggering d2d debug layer leak on shutdown
|
||||||
|
dxgiAPI = DxgiAPI{ DxgiAPI::Uninitialized{} };
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
winrt::com_ptr<IDXGIDebug> dxgiDebug;
|
||||||
|
winrt::check_hresult(DXGIGetDebugInterface1({},
|
||||||
|
winrt::guid_of<IDXGIDebug>(),
|
||||||
|
dxgiDebug.put_void()));
|
||||||
|
dxgiDebug->ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_ALL);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!_stopMouseCaptureThreadSignal.is_signaled())
|
||||||
|
_stopMouseCaptureThreadSignal.SetEvent();
|
||||||
|
|
||||||
|
if (_mouseCaptureThread.joinable())
|
||||||
|
_mouseCaptureThread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Core::ResetState()
|
void Core::ResetState()
|
||||||
@@ -67,6 +82,7 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
|
|||||||
|
|
||||||
_settings = Settings::LoadFromFile();
|
_settings = Settings::LoadFromFile();
|
||||||
|
|
||||||
|
_commonState.units = _settings.units;
|
||||||
_commonState.lineColor.r = _settings.lineColor[0] / 255.f;
|
_commonState.lineColor.r = _settings.lineColor[0] / 255.f;
|
||||||
_commonState.lineColor.g = _settings.lineColor[1] / 255.f;
|
_commonState.lineColor.g = _settings.lineColor[1] / 255.f;
|
||||||
_commonState.lineColor.b = _settings.lineColor[2] / 255.f;
|
_commonState.lineColor.b = _settings.lineColor[2] / 255.f;
|
||||||
@@ -78,13 +94,15 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
|
|||||||
ResetState();
|
ResetState();
|
||||||
|
|
||||||
#if defined(DEBUG_PRIMARY_MONITOR_ONLY)
|
#if defined(DEBUG_PRIMARY_MONITOR_ONLY)
|
||||||
const auto& monitorInfo = MonitorInfo::GetPrimaryMonitor();
|
std::vector<MonitorInfo> monitors = { MonitorInfo::GetPrimaryMonitor() };
|
||||||
|
const auto& monitorInfo = monitors[0];
|
||||||
#else
|
#else
|
||||||
const auto monitors = MonitorInfo::GetMonitors(true);
|
const auto monitors = MonitorInfo::GetMonitors(true);
|
||||||
for (const auto& monitorInfo : monitors)
|
for (const auto& monitorInfo : monitors)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
auto overlayUI = OverlayUIState::Create(_boundsToolState,
|
auto overlayUI = OverlayUIState::Create(&dxgiAPI,
|
||||||
|
_boundsToolState,
|
||||||
_commonState,
|
_commonState,
|
||||||
monitorInfo);
|
monitorInfo);
|
||||||
#if !defined(DEBUG_PRIMARY_MONITOR_ONLY)
|
#if !defined(DEBUG_PRIMARY_MONITOR_ONLY)
|
||||||
@@ -120,7 +138,8 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
|
|||||||
for (const auto& monitorInfo : monitors)
|
for (const auto& monitorInfo : monitors)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
auto overlayUI = OverlayUIState::Create(_measureToolState,
|
auto overlayUI = OverlayUIState::Create(&dxgiAPI,
|
||||||
|
_measureToolState,
|
||||||
_commonState,
|
_commonState,
|
||||||
monitorInfo);
|
monitorInfo);
|
||||||
#if !defined(DEBUG_PRIMARY_MONITOR_ONLY)
|
#if !defined(DEBUG_PRIMARY_MONITOR_ONLY)
|
||||||
@@ -133,7 +152,7 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
|
|||||||
for (size_t i = 0; i < monitors.size(); ++i)
|
for (size_t i = 0; i < monitors.size(); ++i)
|
||||||
{
|
{
|
||||||
auto thread = StartCapturingThread(
|
auto thread = StartCapturingThread(
|
||||||
&_d3dState,
|
&dxgiAPI,
|
||||||
_commonState,
|
_commonState,
|
||||||
_measureToolState,
|
_measureToolState,
|
||||||
_overlayUIStates[i]->overlayWindowHandle(),
|
_overlayUIStates[i]->overlayWindowHandle(),
|
||||||
|
|||||||
@@ -8,12 +8,29 @@
|
|||||||
#include <common/utils/serialized.h>
|
#include <common/utils/serialized.h>
|
||||||
#include "ScreenCapturing.h"
|
#include "ScreenCapturing.h"
|
||||||
|
|
||||||
|
struct PowerToysMisc
|
||||||
|
{
|
||||||
|
PowerToysMisc()
|
||||||
|
{
|
||||||
|
Trace::RegisterProvider();
|
||||||
|
LoggerHelpers::init_logger(L"Measure Tool", L"Core", "Measure Tool");
|
||||||
|
InitUnhandledExceptionHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
~PowerToysMisc()
|
||||||
|
{
|
||||||
|
Trace::UnregisterProvider();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
namespace winrt::PowerToys::MeasureToolCore::implementation
|
namespace winrt::PowerToys::MeasureToolCore::implementation
|
||||||
{
|
{
|
||||||
struct Core : CoreT<Core>
|
struct Core : PowerToysMisc, CoreT<Core>
|
||||||
{
|
{
|
||||||
Core();
|
Core();
|
||||||
~Core();
|
~Core();
|
||||||
|
void Close();
|
||||||
|
|
||||||
void StartBoundsTool();
|
void StartBoundsTool();
|
||||||
void StartMeasureTool(const bool horizontal, const bool vertical);
|
void StartMeasureTool(const bool horizontal, const bool vertical);
|
||||||
void SetToolCompletionEvent(ToolSessionCompleted sessionCompletedTrigger);
|
void SetToolCompletionEvent(ToolSessionCompleted sessionCompletedTrigger);
|
||||||
@@ -22,7 +39,7 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
|
|||||||
float GetDPIScaleForWindow(uint64_t windowHandle);
|
float GetDPIScaleForWindow(uint64_t windowHandle);
|
||||||
void MouseCaptureThread();
|
void MouseCaptureThread();
|
||||||
|
|
||||||
D3DState _d3dState;
|
DxgiAPI dxgiAPI;
|
||||||
|
|
||||||
wil::shared_event _stopMouseCaptureThreadSignal;
|
wil::shared_event _stopMouseCaptureThreadSignal;
|
||||||
std::thread _mouseCaptureThread;
|
std::thread _mouseCaptureThread;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace PowerToys
|
|||||||
delegate void ToolSessionCompleted();
|
delegate void ToolSessionCompleted();
|
||||||
|
|
||||||
[default_interface]
|
[default_interface]
|
||||||
runtimeclass Core
|
runtimeclass Core : Windows.Foundation.IClosable
|
||||||
{
|
{
|
||||||
Core();
|
Core();
|
||||||
void SetToolCompletionEvent(event ToolSessionCompleted completionTrigger);
|
void SetToolCompletionEvent(event ToolSessionCompleted completionTrigger);
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
<SubSystem>Windows</SubSystem>
|
<SubSystem>Windows</SubSystem>
|
||||||
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
|
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
|
||||||
<ModuleDefinitionFile>MeasureTool.def</ModuleDefinitionFile>
|
<ModuleDefinitionFile>MeasureTool.def</ModuleDefinitionFile>
|
||||||
<AdditionalDependencies>Shell32.lib;Shcore.lib;Dwmapi.lib;Gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
<AdditionalDependencies>Dbghelp.lib;Shell32.lib;Shcore.lib;dcomp.lib;DXGI.lib;Dwmapi.lib;Gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemDefinitionGroup>
|
<ItemDefinitionGroup>
|
||||||
@@ -74,8 +74,11 @@
|
|||||||
<ClInclude Include="..\MeasureToolModuleInterface\trace.h" />
|
<ClInclude Include="..\MeasureToolModuleInterface\trace.h" />
|
||||||
<ClInclude Include="BoundsToolOverlayUI.h" />
|
<ClInclude Include="BoundsToolOverlayUI.h" />
|
||||||
<ClInclude Include="Clipboard.h" />
|
<ClInclude Include="Clipboard.h" />
|
||||||
|
<ClInclude Include="CoordinateSystemConversion.h" />
|
||||||
<ClInclude Include="constants.h" />
|
<ClInclude Include="constants.h" />
|
||||||
<ClInclude Include="D2DState.h" />
|
<ClInclude Include="D2DState.h" />
|
||||||
|
<ClInclude Include="DxgiAPI.h" />
|
||||||
|
<ClInclude Include="Measurement.h" />
|
||||||
<ClInclude Include="MeasureToolOverlayUI.h" />
|
<ClInclude Include="MeasureToolOverlayUI.h" />
|
||||||
<ClInclude Include="PerGlyphOpacityTextRender.h" />
|
<ClInclude Include="PerGlyphOpacityTextRender.h" />
|
||||||
<ClInclude Include="resource.h" />
|
<ClInclude Include="resource.h" />
|
||||||
@@ -96,7 +99,9 @@
|
|||||||
<ClCompile Include="BoundsToolOverlayUI.cpp" />
|
<ClCompile Include="BoundsToolOverlayUI.cpp" />
|
||||||
<ClCompile Include="Clipboard.cpp" />
|
<ClCompile Include="Clipboard.cpp" />
|
||||||
<ClCompile Include="D2DState.cpp" />
|
<ClCompile Include="D2DState.cpp" />
|
||||||
|
<ClCompile Include="DxgiAPI.cpp" />
|
||||||
<ClCompile Include="EdgeDetection.cpp" />
|
<ClCompile Include="EdgeDetection.cpp" />
|
||||||
|
<ClCompile Include="Measurement.cpp" />
|
||||||
<ClCompile Include="MeasureToolOverlayUI.cpp" />
|
<ClCompile Include="MeasureToolOverlayUI.cpp" />
|
||||||
<ClCompile Include="OverlayUI.cpp" />
|
<ClCompile Include="OverlayUI.cpp" />
|
||||||
<ClCompile Include="PerGlyphOpacityTextRender.cpp" />
|
<ClCompile Include="PerGlyphOpacityTextRender.cpp" />
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
<ClCompile Include="..\MeasureToolModuleInterface\trace.cpp" />
|
<ClCompile Include="..\MeasureToolModuleInterface\trace.cpp" />
|
||||||
<ClCompile Include="Clipboard.cpp" />
|
<ClCompile Include="Clipboard.cpp" />
|
||||||
<ClCompile Include="PerGlyphOpacityTextRender.cpp" />
|
<ClCompile Include="PerGlyphOpacityTextRender.cpp" />
|
||||||
|
<ClCompile Include="Measurement.cpp" />
|
||||||
|
<ClCompile Include="DxgiAPI.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="pch.h" />
|
<ClInclude Include="pch.h" />
|
||||||
@@ -34,6 +36,9 @@
|
|||||||
<ClInclude Include="Clipboard.h" />
|
<ClInclude Include="Clipboard.h" />
|
||||||
<ClInclude Include="PerGlyphOpacityTextRender.h" />
|
<ClInclude Include="PerGlyphOpacityTextRender.h" />
|
||||||
<ClInclude Include="resource.h" />
|
<ClInclude Include="resource.h" />
|
||||||
|
<ClInclude Include="CoordinateSystemConversion.h" />
|
||||||
|
<ClInclude Include="Measurement.h" />
|
||||||
|
<ClInclude Include="DxgiAPI.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Filter Include="Assets">
|
<Filter Include="Assets">
|
||||||
|
|||||||
@@ -10,65 +10,47 @@
|
|||||||
|
|
||||||
//#define DEBUG_EDGES
|
//#define DEBUG_EDGES
|
||||||
|
|
||||||
D3DState::D3DState()
|
namespace
|
||||||
{
|
{
|
||||||
UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
winrt::GraphicsCaptureItem CreateCaptureItemForMonitor(HMONITOR monitor)
|
||||||
#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,
|
auto captureInterop = winrt::get_activation_factory<
|
||||||
D3D_DRIVER_TYPE_WARP,
|
winrt::GraphicsCaptureItem,
|
||||||
nullptr,
|
IGraphicsCaptureItemInterop>();
|
||||||
flags,
|
|
||||||
nullptr,
|
|
||||||
0,
|
|
||||||
D3D11_SDK_VERSION,
|
|
||||||
d3dDevice.put(),
|
|
||||||
nullptr,
|
|
||||||
nullptr);
|
|
||||||
}
|
|
||||||
winrt::check_hresult(hr);
|
|
||||||
|
|
||||||
dxgiDevice = d3dDevice.as<IDXGIDevice>();
|
winrt::GraphicsCaptureItem item = nullptr;
|
||||||
winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.get(), d3dDeviceInspectable.put()));
|
|
||||||
|
winrt::check_hresult(captureInterop->CreateForMonitor(
|
||||||
|
monitor,
|
||||||
|
winrt::guid_of<winrt::GraphicsCaptureItem>(),
|
||||||
|
winrt::put_abi(item)));
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class D3DCaptureState final
|
class D3DCaptureState final
|
||||||
{
|
{
|
||||||
D3DState* d3dState = nullptr;
|
DxgiAPI* dxgiAPI = nullptr;
|
||||||
winrt::IDirect3DDevice device;
|
winrt::IDirect3DDevice device;
|
||||||
winrt::com_ptr<IDXGISwapChain1> swapChain;
|
winrt::com_ptr<IDXGISwapChain1> swapChain;
|
||||||
winrt::com_ptr<ID3D11DeviceContext> context;
|
|
||||||
winrt::SizeInt32 frameSize;
|
|
||||||
|
|
||||||
|
winrt::SizeInt32 frameSize;
|
||||||
|
HMONITOR monitor = {};
|
||||||
winrt::DirectXPixelFormat pixelFormat;
|
winrt::DirectXPixelFormat pixelFormat;
|
||||||
winrt::Direct3D11CaptureFramePool framePool;
|
|
||||||
winrt::GraphicsCaptureSession session;
|
winrt::Direct3D11CaptureFramePool framePool = nullptr;
|
||||||
|
winrt::GraphicsCaptureSession session = nullptr;
|
||||||
|
|
||||||
std::function<void(MappedTextureView)> frameCallback;
|
std::function<void(MappedTextureView)> frameCallback;
|
||||||
Box monitorArea;
|
Box monitorArea;
|
||||||
bool captureOutsideOfMonitor = false;
|
bool continuousCapture = false;
|
||||||
|
|
||||||
D3DCaptureState(D3DState* d3dState,
|
D3DCaptureState(DxgiAPI* dxgiAPI,
|
||||||
winrt::com_ptr<IDXGISwapChain1> _swapChain,
|
winrt::com_ptr<IDXGISwapChain1> swapChain,
|
||||||
winrt::com_ptr<ID3D11DeviceContext> _context,
|
winrt::DirectXPixelFormat pixelFormat,
|
||||||
const winrt::GraphicsCaptureItem& item,
|
MonitorInfo monitorInfo,
|
||||||
winrt::DirectXPixelFormat _pixelFormat,
|
const bool continuousCapture);
|
||||||
Box monitorArea,
|
|
||||||
const bool captureOutsideOfMonitor);
|
|
||||||
|
|
||||||
winrt::com_ptr<ID3D11Texture2D> CopyFrameToCPU(const winrt::com_ptr<ID3D11Texture2D>& texture);
|
winrt::com_ptr<ID3D11Texture2D> CopyFrameToCPU(const winrt::com_ptr<ID3D11Texture2D>& texture);
|
||||||
|
|
||||||
@@ -76,14 +58,13 @@ class D3DCaptureState final
|
|||||||
|
|
||||||
void StartSessionInPreferredMode();
|
void StartSessionInPreferredMode();
|
||||||
|
|
||||||
std::mutex destructorMutex;
|
std::mutex frameArrivedMutex;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static std::unique_ptr<D3DCaptureState> Create(D3DState* d3dState,
|
static std::unique_ptr<D3DCaptureState> Create(DxgiAPI* dxgiAPI,
|
||||||
winrt::GraphicsCaptureItem item,
|
MonitorInfo monitorInfo,
|
||||||
const winrt::DirectXPixelFormat pixelFormat,
|
const winrt::DirectXPixelFormat pixelFormat,
|
||||||
Box monitorSize,
|
const bool continuousCapture);
|
||||||
const bool captureOutsideOfMonitor);
|
|
||||||
|
|
||||||
~D3DCaptureState();
|
~D3DCaptureState();
|
||||||
|
|
||||||
@@ -93,25 +74,19 @@ public:
|
|||||||
void StopCapture();
|
void StopCapture();
|
||||||
};
|
};
|
||||||
|
|
||||||
D3DCaptureState::D3DCaptureState(D3DState* _d3dState,
|
D3DCaptureState::D3DCaptureState(DxgiAPI* dxgiAPI,
|
||||||
winrt::com_ptr<IDXGISwapChain1> _swapChain,
|
winrt::com_ptr<IDXGISwapChain1> _swapChain,
|
||||||
winrt::com_ptr<ID3D11DeviceContext> _context,
|
winrt::DirectXPixelFormat pixelFormat_,
|
||||||
const winrt::GraphicsCaptureItem& item,
|
MonitorInfo monitorInfo,
|
||||||
winrt::DirectXPixelFormat _pixelFormat,
|
const bool continuousCapture_) :
|
||||||
Box _monitorArea,
|
dxgiAPI{ dxgiAPI },
|
||||||
const bool _captureOutsideOfMonitor) :
|
device{ dxgiAPI->d3dForCapture.d3dDeviceInspectable.as<winrt::IDirect3DDevice>() },
|
||||||
d3dState{ _d3dState },
|
|
||||||
device{ _d3dState->d3dDeviceInspectable.as<winrt::IDirect3DDevice>() },
|
|
||||||
swapChain{ std::move(_swapChain) },
|
swapChain{ std::move(_swapChain) },
|
||||||
context{ std::move(_context) },
|
pixelFormat{ std::move(pixelFormat_) },
|
||||||
frameSize{ item.Size() },
|
monitor{ monitorInfo.GetHandle() },
|
||||||
pixelFormat{ std::move(_pixelFormat) },
|
monitorArea{ monitorInfo.GetScreenSize(true) },
|
||||||
framePool{ winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(device, pixelFormat, 1, item.Size()) },
|
continuousCapture{ continuousCapture_ }
|
||||||
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)
|
winrt::com_ptr<ID3D11Texture2D> D3DCaptureState::CopyFrameToCPU(const winrt::com_ptr<ID3D11Texture2D>& frameTexture)
|
||||||
@@ -124,8 +99,8 @@ winrt::com_ptr<ID3D11Texture2D> D3DCaptureState::CopyFrameToCPU(const winrt::com
|
|||||||
desc.BindFlags = 0;
|
desc.BindFlags = 0;
|
||||||
|
|
||||||
winrt::com_ptr<ID3D11Texture2D> cpuTexture;
|
winrt::com_ptr<ID3D11Texture2D> cpuTexture;
|
||||||
winrt::check_hresult(d3dState->d3dDevice->CreateTexture2D(&desc, nullptr, cpuTexture.put()));
|
winrt::check_hresult(dxgiAPI->d3dForCapture.d3dDevice->CreateTexture2D(&desc, nullptr, cpuTexture.put()));
|
||||||
context->CopyResource(cpuTexture.get(), frameTexture.get());
|
dxgiAPI->d3dForCapture.d3dContext->CopyResource(cpuTexture.get(), frameTexture.get());
|
||||||
|
|
||||||
return cpuTexture;
|
return cpuTexture;
|
||||||
}
|
}
|
||||||
@@ -142,15 +117,25 @@ auto GetDXGIInterfaceFromObject(winrt::IInspectable const& object)
|
|||||||
void D3DCaptureState::OnFrameArrived(const winrt::Direct3D11CaptureFramePool& sender, const winrt::IInspectable&)
|
void D3DCaptureState::OnFrameArrived(const winrt::Direct3D11CaptureFramePool& sender, const winrt::IInspectable&)
|
||||||
{
|
{
|
||||||
// Prevent calling a callback on a partially destroyed state
|
// Prevent calling a callback on a partially destroyed state
|
||||||
std::unique_lock callbackLock{ destructorMutex };
|
std::lock_guard callbackLock{ frameArrivedMutex };
|
||||||
|
|
||||||
bool resized = false;
|
bool resized = false;
|
||||||
POINT cursorPos = {};
|
POINT cursorPos = {};
|
||||||
GetCursorPos(&cursorPos);
|
GetCursorPos(&cursorPos);
|
||||||
|
|
||||||
auto frame = sender.TryGetNextFrame();
|
winrt::Direct3D11CaptureFrame frame = nullptr;
|
||||||
winrt::check_bool(frame);
|
try
|
||||||
if (monitorArea.inside(cursorPos) || captureOutsideOfMonitor)
|
{
|
||||||
|
frame = sender.TryGetNextFrame();
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!frame)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (monitorArea.inside(cursorPos) || !continuousCapture)
|
||||||
{
|
{
|
||||||
winrt::com_ptr<ID3D11Texture2D> texture;
|
winrt::com_ptr<ID3D11Texture2D> texture;
|
||||||
{
|
{
|
||||||
@@ -170,7 +155,10 @@ void D3DCaptureState::OnFrameArrived(const winrt::Direct3D11CaptureFramePool& se
|
|||||||
auto gpuTexture = GetDXGIInterfaceFromObject<ID3D11Texture2D>(surface);
|
auto gpuTexture = GetDXGIInterfaceFromObject<ID3D11Texture2D>(surface);
|
||||||
texture = CopyFrameToCPU(gpuTexture);
|
texture = CopyFrameToCPU(gpuTexture);
|
||||||
surface.Close();
|
surface.Close();
|
||||||
MappedTextureView textureView{ texture, context, static_cast<size_t>(frameSize.Width), static_cast<size_t>(frameSize.Height) };
|
MappedTextureView textureView{ texture,
|
||||||
|
dxgiAPI->d3dForCapture.d3dContext,
|
||||||
|
static_cast<size_t>(frameSize.Width),
|
||||||
|
static_cast<size_t>(frameSize.Height) };
|
||||||
|
|
||||||
frameCallback(std::move(textureView));
|
frameCallback(std::move(textureView));
|
||||||
}
|
}
|
||||||
@@ -178,69 +166,60 @@ void D3DCaptureState::OnFrameArrived(const winrt::Direct3D11CaptureFramePool& se
|
|||||||
|
|
||||||
frame.Close();
|
frame.Close();
|
||||||
|
|
||||||
DXGI_PRESENT_PARAMETERS presentParameters = {};
|
|
||||||
swapChain->Present1(1, 0, &presentParameters);
|
|
||||||
|
|
||||||
if (resized)
|
if (resized)
|
||||||
{
|
{
|
||||||
framePool.Recreate(device, pixelFormat, 2, frameSize);
|
framePool.Recreate(device, pixelFormat, 2, frameSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<D3DCaptureState> D3DCaptureState::Create(D3DState* d3dState,
|
std::unique_ptr<D3DCaptureState> D3DCaptureState::Create(DxgiAPI* dxgiAPI,
|
||||||
winrt::GraphicsCaptureItem item,
|
MonitorInfo monitorInfo,
|
||||||
const winrt::DirectXPixelFormat pixelFormat,
|
const winrt::DirectXPixelFormat pixelFormat,
|
||||||
Box monitorArea,
|
const bool continuousCapture)
|
||||||
const bool captureOutsideOfMonitor)
|
|
||||||
{
|
{
|
||||||
std::lock_guard guard{ gpuAccessLock };
|
const auto dims = monitorInfo.GetScreenSize(true);
|
||||||
|
|
||||||
const DXGI_SWAP_CHAIN_DESC1 desc = {
|
const DXGI_SWAP_CHAIN_DESC1 desc = {
|
||||||
.Width = static_cast<uint32_t>(item.Size().Width),
|
.Width = static_cast<uint32_t>(dims.width()),
|
||||||
.Height = static_cast<uint32_t>(item.Size().Height),
|
.Height = static_cast<uint32_t>(dims.height()),
|
||||||
.Format = static_cast<DXGI_FORMAT>(pixelFormat),
|
.Format = static_cast<DXGI_FORMAT>(pixelFormat),
|
||||||
.SampleDesc = { .Count = 1, .Quality = 0 },
|
.SampleDesc = { .Count = 1, .Quality = 0 },
|
||||||
.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
|
.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
|
||||||
.BufferCount = 2,
|
.BufferCount = 2,
|
||||||
.Scaling = DXGI_SCALING_STRETCH,
|
.Scaling = DXGI_SCALING_STRETCH,
|
||||||
.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
|
.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
|
||||||
.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED,
|
.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED,
|
||||||
};
|
};
|
||||||
winrt::com_ptr<IDXGIAdapter> adapter;
|
|
||||||
winrt::check_hresult(d3dState->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::com_ptr<IDXGISwapChain1> swapChain;
|
||||||
winrt::check_hresult(factory->CreateSwapChainForComposition(d3dState->d3dDevice.get(), &desc, nullptr, swapChain.put()));
|
winrt::check_hresult(dxgiAPI->d3dForCapture.dxgiFactory2->CreateSwapChainForComposition(dxgiAPI->d3dForCapture.d3dDevice.get(),
|
||||||
|
&desc,
|
||||||
winrt::com_ptr<ID3D11DeviceContext> context;
|
nullptr,
|
||||||
d3dState->d3dDevice->GetImmediateContext(context.put());
|
swapChain.put()));
|
||||||
winrt::check_bool(context);
|
|
||||||
auto contextMultithread = context.as<ID3D11Multithread>();
|
|
||||||
contextMultithread->SetMultithreadProtected(true);
|
|
||||||
|
|
||||||
// We must create the object in a heap, since we need to pin it in memory to receive callbacks
|
// We must create the object in a heap, since we need to pin it in memory to receive callbacks
|
||||||
auto statePtr = new D3DCaptureState{ d3dState,
|
auto statePtr = new D3DCaptureState{ dxgiAPI,
|
||||||
std::move(swapChain),
|
std::move(swapChain),
|
||||||
std::move(context),
|
|
||||||
item,
|
|
||||||
pixelFormat,
|
pixelFormat,
|
||||||
monitorArea,
|
std::move(monitorInfo),
|
||||||
captureOutsideOfMonitor };
|
continuousCapture };
|
||||||
|
|
||||||
return std::unique_ptr<D3DCaptureState>{ statePtr };
|
return std::unique_ptr<D3DCaptureState>{ statePtr };
|
||||||
}
|
}
|
||||||
|
|
||||||
D3DCaptureState::~D3DCaptureState()
|
D3DCaptureState::~D3DCaptureState()
|
||||||
{
|
{
|
||||||
std::unique_lock callbackLock{ destructorMutex };
|
std::unique_lock callbackLock{ frameArrivedMutex };
|
||||||
StopCapture();
|
StopCapture();
|
||||||
framePool.Close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void D3DCaptureState::StartSessionInPreferredMode()
|
void D3DCaptureState::StartSessionInPreferredMode()
|
||||||
{
|
{
|
||||||
|
auto item = CreateCaptureItemForMonitor(monitor);
|
||||||
|
frameSize = item.Size();
|
||||||
|
framePool = winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(device, pixelFormat, 2, item.Size());
|
||||||
|
session = framePool.CreateCaptureSession(item);
|
||||||
|
framePool.FrameArrived({ this, &D3DCaptureState::OnFrameArrived });
|
||||||
|
|
||||||
// Try disable border if possible (available on Windows ver >= 20348)
|
// Try disable border if possible (available on Windows ver >= 20348)
|
||||||
if (auto session3 = session.try_as<winrt::IGraphicsCaptureSession3>())
|
if (auto session3 = session.try_as<winrt::IGraphicsCaptureSession3>())
|
||||||
{
|
{
|
||||||
@@ -270,27 +249,35 @@ MappedTextureView D3DCaptureState::CaptureSingleFrame()
|
|||||||
result.emplace(std::move(tex));
|
result.emplace(std::move(tex));
|
||||||
frameArrivedEvent.SetEvent();
|
frameArrivedEvent.SetEvent();
|
||||||
};
|
};
|
||||||
std::lock_guard guard{ gpuAccessLock };
|
|
||||||
StartSessionInPreferredMode();
|
StartSessionInPreferredMode();
|
||||||
|
|
||||||
frameArrivedEvent.wait();
|
frameArrivedEvent.wait();
|
||||||
|
|
||||||
assert(result.has_value());
|
|
||||||
return std::move(*result);
|
return std::move(*result);
|
||||||
}
|
}
|
||||||
|
|
||||||
void D3DCaptureState::StopCapture()
|
void D3DCaptureState::StopCapture()
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (session)
|
||||||
session.Close();
|
session.Close();
|
||||||
|
|
||||||
|
if (framePool)
|
||||||
|
framePool.Close();
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
// RPC call might fail here
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateCaptureState(const CommonState& commonState,
|
void UpdateCaptureState(const CommonState& commonState,
|
||||||
Serialized<MeasureToolState>& state,
|
Serialized<MeasureToolState>& state,
|
||||||
HWND window,
|
HWND window,
|
||||||
const MappedTextureView& textureView,
|
const MappedTextureView& textureView)
|
||||||
const bool continuousCapture)
|
|
||||||
{
|
{
|
||||||
const auto cursorPos = convert::FromSystemToRelative(window, commonState.cursorPosSystemSpace);
|
const auto cursorPos = convert::FromSystemToWindow(window, commonState.cursorPosSystemSpace);
|
||||||
const bool cursorInLeftScreenHalf = cursorPos.x < textureView.view.width / 2;
|
const bool cursorInLeftScreenHalf = cursorPos.x < textureView.view.width / 2;
|
||||||
const bool cursorInTopScreenHalf = cursorPos.y < textureView.view.height / 2;
|
const bool cursorInTopScreenHalf = cursorPos.y < textureView.view.height / 2;
|
||||||
uint8_t pixelTolerance = {};
|
uint8_t pixelTolerance = {};
|
||||||
@@ -310,8 +297,7 @@ void UpdateCaptureState(const CommonState& commonState,
|
|||||||
const RECT bounds = DetectEdges(textureView.view,
|
const RECT bounds = DetectEdges(textureView.view,
|
||||||
cursorPos,
|
cursorPos,
|
||||||
perColorChannelEdgeDetection,
|
perColorChannelEdgeDetection,
|
||||||
pixelTolerance,
|
pixelTolerance);
|
||||||
continuousCapture);
|
|
||||||
|
|
||||||
#if defined(DEBUG_EDGES)
|
#if defined(DEBUG_EDGES)
|
||||||
char buffer[256];
|
char buffer[256];
|
||||||
@@ -328,51 +314,55 @@ void UpdateCaptureState(const CommonState& commonState,
|
|||||||
OutputDebugStringA(buffer);
|
OutputDebugStringA(buffer);
|
||||||
#endif
|
#endif
|
||||||
state.Access([&](MeasureToolState& state) {
|
state.Access([&](MeasureToolState& state) {
|
||||||
state.perScreen[window].measuredEdges = bounds;
|
state.perScreen[window].measuredEdges = Measurement{ bounds };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::thread StartCapturingThread(D3DState* d3dState,
|
std::thread StartCapturingThread(DxgiAPI* dxgiAPI,
|
||||||
const CommonState& commonState,
|
const CommonState& commonState,
|
||||||
Serialized<MeasureToolState>& state,
|
Serialized<MeasureToolState>& state,
|
||||||
HWND window,
|
HWND window,
|
||||||
MonitorInfo monitor)
|
MonitorInfo monitor)
|
||||||
{
|
{
|
||||||
return SpawnLoggedThread(L"Screen Capture thread", [&state, &commonState, monitor, window, d3dState] {
|
return SpawnLoggedThread(L"Screen Capture thread", [&state, &commonState, monitor, window, dxgiAPI] {
|
||||||
auto captureInterop = winrt::get_activation_factory<
|
|
||||||
winrt::GraphicsCaptureItem,
|
|
||||||
IGraphicsCaptureItemInterop>();
|
|
||||||
|
|
||||||
winrt::GraphicsCaptureItem item = nullptr;
|
|
||||||
|
|
||||||
winrt::check_hresult(captureInterop->CreateForMonitor(
|
|
||||||
monitor.GetHandle(),
|
|
||||||
winrt::guid_of<winrt::GraphicsCaptureItem>(),
|
|
||||||
winrt::put_abi(item)));
|
|
||||||
|
|
||||||
bool continuousCapture = {};
|
bool continuousCapture = {};
|
||||||
state.Read([&](const MeasureToolState& state) {
|
state.Read([&](const MeasureToolState& state) {
|
||||||
continuousCapture = state.global.continuousCapture;
|
continuousCapture = state.global.continuousCapture;
|
||||||
});
|
});
|
||||||
|
|
||||||
const auto monitorArea = monitor.GetScreenSize(true);
|
auto captureState = D3DCaptureState::Create(dxgiAPI,
|
||||||
auto captureState = D3DCaptureState::Create(d3dState,
|
monitor,
|
||||||
item,
|
|
||||||
winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized,
|
winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized,
|
||||||
monitorArea,
|
continuousCapture);
|
||||||
!continuousCapture);
|
const auto monitorArea = monitor.GetScreenSize(true);
|
||||||
|
bool mouseOnMonitor = false;
|
||||||
if (continuousCapture)
|
if (continuousCapture)
|
||||||
{
|
{
|
||||||
captureState->StartCapture([&, window](MappedTextureView textureView) {
|
|
||||||
UpdateCaptureState(commonState, state, window, textureView, continuousCapture);
|
|
||||||
});
|
|
||||||
|
|
||||||
while (IsWindow(window) && !commonState.closeOnOtherMonitors)
|
while (IsWindow(window) && !commonState.closeOnOtherMonitors)
|
||||||
|
{
|
||||||
|
if (mouseOnMonitor == monitorArea.inside(commonState.cursorPosSystemSpace))
|
||||||
{
|
{
|
||||||
std::this_thread::sleep_for(consts::TARGET_FRAME_DURATION);
|
std::this_thread::sleep_for(consts::TARGET_FRAME_DURATION);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mouseOnMonitor = !mouseOnMonitor;
|
||||||
|
if (mouseOnMonitor)
|
||||||
|
{
|
||||||
|
captureState->StartCapture([&, window](MappedTextureView textureView) {
|
||||||
|
UpdateCaptureState(commonState, state, window, textureView);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state.Access([&](MeasureToolState& state) {
|
||||||
|
state.perScreen[window].measuredEdges = {};
|
||||||
|
});
|
||||||
|
|
||||||
captureState->StopCapture();
|
captureState->StopCapture();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const auto textureView = captureState->CaptureSingleFrame();
|
const auto textureView = captureState->CaptureSingleFrame();
|
||||||
@@ -394,7 +384,15 @@ std::thread StartCapturingThread(D3DState* d3dState,
|
|||||||
auto path = std::filesystem::temp_directory_path() / buf;
|
auto path = std::filesystem::temp_directory_path() / buf;
|
||||||
textureView.view.SaveAsBitmap(path.string().c_str());
|
textureView.view.SaveAsBitmap(path.string().c_str());
|
||||||
#endif
|
#endif
|
||||||
UpdateCaptureState(commonState, state, window, textureView, continuousCapture);
|
UpdateCaptureState(commonState, state, window, textureView);
|
||||||
|
mouseOnMonitor = true;
|
||||||
|
}
|
||||||
|
else if (mouseOnMonitor)
|
||||||
|
{
|
||||||
|
state.Access([&](MeasureToolState& state) {
|
||||||
|
state.perScreen[window].measuredEdges = {};
|
||||||
|
});
|
||||||
|
mouseOnMonitor = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - now);
|
const auto frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - now);
|
||||||
@@ -404,5 +402,7 @@ std::thread StartCapturingThread(D3DState* d3dState,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
captureState->StopCapture();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "DxgiAPI.h"
|
||||||
#include "ToolState.h"
|
#include "ToolState.h"
|
||||||
|
|
||||||
#include <common/utils/serialized.h>
|
#include <common/utils/serialized.h>
|
||||||
|
|
||||||
struct D3DState
|
std::thread StartCapturingThread(DxgiAPI* dxgiAPI,
|
||||||
{
|
|
||||||
winrt::com_ptr<ID3D11Device> d3dDevice;
|
|
||||||
winrt::com_ptr<IDXGIDevice> dxgiDevice;
|
|
||||||
winrt::com_ptr<IInspectable> d3dDeviceInspectable;
|
|
||||||
|
|
||||||
D3DState();
|
|
||||||
};
|
|
||||||
|
|
||||||
std::thread StartCapturingThread(D3DState* d3dState,
|
|
||||||
const CommonState& commonState,
|
const CommonState& commonState,
|
||||||
Serialized<MeasureToolState>& state,
|
Serialized<MeasureToolState>& state,
|
||||||
HWND targetWindow,
|
HWND targetWindow,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ namespace
|
|||||||
const wchar_t JSON_KEY_PIXEL_TOLERANCE[] = L"PixelTolerance";
|
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_PER_COLOR_CHANNEL_EDGE_DETECTION[] = L"PerColorChannelEdgeDetection";
|
||||||
const wchar_t JSON_KEY_MEASURE_CROSS_COLOR[] = L"MeasureCrossColor";
|
const wchar_t JSON_KEY_MEASURE_CROSS_COLOR[] = L"MeasureCrossColor";
|
||||||
|
const wchar_t JSON_KEY_UNITS_OF_MEASURE[] = L"UnitsOfMeasure";
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings Settings::LoadFromFile()
|
Settings Settings::LoadFromFile()
|
||||||
@@ -65,6 +66,14 @@ Settings Settings::LoadFromFile()
|
|||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result.units = static_cast<Measurement::Unit>(props.GetNamedObject(JSON_KEY_UNITS_OF_MEASURE).GetNamedNumber(JSON_KEY_VALUE));
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
|
#include "Measurement.h"
|
||||||
|
|
||||||
struct Settings
|
struct Settings
|
||||||
{
|
{
|
||||||
uint8_t pixelTolerance = 30;
|
uint8_t pixelTolerance = 30;
|
||||||
@@ -10,6 +12,7 @@ struct Settings
|
|||||||
bool drawFeetOnCross = true;
|
bool drawFeetOnCross = true;
|
||||||
bool perColorChannelEdgeDetection = false;
|
bool perColorChannelEdgeDetection = false;
|
||||||
std::array<uint8_t, 3> lineColor = {255, 69, 0};
|
std::array<uint8_t, 3> lineColor = {255, 69, 0};
|
||||||
|
Measurement::Unit units = Measurement::Unit::Pixel;
|
||||||
|
|
||||||
static Settings LoadFromFile();
|
static Settings LoadFromFile();
|
||||||
};
|
};
|
||||||
@@ -16,10 +16,11 @@
|
|||||||
|
|
||||||
//#define DEBUG_OVERLAY
|
//#define DEBUG_OVERLAY
|
||||||
#include "BGRATextureView.h"
|
#include "BGRATextureView.h"
|
||||||
|
#include "Measurement.h"
|
||||||
|
|
||||||
struct OverlayBoxText
|
struct OverlayBoxText
|
||||||
{
|
{
|
||||||
std::array<wchar_t, 32> buffer = {};
|
std::array<wchar_t, 128> buffer = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CommonState
|
struct CommonState
|
||||||
@@ -28,6 +29,8 @@ struct CommonState
|
|||||||
D2D1::ColorF lineColor = D2D1::ColorF::OrangeRed;
|
D2D1::ColorF lineColor = D2D1::ColorF::OrangeRed;
|
||||||
Box toolbarBoundingBox;
|
Box toolbarBoundingBox;
|
||||||
|
|
||||||
|
Measurement::Unit units = Measurement::Unit::Pixel;
|
||||||
|
|
||||||
mutable Serialized<OverlayBoxText> overlayBoxText;
|
mutable Serialized<OverlayBoxText> overlayBoxText;
|
||||||
POINT cursorPosSystemSpace = {}; // updated atomically
|
POINT cursorPosSystemSpace = {}; // updated atomically
|
||||||
std::atomic_bool closeOnOtherMonitors = false;
|
std::atomic_bool closeOnOtherMonitors = false;
|
||||||
@@ -38,7 +41,7 @@ struct BoundsToolState
|
|||||||
struct PerScreen
|
struct PerScreen
|
||||||
{
|
{
|
||||||
std::optional<D2D_POINT_2F> currentRegionStart;
|
std::optional<D2D_POINT_2F> currentRegionStart;
|
||||||
std::vector<D2D1_RECT_F> measurements;
|
std::vector<Measurement> measurements;
|
||||||
};
|
};
|
||||||
std::unordered_map<HWND, PerScreen> perScreen;
|
std::unordered_map<HWND, PerScreen> perScreen;
|
||||||
|
|
||||||
@@ -67,7 +70,7 @@ struct MeasureToolState
|
|||||||
{
|
{
|
||||||
bool cursorInLeftScreenHalf = false;
|
bool cursorInLeftScreenHalf = false;
|
||||||
bool cursorInTopScreenHalf = false;
|
bool cursorInTopScreenHalf = false;
|
||||||
RECT measuredEdges = {};
|
std::optional<Measurement> measuredEdges;
|
||||||
// While not in a continuous capturing mode, we need to draw captured backgrounds. These are passed
|
// While not in a continuous capturing mode, we need to draw captured backgrounds. These are passed
|
||||||
// directly from a capturing thread.
|
// directly from a capturing thread.
|
||||||
const MappedTextureView* capturedScreenTexture = nullptr;
|
const MappedTextureView* capturedScreenTexture = nullptr;
|
||||||
@@ -79,6 +82,3 @@ struct MeasureToolState
|
|||||||
|
|
||||||
CommonState* commonState = nullptr; // required for WndProc
|
CommonState* commonState = nullptr; // required for WndProc
|
||||||
};
|
};
|
||||||
|
|
||||||
// Concurrently accessing Direct2D and Direct3D APIs make the driver go boom
|
|
||||||
extern std::recursive_mutex gpuAccessLock;
|
|
||||||
@@ -4,12 +4,11 @@
|
|||||||
|
|
||||||
namespace consts
|
namespace consts
|
||||||
{
|
{
|
||||||
constexpr inline size_t TARGET_FRAME_RATE = 120;
|
constexpr inline size_t TARGET_FRAME_RATE = 90;
|
||||||
constexpr inline auto TARGET_FRAME_DURATION = std::chrono::milliseconds{ 1000 } / TARGET_FRAME_RATE;
|
constexpr inline auto TARGET_FRAME_DURATION = std::chrono::microseconds{ 1000000 } / TARGET_FRAME_RATE;
|
||||||
|
|
||||||
constexpr inline float FONT_SIZE = 14.f;
|
constexpr inline float FONT_SIZE = 14.f;
|
||||||
constexpr inline float TEXT_BOX_CORNER_RADIUS = 4.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 FEET_HALF_LENGTH = 2.f;
|
||||||
constexpr inline float SHADOW_OPACITY = .4f;
|
constexpr inline float SHADOW_OPACITY = .4f;
|
||||||
constexpr inline float SHADOW_RADIUS = 6.f;
|
constexpr inline float SHADOW_RADIUS = 6.f;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <d3d11.h>
|
#include <d3d11.h>
|
||||||
#include <d3d11_4.h>
|
#include <d3d11_4.h>
|
||||||
#include <dxgi1_6.h>
|
#include <dxgi1_6.h>
|
||||||
|
#include <dxgidebug.h>
|
||||||
#include <d2d1_3.h>
|
#include <d2d1_3.h>
|
||||||
#include <dwrite.h>
|
#include <dwrite.h>
|
||||||
#include <dwmapi.h>
|
#include <dwmapi.h>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
|
using MeasureToolUI.Helpers;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
|
|
||||||
namespace MeasureToolUI
|
namespace MeasureToolUI
|
||||||
@@ -42,7 +43,18 @@ namespace MeasureToolUI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_window = new MainWindow();
|
PowerToys.MeasureToolCore.Core core = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
core = new PowerToys.MeasureToolCore.Core();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError($"MeasureToolCore failed to initialize: {ex}");
|
||||||
|
Environment.Exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
_window = new MainWindow(core);
|
||||||
_window.Activate();
|
_window.Activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
79
src/modules/MeasureTool/MeasureToolUI/Logger.cs
Normal file
79
src/modules/MeasureTool/MeasureToolUI/Logger.cs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using interop;
|
||||||
|
using Microsoft.VisualBasic;
|
||||||
|
using Windows.Storage;
|
||||||
|
|
||||||
|
namespace MeasureToolUI.Helpers
|
||||||
|
{
|
||||||
|
public static class Logger
|
||||||
|
{
|
||||||
|
private static readonly string ApplicationLogPath = Path.Combine(interop.Constants.AppDataPath(), "Measure Tool\\MeasureToolUI\\Logs");
|
||||||
|
|
||||||
|
static Logger()
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(ApplicationLogPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(ApplicationLogPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using InvariantCulture since this is used for a log file name
|
||||||
|
var logFilePath = Path.Combine(ApplicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".txt");
|
||||||
|
|
||||||
|
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));
|
||||||
|
|
||||||
|
Trace.AutoFlush = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogError(string message)
|
||||||
|
{
|
||||||
|
Log(message, "ERROR");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogError(string message, Exception ex)
|
||||||
|
{
|
||||||
|
Log(
|
||||||
|
message + Environment.NewLine +
|
||||||
|
ex?.Message + Environment.NewLine +
|
||||||
|
"Inner exception: " + Environment.NewLine +
|
||||||
|
ex?.InnerException?.Message + Environment.NewLine +
|
||||||
|
"Stack trace: " + Environment.NewLine +
|
||||||
|
ex?.StackTrace,
|
||||||
|
"ERROR");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogWarning(string message)
|
||||||
|
{
|
||||||
|
Log(message, "WARNING");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogInfo(string message)
|
||||||
|
{
|
||||||
|
Log(message, "INFO");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Log(string message, string type)
|
||||||
|
{
|
||||||
|
Trace.WriteLine(type + ": " + DateTime.Now.TimeOfDay);
|
||||||
|
Trace.Indent();
|
||||||
|
Trace.WriteLine(GetCallerInfo());
|
||||||
|
Trace.WriteLine(message);
|
||||||
|
Trace.Unindent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetCallerInfo()
|
||||||
|
{
|
||||||
|
StackTrace stackTrace = new StackTrace();
|
||||||
|
|
||||||
|
var methodName = stackTrace.GetFrame(3)?.GetMethod();
|
||||||
|
var className = methodName?.DeclaringType.Name;
|
||||||
|
return "[Method]: " + methodName?.Name + " [Class]: " + className;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,12 +18,12 @@ namespace MeasureToolUI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// An empty window that can be used on its own or navigated to within a Frame.
|
/// An empty window that can be used on its own or navigated to within a Frame.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class MainWindow : WindowEx
|
public sealed partial class MainWindow : WindowEx, IDisposable
|
||||||
{
|
{
|
||||||
private const int WindowWidth = 216;
|
private const int WindowWidth = 216;
|
||||||
private const int WindowHeight = 50;
|
private const int WindowHeight = 50;
|
||||||
|
|
||||||
private PowerToys.MeasureToolCore.Core _coreLogic = new PowerToys.MeasureToolCore.Core();
|
private PowerToys.MeasureToolCore.Core _coreLogic;
|
||||||
|
|
||||||
private AppWindow _appWindow;
|
private AppWindow _appWindow;
|
||||||
private PointInt32 _initialPosition;
|
private PointInt32 _initialPosition;
|
||||||
@@ -34,14 +34,14 @@ namespace MeasureToolUI
|
|||||||
this.SetWindowSize(WindowWidth, WindowHeight);
|
this.SetWindowSize(WindowWidth, WindowHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow(PowerToys.MeasureToolCore.Core core)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
|
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
|
||||||
WindowId windowId = Win32Interop.GetWindowIdFromWindow(hwnd);
|
WindowId windowId = Win32Interop.GetWindowIdFromWindow(hwnd);
|
||||||
_appWindow = AppWindow.GetFromWindowId(windowId);
|
_appWindow = AppWindow.GetFromWindowId(windowId);
|
||||||
|
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||||
var presenter = _appWindow.Presenter as OverlappedPresenter;
|
var presenter = _appWindow.Presenter as OverlappedPresenter;
|
||||||
presenter.IsAlwaysOnTop = true;
|
presenter.IsAlwaysOnTop = true;
|
||||||
this.SetIsAlwaysOnTop(true);
|
this.SetIsAlwaysOnTop(true);
|
||||||
@@ -51,6 +51,8 @@ namespace MeasureToolUI
|
|||||||
this.SetIsMaximizable(false);
|
this.SetIsMaximizable(false);
|
||||||
IsTitleBarVisible = false;
|
IsTitleBarVisible = false;
|
||||||
|
|
||||||
|
_coreLogic = core;
|
||||||
|
Closed += MainWindow_Closed;
|
||||||
DisplayArea displayArea = DisplayArea.GetFromWindowId(windowId, DisplayAreaFallback.Nearest);
|
DisplayArea displayArea = DisplayArea.GetFromWindowId(windowId, DisplayAreaFallback.Nearest);
|
||||||
float dpiScale = _coreLogic.GetDPIScaleForWindow((int)hwnd);
|
float dpiScale = _coreLogic.GetDPIScaleForWindow((int)hwnd);
|
||||||
|
|
||||||
@@ -64,6 +66,12 @@ namespace MeasureToolUI
|
|||||||
OnPositionChanged(_initialPosition);
|
OnPositionChanged(_initialPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MainWindow_Closed(object sender, WindowEventArgs args)
|
||||||
|
{
|
||||||
|
_coreLogic?.Dispose();
|
||||||
|
_coreLogic = null;
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateToolUsageCompletionEvent(object sender)
|
private void UpdateToolUsageCompletionEvent(object sender)
|
||||||
{
|
{
|
||||||
_coreLogic.SetToolCompletionEvent(new PowerToys.MeasureToolCore.ToolSessionCompleted(() =>
|
_coreLogic.SetToolCompletionEvent(new PowerToys.MeasureToolCore.ToolSessionCompleted(() =>
|
||||||
@@ -136,5 +144,10 @@ namespace MeasureToolUI
|
|||||||
_coreLogic.ResetState();
|
_coreLogic.ResetState();
|
||||||
this.Close();
|
this.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_coreLogic?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,7 @@
|
|||||||
<Folder Include="Assets\Icons\" />
|
<Folder Include="Assets\Icons\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
|
||||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||||
<ProjectReference Include="..\MeasureToolCore\PowerToys.MeasureToolCore.vcxproj" />
|
<ProjectReference Include="..\MeasureToolCore\PowerToys.MeasureToolCore.vcxproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -13,5 +13,6 @@ internal static class NativeMethods
|
|||||||
internal static readonly IntPtr HWND_TOPMOST = new System.IntPtr(-1);
|
internal static readonly IntPtr HWND_TOPMOST = new System.IntPtr(-1);
|
||||||
internal const uint SWP_NOSIZE = 0x0001;
|
internal const uint SWP_NOSIZE = 0x0001;
|
||||||
internal const uint SWP_NOMOVE = 0x0002;
|
internal const uint SWP_NOMOVE = 0x0002;
|
||||||
|
internal const uint SWP_NOACTIVATE = 0x0010;
|
||||||
internal const uint SWP_SHOWWINDOW = 0x0040;
|
internal const uint SWP_SHOWWINDOW = 0x0040;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
public MeasureToolProperties()
|
public MeasureToolProperties()
|
||||||
{
|
{
|
||||||
ActivationShortcut = new HotkeySettings(true, false, false, true, 0x4D);
|
ActivationShortcut = new HotkeySettings(true, false, false, true, 0x4D);
|
||||||
|
UnitsOfMeasure = new IntProperty(0);
|
||||||
PixelTolerance = new IntProperty(30);
|
PixelTolerance = new IntProperty(30);
|
||||||
ContinuousCapture = true;
|
ContinuousCapture = true;
|
||||||
DrawFeetOnCross = true;
|
DrawFeetOnCross = true;
|
||||||
@@ -32,6 +33,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
[JsonConverter(typeof(BoolPropertyJsonConverter))]
|
||||||
public bool PerColorChannelEdgeDetection { get; set; }
|
public bool PerColorChannelEdgeDetection { get; set; }
|
||||||
|
|
||||||
|
public IntProperty UnitsOfMeasure { get; set; }
|
||||||
|
|
||||||
public IntProperty PixelTolerance { get; set; }
|
public IntProperty PixelTolerance { get; set; }
|
||||||
|
|
||||||
public StringProperty MeasureCrossColor { get; set; }
|
public StringProperty MeasureCrossColor { get; set; }
|
||||||
|
|||||||
@@ -129,6 +129,23 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int UnitsOfMeasure
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Settings.Properties.UnitsOfMeasure.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Settings.Properties.UnitsOfMeasure.Value != value)
|
||||||
|
{
|
||||||
|
Settings.Properties.UnitsOfMeasure.Value = value;
|
||||||
|
NotifyPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int PixelTolerance
|
public int PixelTolerance
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
@@ -155,6 +155,18 @@
|
|||||||
<value>Customize the shortcut to bring up the command bar</value>
|
<value>Customize the shortcut to bring up the command bar</value>
|
||||||
<comment>"Screen Ruler" is the name of the utility</comment>
|
<comment>"Screen Ruler" is the name of the utility</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="MeasureTool_UnitsOfMeasure.Header" xml:space="preserve">
|
||||||
|
<value>Units of measurement</value>
|
||||||
|
</data>
|
||||||
|
<data name="MeasureTool_UnitsOfMeasure_Pixels.Content" xml:space="preserve">
|
||||||
|
<value>Pixels</value>
|
||||||
|
</data>
|
||||||
|
<data name="MeasureTool_UnitsOfMeasure_Inches.Content" xml:space="preserve">
|
||||||
|
<value>Inches</value>
|
||||||
|
</data>
|
||||||
|
<data name="MeasureTool_UnitsOfMeasure_Centimeters.Content" xml:space="preserve">
|
||||||
|
<value>Centimeters</value>
|
||||||
|
</data>
|
||||||
<data name="MeasureTool_PixelTolerance.Header" xml:space="preserve">
|
<data name="MeasureTool_PixelTolerance.Header" xml:space="preserve">
|
||||||
<value>Pixel tolerance for edge detection</value>
|
<value>Pixel tolerance for edge detection</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
IsOpen="{x:Bind Mode=OneWay, Path=ViewModel.ShowContinuousCaptureWarning}"
|
IsOpen="{x:Bind Mode=OneWay, Path=ViewModel.ShowContinuousCaptureWarning}"
|
||||||
IsTabStop="{x:Bind Mode=OneWay, Path=ViewModel.ShowContinuousCaptureWarning}"
|
IsTabStop="{x:Bind Mode=OneWay, Path=ViewModel.ShowContinuousCaptureWarning}"
|
||||||
IsClosable="False" />
|
IsClosable="False" />
|
||||||
|
|
||||||
<controls:Setting x:Uid="MeasureTool_PerColorChannelEdgeDetection" Icon="">
|
<controls:Setting x:Uid="MeasureTool_PerColorChannelEdgeDetection" Icon="">
|
||||||
<controls:Setting.ActionContent>
|
<controls:Setting.ActionContent>
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
@@ -56,6 +57,7 @@
|
|||||||
x:Uid="MeasureTool_PerColorChannelEdgeDetection_ToggleSwitch" />
|
x:Uid="MeasureTool_PerColorChannelEdgeDetection_ToggleSwitch" />
|
||||||
</controls:Setting.ActionContent>
|
</controls:Setting.ActionContent>
|
||||||
</controls:Setting>
|
</controls:Setting>
|
||||||
|
|
||||||
<controls:Setting x:Uid="MeasureTool_PixelTolerance">
|
<controls:Setting x:Uid="MeasureTool_PixelTolerance">
|
||||||
<controls:Setting.ActionContent>
|
<controls:Setting.ActionContent>
|
||||||
<Slider Minimum="0"
|
<Slider Minimum="0"
|
||||||
@@ -65,6 +67,16 @@
|
|||||||
</controls:Setting.ActionContent>
|
</controls:Setting.ActionContent>
|
||||||
</controls:Setting>
|
</controls:Setting>
|
||||||
|
|
||||||
|
<!--<controls:Setting x:Uid="MeasureTool_UnitsOfMeasure" Icon="">
|
||||||
|
<controls:Setting.ActionContent>
|
||||||
|
<ComboBox SelectedIndex="{x:Bind Path=ViewModel.UnitsOfMeasure, Mode=TwoWay}" MinWidth="{StaticResource SettingActionControlMinWidth}">
|
||||||
|
<ComboBoxItem x:Uid="MeasureTool_UnitsOfMeasure_Pixels" />
|
||||||
|
<ComboBoxItem x:Uid="MeasureTool_UnitsOfMeasure_Inches" />
|
||||||
|
<ComboBoxItem x:Uid="MeasureTool_UnitsOfMeasure_Centimeters" />
|
||||||
|
</ComboBox>
|
||||||
|
</controls:Setting.ActionContent>
|
||||||
|
</controls:Setting>-->
|
||||||
|
|
||||||
<controls:Setting x:Uid="MeasureTool_DrawFeetOnCross">
|
<controls:Setting x:Uid="MeasureTool_DrawFeetOnCross">
|
||||||
<controls:Setting.ActionContent>
|
<controls:Setting.ActionContent>
|
||||||
<ToggleSwitch IsOn="{x:Bind ViewModel.DrawFeetOnCross, Mode=TwoWay}" x:Uid="MeasureTool_DrawFeetOnCross_ToggleSwitch" />
|
<ToggleSwitch IsOn="{x:Bind ViewModel.DrawFeetOnCross, Mode=TwoWay}" x:Uid="MeasureTool_DrawFeetOnCross_ToggleSwitch" />
|
||||||
|
|||||||
Reference in New Issue
Block a user