add mouse wheel to adjust pixel tolerance + per channel detection algorithm setting

This commit is contained in:
yuyoyuppe
2022-08-22 23:54:59 +02:00
parent 7160eb3a1e
commit 87ec6254ad
14 changed files with 191 additions and 76 deletions

View File

@@ -52,7 +52,7 @@ inline int _mm_cvtsi128_si32(__m128i a)
#endif
inline __m128i distance_epi8(const __m128i a, __m128i b)
inline __m128i distance_epu8(const __m128i a, __m128i b)
{
return _mm_or_si128(_mm_subs_epu8(a, b),
_mm_subs_epu8(b, a));
@@ -80,18 +80,25 @@ struct BGRATextureView
return pixels[x + width * y];
}
static inline bool PixelsClose(const uint32_t pixel1, const uint32_t pixel2, const uint8_t tolerance)
template<bool perChannel>
static inline bool PixelsClose(const uint32_t pixel1, const uint32_t pixel2, uint8_t tolerance)
{
const __m128i rgba1 = _mm_cvtsi32_si128(pixel1);
const __m128i rgba2 = _mm_cvtsi32_si128(pixel2);
const __m128i distances = distance_epi8(rgba1, rgba2);
const __m128i distances = distance_epu8(rgba1, rgba2);
// Method 1: Test whether each channel distance is not great than tolerance
//const __m128i tolerances = _mm_set1_epi8(tolerance);
//return _mm_cvtsi128_si32(_mm_cmpgt_epi8(distances, tolerances)) == 0;
// Method 2: Test whether sum of all channel differences is smaller than tolerance
return _mm_cvtsi128_si32(_mm_sad_epu8(distances, _mm_setzero_si128())) <= tolerance;
// Method 1: Test whether each channel distance is not greater than tolerance
if constexpr (perChannel)
{
const __m128i tolerances = _mm_set1_epi16(tolerance);
const int gtResults = _mm_cvtsi128_si32(_mm_cmpgt_epi16(_mm_cvtepu8_epi16(distances), tolerances));
return gtResults == 0;
}
else
{
// Method 2: Test whether sum of all channel differences is smaller than tolerance
return _mm_cvtsi128_si32(_mm_sad_epu8(distances, _mm_setzero_si128())) <= tolerance;
}
}
#if !defined(NDEBUG)

View File

@@ -2,7 +2,10 @@
#include "constants.h"
#include "EdgeDetection.h"
template<bool continuousCapture, bool IsX, bool Increment>
template<bool PerChannel,
bool ContinuousCapture,
bool IsX,
bool Increment>
inline long FindEdge(const BGRATextureView& texture, const POINT centerPoint, const uint8_t tolerance)
{
using namespace consts;
@@ -11,7 +14,7 @@ inline long FindEdge(const BGRATextureView& texture, const POINT centerPoint, co
long yOffset = 0;
// For continuous capture, we'll be a bit off center from the cursor so the cross we draw won't interfere with the measurement.
if constexpr (continuousCapture)
if constexpr (ContinuousCapture)
{
if constexpr (IsX)
{
@@ -63,7 +66,7 @@ inline long FindEdge(const BGRATextureView& texture, const POINT centerPoint, co
}
const uint32_t nextPixel = texture.GetPixel(x, y);
if (!texture.PixelsClose(startPixel, nextPixel, tolerance))
if (!texture.PixelsClose<PerChannel>(startPixel, nextPixel, tolerance))
{
return IsX ? oldX : oldY;
}
@@ -72,16 +75,36 @@ inline long FindEdge(const BGRATextureView& texture, const POINT centerPoint, co
return Increment ? static_cast<long>(IsX ? texture.width : texture.height) - 1 : 0;
}
template<bool continuousCapture>
template<bool PerChannel, bool ContinuousCapture>
inline RECT DetectEdgesInternal(const BGRATextureView& texture, const POINT centerPoint, const uint8_t tolerance)
{
return RECT{ .left = FindEdge<continuousCapture, true, false>(texture, centerPoint, tolerance),
.top = FindEdge<continuousCapture, false, false>(texture, centerPoint, tolerance),
.right = FindEdge<continuousCapture, true, true>(texture, centerPoint, tolerance),
.bottom = FindEdge<continuousCapture, false, true>(texture, centerPoint, tolerance) };
return RECT{ .left = FindEdge<PerChannel,
ContinuousCapture,
true,
false>(texture, centerPoint, tolerance),
.top = FindEdge<PerChannel,
ContinuousCapture,
false,
false>(texture, centerPoint, tolerance),
.right = FindEdge<PerChannel,
ContinuousCapture,
true,
true>(texture, centerPoint, tolerance),
.bottom = FindEdge<PerChannel,
ContinuousCapture,
false,
true>(texture, centerPoint, tolerance) };
}
RECT DetectEdges(const BGRATextureView& texture, const POINT centerPoint, const uint8_t tolerance, const bool continuousCapture)
RECT DetectEdges(const BGRATextureView& texture,
const POINT centerPoint,
const bool perChannel,
const uint8_t tolerance,
const bool continuousCapture)
{
return (continuousCapture ? &DetectEdgesInternal<true> : &DetectEdgesInternal<false>)(texture, centerPoint, tolerance);
auto function = perChannel ? &DetectEdgesInternal<true, false> : DetectEdgesInternal<false, false>;
if (continuousCapture)
function = perChannel ? &DetectEdgesInternal<true, true> : &DetectEdgesInternal<false, true>;
return function(texture, centerPoint, tolerance);
}

View File

@@ -2,4 +2,8 @@
#include "BGRATextureView.h"
RECT DetectEdges(const BGRATextureView& texture, const POINT centerPoint, const uint8_t tolerance, const bool continuousCapture);
RECT DetectEdges(const BGRATextureView& texture,
const POINT centerPoint,
const bool perChannel,
const uint8_t tolerance,
const bool continuousCapture);

View File

@@ -49,8 +49,8 @@ LRESULT CALLBACK measureToolWndProc(HWND window, UINT message, WPARAM wparam, LP
{
case WM_CREATE:
{
auto commonState = GetWindowCreateParam<const CommonState*>(lparam);
StoreWindowParam(window, commonState);
auto state = GetWindowCreateParam<Serialized<MeasureToolState>*>(lparam);
StoreWindowParam(window, state);
#if !defined(DEBUG_OVERLAY)
for (; ShowCursor(false) > 0;)
@@ -58,6 +58,8 @@ LRESULT CALLBACK measureToolWndProc(HWND window, UINT message, WPARAM wparam, LP
#endif
break;
}
case WM_ERASEBKGND:
return 1;
case WM_CLOSE:
DestroyWindow(window);
break;
@@ -71,15 +73,24 @@ LRESULT CALLBACK measureToolWndProc(HWND window, UINT message, WPARAM wparam, LP
PostMessageW(window, WM_CLOSE, {}, {});
break;
case WM_LBUTTONUP:
if (auto commonState = GetWindowParam<const CommonState*>(window))
if (auto state = GetWindowParam<Serialized<MeasureToolState>*>(window))
{
commonState->overlayBoxText.Read([](const OverlayBoxText& text) {
SetClipBoardToText(text.buffer);
state->Read([](const MeasureToolState& s) { s.commonState->overlayBoxText.Read([](const OverlayBoxText& text) {
SetClipBoardToText(text.buffer);
}); });
}
break;
case WM_MOUSEWHEEL:
if (auto state = GetWindowParam<Serialized<MeasureToolState>*>(window))
{
const int8_t step = static_cast<short>(HIWORD(wparam)) < 0 ? -15: 15;
state->Access([step](MeasureToolState& s) {
int wideVal = s.pixelTolerance;
wideVal += step;
s.pixelTolerance = static_cast<uint8_t>(std::clamp(wideVal, 1, 254));
});
}
break;
case WM_ERASEBKGND:
return 1;
}
return DefWindowProcW(window, message, wparam, lparam);
@@ -89,15 +100,17 @@ LRESULT CALLBACK boundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
{
switch (message)
{
case WM_CLOSE:
DestroyWindow(window);
break;
case WM_CREATE:
{
auto toolState = GetWindowCreateParam<BoundsToolState*>(lparam);
StoreWindowParam(window, toolState);
break;
}
case WM_ERASEBKGND:
return 1;
case WM_CLOSE:
DestroyWindow(window);
break;
case WM_KEYUP:
if (wparam == VK_ESCAPE)
{
@@ -107,6 +120,8 @@ LRESULT CALLBACK boundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
case WM_LBUTTONDOWN:
{
auto toolState = GetWindowParam<BoundsToolState*>(window);
if (!toolState)
break;
POINT cursorPos = toolState->commonState->cursorPos;
ScreenToClient(window, &cursorPos);
@@ -118,26 +133,28 @@ LRESULT CALLBACK boundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
case WM_USER:
{
auto toolState = GetWindowParam<BoundsToolState*>(window);
if (!toolState)
break;
toolState->currentRegionStart = std::nullopt;
break;
}
case WM_LBUTTONUP:
if (auto toolState = GetWindowParam<BoundsToolState*>(window); toolState->currentRegionStart.has_value())
{
const auto cursorPos = toolState->commonState->cursorPos;
D2D_POINT_2F newRegionEnd = { .x = static_cast<float>(cursorPos.x), .y = static_cast<float>(cursorPos.y) };
toolState->currentRegionStart = std::nullopt;
{
auto toolState = GetWindowParam<BoundsToolState*>(window);
if (!toolState)
break;
const auto cursorPos = toolState->commonState->cursorPos;
D2D_POINT_2F newRegionEnd = { .x = static_cast<float>(cursorPos.x), .y = static_cast<float>(cursorPos.y) };
toolState->currentRegionStart = std::nullopt;
toolState->commonState->overlayBoxText.Read([](const OverlayBoxText& text) {
SetClipBoardToText(text.buffer);
});
}
toolState->commonState->overlayBoxText.Read([](const OverlayBoxText& text) {
SetClipBoardToText(text.buffer);
});
break;
}
case WM_RBUTTONUP:
PostMessageW(window, WM_CLOSE, {}, {});
break;
case WM_ERASEBKGND:
return 1;
}
return DefWindowProcW(window, message, wparam, lparam);
@@ -228,7 +245,7 @@ std::vector<D2D1::ColorF> AppendCommonOverlayUIColors(const D2D1::ColorF lineCol
void OverlayUIState::RunUILoop()
{
while (IsWindow(_window))
while (IsWindow(_window) && !_commonState.closeOnOtherMonitors)
{
_d2dState.rt->BeginDraw();
_d2dState.rt->Clear(D2D1::ColorF(1.f, 1.f, 1.f, 0.f));
@@ -272,13 +289,13 @@ OverlayUIState::OverlayUIState(StateT& toolState,
{
}
OverlayUIState::~OverlayUIState() noexcept
OverlayUIState::~OverlayUIState()
{
PostMessageW(_window, WM_CLOSE, {}, {});
// Extra cautious to not trigger termination due to noexcept
try
{
_uiThread.join();
if (_uiThread.joinable())
_uiThread.join();
}
catch (...)
{
@@ -289,7 +306,7 @@ OverlayUIState::~OverlayUIState() noexcept
template<typename ToolT, typename TickFuncT>
inline std::unique_ptr<OverlayUIState> OverlayUIState::CreateInternal(ToolT& toolState,
TickFuncT tickFunc,
const CommonState& commonState,
CommonState& commonState,
const wchar_t* toolWindowClassName,
void* windowParam,
const MonitorInfo& monitor)
@@ -306,6 +323,7 @@ inline std::unique_ptr<OverlayUIState> OverlayUIState::CreateInternal(ToolT& too
uiCreatedEvent.SetEvent();
state->RunUILoop();
commonState.closeOnOtherMonitors = true;
commonState.sessionCompletedCallback();
});
@@ -315,20 +333,19 @@ inline std::unique_ptr<OverlayUIState> OverlayUIState::CreateInternal(ToolT& too
}
std::unique_ptr<OverlayUIState> OverlayUIState::Create(Serialized<MeasureToolState>& toolState,
const CommonState& commonState,
CommonState& commonState,
const MonitorInfo& monitor)
{
return OverlayUIState::CreateInternal(toolState,
DrawMeasureToolTick,
commonState,
NonLocalizable::MeasureToolOverlayWindowName,
// ok to cast away const, since member access is serialized
(void*)(&commonState),
&toolState,
monitor);
}
std::unique_ptr<OverlayUIState> OverlayUIState::Create(BoundsToolState& toolState,
const CommonState& commonState,
CommonState& commonState,
const MonitorInfo& monitor)
{
return OverlayUIState::CreateInternal(toolState,

View File

@@ -25,20 +25,20 @@ class OverlayUIState final
template<typename ToolT, typename TickFuncT>
static std::unique_ptr<OverlayUIState> CreateInternal(ToolT& toolState,
TickFuncT tickFunc,
const CommonState& commonState,
CommonState& commonState,
const wchar_t* toolWindowClassName,
void* windowParam,
const MonitorInfo& monitor);
public:
OverlayUIState(OverlayUIState&&) noexcept = default;
~OverlayUIState() noexcept;
~OverlayUIState();
static std::unique_ptr<OverlayUIState> Create(BoundsToolState& toolState,
const CommonState& commonState,
CommonState& commonState,
const MonitorInfo& monitor);
static std::unique_ptr<OverlayUIState> Create(Serialized<MeasureToolState>& toolState,
const CommonState& commonState,
CommonState& commonState,
const MonitorInfo& monitor);
inline HWND overlayWindowHandle() const
{

View File

@@ -44,7 +44,6 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
{
_overlayUIStates.clear();
_boundsToolState = { .commonState = &_commonState };
_measureToolState.Reset();
for (auto& thread : _screenCaptureThreads)
{
if (thread.joinable())
@@ -53,12 +52,17 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
}
}
_screenCaptureThreads.clear();
_measureToolState.Reset();
_measureToolState.Access([&](MeasureToolState& s) {
s.commonState = &_commonState;
});
_settings = Settings::LoadFromFile();
_commonState.lineColor.r = _settings.lineColor[0] / 255.f;
_commonState.lineColor.g = _settings.lineColor[1] / 255.f;
_commonState.lineColor.b = _settings.lineColor[2] / 255.f;
_commonState.closeOnOtherMonitors = false;
}
void Core::StartBoundsTool()
@@ -87,6 +91,7 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
state.continuousCapture = _settings.continuousCapture;
state.drawFeetOnCross = _settings.drawFeetOnCross;
state.pixelTolerance = _settings.pixelTolerance;
state.perColorChannelEdgeDetection = _settings.perColorChannelEdgeDetection;
});
for (const auto& monitorInfo : MonitorInfo::GetMonitors(true))

View File

@@ -314,7 +314,6 @@ void D3DCaptureState::StopCapture()
void UpdateCaptureState(const CommonState& commonState,
Serialized<MeasureToolState>& state,
HWND targetWindow,
const uint8_t pixelTolerance,
const OwnedTextureView& textureView,
const bool continuousCapture)
{
@@ -322,13 +321,25 @@ void UpdateCaptureState(const CommonState& commonState,
ScreenToClient(targetWindow, &cursorPos);
const bool cursorInLeftScreenHalf = cursorPos.x < textureView.view.width / 2;
const bool cursorInTopScreenHalf = cursorPos.y < textureView.view.height / 2;
uint8_t pixelTolerance = {};
bool perColorChannelEdgeDetection = {};
state.Access([&](MeasureToolState& state) {
state.cursorInLeftScreenHalf = cursorInLeftScreenHalf;
state.cursorInTopScreenHalf = cursorInTopScreenHalf;
pixelTolerance = state.pixelTolerance;
perColorChannelEdgeDetection = state.perColorChannelEdgeDetection;
});
// Every one of 4 edges is a coordinate of the last similar pixel in a row
// Example: given a 5x5 green square on a blue background with its top-left pixel
// at 20x100, bounds should be [20,100]-[24,104]. We don't include [25,105] or
// [19,99], since those pixels are blue. Thus, square dims are equal to
// [24-20+1,104-100+1]=[5,5].
const RECT bounds = DetectEdges(textureView.view, cursorPos, pixelTolerance, continuousCapture);
const RECT bounds = DetectEdges(textureView.view,
cursorPos,
perColorChannelEdgeDetection,
pixelTolerance,
continuousCapture);
#if defined(DEBUG_EDGES)
char buffer[256];
@@ -344,8 +355,6 @@ void UpdateCaptureState(const CommonState& commonState,
#endif
state.Access([&](MeasureToolState& state) {
state.measuredEdges = bounds;
state.cursorInLeftScreenHalf = cursorInLeftScreenHalf;
state.cursorInTopScreenHalf = cursorInTopScreenHalf;
});
}
@@ -366,10 +375,8 @@ std::thread StartCapturingThread(const CommonState& commonState,
winrt::guid_of<winrt::GraphicsCaptureItem>(),
winrt::put_abi(item)));
uint8_t pixelTolerance = {};
bool continuousCapture = {};
state.Access([&](MeasureToolState& state) {
pixelTolerance = state.pixelTolerance;
state.Read([&](const MeasureToolState& state) {
continuousCapture = state.continuousCapture;
});
@@ -380,8 +387,8 @@ std::thread StartCapturingThread(const CommonState& commonState,
!continuousCapture);
if (continuousCapture)
{
captureState->StartCapture([&, targetWindow, pixelTolerance](OwnedTextureView textureView) {
UpdateCaptureState(commonState, state, targetWindow, pixelTolerance, textureView, continuousCapture);
captureState->StartCapture([&, targetWindow](OwnedTextureView textureView) {
UpdateCaptureState(commonState, state, targetWindow, textureView, continuousCapture);
});
while (IsWindow(targetWindow))
@@ -398,7 +405,7 @@ std::thread StartCapturingThread(const CommonState& commonState,
const auto now = std::chrono::high_resolution_clock::now();
if (monitorArea.inside(commonState.cursorPos))
{
UpdateCaptureState(commonState, state, targetWindow, pixelTolerance, textureView, continuousCapture);
UpdateCaptureState(commonState, state, targetWindow, textureView, continuousCapture);
}
const auto frameTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - now);
if (frameTime < consts::TARGET_FRAME_DURATION)

View File

@@ -13,6 +13,7 @@ namespace
const wchar_t JSON_KEY_CONTINUOUS_CAPTURE[] = L"ContinuousCapture";
const wchar_t JSON_KEY_DRAW_FEET_ON_CROSS[] = L"DrawFeetOnCross";
const wchar_t JSON_KEY_PIXEL_TOLERANCE[] = L"PixelTolerance";
const wchar_t JSON_KEY_PER_COLOR_CHANNEL_EDGE_DETECTION[] = L"PerColorChannelEdgeDetection";
const wchar_t JSON_KEY_MEASURE_CROSS_COLOR[] = L"MeasureCrossColor";
}
@@ -56,6 +57,14 @@ Settings Settings::LoadFromFile()
catch (...)
{
}
try
{
result.perColorChannelEdgeDetection = props.GetNamedObject(JSON_KEY_PER_COLOR_CHANNEL_EDGE_DETECTION).GetNamedBoolean(JSON_KEY_VALUE);
}
catch (...)
{
}
}
catch (...)
{

View File

@@ -8,6 +8,7 @@ struct Settings
uint8_t pixelTolerance = 5;
bool continuousCapture = false;
bool drawFeetOnCross = true;
bool perColorChannelEdgeDetection = false;
std::array<uint8_t, 3> lineColor = {255, 69, 0};
static Settings LoadFromFile();

View File

@@ -26,6 +26,7 @@ struct CommonState
mutable Serialized<OverlayBoxText> overlayBoxText;
POINT cursorPos = {}; // updated atomically
std::atomic_bool closeOnOtherMonitors = false;
};
struct BoundsToolState
@@ -48,5 +49,7 @@ struct MeasureToolState
RECT measuredEdges = {};
bool cursorInLeftScreenHalf = false;
bool cursorInTopScreenHalf = false;
bool perColorChannelEdgeDetection = false;
Mode mode = Mode::Cross;
CommonState* commonState = nullptr; // required for WndProc
};

View File

@@ -17,6 +17,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
PixelTolerance = new IntProperty(5);
ContinuousCapture = false;
DrawFeetOnCross = true;
PerColorChannelEdgeDetection = false;
MeasureCrossColor = new StringProperty("#FF4500");
}
@@ -28,6 +29,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonConverter(typeof(BoolPropertyJsonConverter))]
public bool DrawFeetOnCross { get; set; }
[JsonConverter(typeof(BoolPropertyJsonConverter))]
public bool PerColorChannelEdgeDetection { get; set; }
public IntProperty PixelTolerance { get; set; }
public StringProperty MeasureCrossColor { get; set; }

View File

@@ -108,6 +108,23 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
}
}
public bool PerColorChannelEdgeDetection
{
get
{
return Settings.Properties.PerColorChannelEdgeDetection;
}
set
{
if (Settings.Properties.PerColorChannelEdgeDetection != value)
{
Settings.Properties.PerColorChannelEdgeDetection = value;
NotifyPropertyChanged();
}
}
}
public int PixelTolerance
{
get

View File

@@ -134,6 +134,9 @@ Inspired by Roolr.</value>
<value>Screen Ruler</value>
<comment>"Screen Ruler" is the name of the utility</comment>
</data>
<data name="MeasureTool_ActivationSettings.Header" xml:space="preserve">
<value>Activation</value>
</data>
<data name="MeasureTool_Settings.Header" xml:space="preserve">
<value>Behavior</value>
<comment>"Screen Ruler" is the name of the utility</comment>
@@ -157,6 +160,12 @@ Inspired by Roolr.</value>
<data name="MeasureTool_ContinuousCapture.Description" xml:space="preserve">
<value>For measuring animated graphics. Measurement is less precise.</value>
</data>
<data name="MeasureTool_PerColorChannelEdgeDetection.Header" xml:space="preserve">
<value>Per color channel edge detection</value>
</data>
<data name="MeasureTool_PerColorChannelEdgeDetection.Description" xml:space="preserve">
<value>If enabled, test that all color channels are within a tolerance distance from each other. Otherwise, check that the sum of all color channels differences is smaller than the tolerance.</value>
</data>
<data name="MeasureTool_DrawFeetOnCross.Header" xml:space="preserve">
<value>Draw feet on cross</value>
</data>

View File

@@ -22,14 +22,29 @@
</controls:Setting.ActionContent>
</controls:Setting>
<controls:SettingsGroup x:Uid="MeasureTool_Settings" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}">
<controls:SettingsGroup x:Uid="MeasureTool_ActivationSettings" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}">
<controls:Setting x:Uid="MeasureTool_ActivationShortcut" Icon="&#xEDA7;">
<controls:Setting.ActionContent>
<controls:ShortcutControl HotkeySettings="{x:Bind Path=ViewModel.ActivationShortcut, Mode=TwoWay}"
MinWidth="{StaticResource SettingActionControlMinWidth}"/>
</controls:Setting.ActionContent>
</controls:Setting>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="MeasureTool_Settings" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}">
<controls:Setting x:Uid="MeasureTool_ContinuousCapture" Icon="&#xE7FB;">
<controls:Setting.ActionContent>
<ToggleSwitch IsOn="{x:Bind ViewModel.ContinuousCapture, Mode=TwoWay}" x:Uid="MeasureTool_ContinuousCapture_ToggleSwitch" />
</controls:Setting.ActionContent>
</controls:Setting>
<controls:Setting x:Uid="MeasureTool_PerColorChannelEdgeDetection" Icon="&#xE7FB;">
<controls:Setting.ActionContent>
<ToggleSwitch
IsOn="{x:Bind ViewModel.PerColorChannelEdgeDetection, Mode=TwoWay}"
x:Uid="MeasureTool_PerColorChannelEdgeDetection_ToggleSwitch" />
</controls:Setting.ActionContent>
</controls:Setting>
<controls:Setting x:Uid="MeasureTool_PixelTolerance">
<controls:Setting.ActionContent>
@@ -39,22 +54,16 @@
Value="{x:Bind Mode=TwoWay, Path=ViewModel.PixelTolerance}"/>
</controls:Setting.ActionContent>
</controls:Setting>
<controls:Setting x:Uid="MeasureTool_MeasureCrossColor">
<controls:Setting.ActionContent>
<controls:ColorPickerButton SelectedColor="{x:Bind Path=ViewModel.CrossColor, Mode=TwoWay}" />
</controls:Setting.ActionContent>
</controls:Setting>
<controls:Setting x:Uid="MeasureTool_DrawFeetOnCross">
<controls:Setting.ActionContent>
<ToggleSwitch IsOn="{x:Bind ViewModel.DrawFeetOnCross, Mode=TwoWay}" x:Uid="MeasureTool_DrawFeetOnCross_ToggleSwitch" />
</controls:Setting.ActionContent>
</controls:Setting>
<controls:Setting x:Uid="MeasureTool_ContinuousCapture" Icon="&#xE7FB;">
<controls:Setting x:Uid="MeasureTool_MeasureCrossColor">
<controls:Setting.ActionContent>
<ToggleSwitch IsOn="{x:Bind ViewModel.ContinuousCapture, Mode=TwoWay}" x:Uid="MeasureTool_ContinuousCapture_ToggleSwitch" />
<controls:ColorPickerButton SelectedColor="{x:Bind Path=ViewModel.CrossColor, Mode=TwoWay}" />
</controls:Setting.ActionContent>
</controls:Setting>