[FindMyMouse]Add additional settings (#14590)
* [FindMyMouse]Add additional settings * Add setting for Spotlight Initial Zoom * PR feedback: lowercase settings names * PR feedback: settings descriptions * PR feedback: change opacity to percentage * PR feedback: increase maximum zoom factor * PR feedback: group spotlight settings * PR feedback: Expanders start collapsed initially * Remove extra settings file save in dllmain * PR feedback: change initial zoom description * PR feedback: Add warning for photo sensitive users * PR feedback: remove warning and add description instead * PR feedback: size->factor in initial zoom description * Feedback PR: remove opacity description * PR feedback: remove photo sensitivity warning
This commit is contained in:
parent
8afac77841
commit
46244e8e84
|
@ -262,6 +262,7 @@ EndProject
|
|||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643-4663-475E-B329-03F0C9918D48}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
src\common\utils\appMutex.h = src\common\utils\appMutex.h
|
||||
src\common\utils\color.h = src\common\utils\color.h
|
||||
src\common\utils\com_object_factory.h = src\common\utils\com_object_factory.h
|
||||
src\common\utils\elevation.h = src\common\utils\elevation.h
|
||||
src\common\utils\EventLocker.h = src\common\utils\EventLocker.h
|
||||
|
|
21
src/common/utils/color.h
Normal file
21
src/common/utils/color.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
// helper function to get the RGB from a #FFFFFF string.
|
||||
inline bool checkValidRGB(std::wstring_view hex, uint8_t* R, uint8_t* G, uint8_t* B)
|
||||
{
|
||||
if (hex.length() != 7)
|
||||
return false;
|
||||
hex = hex.substr(1, 6); // remove #
|
||||
for (auto& c : hex)
|
||||
{
|
||||
if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (swscanf_s(hex.data(), L"%2hhx%2hhx%2hhx", R, G, B) != 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -19,8 +19,6 @@ namespace ABI
|
|||
}
|
||||
#endif
|
||||
|
||||
bool m_doNotActivateOnGameMode = true;
|
||||
|
||||
#pragma region Super_Sonar_Base_Code
|
||||
|
||||
template<typename D>
|
||||
|
@ -58,11 +56,13 @@ protected:
|
|||
// At actual check, time a fifth of the current double click setting might be used instead to take into account users who might have low values.
|
||||
static const int MIN_DOUBLE_CLICK_TIME = 100;
|
||||
|
||||
static constexpr int SonarRadius = 100;
|
||||
static constexpr int SonarZoomFactor = 9;
|
||||
static constexpr DWORD FadeDuration = 500;
|
||||
static constexpr int FinalAlphaNumerator = 1;
|
||||
static constexpr int FinalAlphaDenominator = 2;
|
||||
bool m_destroyed = false;
|
||||
bool m_doNotActivateOnGameMode = true;
|
||||
int m_sonarRadius = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS;
|
||||
int m_sonarZoomFactor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM;
|
||||
DWORD m_fadeDuration = FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS;
|
||||
int m_finalAlphaNumerator = FIND_MY_MOUSE_DEFAULT_OVERLAY_OPACITY;
|
||||
static constexpr int FinalAlphaDenominator = 100;
|
||||
winrt::DispatcherQueueController m_dispatcherQueueController{ nullptr };
|
||||
|
||||
private:
|
||||
|
@ -150,6 +150,7 @@ void SuperSonar<D>::Terminate()
|
|||
{
|
||||
auto dispatcherQueue = m_dispatcherQueueController.DispatcherQueue();
|
||||
bool enqueueSucceeded = dispatcherQueue.TryEnqueue([=]() {
|
||||
m_destroyed = true;
|
||||
DestroyWindow(m_hwndOwner);
|
||||
});
|
||||
if (!enqueueSucceeded)
|
||||
|
@ -457,7 +458,7 @@ void SuperSonar<D>::UpdateMouseSnooping()
|
|||
struct CompositionSpotlight : SuperSonar<CompositionSpotlight>
|
||||
{
|
||||
static constexpr UINT WM_OPACITY_ANIMATION_COMPLETED = WM_APP;
|
||||
static constexpr float SonarRadiusFloat = static_cast<float>(SonarRadius);
|
||||
float m_sonarRadiusFloat = static_cast<float>(m_sonarRadius);
|
||||
|
||||
DWORD GetExtendedStyle()
|
||||
{
|
||||
|
@ -489,7 +490,7 @@ struct CompositionSpotlight : SuperSonar<CompositionSpotlight>
|
|||
m_batch.Completed([hwnd = m_hwnd](auto&&, auto&&) {
|
||||
PostMessage(hwnd, WM_OPACITY_ANIMATION_COMPLETED, 0, 0);
|
||||
});
|
||||
m_root.Opacity(visible ? static_cast<float>(FinalAlphaNumerator) / FinalAlphaDenominator : 0.0f);
|
||||
m_root.Opacity(visible ? static_cast<float>(m_finalAlphaNumerator) / FinalAlphaDenominator : 0.0f);
|
||||
if (visible)
|
||||
{
|
||||
ShowWindow(m_hwnd, SW_SHOWNOACTIVATE);
|
||||
|
@ -531,38 +532,38 @@ private:
|
|||
layer.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent
|
||||
m_root.Children().InsertAtTop(layer);
|
||||
|
||||
auto backdrop = m_compositor.CreateSpriteVisual();
|
||||
backdrop.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent
|
||||
backdrop.Brush(m_compositor.CreateColorBrush({ 255, 0, 0, 0 }));
|
||||
layer.Children().InsertAtTop(backdrop);
|
||||
m_backdrop = m_compositor.CreateSpriteVisual();
|
||||
m_backdrop.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent
|
||||
m_backdrop.Brush(m_compositor.CreateColorBrush(m_backgroundColor));
|
||||
layer.Children().InsertAtTop(m_backdrop);
|
||||
|
||||
m_circleGeometry = m_compositor.CreateEllipseGeometry(); // radius set via expression animation
|
||||
auto circleShape = m_compositor.CreateSpriteShape(m_circleGeometry);
|
||||
circleShape.FillBrush(m_compositor.CreateColorBrush({ 255, 255, 255, 255 }));
|
||||
circleShape.Offset({ SonarRadiusFloat * SonarZoomFactor, SonarRadiusFloat * SonarZoomFactor });
|
||||
m_circleShape = m_compositor.CreateSpriteShape(m_circleGeometry);
|
||||
m_circleShape.FillBrush(m_compositor.CreateColorBrush(m_spotlightColor));
|
||||
m_circleShape.Offset({ m_sonarRadiusFloat * m_sonarZoomFactor, m_sonarRadiusFloat * m_sonarZoomFactor });
|
||||
m_spotlight = m_compositor.CreateShapeVisual();
|
||||
m_spotlight.Size({ SonarRadiusFloat * 2 * SonarZoomFactor, SonarRadiusFloat * 2 * SonarZoomFactor });
|
||||
m_spotlight.Size({ m_sonarRadiusFloat * 2 * m_sonarZoomFactor, m_sonarRadiusFloat * 2 * m_sonarZoomFactor });
|
||||
m_spotlight.AnchorPoint({ 0.5f, 0.5f });
|
||||
m_spotlight.Shapes().Append(circleShape);
|
||||
m_spotlight.Shapes().Append(m_circleShape);
|
||||
|
||||
layer.Children().InsertAtTop(m_spotlight);
|
||||
|
||||
// Implicitly animate the alpha.
|
||||
auto animation = m_compositor.CreateScalarKeyFrameAnimation();
|
||||
animation.Target(L"Opacity");
|
||||
animation.InsertExpressionKeyFrame(1.0f, L"this.FinalValue");
|
||||
animation.Duration(std::chrono::milliseconds{ FadeDuration });
|
||||
m_animation = m_compositor.CreateScalarKeyFrameAnimation();
|
||||
m_animation.Target(L"Opacity");
|
||||
m_animation.InsertExpressionKeyFrame(1.0f, L"this.FinalValue");
|
||||
m_animation.Duration(std::chrono::milliseconds{ m_fadeDuration });
|
||||
auto collection = m_compositor.CreateImplicitAnimationCollection();
|
||||
collection.Insert(L"Opacity", animation);
|
||||
collection.Insert(L"Opacity", m_animation);
|
||||
m_root.ImplicitAnimations(collection);
|
||||
|
||||
// Radius of spotlight shrinks as opacity increases.
|
||||
// At opacity zero, it is SonarRadius * SonarZoomFactor.
|
||||
// At maximum opacity, it is SonarRadius.
|
||||
// At opacity zero, it is m_sonarRadius * SonarZoomFactor.
|
||||
// At maximum opacity, it is m_sonarRadius.
|
||||
auto radiusExpression = m_compositor.CreateExpressionAnimation();
|
||||
radiusExpression.SetReferenceParameter(L"Root", m_root);
|
||||
wchar_t expressionText[256];
|
||||
winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity * %d / %d)", SonarRadius * SonarZoomFactor, SonarRadius * SonarZoomFactor, SonarRadius, SonarRadius, FinalAlphaDenominator, FinalAlphaNumerator));
|
||||
winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity * %d / %d)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius, FinalAlphaDenominator, m_finalAlphaNumerator));
|
||||
radiusExpression.Expression(expressionText);
|
||||
m_circleGeometry.StartAnimation(L"Radius", radiusExpression);
|
||||
|
||||
|
@ -581,6 +582,62 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void ApplySettings(const FindMyMouseSettings& settings, bool applyToRuntimeObjects) {
|
||||
if (!applyToRuntimeObjects)
|
||||
{
|
||||
// Runtime objects not created yet. Just update fields.
|
||||
m_sonarRadius = settings.spotlightRadius;
|
||||
m_sonarRadiusFloat = static_cast<float>(m_sonarRadius);
|
||||
m_backgroundColor = settings.backgroundColor;
|
||||
m_spotlightColor = settings.spotlightColor;
|
||||
m_doNotActivateOnGameMode = settings.doNotActivateOnGameMode;
|
||||
m_fadeDuration = settings.animationDurationMs > 0 ? settings.animationDurationMs : 1;
|
||||
m_finalAlphaNumerator = settings.overlayOpacity;
|
||||
m_sonarZoomFactor = settings.spotlightInitialZoom;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Runtime objects already created. Should update in the owner thread.
|
||||
auto dispatcherQueue = m_dispatcherQueueController.DispatcherQueue();
|
||||
FindMyMouseSettings localSettings = settings;
|
||||
bool enqueueSucceeded = dispatcherQueue.TryEnqueue([=]() {
|
||||
if (!m_destroyed)
|
||||
{
|
||||
// Runtime objects not created yet. Just update fields.
|
||||
m_sonarRadius = localSettings.spotlightRadius;
|
||||
m_sonarRadiusFloat = static_cast<float>(m_sonarRadius);
|
||||
m_backgroundColor = localSettings.backgroundColor;
|
||||
m_spotlightColor = localSettings.spotlightColor;
|
||||
m_doNotActivateOnGameMode = localSettings.doNotActivateOnGameMode;
|
||||
m_fadeDuration = localSettings.animationDurationMs > 0 ? localSettings.animationDurationMs : 1;
|
||||
m_finalAlphaNumerator = localSettings.overlayOpacity;
|
||||
m_sonarZoomFactor = localSettings.spotlightInitialZoom;
|
||||
|
||||
// Apply new settings to runtime composition objects.
|
||||
m_backdrop.Brush().as<winrt::CompositionColorBrush>().Color(m_backgroundColor);
|
||||
m_circleShape.FillBrush().as<winrt::CompositionColorBrush>().Color(m_spotlightColor);
|
||||
m_circleShape.Offset({ m_sonarRadiusFloat * m_sonarZoomFactor, m_sonarRadiusFloat * m_sonarZoomFactor });
|
||||
m_spotlight.Size({ m_sonarRadiusFloat * 2 * m_sonarZoomFactor, m_sonarRadiusFloat * 2 * m_sonarZoomFactor });
|
||||
m_animation.Duration(std::chrono::milliseconds{ m_fadeDuration });
|
||||
m_circleGeometry.StopAnimation(L"Radius");
|
||||
|
||||
// Update animation
|
||||
auto radiusExpression = m_compositor.CreateExpressionAnimation();
|
||||
radiusExpression.SetReferenceParameter(L"Root", m_root);
|
||||
wchar_t expressionText[256];
|
||||
winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity * %d / %d)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius, FinalAlphaDenominator, m_finalAlphaNumerator));
|
||||
radiusExpression.Expression(expressionText);
|
||||
m_circleGeometry.StartAnimation(L"Radius", radiusExpression);
|
||||
}
|
||||
});
|
||||
if (!enqueueSucceeded)
|
||||
{
|
||||
Logger::error("Couldn't enqueue message to update the sonar settings.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
winrt::Compositor m_compositor{ nullptr };
|
||||
winrt::Desktop::DesktopWindowTarget m_target{ nullptr };
|
||||
|
@ -588,6 +645,11 @@ private:
|
|||
winrt::CompositionEllipseGeometry m_circleGeometry{ nullptr };
|
||||
winrt::ShapeVisual m_spotlight{ nullptr };
|
||||
winrt::CompositionCommitBatch m_batch{ nullptr };
|
||||
winrt::SpriteVisual m_backdrop{ nullptr };
|
||||
winrt::CompositionSpriteShape m_circleShape{ nullptr };
|
||||
winrt::Windows::UI::Color m_backgroundColor = FIND_MY_MOUSE_DEFAULT_BACKGROUND_COLOR;
|
||||
winrt::Windows::UI::Color m_spotlightColor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_COLOR;
|
||||
winrt::ScalarKeyFrameAnimation m_animation{ nullptr };
|
||||
};
|
||||
|
||||
template<typename D>
|
||||
|
@ -631,7 +693,7 @@ struct GdiSonar : SuperSonar<D>
|
|||
void OnFadeTimer()
|
||||
{
|
||||
auto now = GetTickCount();
|
||||
auto step = (int)((now - m_fadeStart) * MaxAlpha / this->FadeDuration);
|
||||
auto step = (int)((now - m_fadeStart) * MaxAlpha / this->m_fadeDuration);
|
||||
|
||||
this->Shim()->InvalidateSonar();
|
||||
if (m_alpha < m_alphaTarget)
|
||||
|
@ -666,13 +728,13 @@ protected:
|
|||
int CurrentSonarRadius()
|
||||
{
|
||||
int range = MaxAlpha - m_alpha;
|
||||
int radius = this->SonarRadius + this->SonarRadius * range * (this->SonarZoomFactor - 1) / MaxAlpha;
|
||||
int radius = this->m_sonarRadius + this->m_sonarRadius * range * (this->m_sonarZoomFactor - 1) / MaxAlpha;
|
||||
return radius;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr DWORD FadeFramePeriod = 10;
|
||||
static constexpr int MaxAlpha = SuperSonar<D>::FinalAlphaNumerator * 255 / SuperSonar<D>::FinalAlphaDenominator;
|
||||
int MaxAlpha = SuperSonar<D>::m_finalAlphaNumerator * 255 / SuperSonar<D>::FinalAlphaDenominator;
|
||||
static constexpr DWORD TIMER_ID_FADE = 101;
|
||||
|
||||
private:
|
||||
|
@ -792,6 +854,14 @@ struct GdiCrosshairs : GdiSonar<GdiCrosshairs>
|
|||
#pragma region Super_Sonar_API
|
||||
|
||||
CompositionSpotlight* m_sonar = nullptr;
|
||||
void FindMyMouseApplySettings(const FindMyMouseSettings& settings)
|
||||
{
|
||||
if (m_sonar != nullptr)
|
||||
{
|
||||
Logger::info("Applying settings.");
|
||||
m_sonar->ApplySettings(settings, true);
|
||||
}
|
||||
}
|
||||
|
||||
void FindMyMouseDisable()
|
||||
{
|
||||
|
@ -807,13 +877,8 @@ bool FindMyMouseIsEnabled()
|
|||
return (m_sonar != nullptr);
|
||||
}
|
||||
|
||||
void FindMyMouseSetDoNotActivateOnGameMode(bool doNotActivate)
|
||||
{
|
||||
m_doNotActivateOnGameMode = doNotActivate;
|
||||
}
|
||||
|
||||
// Based on SuperSonar's original wWinMain.
|
||||
int FindMyMouseMain(HINSTANCE hinst)
|
||||
int FindMyMouseMain(HINSTANCE hinst, const FindMyMouseSettings& settings)
|
||||
{
|
||||
Logger::info("Starting a sonar instance.");
|
||||
if (m_sonar != nullptr)
|
||||
|
@ -823,6 +888,7 @@ int FindMyMouseMain(HINSTANCE hinst)
|
|||
}
|
||||
|
||||
CompositionSpotlight sonar;
|
||||
sonar.ApplySettings(settings, false);
|
||||
if (!sonar.Initialize(hinst))
|
||||
{
|
||||
Logger::error("Couldn't initialize a sonar instance.");
|
||||
|
|
|
@ -1,6 +1,26 @@
|
|||
#pragma once
|
||||
#include "pch.h"
|
||||
int FindMyMouseMain(HINSTANCE hinst);
|
||||
|
||||
constexpr bool FIND_MY_MOUSE_DEFAULT_DO_NOT_ACTIVATE_ON_GAME_MODE = true;
|
||||
const winrt::Windows::UI::Color FIND_MY_MOUSE_DEFAULT_BACKGROUND_COLOR = winrt::Windows::UI::ColorHelper::FromArgb(255, 0, 0, 0);
|
||||
const winrt::Windows::UI::Color FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_COLOR = winrt::Windows::UI::ColorHelper::FromArgb(255, 255, 255, 255);
|
||||
constexpr int FIND_MY_MOUSE_DEFAULT_OVERLAY_OPACITY = 50;
|
||||
constexpr int FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS = 100;
|
||||
constexpr int FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS = 500;
|
||||
constexpr int FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM = 9;
|
||||
|
||||
struct FindMyMouseSettings
|
||||
{
|
||||
bool doNotActivateOnGameMode = FIND_MY_MOUSE_DEFAULT_DO_NOT_ACTIVATE_ON_GAME_MODE;
|
||||
winrt::Windows::UI::Color backgroundColor = FIND_MY_MOUSE_DEFAULT_BACKGROUND_COLOR;
|
||||
winrt::Windows::UI::Color spotlightColor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_COLOR;
|
||||
int overlayOpacity = FIND_MY_MOUSE_DEFAULT_OVERLAY_OPACITY;
|
||||
int spotlightRadius = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS;
|
||||
int animationDurationMs = FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS;
|
||||
int spotlightInitialZoom = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM;
|
||||
};
|
||||
|
||||
int FindMyMouseMain(HINSTANCE hinst, const FindMyMouseSettings& settings);
|
||||
void FindMyMouseDisable();
|
||||
bool FindMyMouseIsEnabled();
|
||||
void FindMyMouseSetDoNotActivateOnGameMode(bool doNotActivate);
|
||||
void FindMyMouseApplySettings(const FindMyMouseSettings& settings);
|
||||
|
|
|
@ -5,13 +5,19 @@
|
|||
#include "FindMyMouse.h"
|
||||
#include <thread>
|
||||
#include <common/utils/logger_helper.h>
|
||||
|
||||
#include <common/utils/color.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
|
||||
const wchar_t JSON_KEY_VALUE[] = L"value";
|
||||
const wchar_t JSON_KEY_DO_NOT_ACTIVATE_ON_GAME_MODE[] = L"do_not_activate_on_game_mode";
|
||||
const wchar_t JSON_KEY_BACKGROUND_COLOR[] = L"background_color";
|
||||
const wchar_t JSON_KEY_SPOTLIGHT_COLOR[] = L"spotlight_color";
|
||||
const wchar_t JSON_KEY_OVERLAY_OPACITY[] = L"overlay_opacity";
|
||||
const wchar_t JSON_KEY_SPOTLIGHT_RADIUS[] = L"spotlight_radius";
|
||||
const wchar_t JSON_KEY_ANIMATION_DURATION_MS[] = L"animation_duration_ms";
|
||||
const wchar_t JSON_KEY_SPOTLIGHT_INITIAL_ZOOM[] = L"spotlight_initial_zoom";
|
||||
}
|
||||
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
|
@ -48,6 +54,9 @@ private:
|
|||
// The PowerToy state.
|
||||
bool m_enabled = false;
|
||||
|
||||
// Find My Mouse specific settings
|
||||
FindMyMouseSettings m_findMyMouseSettings;
|
||||
|
||||
// Load initial settings from the persisted values.
|
||||
void init_settings();
|
||||
|
||||
|
@ -109,7 +118,7 @@ public:
|
|||
|
||||
parse_settings(values);
|
||||
|
||||
values.save_to_settings_file();
|
||||
FindMyMouseApplySettings(m_findMyMouseSettings);
|
||||
}
|
||||
catch (std::exception&)
|
||||
{
|
||||
|
@ -122,7 +131,7 @@ public:
|
|||
{
|
||||
m_enabled = true;
|
||||
Trace::EnableFindMyMouse(true);
|
||||
std::thread([]() { FindMyMouseMain(m_hModule); }).detach();
|
||||
std::thread([=]() { FindMyMouseMain(m_hModule, m_findMyMouseSettings); }).detach();
|
||||
}
|
||||
|
||||
// Disable the powertoy
|
||||
|
@ -158,25 +167,103 @@ void FindMyMouse::init_settings()
|
|||
|
||||
void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings)
|
||||
{
|
||||
FindMyMouseSetDoNotActivateOnGameMode(true);
|
||||
|
||||
auto settingsObject = settings.get_raw_json();
|
||||
FindMyMouseSettings findMyMouseSettings;
|
||||
if (settingsObject.GetView().Size())
|
||||
{
|
||||
try
|
||||
{
|
||||
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_DO_NOT_ACTIVATE_ON_GAME_MODE);
|
||||
FindMyMouseSetDoNotActivateOnGameMode((bool)jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE));
|
||||
findMyMouseSettings.doNotActivateOnGameMode = (bool)jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::warn("Failed to get 'do not activate on game mode' setting");
|
||||
}
|
||||
try
|
||||
{
|
||||
// Parse background color
|
||||
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_BACKGROUND_COLOR);
|
||||
auto backgroundColor = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE);
|
||||
uint8_t r, g, b;
|
||||
if (!checkValidRGB(backgroundColor, &r, &g, &b))
|
||||
{
|
||||
Logger::error("Background color RGB value is invalid. Will use default value");
|
||||
}
|
||||
else
|
||||
{
|
||||
findMyMouseSettings.backgroundColor = winrt::Windows::UI::ColorHelper::FromArgb(255, r, g, b);
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::warn("Failed to initialize background color from settings. Will use default value");
|
||||
}
|
||||
try
|
||||
{
|
||||
// Parse spotlight color
|
||||
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SPOTLIGHT_COLOR);
|
||||
auto spotlightColor = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE);
|
||||
uint8_t r, g, b;
|
||||
if (!checkValidRGB(spotlightColor, &r, &g, &b))
|
||||
{
|
||||
Logger::error("Spotlight color RGB value is invalid. Will use default value");
|
||||
}
|
||||
else
|
||||
{
|
||||
findMyMouseSettings.spotlightColor = winrt::Windows::UI::ColorHelper::FromArgb(255, r, g, b);
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::warn("Failed to initialize spotlight color from settings. Will use default value");
|
||||
}
|
||||
try
|
||||
{
|
||||
// Parse Overlay Opacity
|
||||
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_OVERLAY_OPACITY);
|
||||
findMyMouseSettings.overlayOpacity = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::warn("Failed to initialize Overlay Opacity from settings. Will use default value");
|
||||
}
|
||||
try
|
||||
{
|
||||
// Parse Spotlight Radius
|
||||
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SPOTLIGHT_RADIUS);
|
||||
findMyMouseSettings.spotlightRadius = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::warn("Failed to initialize Spotlight Radius from settings. Will use default value");
|
||||
}
|
||||
try
|
||||
{
|
||||
// Parse Animation Duration
|
||||
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ANIMATION_DURATION_MS);
|
||||
findMyMouseSettings.animationDurationMs = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::warn("Failed to initialize Animation Duration from settings. Will use default value");
|
||||
}
|
||||
try
|
||||
{
|
||||
// Parse Spotlight Initial Zoom
|
||||
auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SPOTLIGHT_INITIAL_ZOOM);
|
||||
findMyMouseSettings.spotlightInitialZoom = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::warn("Failed to initialize Spotlight Initial Zoom from settings. Will use default value");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::info("Find My Mouse settings are empty");
|
||||
}
|
||||
m_findMyMouseSettings = findMyMouseSettings;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <common/SettingsAPI/settings_objects.h>
|
||||
#include "trace.h"
|
||||
#include "MouseHighlighter.h"
|
||||
#include "common/utils/color.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -57,26 +58,6 @@ private:
|
|||
// Mouse Highlighter specific settings
|
||||
MouseHighlighterSettings m_highlightSettings;
|
||||
|
||||
// helper function to get the RGB from a #FFFFFF string.
|
||||
bool checkValidRGB(std::wstring_view hex, uint8_t* R, uint8_t* G, uint8_t* B)
|
||||
{
|
||||
if (hex.length() != 7)
|
||||
return false;
|
||||
hex = hex.substr(1, 6); // remove #
|
||||
for (auto& c : hex)
|
||||
{
|
||||
if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (swscanf_s(hex.data(), L"%2hhx%2hhx%2hhx", R, G, B) != 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
MouseHighlighter()
|
||||
|
|
|
@ -11,9 +11,33 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||
[JsonPropertyName("do_not_activate_on_game_mode")]
|
||||
public BoolProperty DoNotActivateOnGameMode { get; set; }
|
||||
|
||||
[JsonPropertyName("background_color")]
|
||||
public StringProperty BackgroundColor { get; set; }
|
||||
|
||||
[JsonPropertyName("spotlight_color")]
|
||||
public StringProperty SpotlightColor { get; set; }
|
||||
|
||||
[JsonPropertyName("overlay_opacity")]
|
||||
public IntProperty OverlayOpacity { get; set; }
|
||||
|
||||
[JsonPropertyName("spotlight_radius")]
|
||||
public IntProperty SpotlightRadius { get; set; }
|
||||
|
||||
[JsonPropertyName("animation_duration_ms")]
|
||||
public IntProperty AnimationDurationMs { get; set; }
|
||||
|
||||
[JsonPropertyName("spotlight_initial_zoom")]
|
||||
public IntProperty SpotlightInitialZoom { get; set; }
|
||||
|
||||
public FindMyMouseProperties()
|
||||
{
|
||||
DoNotActivateOnGameMode = new BoolProperty(true);
|
||||
BackgroundColor = new StringProperty("#000000");
|
||||
SpotlightColor = new StringProperty("#FFFFFF");
|
||||
OverlayOpacity = new IntProperty(50);
|
||||
SpotlightRadius = new IntProperty(100);
|
||||
AnimationDurationMs = new IntProperty(500);
|
||||
SpotlightInitialZoom = new IntProperty(9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,17 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
|
|||
FindMyMouseSettingsConfig = findMyMouseSettingsRepository.SettingsConfig;
|
||||
_findMyMouseDoNotActivateOnGameMode = FindMyMouseSettingsConfig.Properties.DoNotActivateOnGameMode.Value;
|
||||
|
||||
string backgroundColor = FindMyMouseSettingsConfig.Properties.BackgroundColor.Value;
|
||||
_findMyMouseBackgroundColor = !string.IsNullOrEmpty(backgroundColor) ? backgroundColor : "#000000";
|
||||
|
||||
string spotlightColor = FindMyMouseSettingsConfig.Properties.SpotlightColor.Value;
|
||||
_findMyMouseSpotlightColor = !string.IsNullOrEmpty(spotlightColor) ? spotlightColor : "#FFFFFF";
|
||||
|
||||
_findMyMouseOverlayOpacity = FindMyMouseSettingsConfig.Properties.OverlayOpacity.Value;
|
||||
_findMyMouseSpotlightRadius = FindMyMouseSettingsConfig.Properties.SpotlightRadius.Value;
|
||||
_findMyMouseAnimationDurationMs = FindMyMouseSettingsConfig.Properties.AnimationDurationMs.Value;
|
||||
_findMyMouseSpotlightInitialZoom = FindMyMouseSettingsConfig.Properties.SpotlightInitialZoom.Value;
|
||||
|
||||
if (mouseHighlighterSettingsRepository == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(mouseHighlighterSettingsRepository));
|
||||
|
@ -104,6 +115,116 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
public string FindMyMouseBackgroundColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return _findMyMouseBackgroundColor;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#000000";
|
||||
if (!value.Equals(_findMyMouseBackgroundColor, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_findMyMouseBackgroundColor = value;
|
||||
FindMyMouseSettingsConfig.Properties.BackgroundColor.Value = value;
|
||||
NotifyFindMyMousePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string FindMyMouseSpotlightColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return _findMyMouseSpotlightColor;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#FFFFFF";
|
||||
if (!value.Equals(_findMyMouseSpotlightColor, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_findMyMouseSpotlightColor = value;
|
||||
FindMyMouseSettingsConfig.Properties.SpotlightColor.Value = value;
|
||||
NotifyFindMyMousePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int FindMyMouseOverlayOpacity
|
||||
{
|
||||
get
|
||||
{
|
||||
return _findMyMouseOverlayOpacity;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != _findMyMouseOverlayOpacity)
|
||||
{
|
||||
_findMyMouseOverlayOpacity = value;
|
||||
FindMyMouseSettingsConfig.Properties.OverlayOpacity.Value = value;
|
||||
NotifyFindMyMousePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int FindMyMouseSpotlightRadius
|
||||
{
|
||||
get
|
||||
{
|
||||
return _findMyMouseSpotlightRadius;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != _findMyMouseSpotlightRadius)
|
||||
{
|
||||
_findMyMouseSpotlightRadius = value;
|
||||
FindMyMouseSettingsConfig.Properties.SpotlightRadius.Value = value;
|
||||
NotifyFindMyMousePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int FindMyMouseAnimationDurationMs
|
||||
{
|
||||
get
|
||||
{
|
||||
return _findMyMouseAnimationDurationMs;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != _findMyMouseAnimationDurationMs)
|
||||
{
|
||||
_findMyMouseAnimationDurationMs = value;
|
||||
FindMyMouseSettingsConfig.Properties.AnimationDurationMs.Value = value;
|
||||
NotifyFindMyMousePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int FindMyMouseSpotlightInitialZoom
|
||||
{
|
||||
get
|
||||
{
|
||||
return _findMyMouseSpotlightInitialZoom;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != _findMyMouseSpotlightInitialZoom)
|
||||
{
|
||||
_findMyMouseSpotlightInitialZoom = value;
|
||||
FindMyMouseSettingsConfig.Properties.SpotlightInitialZoom.Value = value;
|
||||
NotifyFindMyMousePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void NotifyFindMyMousePropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
OnPropertyChanged(propertyName);
|
||||
|
@ -281,6 +402,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
|
|||
|
||||
private bool _isFindMyMouseEnabled;
|
||||
private bool _findMyMouseDoNotActivateOnGameMode;
|
||||
private string _findMyMouseBackgroundColor;
|
||||
private string _findMyMouseSpotlightColor;
|
||||
private int _findMyMouseOverlayOpacity;
|
||||
private int _findMyMouseSpotlightRadius;
|
||||
private int _findMyMouseAnimationDurationMs;
|
||||
private int _findMyMouseSpotlightInitialZoom;
|
||||
|
||||
private bool _isMouseHighlighterEnabled;
|
||||
private string _highlighterLeftButtonClickColor;
|
||||
|
|
|
@ -1733,6 +1733,30 @@ From there, simply click on a Markdown file, PDF file or SVG icon in the File Ex
|
|||
<value>Do not activate when Game Mode is on</value>
|
||||
<comment>"Game mode" is the Windows feature to prevent notification when playing a game.</comment>
|
||||
</data>
|
||||
<data name="MouseUtils_FindMyMouse_BackgroundColor.Header" xml:space="preserve">
|
||||
<value>Background color</value>
|
||||
</data>
|
||||
<data name="MouseUtils_FindMyMouse_SpotlightColor.Header" xml:space="preserve">
|
||||
<value>Spotlight color</value>
|
||||
</data>
|
||||
<data name="MouseUtils_FindMyMouse_OverlayOpacity.Header" xml:space="preserve">
|
||||
<value>Overlay opacity</value>
|
||||
</data>
|
||||
<data name="MouseUtils_FindMyMouse_SpotlightRadius.Header" xml:space="preserve">
|
||||
<value>Spotlight radius</value>
|
||||
</data>
|
||||
<data name="MouseUtils_FindMyMouse_SpotlightInitialZoom.Header" xml:space="preserve">
|
||||
<value>Spotlight initial zoom</value>
|
||||
</data>
|
||||
<data name="MouseUtils_FindMyMouse_SpotlightInitialZoom.Description" xml:space="preserve">
|
||||
<value>Spotlight zoom factor at animation start</value>
|
||||
</data>
|
||||
<data name="MouseUtils_FindMyMouse_AnimationDurationMs.Header" xml:space="preserve">
|
||||
<value>Animation duration</value>
|
||||
</data>
|
||||
<data name="MouseUtils_FindMyMouse_AnimationDurationMs.Description" xml:space="preserve">
|
||||
<value>How long it takes for the spotlight to appear/disappear (in ms)</value>
|
||||
</data>
|
||||
<data name="MouseUtils_MouseHighlighter.Header" xml:space="preserve">
|
||||
<value>Mouse Highlighter</value>
|
||||
<comment>Refers to the utility name</comment>
|
||||
|
@ -1745,7 +1769,6 @@ From there, simply click on a Markdown file, PDF file or SVG icon in the File Ex
|
|||
<value>Mouse Highlighter mode will highlight mouse clicks.</value>
|
||||
<comment>"Mouse Highlighter" is the name of the utility. Mouse is the hardware mouse.</comment>
|
||||
</data>
|
||||
|
||||
<data name="MouseUtils_MouseHighlighter_ActivationShortcut.Header" xml:space="preserve">
|
||||
<value>Activation shortcut</value>
|
||||
</data>
|
||||
|
@ -1753,7 +1776,6 @@ From there, simply click on a Markdown file, PDF file or SVG icon in the File Ex
|
|||
<value>Customize the shortcut to turn on or off this mode</value>
|
||||
<comment>"Mouse Highlighter" is the name of the utility. Mouse is the hardware mouse.</comment>
|
||||
</data>
|
||||
|
||||
<data name="MouseUtils_MouseHighlighter_LeftButtonClickColor.Header" xml:space="preserve">
|
||||
<value>Left button highlight color</value>
|
||||
</data>
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
<StackPanel Orientation="Vertical">
|
||||
<controls:SettingsGroup x:Uid="MouseUtils_FindMyMouse">
|
||||
<TextBlock x:Uid="MouseUtils_FindMyMouse_Description" Margin="0,0,0,8" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<controls:SettingExpander IsExpanded="True">
|
||||
<controls:SettingExpander.Header>
|
||||
<controls:Setting x:Uid="MouseUtils_Enable_FindMyMouse">
|
||||
<controls:Setting.Icon>
|
||||
<BitmapIcon UriSource="ms-appx:///Assets/FluentIcons/FluentIconsFindMyMouse.png" ShowAsMonochrome="False" />
|
||||
|
@ -25,15 +23,71 @@
|
|||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsFindMyMouseEnabled, Mode=TwoWay}" HorizontalAlignment="Right"/>
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
<controls:SettingExpander IsEnabled="{x:Bind ViewModel.IsFindMyMouseEnabled, Mode=OneWay}" IsExpanded="False" >
|
||||
<controls:SettingExpander.Header>
|
||||
<controls:Setting x:Uid="ShortcutGuide_Appearance_Behavior" Icon="" />
|
||||
</controls:SettingExpander.Header>
|
||||
<controls:SettingExpander.Content>
|
||||
<StackPanel>
|
||||
<CheckBox x:Uid="MouseUtils_Prevent_Activation_On_Game_Mode"
|
||||
IsChecked="{x:Bind ViewModel.FindMyMouseDoNotActivateOnGameMode, Mode=TwoWay}"
|
||||
Margin="{StaticResource ExpanderSettingMargin}"
|
||||
IsEnabled="{x:Bind ViewModel.IsFindMyMouseEnabled, Mode=OneWay}" />
|
||||
<controls:Setting x:Uid="MouseUtils_FindMyMouse_OverlayOpacity" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsFindMyMouseEnabled}" Style="{StaticResource ExpanderContentSettingStyle}">
|
||||
<controls:Setting.ActionContent>
|
||||
<Slider Minimum="1"
|
||||
Maximum="100"
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
Value="{x:Bind Mode=TwoWay, Path=ViewModel.FindMyMouseOverlayOpacity}"
|
||||
HorizontalAlignment="Right"/>
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
<controls:Setting x:Uid="MouseUtils_FindMyMouse_BackgroundColor" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsFindMyMouseEnabled}" Style="{StaticResource ExpanderContentSettingStyle}">
|
||||
<controls:Setting.ActionContent>
|
||||
<controls:ColorPickerButton SelectedColor="{x:Bind Path=ViewModel.FindMyMouseBackgroundColor, Mode=TwoWay}" />
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
<controls:Setting x:Uid="MouseUtils_FindMyMouse_SpotlightColor" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsFindMyMouseEnabled}" Style="{StaticResource ExpanderContentSettingStyle}">
|
||||
<controls:Setting.ActionContent>
|
||||
<controls:ColorPickerButton SelectedColor="{x:Bind Path=ViewModel.FindMyMouseSpotlightColor, Mode=TwoWay}" />
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
<controls:Setting x:Uid="MouseUtils_FindMyMouse_SpotlightRadius" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsFindMyMouseEnabled}" Style="{StaticResource ExpanderContentSettingStyle}">
|
||||
<controls:Setting.ActionContent>
|
||||
<muxc:NumberBox Minimum="5"
|
||||
Value="{x:Bind Mode=TwoWay, Path=ViewModel.FindMyMouseSpotlightRadius}"
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
SpinButtonPlacementMode="Compact"
|
||||
HorizontalAlignment="Left"
|
||||
SmallChange="1"
|
||||
LargeChange="10"/>
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
<controls:Setting x:Uid="MouseUtils_FindMyMouse_SpotlightInitialZoom" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsFindMyMouseEnabled}" Style="{StaticResource ExpanderContentSettingStyle}">
|
||||
<controls:Setting.ActionContent>
|
||||
<Slider Minimum="1"
|
||||
Maximum="40"
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
Value="{x:Bind Mode=TwoWay, Path=ViewModel.FindMyMouseSpotlightInitialZoom}"
|
||||
HorizontalAlignment="Right"/>
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
<controls:Setting x:Uid="MouseUtils_FindMyMouse_AnimationDurationMs" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsFindMyMouseEnabled}" Style="{StaticResource ExpanderContentSettingStyle}">
|
||||
<controls:Setting.ActionContent>
|
||||
<muxc:NumberBox Minimum="0"
|
||||
Value="{x:Bind Mode=TwoWay, Path=ViewModel.FindMyMouseAnimationDurationMs}"
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
SpinButtonPlacementMode="Compact"
|
||||
HorizontalAlignment="Left"
|
||||
SmallChange="10"
|
||||
LargeChange="100"/>
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
</StackPanel>
|
||||
</controls:SettingExpander.Content>
|
||||
</controls:SettingExpander>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="MouseUtils_MouseHighlighter">
|
||||
<TextBlock x:Uid="MouseUtils_MouseHighlighter_Description" Margin="0,0,0,8" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<controls:Setting x:Uid="MouseUtils_Enable_MouseHighlighter">
|
||||
|
@ -53,7 +107,7 @@
|
|||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
|
||||
<controls:SettingExpander IsEnabled="{x:Bind ViewModel.IsMouseHighlighterEnabled, Mode=OneWay}" IsExpanded="True" >
|
||||
<controls:SettingExpander IsEnabled="{x:Bind ViewModel.IsMouseHighlighterEnabled, Mode=OneWay}" IsExpanded="False" >
|
||||
<controls:SettingExpander.Header>
|
||||
<controls:Setting x:Uid="ShortcutGuide_Appearance_Behavior" Icon="" />
|
||||
</controls:SettingExpander.Header>
|
||||
|
|
Loading…
Reference in a new issue