[Screen Ruler] Improve UX (#20400)

* [Screen Ruler] add 7% opacity to tooltip background

* [Screen Ruler] restrict mouse cursor to monitor while in bounds mode

* [Screen Ruler] Do not preview overlay ui on all virtual desktops (Win + tab)

* [Screen Ruler] add hotkeys for toolbar #20345

* [Screen Ruler] Make single snapshot capture mode a default and update warning

* [Screen Ruler] Fix touch input in bounds mode #20286

* [Screen Ruler] activate window and set HWND_TOPMOST flag again after initialization
This commit is contained in:
Andrey Nekrasov
2022-09-09 21:25:05 +03:00
committed by GitHub
parent 03cf77723e
commit 675d79a4aa
12 changed files with 256 additions and 72 deletions

View File

@@ -644,6 +644,7 @@ FRAMECHANGED
franky franky
frankychen frankychen
Froml Froml
FROMTOUCH
fstream fstream
FTYPE FTYPE
func func
@@ -784,6 +785,7 @@ hsv
htcfreek htcfreek
HTCLIENT HTCLIENT
HTHUMBNAIL HTHUMBNAIL
HTOUCHINPUT
HTTRANSPARENT HTTRANSPARENT
HValue HValue
Hvci Hvci
@@ -869,6 +871,7 @@ IImage
Iindex Iindex
IInitialize IInitialize
IInspectable IInspectable
IInvoke
IIO IIO
IItem IItem
IJson IJson
@@ -1276,6 +1279,7 @@ monitorinfof
Monthand Monthand
Moq Moq
MOUSEACTIVATE MOUSEACTIVATE
MOUSEEVENTF
MOUSEHWHEEL MOUSEHWHEEL
MOUSEINPUT MOUSEINPUT
MOUSELEAVE MOUSELEAVE
@@ -2089,6 +2093,8 @@ Toolset
toolwindow toolwindow
TOPDOWNDIB TOPDOWNDIB
toplevel toplevel
TOUCHEVENTF
TOUCHINPUT
touchpad touchpad
toupper toupper
Towindow Towindow
@@ -2108,6 +2114,7 @@ Tshuapa
TStr TStr
Tuva Tuva
TValue TValue
TWF
TYMED TYMED
typedef typedef
TYPEKEY TYPEKEY
@@ -2262,6 +2269,7 @@ VSTT
vswhere vswhere
vtable vtable
Vtbl Vtbl
WANTPALM
wbem wbem
wbemuuid wbemuuid
WBounds WBounds

View File

@@ -5,6 +5,72 @@
#include <common/utils/window.h> #include <common/utils/window.h>
#define MOUSEEVENTF_FROMTOUCH 0xFF515700
namespace
{
void ToggleCursor(const bool show)
{
if (show)
{
for (; ShowCursor(show) < 0;)
;
}
else
{
for (; ShowCursor(show) >= 0;)
;
}
}
void HandleCursorMove(HWND window, BoundsToolState* toolState, const POINT cursorPos, const DWORD touchID = 0)
{
if (!toolState->perScreen[window].currentBounds || (toolState->perScreen[window].currentBounds->touchID != touchID))
return;
toolState->perScreen[window].currentBounds->currentPos =
D2D_POINT_2F{ .x = static_cast<float>(cursorPos.x), .y = static_cast<float>(cursorPos.y) };
}
void HandleCursorDown(HWND window, BoundsToolState* toolState, const POINT cursorPos, const DWORD touchID = 0)
{
ToggleCursor(false);
RECT windowRect;
if (GetWindowRect(window, &windowRect))
ClipCursor(&windowRect);
const D2D_POINT_2F newBoundsStart = { .x = static_cast<float>(cursorPos.x), .y = static_cast<float>(cursorPos.y) };
toolState->perScreen[window].currentBounds = CursorDrag{
.startPos = newBoundsStart,
.currentPos = newBoundsStart,
.touchID = touchID
};
}
void HandleCursorUp(HWND window, BoundsToolState* toolState, const POINT cursorPos)
{
ToggleCursor(true);
ClipCursor(nullptr);
toolState->commonState->overlayBoxText.Read([](const OverlayBoxText& text) {
SetClipBoardToText(text.buffer);
});
if (const bool shiftPress = GetKeyState(VK_SHIFT) & 0x8000; shiftPress && toolState->perScreen[window].currentBounds)
{
D2D1_RECT_F rect;
std::tie(rect.left, rect.right) =
std::minmax(static_cast<float>(cursorPos.x), toolState->perScreen[window].currentBounds->startPos.x);
std::tie(rect.top, rect.bottom) =
std::minmax(static_cast<float>(cursorPos.y), toolState->perScreen[window].currentBounds->startPos.y);
toolState->perScreen[window].measurements.push_back(Measurement{ rect });
}
toolState->perScreen[window].currentBounds = std::nullopt;
}
}
LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept
{ {
switch (message) switch (message)
@@ -25,64 +91,122 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
break; break;
case WM_LBUTTONDOWN: case WM_LBUTTONDOWN:
{ {
for (; ShowCursor(false) >= 0;) const bool touchEvent = (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH;
; if (touchEvent)
break;
auto toolState = GetWindowParam<BoundsToolState*>(window); auto toolState = GetWindowParam<BoundsToolState*>(window);
if (!toolState) if (!toolState)
break; break;
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) }; HandleCursorDown(window,
toolState->perScreen[window].currentRegionStart = newRegionStart; toolState,
convert::FromSystemToWindow(window, toolState->commonState->cursorPosSystemSpace));
break; break;
} }
case WM_CURSOR_LEFT_MONITOR: case WM_CURSOR_LEFT_MONITOR:
{ {
for (; ShowCursor(true) < 0;) ToggleCursor(true);
;
ClipCursor(nullptr);
auto toolState = GetWindowParam<BoundsToolState*>(window); auto toolState = GetWindowParam<BoundsToolState*>(window);
if (!toolState) if (!toolState)
break; break;
toolState->perScreen[window].currentRegionStart = std::nullopt; toolState->perScreen[window].currentBounds = std::nullopt;
break; break;
} }
case WM_LBUTTONUP: case WM_TOUCH:
{ {
for (; ShowCursor(true) < 0;) auto toolState = GetWindowParam<BoundsToolState*>(window);
; if (!toolState)
break;
std::array<TOUCHINPUT, 8> inputs;
const size_t nInputs = std::min(static_cast<size_t>(LOWORD(wparam)), inputs.size());
const auto inputHandle = std::bit_cast<HTOUCHINPUT>(lparam);
GetTouchInputInfo(inputHandle, static_cast<UINT>(nInputs), inputs.data(), sizeof(TOUCHINPUT));
for (UINT i = 0; i < nInputs; ++i)
{
const auto& input = inputs[i];
if (const bool down = (input.dwFlags & TOUCHEVENTF_DOWN) && (input.dwFlags & TOUCHEVENTF_PRIMARY); down)
{
HandleCursorDown(
window,
toolState,
POINT{ TOUCH_COORD_TO_PIXEL(input.x), TOUCH_COORD_TO_PIXEL(input.y) },
input.dwID);
continue;
}
if (const bool up = input.dwFlags & TOUCHEVENTF_UP; up)
{
HandleCursorUp(
window,
toolState,
POINT{ TOUCH_COORD_TO_PIXEL(input.x), TOUCH_COORD_TO_PIXEL(input.y) });
continue;
}
if (const bool move = input.dwFlags & TOUCHEVENTF_MOVE; move)
{
HandleCursorMove(window,
toolState,
POINT{ TOUCH_COORD_TO_PIXEL(input.x), TOUCH_COORD_TO_PIXEL(input.y) },
input.dwID);
continue;
}
}
CloseTouchInputHandle(inputHandle);
break;
}
case WM_MOUSEMOVE:
{
const bool touchEvent = (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH;
if (touchEvent)
break;
auto toolState = GetWindowParam<BoundsToolState*>(window); auto toolState = GetWindowParam<BoundsToolState*>(window);
if (!toolState || !toolState->perScreen[window].currentRegionStart) if (!toolState)
break; break;
toolState->commonState->overlayBoxText.Read([](const OverlayBoxText& text) { HandleCursorMove(window,
SetClipBoardToText(text.buffer); toolState,
}); convert::FromSystemToWindow(window, toolState->commonState->cursorPosSystemSpace));
break;
if (const bool shiftPress = GetKeyState(VK_SHIFT) & 0x8000; shiftPress)
{
const auto cursorPos = convert::FromSystemToWindow(window, toolState->commonState->cursorPosSystemSpace);
D2D1_RECT_F rect;
std::tie(rect.left, rect.right) = std::minmax(static_cast<float>(cursorPos.x), toolState->perScreen[window].currentRegionStart->x);
std::tie(rect.top, rect.bottom) = std::minmax(static_cast<float>(cursorPos.y), toolState->perScreen[window].currentRegionStart->y);
toolState->perScreen[window].measurements.push_back(Measurement{ rect });
} }
toolState->perScreen[window].currentRegionStart = std::nullopt; case WM_LBUTTONUP:
{
const bool touchEvent = (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH;
if (touchEvent)
break;
auto toolState = GetWindowParam<BoundsToolState*>(window);
if (!toolState)
break;
HandleCursorUp(window,
toolState,
convert::FromSystemToWindow(window, toolState->commonState->cursorPosSystemSpace));
break; break;
} }
case WM_RBUTTONUP: case WM_RBUTTONUP:
{ {
for (; ShowCursor(true) < 0;) const bool touchEvent = (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH;
; if (touchEvent)
break;
ToggleCursor(true);
auto toolState = GetWindowParam<BoundsToolState*>(window); auto toolState = GetWindowParam<BoundsToolState*>(window);
if (!toolState) if (!toolState)
break; break;
if (toolState->perScreen[window].currentRegionStart) if (toolState->perScreen[window].currentBounds)
toolState->perScreen[window].currentRegionStart = std::nullopt; toolState->perScreen[window].currentBounds = std::nullopt;
else else
{ {
if (toolState->perScreen[window].measurements.empty()) if (toolState->perScreen[window].measurements.empty())
@@ -100,14 +224,12 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
namespace namespace
{ {
void DrawMeasurement(const Measurement& measurement, void DrawMeasurement(const Measurement& measurement,
const bool alignTextBoxToCenter,
const CommonState& commonState, const CommonState& commonState,
HWND window, HWND window,
const D2DState& d2dState, const D2DState& d2dState,
float mouseX, std::optional<D2D_POINT_2F> textBoxCenter)
float mouseY)
{ {
const bool screenQuadrantAware = !alignTextBoxToCenter; const bool screenQuadrantAware = textBoxCenter.has_value();
d2dState.ToggleAliasedLinesMode(true); d2dState.ToggleAliasedLinesMode(true);
d2dState.dxgiWindowState.rt->DrawRectangle(measurement.rect, d2dState.solidBrushes[Brush::line].get()); d2dState.dxgiWindowState.rt->DrawRectangle(measurement.rect, d2dState.solidBrushes[Brush::line].get());
d2dState.ToggleAliasedLinesMode(false); d2dState.ToggleAliasedLinesMode(false);
@@ -124,17 +246,19 @@ namespace
v = text; v = text;
}); });
if (alignTextBoxToCenter) D2D_POINT_2F textBoxPos;
if (textBoxCenter)
textBoxPos = *textBoxCenter;
else
{ {
mouseX = measurement.rect.left + measurement.Width(Measurement::Unit::Pixel) / 2; textBoxPos.x = measurement.rect.left + measurement.Width(Measurement::Unit::Pixel) / 2;
mouseY = measurement.rect.top + measurement.Height(Measurement::Unit::Pixel) / 2; textBoxPos.y = measurement.rect.top + measurement.Height(Measurement::Unit::Pixel) / 2;
} }
d2dState.DrawTextBox(text.buffer.data(), d2dState.DrawTextBox(text.buffer.data(),
measureStringBufLen, measureStringBufLen,
crossSymbolPos, crossSymbolPos,
mouseX, textBoxPos,
mouseY,
screenQuadrantAware, screenQuadrantAware,
window); window);
} }
@@ -153,17 +277,13 @@ void DrawBoundsToolTick(const CommonState& commonState,
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, measure.rect.right, measure.rect.bottom); DrawMeasurement(measure, commonState, window, d2dState, {});
if (!perScreen.currentRegionStart.has_value())
return;
const auto cursorPos = convert::FromSystemToWindow(window, commonState.cursorPosSystemSpace);
if (perScreen.currentBounds.has_value())
{
D2D1_RECT_F rect; D2D1_RECT_F rect;
const float cursorX = static_cast<float>(cursorPos.x); std::tie(rect.left, rect.right) = std::minmax(perScreen.currentBounds->startPos.x, perScreen.currentBounds->currentPos.x);
const float cursorY = static_cast<float>(cursorPos.y); std::tie(rect.top, rect.bottom) = std::minmax(perScreen.currentBounds->startPos.y, perScreen.currentBounds->currentPos.y);
std::tie(rect.left, rect.right) = std::minmax(cursorX, perScreen.currentRegionStart->x); DrawMeasurement(Measurement{ rect }, commonState, window, d2dState, perScreen.currentBounds->currentPos);
std::tie(rect.top, rect.bottom) = std::minmax(cursorY, perScreen.currentRegionStart->y); }
DrawMeasurement(Measurement{ rect }, false, commonState, window, d2dState, cursorX, cursorY);
} }

View File

@@ -66,8 +66,7 @@ D2DState::D2DState(const DxgiAPI* dxgi,
void D2DState::DrawTextBox(const wchar_t* text, void D2DState::DrawTextBox(const wchar_t* text,
const size_t textLen, const size_t textLen,
const std::optional<size_t> halfOpaqueSymbolPos, const std::optional<size_t> halfOpaqueSymbolPos,
const float centerX, const D2D_POINT_2F center,
const float centerY,
const bool screenQuadrantAware, const bool screenQuadrantAware,
const HWND window) const const HWND window) const
{ {
@@ -88,10 +87,10 @@ void D2DState::DrawTextBox(const wchar_t* text,
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));
D2D1_RECT_F textRect{ .left = centerX - textMetrics.width / 2.f, D2D1_RECT_F textRect{ .left = center.x - textMetrics.width / 2.f,
.top = centerY - textMetrics.height / 2.f, .top = center.y - textMetrics.height / 2.f,
.right = centerX + textMetrics.width / 2.f, .right = center.x + textMetrics.width / 2.f,
.bottom = centerY + textMetrics.height / 2.f }; .bottom = center.y + textMetrics.height / 2.f };
const float SHADOW_OFFSET = consts::SHADOW_OFFSET * dpiScale; const float SHADOW_OFFSET = consts::SHADOW_OFFSET * dpiScale;
if (screenQuadrantAware) if (screenQuadrantAware)
@@ -99,8 +98,8 @@ void D2DState::DrawTextBox(const wchar_t* text,
bool cursorInLeftScreenHalf = false; bool cursorInLeftScreenHalf = false;
bool cursorInTopScreenHalf = false; bool cursorInTopScreenHalf = false;
DetermineScreenQuadrant(window, DetermineScreenQuadrant(window,
static_cast<long>(centerX), static_cast<long>(center.x),
static_cast<long>(centerY), static_cast<long>(center.y),
cursorInLeftScreenHalf, cursorInLeftScreenHalf,
cursorInTopScreenHalf); cursorInTopScreenHalf);
float textQuadrantOffsetX = textMetrics.width / 2.f + SHADOW_OFFSET; float textQuadrantOffsetX = textMetrics.width / 2.f + SHADOW_OFFSET;

View File

@@ -36,8 +36,7 @@ struct D2DState
void DrawTextBox(const wchar_t* text, void DrawTextBox(const wchar_t* text,
const size_t textLen, const size_t textLen,
const std::optional<size_t> halfOpaqueSymbolPos, const std::optional<size_t> halfOpaqueSymbolPos,
const float centerX, const D2D_POINT_2F center,
const float centerY,
const bool screenQuadrantAware, const bool screenQuadrantAware,
const HWND window) const; const HWND window) const;
void ToggleAliasedLinesMode(const bool enabled) const; void ToggleAliasedLinesMode(const bool enabled) const;

View File

@@ -257,8 +257,7 @@ void DrawMeasureToolTick(const CommonState& commonState,
d2dState.DrawTextBox(text.buffer.data(), d2dState.DrawTextBox(text.buffer.data(),
measureStringBufLen, measureStringBufLen,
crossSymbolPos, crossSymbolPos,
static_cast<float>(cursorPos.x), D2D_POINT_2F{ static_cast<float>(cursorPos.x), static_cast<float>(cursorPos.y) },
static_cast<float>(cursorPos.y),
true, true,
window); window);
} }

View File

@@ -60,6 +60,15 @@ HWND CreateOverlayUIWindow(const CommonState& commonState,
extraParam) extraParam)
}; };
winrt::check_bool(window); winrt::check_bool(window);
// Exclude overlay window from displaying in WIN+TAB preview, since WS_EX_TOOLWINDOW windows are displayed simultaneously on all virtual desktops.
// We can't remove WS_EX_TOOLWINDOW/WS_EX_NOACTIVATE flag, since we want to exclude the window from taskbar
BOOL val = TRUE;
DwmSetWindowAttribute(window, DWMWA_EXCLUDED_FROM_PEEK, &val, sizeof(val));
// We want to receive input events as soon as possible to prevent issues with touch input
RegisterTouchWindow(window, TWF_WANTPALM);
ShowWindow(window, SW_SHOWNORMAL); ShowWindow(window, SW_SHOWNORMAL);
UpdateWindow(window); UpdateWindow(window);
if (excludeFromCapture) if (excludeFromCapture)
@@ -100,13 +109,13 @@ HWND CreateOverlayUIWindow(const CommonState& commonState,
std::vector<D2D1::ColorF> AppendCommonOverlayUIColors(const D2D1::ColorF& lineColor) std::vector<D2D1::ColorF> AppendCommonOverlayUIColors(const D2D1::ColorF& lineColor)
{ {
D2D1::ColorF foreground = D2D1::ColorF::Black; D2D1::ColorF foreground = D2D1::ColorF::Black;
D2D1::ColorF background = D2D1::ColorF(0.96f, 0.96f, 0.96f, 1.0f); D2D1::ColorF background = D2D1::ColorF(0.96f, 0.96f, 0.96f, .93f);
D2D1::ColorF border = D2D1::ColorF(0.44f, 0.44f, 0.44f, 0.4f); D2D1::ColorF border = D2D1::ColorF(0.44f, 0.44f, 0.44f, 0.4f);
if (WindowsColors::is_dark_mode()) if (WindowsColors::is_dark_mode())
{ {
foreground = D2D1::ColorF::White; foreground = D2D1::ColorF::White;
background = D2D1::ColorF(0.17f, 0.17f, 0.17f, 1.0f); background = D2D1::ColorF(0.17f, 0.17f, 0.17f, .93f);
border = D2D1::ColorF(0.44f, 0.44f, 0.44f, 0.4f); border = D2D1::ColorF(0.44f, 0.44f, 0.44f, 0.4f);
} }
@@ -115,7 +124,7 @@ std::vector<D2D1::ColorF> AppendCommonOverlayUIColors(const D2D1::ColorF& lineCo
void OverlayUIState::RunUILoop() void OverlayUIState::RunUILoop()
{ {
bool cursorOnScreen = true; bool cursorOnScreen = false;
while (IsWindow(_window) && !_commonState.closeOnOtherMonitors) while (IsWindow(_window) && !_commonState.closeOnOtherMonitors)
{ {

View File

@@ -8,7 +8,7 @@
struct Settings struct Settings
{ {
uint8_t pixelTolerance = 30; uint8_t pixelTolerance = 30;
bool continuousCapture = true; bool continuousCapture = false;
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};

View File

@@ -36,13 +36,22 @@ struct CommonState
std::atomic_bool closeOnOtherMonitors = false; std::atomic_bool closeOnOtherMonitors = false;
}; };
struct CursorDrag
{
D2D_POINT_2F startPos = {};
D2D_POINT_2F currentPos = {};
DWORD touchID = 0; // indicate whether the drag belongs to a touch input sequence
};
struct BoundsToolState struct BoundsToolState
{ {
struct PerScreen struct PerScreen
{ {
std::optional<D2D_POINT_2F> currentRegionStart; std::optional<CursorDrag> currentBounds;
std::vector<Measurement> measurements; std::vector<Measurement> measurements;
}; };
// TODO: refactor so we don't need unordered_map
std::unordered_map<HWND, PerScreen> perScreen; std::unordered_map<HWND, PerScreen> perScreen;
CommonState* commonState = nullptr; // required for WndProc CommonState* commonState = nullptr; // required for WndProc
@@ -60,7 +69,7 @@ struct MeasureToolState
struct Global struct Global
{ {
uint8_t pixelTolerance = 30; uint8_t pixelTolerance = 30;
bool continuousCapture = true; bool continuousCapture = false;
bool drawFeetOnCross = true; bool drawFeetOnCross = true;
bool perColorChannelEdgeDetection = false; bool perColorChannelEdgeDetection = false;
Mode mode = Mode::Cross; Mode mode = Mode::Cross;

View File

@@ -261,13 +261,21 @@
Click="BoundsTool_Click" Click="BoundsTool_Click"
Content="&#xEF20;" Content="&#xEF20;"
Style="{StaticResource ToggleButtonRadioButtonStyle}" Style="{StaticResource ToggleButtonRadioButtonStyle}"
ToolTipService.ToolTip="{x:Bind p:Resources.Bounds}" /> KeyboardAcceleratorPlacementMode="Auto"
ToolTipService.ToolTip="{x:Bind p:Resources.Bounds}">
<ToggleButton.KeyboardAccelerators>
<KeyboardAccelerator Key="Number1" Modifiers="Control" Invoked="KeyboardAccelerator_Invoked"/>
</ToggleButton.KeyboardAccelerators>
</ToggleButton>
<ToggleButton <ToggleButton
AutomationProperties.Name="{x:Bind p:Resources.Spacing}" AutomationProperties.Name="{x:Bind p:Resources.Spacing}"
Click="MeasureTool_Click" Click="MeasureTool_Click"
Style="{StaticResource ToggleButtonRadioButtonStyle}" Style="{StaticResource ToggleButtonRadioButtonStyle}"
ToolTipService.ToolTip="{x:Bind p:Resources.Spacing}"> ToolTipService.ToolTip="{x:Bind p:Resources.Spacing}">
<FontIcon Margin="1,0,0,0" Glyph="&#xE948;" /> <FontIcon Margin="1,0,0,0" Glyph="&#xE948;" />
<ToggleButton.KeyboardAccelerators>
<KeyboardAccelerator Key="Number2" Modifiers="Control" Invoked="KeyboardAccelerator_Invoked"/>
</ToggleButton.KeyboardAccelerators>
</ToggleButton> </ToggleButton>
<ToggleButton <ToggleButton
@@ -276,6 +284,9 @@
Style="{StaticResource ToggleButtonRadioButtonStyle}" Style="{StaticResource ToggleButtonRadioButtonStyle}"
ToolTipService.ToolTip="{x:Bind p:Resources.HorizontalSpacing}"> ToolTipService.ToolTip="{x:Bind p:Resources.HorizontalSpacing}">
<FontIcon Margin="1,0,0,0" Glyph="&#xE949;" /> <FontIcon Margin="1,0,0,0" Glyph="&#xE949;" />
<ToggleButton.KeyboardAccelerators>
<KeyboardAccelerator Key="Number3" Modifiers="Control" Invoked="KeyboardAccelerator_Invoked"/>
</ToggleButton.KeyboardAccelerators>
</ToggleButton> </ToggleButton>
<ToggleButton <ToggleButton
AutomationProperties.Name="{x:Bind p:Resources.VerticalSpacing}" AutomationProperties.Name="{x:Bind p:Resources.VerticalSpacing}"
@@ -287,12 +298,19 @@
<RotateTransform Angle="90" /> <RotateTransform Angle="90" />
</FontIcon.RenderTransform> </FontIcon.RenderTransform>
</FontIcon> </FontIcon>
<ToggleButton.KeyboardAccelerators>
<KeyboardAccelerator Key="Number4" Modifiers="Control" Invoked="KeyboardAccelerator_Invoked"/>
</ToggleButton.KeyboardAccelerators>
</ToggleButton> </ToggleButton>
<AppBarSeparator Height="36" /> <AppBarSeparator Height="36" />
<Button <Button
Click="ClosePanelTool_Click" Click="ClosePanelTool_Click"
Content="&#xE8BB;" Content="&#xE8BB;"
ToolTipService.ToolTip="Close" /> ToolTipService.ToolTip="Close">
<Button.KeyboardAccelerators>
<KeyboardAccelerator Key="Escape" Invoked="KeyboardAccelerator_Invoked"/>
</Button.KeyboardAccelerators>
</Button>
</StackPanel> </StackPanel>
</Grid> </Grid>
</winuiex:WindowEx> </winuiex:WindowEx>

View File

@@ -6,8 +6,11 @@ using System;
using Microsoft.UI; using Microsoft.UI;
using Microsoft.UI.Windowing; using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation.Peers;
using Microsoft.UI.Xaml.Automation.Provider;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using Windows.Graphics; using Windows.Graphics;
using WinUIEx; using WinUIEx;
@@ -64,6 +67,7 @@ namespace MeasureToolUI
_initialPosition.X + (int)(dpiScale * WindowWidth), _initialPosition.X + (int)(dpiScale * WindowWidth),
_initialPosition.Y + (int)(dpiScale * WindowHeight)); _initialPosition.Y + (int)(dpiScale * WindowHeight));
OnPositionChanged(_initialPosition); OnPositionChanged(_initialPosition);
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
} }
private void MainWindow_Closed(object sender, WindowEventArgs args) private void MainWindow_Closed(object sender, WindowEventArgs args)
@@ -149,5 +153,24 @@ namespace MeasureToolUI
{ {
_coreLogic?.Dispose(); _coreLogic?.Dispose();
} }
private void KeyboardAccelerator_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
{
if (args.Element is ToggleButton toggle)
{
var peer = new ToggleButtonAutomationPeer(toggle);
peer.Toggle();
args.Handled = true;
}
else if (args.Element is Button button)
{
var peer = new ButtonAutomationPeer(button);
if (peer.GetPattern(PatternInterface.Invoke) is IInvokeProvider provider)
{
provider.Invoke();
args.Handled = true;
}
}
}
} }
} }

View File

@@ -16,7 +16,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
ActivationShortcut = new HotkeySettings(true, false, false, true, 0x4D); ActivationShortcut = new HotkeySettings(true, false, false, true, 0x4D);
UnitsOfMeasure = new IntProperty(0); UnitsOfMeasure = new IntProperty(0);
PixelTolerance = new IntProperty(30); PixelTolerance = new IntProperty(30);
ContinuousCapture = true; ContinuousCapture = false;
DrawFeetOnCross = true; DrawFeetOnCross = true;
PerColorChannelEdgeDetection = false; PerColorChannelEdgeDetection = false;
MeasureCrossColor = new StringProperty("#FF4500"); MeasureCrossColor = new StringProperty("#FF4500");

View File

@@ -2396,7 +2396,7 @@ Activate by holding the key for the character you want to add an accent to, then
<value>Learn more about conflicting activation commands</value> <value>Learn more about conflicting activation commands</value>
</data> </data>
<data name="MeasureTool_ContinuousCapture_Information.Title" xml:space="preserve"> <data name="MeasureTool_ContinuousCapture_Information.Title" xml:space="preserve">
<value>The continuous capture mode will consume more resources when in use.</value> <value>The continuous capture mode will consume more resources when in use. Also, measurements will be excluded from screenshots and screen capture.</value>
<comment>pointer as in mouse pointer. Resources refer to things like CPU, GPU, RAM</comment> <comment>pointer as in mouse pointer. Resources refer to things like CPU, GPU, RAM</comment>
</data> </data>
</root> </root>