mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-06 04:17:04 +01:00
Compare commits
9 Commits
mikehall/d
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f058305d6f | ||
|
|
cb79a00aeb | ||
|
|
a0b49ff647 | ||
|
|
6defcd52f3 | ||
|
|
f4984646dc | ||
|
|
1887c22e87 | ||
|
|
63042dad31 | ||
|
|
c87ef438c9 | ||
|
|
14a45e3e8c |
3
.github/actions/spell-check/allow/code.txt
vendored
3
.github/actions/spell-check/allow/code.txt
vendored
@@ -94,6 +94,7 @@ onefuzzingestionpreparationtool
|
||||
OTP
|
||||
Yubi
|
||||
Yubico
|
||||
Perplexity
|
||||
svgl
|
||||
|
||||
# KEYS
|
||||
@@ -319,4 +320,4 @@ MRUINFO
|
||||
REGSTR
|
||||
|
||||
# Misc Win32 APIs and PInvokes
|
||||
INVOKEIDLIST
|
||||
INVOKEIDLIST
|
||||
|
||||
2
.github/actions/spell-check/allow/names.txt
vendored
2
.github/actions/spell-check/allow/names.txt
vendored
@@ -29,6 +29,8 @@ shortcutguide
|
||||
|
||||
# 8LWXpg is user name but user folder causes a flag
|
||||
LWXpg
|
||||
# 0x6f677548 is user name but user folder causes a flag
|
||||
x6f677548
|
||||
Adoumie
|
||||
Advaith
|
||||
alekhyareddy
|
||||
|
||||
2
.github/actions/spell-check/expect.txt
vendored
2
.github/actions/spell-check/expect.txt
vendored
@@ -1451,7 +1451,6 @@ rstringalnum
|
||||
rstringalpha
|
||||
rstringdigit
|
||||
rtb
|
||||
RTB
|
||||
RTLREADING
|
||||
rtm
|
||||
runas
|
||||
@@ -1798,6 +1797,7 @@ UIA
|
||||
UIEx
|
||||
uild
|
||||
uitests
|
||||
UITo
|
||||
ULONGLONG
|
||||
ums
|
||||
uncompilable
|
||||
|
||||
43
.github/copilot-instructions.md
vendored
Normal file
43
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# PowerToys – Copilot guide (concise)
|
||||
|
||||
This is the top-level guide for AI changes. Keep edits small, follow existing patterns, and cite exact paths in PRs.
|
||||
|
||||
Repo map (1‑line per area)
|
||||
- Core apps: `src/runner/**` (tray/loader), `src/settings-ui/**` (Settings app)
|
||||
- Shared libs: `src/common/**`
|
||||
- Modules: `src/modules/*` (one per utility; Command Palette in `src/modules/cmdpal/**`)
|
||||
- Build tools/docs: `tools/**`, `doc/devdocs/**`
|
||||
|
||||
Build and test (defaults)
|
||||
- Prerequisites: Visual Studio 2022 17.4+, minimal Windows 10 1803+.
|
||||
- Build discipline:
|
||||
- One terminal per operation (build → test). Don’t switch/open new ones mid-flow.
|
||||
- After making changes, `cd` to the project folder that changed (`.csproj`/`.vcxproj`).
|
||||
- Use script(s) to build, synchronously block and wait in foreground for it to finish: `tools/build/build.ps1|.cmd` (current folder), `build-essentials.*` (once per brand new build for missing nuget packages)
|
||||
- Treat build **exit code 0** as success; any non-zero exit code is a failure, have Copilot read the errors log in the build folder (e.g., `build.*.*.errors.log`) and surface problems.
|
||||
- Don’t start tests or launch Runner until the previous step succeeded.
|
||||
- Tests (fast + targeted):
|
||||
- Find the test project by product code prefix (e.g., FancyZones, AdvancedPaste). Look for a sibling folder or 1–2 levels up named like `<Product>*UnitTests` or `<Product>*UITests`.
|
||||
- Build the test project, wait for **exit**, then run only those tests via VS Test Explorer or `vstest.console.exe` with filters. Avoid `dotnet test` in this repo.
|
||||
- Add/adjust tests when changing behavior; if skipped, state why (e.g., comment-only, string rename).
|
||||
|
||||
Pull requests (expectations)
|
||||
- Atomic: one logical change; no drive‑by refactors.
|
||||
- Describe: problem / approach / risk / test evidence.
|
||||
- List: touched paths if not obvious.
|
||||
|
||||
When to ask for clarification
|
||||
- Ambiguous spec after scanning relevant docs (see below).
|
||||
- Cross-module impact (shared enum/struct) not clear.
|
||||
- Security / elevation / installer changes.
|
||||
|
||||
Logging (use existing stacks)
|
||||
- C++: `src/common/logger/**` (`Logger::info|warn|error|debug`). Keep hot paths quiet (hooks, tight loops).
|
||||
- C#: `ManagedCommon.Logger` (`LogInfo|LogWarning|LogError|LogDebug|LogTrace`). Some UIs use injected `ILogger` via `LoggerInstance.Logger`.
|
||||
|
||||
Docs to consult
|
||||
- `tools/build/BUILD-GUIDELINES.md`
|
||||
- `doc/devdocs/core/architecture.md`, `doc/devdocs/core/runner.md`, `doc/devdocs/core/settings/readme.md`, `doc/devdocs/modules/readme.md`
|
||||
|
||||
Done checklist (self review before finishing)
|
||||
- Build clean? Tests updated/passed? No unintended formatting? Any new dependency? Documented skips?
|
||||
@@ -793,6 +793,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Window
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.UnitTestBase", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj", "{00D8659C-2068-40B6-8B86-759CD6284BBB}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{E11826E1-76DF-42AC-985C-164CC2EE57A1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenRuler.UITests", "src\modules\MeasureTool\Tests\ScreenRuler.UITests\ScreenRuler.UITests.csproj", "{66C069F8-C548-4CA6-8CDE-239104D68E88}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Apps.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Apps.UnitTests\Microsoft.CmdPal.Ext.Apps.UnitTests.csproj", "{E816D7B1-4688-4ECB-97CC-3D8E798F3830}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Bookmarks.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Bookmarks.UnitTests\Microsoft.CmdPal.Ext.Bookmarks.UnitTests.csproj", "{E816D7B3-4688-4ECB-97CC-3D8E798F3832}"
|
||||
@@ -801,8 +805,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.WebSea
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Shell.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Shell.UnitTests\Microsoft.CmdPal.Ext.Shell.UnitTests.csproj", "{E816D7B4-4688-4ECB-97CC-3D8E798F3833}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DwellCursor", "src\modules\MouseUtils\DwellCursor\DwellCursor.vcxproj", "{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
@@ -2881,6 +2883,14 @@ Global
|
||||
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.ActiveCfg = Release|x64
|
||||
{00D8659C-2068-40B6-8B86-759CD6284BBB}.Release|x64.Build.0 = Release|x64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Debug|x64.Build.0 = Debug|x64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|x64.ActiveCfg = Release|x64
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88}.Release|x64.Build.0 = Release|x64
|
||||
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{E816D7B1-4688-4ECB-97CC-3D8E798F3830}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -2913,14 +2923,6 @@ Global
|
||||
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|x64.ActiveCfg = Release|x64
|
||||
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|x64.Build.0 = Release|x64
|
||||
{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}.Debug|x64.Build.0 = Debug|x64
|
||||
{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}.Release|x64.ActiveCfg = Release|x64
|
||||
{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -3235,11 +3237,12 @@ Global
|
||||
{E816D7AF-4688-4ECB-97CC-3D8E798F3828} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7B0-4688-4ECB-97CC-3D8E798F3829} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{00D8659C-2068-40B6-8B86-759CD6284BBB} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E11826E1-76DF-42AC-985C-164CC2EE57A1} = {7AC943C9-52E8-44CF-9083-744D8049667B}
|
||||
{66C069F8-C548-4CA6-8CDE-239104D68E88} = {E11826E1-76DF-42AC-985C-164CC2EE57A1}
|
||||
{E816D7B1-4688-4ECB-97CC-3D8E798F3830} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7B3-4688-4ECB-97CC-3D8E798F3832} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7B2-4688-4ECB-97CC-3D8E798F3831} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7B4-4688-4ECB-97CC-3D8E798F3833} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61} = {322566EF-20DC-43A6-B9F8-616AF942579A}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
||||
|
||||
@@ -18,8 +18,8 @@ You can build the entire solution from the command line, which is sometimes fast
|
||||
1. Open Developer Command Prompt for VS 2022
|
||||
2. Navigate to the repository root directory
|
||||
3. Run the following command(don't forget to set the correct platform):
|
||||
```
|
||||
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.sln
|
||||
```pwsh
|
||||
msbuild -restore -p:RestorePackagesConfig=true -p:Platform=ARM64 -m PowerToys.sln /tl /p:NuGetInteractive="true"
|
||||
```
|
||||
4. This process should complete in approximately 13-14 minutes for a full build
|
||||
|
||||
|
||||
@@ -73,4 +73,5 @@ Below are community created plugins that target a website or software. They are
|
||||
| [YubicoOauthOTP](https://github.com/dlnilsson/Community.PowerToys.Run.Plugin.YubicoOauthOTP) | [dlnilsson](https://github.com/dlnilsson) | Display generated codes from OATH accounts stored on the YubiKey in powerToys Run |
|
||||
| [Firefox Bookmark](https://github.com/8LWXpg/PowerToysRun-FirefoxBookmark) | [8LWXpg](https://github.com/8LWXpg) | Open bookmarks in Firefox based browser |
|
||||
| [Linear](https://github.com/vednig/powertoys-linear) | [vednig](https://github.com/vednig) | Create Linear Issues directly from Powertoys Run |
|
||||
| [PerplexitySearchShortcut](https://github.com/0x6f677548/PowerToys-Run-PerplexitySearchShortcut) | [0x6f677548](https://github.com/0x6f677548) | Search Perplexity |
|
||||
| [SpeedTest](https://github.com/ruslanlap/PowerToysRun-SpeedTest) | [ruslanlap](https://github.com/ruslanlap) | One-command internet speed tests with real-time results, modern UI, and shareable links. |
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
Workspaces,
|
||||
PowerRename,
|
||||
CommandPalette,
|
||||
ScreenRuler,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -104,6 +105,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
[PowerToysModule.Workspaces] = new ModuleInfo("PowerToys.WorkspacesEditor.exe", "Workspaces Editor"),
|
||||
[PowerToysModule.PowerRename] = new ModuleInfo("PowerToys.PowerRename.exe", "PowerRename", "WinUI3Apps"),
|
||||
[PowerToysModule.CommandPalette] = new ModuleInfo("Microsoft.CmdPal.UI.exe", "PowerToys Command Palette", "WinUI3Apps\\CmdPal"),
|
||||
[PowerToysModule.ScreenRuler] = new ModuleInfo("PowerToys.MeasureToolUI.exe", "PowerToys.ScreenRuler", "WinUI3Apps"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
@@ -94,6 +95,7 @@ namespace Microsoft.PowerToys.UITest
|
||||
{
|
||||
Task.Delay(1000).Wait();
|
||||
AddScreenShotsToTestResultsDirectory();
|
||||
AddLogFilesToTestResultsDirectory();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,6 +600,92 @@ namespace Microsoft.PowerToys.UITest
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies PowerToys log files to test results directory when test fails.
|
||||
/// Renames files to include the directory structure after \PowerToys.
|
||||
/// </summary>
|
||||
protected void AddLogFilesToTestResultsDirectory()
|
||||
{
|
||||
try
|
||||
{
|
||||
var localAppDataLow = Path.Combine(
|
||||
Environment.GetEnvironmentVariable("USERPROFILE") ?? string.Empty,
|
||||
"AppData",
|
||||
"LocalLow",
|
||||
"Microsoft",
|
||||
"PowerToys");
|
||||
|
||||
if (Directory.Exists(localAppDataLow))
|
||||
{
|
||||
CopyLogFilesFromDirectory(localAppDataLow, string.Empty);
|
||||
}
|
||||
|
||||
var localAppData = Path.Combine(
|
||||
Environment.GetEnvironmentVariable("LOCALAPPDATA") ?? string.Empty,
|
||||
"Microsoft",
|
||||
"PowerToys");
|
||||
|
||||
if (Directory.Exists(localAppData))
|
||||
{
|
||||
CopyLogFilesFromDirectory(localAppData, string.Empty);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Don't fail the test if log file copying fails
|
||||
Console.WriteLine($"Failed to copy log files: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively copies log files from a directory and renames them with directory structure.
|
||||
/// </summary>
|
||||
/// <param name="sourceDir">Source directory to copy from</param>
|
||||
/// <param name="relativePath">Relative path from PowerToys folder</param>
|
||||
private void CopyLogFilesFromDirectory(string sourceDir, string relativePath)
|
||||
{
|
||||
if (!Directory.Exists(sourceDir))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Process log files in current directory
|
||||
var logFiles = Directory.GetFiles(sourceDir, "*.log");
|
||||
foreach (var logFile in logFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fileName = Path.GetFileName(logFile);
|
||||
var fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileName);
|
||||
var extension = Path.GetExtension(fileName);
|
||||
|
||||
// Create new filename with directory structure
|
||||
var directoryPart = string.IsNullOrEmpty(relativePath) ? string.Empty : relativePath.Replace("\\", "-") + "-";
|
||||
var newFileName = $"{directoryPart}{fileNameWithoutExt}{extension}";
|
||||
|
||||
// Copy file to test results directory with new name
|
||||
var testResultsDir = TestContext.TestResultsDirectory ?? Path.GetTempPath();
|
||||
var destinationPath = Path.Combine(testResultsDir, newFileName);
|
||||
|
||||
File.Copy(logFile, destinationPath, true);
|
||||
TestContext.AddResultFile(destinationPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to copy log file {logFile}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively process subdirectories
|
||||
var subdirectories = Directory.GetDirectories(sourceDir);
|
||||
foreach (var subdir in subdirectories)
|
||||
{
|
||||
var dirName = Path.GetFileName(subdir);
|
||||
var newRelativePath = string.IsNullOrEmpty(relativePath) ? dirName : Path.Combine(relativePath, dirName);
|
||||
CopyLogFilesFromDirectory(subdir, newRelativePath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restart scope exe.
|
||||
/// </summary>
|
||||
|
||||
16
src/common/common.instructions.md
Normal file
16
src/common/common.instructions.md
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
applyTo: "**/*.cs,**/*.cpp,**/*.c,**/*.h,**/*.hpp"
|
||||
---
|
||||
# Common – shared libraries guidance (concise)
|
||||
|
||||
Scope
|
||||
- Logging, IPC, settings, DPI, telemetry, utilities consumed by multiple modules.
|
||||
|
||||
Guidelines
|
||||
- Avoid breaking public headers/APIs; if changed, search & update all callers.
|
||||
- Coordinate ABI-impacting struct/class layout changes; keep binary compatibility.
|
||||
- Watch perf in hot paths (hooks, timers, serialization); avoid avoidable allocations.
|
||||
- Ask before adding third‑party deps or changing serialization formats.
|
||||
|
||||
Acceptance
|
||||
- No unintended ABI breaks, no noisy logs, new non-obvious symbols briefly commented.
|
||||
@@ -31,11 +31,11 @@
|
||||
<!-- The following sections assume that the machine we're building on is always x64. That means we won't be able to run/inspect arm64 executables, therefore we must always execute x64 generator. -->
|
||||
|
||||
<Target Name="PostBuildAction" AfterTargets="Build" Outputs="$(GeneratedDSCModule)" Condition="'$(Platform)'!='ARM64'">
|
||||
<Exec Command=""$(OutDir)$(AssemblyName).exe" "$(SolutionDir)x64\$(Configuration)\WinUI3Apps\PowerToys.Settings.UI.Lib.dll" $(GeneratedDSCModule) $(GeneratedDSCManifest)" />
|
||||
<Exec Command=""$(OutDir)$(AssemblyName).exe" "..\..\..\x64\$(Configuration)\WinUI3Apps\PowerToys.Settings.UI.Lib.dll" $(GeneratedDSCModule) $(GeneratedDSCManifest)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent" Condition="'$(Platform)'=='ARM64'">
|
||||
<Exec Command=""$(MSBuildToolsPath)\msbuild.exe" PowerToys.sln -p:Configuration="$(Configuration)" -p:Platform="x64" -verbosity:m -t:DSC\PowerToys_Settings_DSC_Schema_Generator" WorkingDirectory="$(SolutionDir)" />
|
||||
<Exec Command=""$(MSBuildToolsPath)\msbuild.exe" PowerToys.sln -p:Configuration="$(Configuration)" -p:Platform="x64" -verbosity:m -t:DSC\PowerToys_Settings_DSC_Schema_Generator" WorkingDirectory="..\..\..\" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:winuiex="using:WinUIEx"
|
||||
Title="PowerToys.ScreenRuler"
|
||||
IsAlwaysOnTop="True"
|
||||
IsMaximizable="False"
|
||||
IsMinimizable="False"
|
||||
@@ -250,6 +251,7 @@
|
||||
<ToggleButton
|
||||
Name="btnBounds"
|
||||
x:Uid="BtnBounds"
|
||||
AutomationProperties.AutomationId="Button_Bounds"
|
||||
Click="BoundsTool_Click"
|
||||
Content=""
|
||||
KeyboardAcceleratorPlacementMode="Auto"
|
||||
@@ -267,6 +269,7 @@
|
||||
<ToggleButton
|
||||
Name="btnSpacing"
|
||||
x:Uid="BtnSpacing"
|
||||
AutomationProperties.AutomationId="Button_Spacing"
|
||||
Click="MeasureTool_Click"
|
||||
Style="{StaticResource ToggleButtonRadioButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
@@ -284,6 +287,7 @@
|
||||
<ToggleButton
|
||||
Name="btnHorizontalSpacing"
|
||||
x:Uid="BtnHorizontalSpacing"
|
||||
AutomationProperties.AutomationId="Button_SpacingHorizontal"
|
||||
Click="HorizontalMeasureTool_Click"
|
||||
Style="{StaticResource ToggleButtonRadioButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
@@ -304,6 +308,7 @@
|
||||
<ToggleButton
|
||||
Name="btnVerticalSpacing"
|
||||
x:Uid="BtnVerticalSpacing"
|
||||
AutomationProperties.AutomationId="Button_SpacingVertical"
|
||||
Click="VerticalMeasureTool_Click"
|
||||
Style="{StaticResource ToggleButtonRadioButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
@@ -324,6 +329,7 @@
|
||||
<AppBarSeparator />
|
||||
<Button
|
||||
x:Uid="BtnClosePanel"
|
||||
AutomationProperties.AutomationId="Button_Close"
|
||||
Click="ClosePanelTool_Click"
|
||||
Content=""
|
||||
Foreground="{StaticResource CloseButtonBackgroundPointerOver}">
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap rescap">
|
||||
|
||||
<Identity
|
||||
Name="d4d0f157-5c12-4390-9689-152b0c86a582"
|
||||
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
|
||||
Version="1.0.0.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="d4d0f157-5c12-4390-9689-152b0c86a582" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
<Properties>
|
||||
<DisplayName>ScreenRuler.UITests</DisplayName>
|
||||
<PublisherDisplayName>Microsoft</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="ScreenRuler.UITests"
|
||||
Description="ScreenRuler.UITests"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<PropertyGroup>
|
||||
<RootNamespace>PowerToys.ScreenRuler.UITests</RootNamespace>
|
||||
<AssemblyName>ScreenRuler.UITests</AssemblyName>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Library</OutputType>
|
||||
|
||||
<!-- This is a UI test, so don't run as part of MSBuild -->
|
||||
<RunVSTest>false</RunVSTest>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\tests\ScreenRuler.UITests\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest" />
|
||||
<ProjectReference Include="..\..\..\..\common\UITestAutomation\UITestAutomation.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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 Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ScreenRuler.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class TestBounds : UITestBase
|
||||
{
|
||||
public TestBounds()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod("ScreenRuler.BoundsTool")]
|
||||
[TestCategory("Spacing")]
|
||||
public void TestScreenRulerBoundsTool()
|
||||
{
|
||||
TestHelper.InitializeTest(this, "bounds test");
|
||||
TestHelper.PerformBoundsToolTest(this);
|
||||
TestHelper.CleanupTest(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
466
src/modules/MeasureTool/Tests/ScreenRuler.UITests/TestHelper.cs
Normal file
466
src/modules/MeasureTool/Tests/ScreenRuler.UITests/TestHelper.cs
Normal file
@@ -0,0 +1,466 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ScreenRuler.UITests
|
||||
{
|
||||
public static class TestHelper
|
||||
{
|
||||
private static readonly string[] ShortcutSeparators = { " + ", "+", " " };
|
||||
|
||||
// Button automation names from Resources.resw
|
||||
public const string BoundsButtonId = "Button_Bounds";
|
||||
public const string SpacingButtonName = "Button_Spacing";
|
||||
public const string HorizontalSpacingButtonName = "Button_SpacingHorizontal";
|
||||
public const string VerticalSpacingButtonName = "Button_SpacingVertical";
|
||||
public const string CloseButtonId = "Button_Close";
|
||||
|
||||
/// <summary>
|
||||
/// Performs common test initialization: navigate to settings, enable toggle, verify shortcut
|
||||
/// </summary>
|
||||
/// <param name="testBase">The test base instance</param>
|
||||
/// <param name="testName">Name of the test for assertions</param>
|
||||
/// <returns>The activation keys for the test</returns>
|
||||
public static Key[] InitializeTest(UITestBase testBase, string testName)
|
||||
{
|
||||
LaunchFromSetting(testBase);
|
||||
|
||||
var toggleSwitch = SetScreenRulerToggle(testBase, enable: true);
|
||||
Assert.IsTrue(
|
||||
toggleSwitch.IsOn,
|
||||
$"Screen Ruler toggle switch should be ON for {testName}");
|
||||
|
||||
var activationKeys = ReadActivationShortcut(testBase);
|
||||
Assert.IsNotNull(activationKeys, "Should be able to read activation shortcut");
|
||||
Assert.IsTrue(activationKeys.Length > 0, "Activation shortcut should contain at least one key");
|
||||
|
||||
return activationKeys;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs common test cleanup: close ScreenRuler UI
|
||||
/// </summary>
|
||||
/// <param name="testBase">The test base instance</param>
|
||||
public static void CleanupTest(UITestBase testBase)
|
||||
{
|
||||
CloseScreenRulerUI(testBase);
|
||||
|
||||
// Ensure we're attached to settings after cleanup
|
||||
try
|
||||
{
|
||||
testBase.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore attachment errors - this is just cleanup
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigate to the Screen Ruler (Measure Tool) settings page
|
||||
/// </summary>
|
||||
public static void LaunchFromSetting(UITestBase testBase)
|
||||
{
|
||||
var screenRulers = testBase.Session.FindAll<NavigationViewItem>(By.AccessibilityId("ScreenRulerNavItem"));
|
||||
|
||||
if (screenRulers.Count == 0)
|
||||
{
|
||||
testBase.Session.Find<NavigationViewItem>(By.AccessibilityId("SystemToolsNavItem"), 5000).Click(msPostAction: 500);
|
||||
}
|
||||
|
||||
testBase.Session.Find<NavigationViewItem>(By.AccessibilityId("ScreenRulerNavItem"), 5000).Click(msPostAction: 500);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the Screen Ruler toggle switch to the specified state
|
||||
/// </summary>
|
||||
public static ToggleSwitch SetScreenRulerToggle(UITestBase testBase, bool enable)
|
||||
{
|
||||
var toggleSwitch = testBase.Session.Find<ToggleSwitch>(By.AccessibilityId("Toggle_ScreenRuler"), 5000);
|
||||
|
||||
if (toggleSwitch.IsOn != enable)
|
||||
{
|
||||
toggleSwitch.Click(msPreAction: 1000, msPostAction: 2000);
|
||||
}
|
||||
|
||||
if (toggleSwitch.IsOn != enable)
|
||||
{
|
||||
testBase.Session.SendKey(Key.Space, msPreAction: 0, msPostAction: 2000);
|
||||
}
|
||||
|
||||
return toggleSwitch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the Screen Ruler toggle and verify its state
|
||||
/// </summary>
|
||||
/// <param name="testBase">The test base instance</param>
|
||||
/// <param name="enable">True to enable, false to disable</param>
|
||||
/// <param name="testName">Name of the test for assertion messages</param>
|
||||
public static void SetAndVerifyScreenRulerToggle(UITestBase testBase, bool enable, string testName)
|
||||
{
|
||||
var toggleSwitch = SetScreenRulerToggle(testBase, enable);
|
||||
Assert.AreEqual(
|
||||
enable,
|
||||
toggleSwitch.IsOn,
|
||||
$"Screen Ruler toggle switch should be {(enable ? "ON" : "OFF")} for {testName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read the current activation shortcut from the ShortcutControl
|
||||
/// </summary>
|
||||
public static Key[] ReadActivationShortcut(UITestBase testBase)
|
||||
{
|
||||
var shortcutCard = testBase.Session.Find<Element>(By.AccessibilityId("Shortcut_ScreenRuler"), 5000);
|
||||
var shortcutButton = shortcutCard.Find<Element>(By.AccessibilityId("EditButton"), 5000);
|
||||
return ParseShortcutText(shortcutButton.HelpText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse shortcut text like "Win + Ctrl + Shift + M" into Key array
|
||||
/// </summary>
|
||||
private static Key[] ParseShortcutText(string shortcutText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(shortcutText))
|
||||
{
|
||||
return new Key[] { Key.Win, Key.Ctrl, Key.Shift, Key.M };
|
||||
}
|
||||
|
||||
var keys = new List<Key>();
|
||||
var parts = shortcutText.Split(ShortcutSeparators, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var cleanPart = part.Trim().ToLowerInvariant();
|
||||
var key = cleanPart switch
|
||||
{
|
||||
"win" or "windows" => Key.Win,
|
||||
"ctrl" or "control" => Key.Ctrl,
|
||||
"shift" => Key.Shift,
|
||||
"alt" => Key.Alt,
|
||||
_ when cleanPart.Length == 1 && char.IsLetter(cleanPart[0]) &&
|
||||
cleanPart[0] >= 'a' && cleanPart[0] <= 'z' =>
|
||||
(Key)Enum.Parse(typeof(Key), cleanPart.ToUpperInvariant()),
|
||||
_ => (Key?)null,
|
||||
};
|
||||
|
||||
if (key.HasValue)
|
||||
{
|
||||
keys.Add(key.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return keys.Count > 0 ? keys.ToArray() : new Key[] { Key.Win, Key.Ctrl, Key.Shift, Key.M };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if ScreenRulerUI window is open
|
||||
/// </summary>
|
||||
public static bool IsScreenRulerUIOpen(UITestBase testBase) => testBase.IsWindowOpen("PowerToys.ScreenRuler");
|
||||
|
||||
/// <summary>
|
||||
/// Wait for ScreenRulerUI to reach the specified state within the timeout
|
||||
/// </summary>
|
||||
public static bool WaitForScreenRulerUIState(UITestBase testBase, bool shouldBeOpen, int timeoutMs = 5000, int pollingIntervalMs = 100)
|
||||
{
|
||||
var endTime = DateTime.Now.AddMilliseconds(timeoutMs);
|
||||
|
||||
while (DateTime.Now < endTime)
|
||||
{
|
||||
if (IsScreenRulerUIOpen(testBase) == shouldBeOpen)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Task.Delay(pollingIntervalMs).Wait();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for ScreenRulerUI to appear within the specified timeout
|
||||
/// </summary>
|
||||
public static bool WaitForScreenRulerUI(UITestBase testBase, int timeoutMs = 5000) =>
|
||||
WaitForScreenRulerUIState(testBase, shouldBeOpen: true, timeoutMs);
|
||||
|
||||
/// <summary>
|
||||
/// Wait for ScreenRulerUI to disappear within the specified timeout
|
||||
/// </summary>
|
||||
public static bool WaitForScreenRulerUIToDisappear(UITestBase testBase, int timeoutMs = 5000) =>
|
||||
WaitForScreenRulerUIState(testBase, shouldBeOpen: false, timeoutMs);
|
||||
|
||||
/// <summary>
|
||||
/// Close ScreenRulerUI if it's open
|
||||
/// </summary>
|
||||
public static void CloseScreenRulerUI(UITestBase testBase)
|
||||
{
|
||||
if (IsScreenRulerUIOpen(testBase))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Attach to ScreenRuler window before trying to find and click close button
|
||||
testBase.Session.Attach(PowerToysModule.ScreenRuler);
|
||||
var closeButton = testBase.Session.Find<Element>(By.AccessibilityId(CloseButtonId), 15000, true);
|
||||
closeButton?.Click();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If we can't find the close button, ignore - the window might have closed already
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Attach back to settings after closing
|
||||
try
|
||||
{
|
||||
testBase.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore attachment errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a specific ScreenRulerUI button by its automation name
|
||||
/// </summary>
|
||||
public static Element? GetScreenRulerButton(UITestBase testBase, string buttonName, int timeoutMs = 1000)
|
||||
{
|
||||
return testBase.Session.Find<Element>(By.AccessibilityId(buttonName), timeoutMs, true);
|
||||
|
||||
/*
|
||||
try
|
||||
{
|
||||
// Attach to ScreenRuler window before trying to find buttons
|
||||
testBase.Session.Attach(PowerToysModule.ScreenRuler);
|
||||
return testBase.Session.Find<Element>(By.AccessibilityId(buttonName), timeoutMs, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Attach back to settings if needed for further operations
|
||||
// This ensures we don't break the test flow
|
||||
try
|
||||
{
|
||||
testBase.Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore attachment errors - the calling code will handle as needed
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear the clipboard content using STA thread
|
||||
/// </summary>
|
||||
public static void ClearClipboard()
|
||||
{
|
||||
ExecuteInSTAThread(() => System.Windows.Forms.Clipboard.Clear());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get text content from clipboard using STA thread
|
||||
/// </summary>
|
||||
public static string GetClipboardText()
|
||||
{
|
||||
string result = string.Empty;
|
||||
ExecuteInSTAThread(() =>
|
||||
{
|
||||
if (System.Windows.Forms.Clipboard.ContainsText())
|
||||
{
|
||||
result = System.Windows.Forms.Clipboard.GetText();
|
||||
}
|
||||
});
|
||||
return result ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute an action in an STA thread with error handling
|
||||
/// </summary>
|
||||
private static void ExecuteInSTAThread(Action action)
|
||||
{
|
||||
try
|
||||
{
|
||||
var staThread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore clipboard errors
|
||||
}
|
||||
});
|
||||
|
||||
staThread.SetApartmentState(ApartmentState.STA);
|
||||
staThread.Start();
|
||||
staThread.Join(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore clipboard errors
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate clipboard content contains valid spacing measurement for the specified type
|
||||
/// </summary>
|
||||
public static bool ValidateSpacingClipboardContent(string clipboardText, string spacingType)
|
||||
{
|
||||
if (string.IsNullOrEmpty(clipboardText))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return spacingType switch
|
||||
{
|
||||
"Spacing" => Regex.IsMatch(clipboardText, @"\d+\s*[<5B>x×]\s*\d+"),
|
||||
"Horizontal Spacing" or "Vertical Spacing" => Regex.IsMatch(clipboardText, @"^\d+$"),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a complete spacing tool test operation
|
||||
/// </summary>
|
||||
public static void PerformSpacingToolTest(UITestBase testBase, string buttonId, string testName)
|
||||
{
|
||||
ClearClipboard();
|
||||
|
||||
// Launch ScreenRuler UI
|
||||
var activationKeys = ReadActivationShortcut(testBase);
|
||||
testBase.SendKeys(activationKeys);
|
||||
|
||||
Assert.IsTrue(
|
||||
WaitForScreenRulerUI(testBase, 2000),
|
||||
$"ScreenRulerUI should appear after pressing activation shortcut for {testName}: {string.Join(" + ", activationKeys)}");
|
||||
|
||||
// Attach to ScreenRuler window and click spacing button
|
||||
// testBase.Session.Attach(PowerToysModule.ScreenRuler);
|
||||
var spacingButton = testBase.Session.Find<Element>(By.AccessibilityId(buttonId), 15000, true);
|
||||
Assert.IsNotNull(spacingButton, $"{testName} button should be found");
|
||||
|
||||
spacingButton!.Click();
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
// Perform measurement action (stay attached to ScreenRuler for this)
|
||||
PerformMeasurementAction(testBase);
|
||||
|
||||
// Validate results
|
||||
ValidateClipboardResults(testName);
|
||||
|
||||
// Cleanup - this will handle session attachment properly
|
||||
CloseScreenRulerUI(testBase);
|
||||
Assert.IsTrue(
|
||||
WaitForScreenRulerUIToDisappear(testBase, 2000),
|
||||
$"{testName}: ScreenRulerUI should close after calling CloseScreenRulerUI");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a bounds tool test operation
|
||||
/// </summary>
|
||||
public static void PerformBoundsToolTest(UITestBase testBase)
|
||||
{
|
||||
ClearClipboard();
|
||||
|
||||
var activationKeys = ReadActivationShortcut(testBase);
|
||||
testBase.SendKeys(activationKeys);
|
||||
|
||||
Assert.IsTrue(
|
||||
WaitForScreenRulerUI(testBase, 2000),
|
||||
$"ScreenRulerUI should appear after pressing activation shortcut: {string.Join(" + ", activationKeys)}");
|
||||
|
||||
// Attach to ScreenRuler window and click bounds button
|
||||
// testBase.Session.Attach(PowerToysModule.ScreenRuler);
|
||||
var boundsButton = testBase.Session.Find<Element>(By.AccessibilityId(BoundsButtonId), 15000, true);
|
||||
Assert.IsNotNull(boundsButton, "Bounds button should be found");
|
||||
|
||||
boundsButton.Click();
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
// Perform drag operation to create 100x100 box (stay attached to ScreenRuler)
|
||||
var currentPos = testBase.GetMousePosition();
|
||||
int startX = currentPos.Item1;
|
||||
int startY = currentPos.Item2 + 200;
|
||||
|
||||
testBase.MoveMouseTo(startX, startY);
|
||||
Task.Delay(200).Wait();
|
||||
|
||||
// Drag operation
|
||||
testBase.Session.PerformMouseAction(MouseActionType.LeftDown);
|
||||
Task.Delay(100).Wait();
|
||||
|
||||
testBase.MoveMouseTo(startX + 99, startY + 99);
|
||||
Task.Delay(200).Wait();
|
||||
|
||||
testBase.Session.PerformMouseAction(MouseActionType.LeftUp);
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
// Dismiss selection
|
||||
testBase.Session.PerformMouseAction(MouseActionType.RightClick);
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
// Validate results
|
||||
string clipboardText = GetClipboardText();
|
||||
Assert.IsFalse(string.IsNullOrEmpty(clipboardText), "Clipboard should contain measurement data");
|
||||
Assert.IsTrue(
|
||||
clipboardText.Contains("100 × 100") || clipboardText.Contains("100 x 100"),
|
||||
$"Clipboard should contain '100 x 100', but contained: '{clipboardText}'");
|
||||
|
||||
// Cleanup - this will handle session attachment properly
|
||||
CloseScreenRulerUI(testBase);
|
||||
Assert.IsTrue(
|
||||
WaitForScreenRulerUIToDisappear(testBase, 2000),
|
||||
"ScreenRulerUI should close after calling CloseScreenRulerUI");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a measurement action (move mouse and click)
|
||||
/// </summary>
|
||||
private static void PerformMeasurementAction(UITestBase testBase)
|
||||
{
|
||||
var currentPos = testBase.GetMousePosition();
|
||||
int startX = currentPos.Item1;
|
||||
int startY = currentPos.Item2 + 200;
|
||||
|
||||
testBase.MoveMouseTo(startX, startY);
|
||||
Task.Delay(200).Wait();
|
||||
|
||||
testBase.Session.PerformMouseAction(MouseActionType.LeftClick);
|
||||
Task.Delay(500).Wait();
|
||||
|
||||
testBase.Session.PerformMouseAction(MouseActionType.RightClick);
|
||||
Task.Delay(500).Wait();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate clipboard results for spacing tests
|
||||
/// </summary>
|
||||
private static void ValidateClipboardResults(string testName)
|
||||
{
|
||||
string clipboardText = GetClipboardText();
|
||||
Assert.IsFalse(string.IsNullOrEmpty(clipboardText), $"{testName}: Clipboard should contain measurement data");
|
||||
|
||||
bool containsValidPattern = ValidateSpacingClipboardContent(clipboardText, testName);
|
||||
Assert.IsTrue(
|
||||
containsValidPattern,
|
||||
$"{testName}: Clipboard should contain valid spacing measurement, but contained: '{clipboardText}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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.Threading.Tasks;
|
||||
using Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ScreenRuler.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class TestShortcutActivation : UITestBase
|
||||
{
|
||||
public TestShortcutActivation()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod("ScreenRuler.ShortcutActivation")]
|
||||
[TestCategory("Activation")]
|
||||
public void TestScreenRulerShortcutActivation()
|
||||
{
|
||||
var activationKeys = TestHelper.InitializeTest(this, "activation test");
|
||||
|
||||
// Test 1: Press the activation shortcut and verify the toolbar appears
|
||||
SendKeys(activationKeys);
|
||||
bool screenRulerAppeared = TestHelper.WaitForScreenRulerUI(this, 1000);
|
||||
Assert.IsTrue(
|
||||
screenRulerAppeared,
|
||||
$"ScreenRulerUI should appear after pressing activation shortcut: {string.Join(" + ", activationKeys)}");
|
||||
|
||||
// Test 2: Press the activation shortcut again and verify the toolbar disappears
|
||||
SendKeys(activationKeys);
|
||||
bool screenRulerDisappeared = TestHelper.WaitForScreenRulerUIToDisappear(this, 1000);
|
||||
Assert.IsTrue(
|
||||
screenRulerDisappeared,
|
||||
$"ScreenRulerUI should disappear after pressing activation shortcut again: {string.Join(" + ", activationKeys)}");
|
||||
|
||||
// Test 3: Disable Screen Ruler and verify that the activation shortcut no longer activates the utility
|
||||
// Ensure we're attached to settings UI before toggling
|
||||
Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
TestHelper.SetAndVerifyScreenRulerToggle(this, enable: false, "disabled state test");
|
||||
|
||||
// Try to activate with shortcut while disabled
|
||||
SendKeys(activationKeys);
|
||||
Task.Delay(1000).Wait();
|
||||
Assert.IsFalse(
|
||||
TestHelper.IsScreenRulerUIOpen(this),
|
||||
"ScreenRulerUI should not appear when Screen Ruler is disabled");
|
||||
|
||||
// Test 4: Enable Screen Ruler and press the activation shortcut and verify the toolbar appears
|
||||
// Ensure we're attached to settings UI before toggling
|
||||
Session.Attach(PowerToysModule.PowerToysSettings);
|
||||
TestHelper.SetAndVerifyScreenRulerToggle(this, enable: true, "re-enabled state test");
|
||||
|
||||
SendKeys(activationKeys);
|
||||
screenRulerAppeared = TestHelper.WaitForScreenRulerUI(this, 1000);
|
||||
Assert.IsTrue(
|
||||
screenRulerAppeared,
|
||||
$"ScreenRulerUI should appear after re-enabling and pressing activation shortcut: {string.Join(" + ", activationKeys)}");
|
||||
|
||||
// Test 5: Verify the utility can be closed via the cleanup method
|
||||
TestHelper.CloseScreenRulerUI(this);
|
||||
bool screenRulerClosed = TestHelper.WaitForScreenRulerUIToDisappear(this, 1000);
|
||||
Assert.IsTrue(
|
||||
screenRulerClosed,
|
||||
"ScreenRulerUI should close after calling CloseScreenRulerUI");
|
||||
|
||||
TestHelper.CleanupTest(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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 Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ScreenRuler.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class TestSpacing : UITestBase
|
||||
{
|
||||
public TestSpacing()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod("ScreenRuler.SpacingTool")]
|
||||
[TestCategory("Spacing")]
|
||||
public void TestScreenRulerSpacingTool()
|
||||
{
|
||||
TestHelper.InitializeTest(this, "spacing test");
|
||||
TestHelper.PerformSpacingToolTest(this, TestHelper.SpacingButtonName, "Spacing");
|
||||
TestHelper.CleanupTest(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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 Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ScreenRuler.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class TestSpacingHorizontal : UITestBase
|
||||
{
|
||||
public TestSpacingHorizontal()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod("ScreenRuler.HorizontalSpacingTool")]
|
||||
[TestCategory("Spacing")]
|
||||
public void TestScreenRulerHorizontalSpacingTool()
|
||||
{
|
||||
TestHelper.InitializeTest(this, "horizontal spacing test");
|
||||
TestHelper.PerformSpacingToolTest(this, TestHelper.HorizontalSpacingButtonName, "Horizontal Spacing");
|
||||
TestHelper.CleanupTest(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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 Microsoft.PowerToys.UITest;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ScreenRuler.UITests
|
||||
{
|
||||
[TestClass]
|
||||
public class TestSpacingVertical : UITestBase
|
||||
{
|
||||
public TestSpacingVertical()
|
||||
: base(PowerToysModule.PowerToysSettings, WindowSize.Large)
|
||||
{
|
||||
}
|
||||
|
||||
[TestMethod("ScreenRuler.VerticalSpacingTool")]
|
||||
[TestCategory("Spacing")]
|
||||
public void TestScreenRulerVerticalSpacingTool()
|
||||
{
|
||||
TestHelper.InitializeTest(this, "vertical spacing test");
|
||||
TestHelper.PerformSpacingToolTest(this, TestHelper.VerticalSpacingButtonName, "Vertical Spacing");
|
||||
TestHelper.CleanupTest(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="ScreenRuler.UITests.app"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- The ID below informs the system that this application is compatible with OS features first introduced in Windows 10.
|
||||
It is necessary to support features in unpackaged applications, for example the custom titlebar implementation.
|
||||
For more info see https://docs.microsoft.com/windows/apps/windows-app-sdk/use-windows-app-sdk-run-time#declare-os-compatibility-in-your-application-manifest -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -1,7 +0,0 @@
|
||||
#include <windows.h>
|
||||
#include "resource.h"
|
||||
|
||||
STRINGTABLE
|
||||
BEGIN
|
||||
IDS_MODULE_NAME "DwellCursor"
|
||||
END
|
||||
@@ -1,125 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{6C2AD2F7-8BDF-4C5E-B3F5-7E1B7A0A8A61}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>DwellCursor</RootNamespace>
|
||||
<ProjectName>DwellCursor</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<Import Project="..\..\..\..\deps\spdlog.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\</OutDir>
|
||||
<TargetName>PowerToys.DwellCursor</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)src\;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="DwellIndicator.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="trace.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="DwellIndicator.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="trace.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="DwellCursor.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
|
||||
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
|
||||
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -1,830 +0,0 @@
|
||||
#include "pch.h"
|
||||
#include "DwellIndicator.h"
|
||||
#include <gdiplus.h>
|
||||
#include <cmath>
|
||||
|
||||
#pragma comment(lib, "gdiplus.lib")
|
||||
#pragma comment(lib, "dwmapi.lib")
|
||||
|
||||
using namespace Gdiplus;
|
||||
|
||||
/**
|
||||
* @brief Implementation class for the dwell indicator using the Pimpl idiom
|
||||
*
|
||||
* This class handles all the visual indicator functionality:
|
||||
* - Creates a transparent, topmost window at cursor position
|
||||
* - Draws a circular progress arc using GDI+
|
||||
* - Updates progress smoothly during countdown
|
||||
* - Uses system accent color for theming
|
||||
*/
|
||||
class DwellIndicatorImpl
|
||||
{
|
||||
public:
|
||||
DwellIndicatorImpl() = default;
|
||||
~DwellIndicatorImpl() = default;
|
||||
|
||||
// Public interface methods
|
||||
bool Initialize();
|
||||
void Show(int x, int y);
|
||||
void UpdateProgress(float progress);
|
||||
void Hide();
|
||||
void Cleanup();
|
||||
|
||||
private:
|
||||
// Window management
|
||||
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) noexcept;
|
||||
bool CreateIndicatorWindow();
|
||||
void DrawIndicator(HDC hdc);
|
||||
float GetDpiScale() const;
|
||||
|
||||
// Window class and visual constants
|
||||
static constexpr auto m_className = L"DwellCursorIndicator";
|
||||
static constexpr auto m_windowTitle = L"PowerToys Dwell Cursor Indicator";
|
||||
static constexpr float kIndicatorRadius = 20.0f; // Circle radius in pixels
|
||||
static constexpr float kStrokeWidth = 3.0f; // Arc stroke width in pixels
|
||||
|
||||
// Window and positioning state
|
||||
HWND m_hwnd = NULL; // Handle to the indicator window
|
||||
HINSTANCE m_hinstance = NULL; // Module instance handle
|
||||
bool m_isVisible = false; // Current visibility state
|
||||
int m_currentX = 0; // Last shown X position
|
||||
int m_currentY = 0; // Last shown Y position
|
||||
float m_progress = 0.0f; // Current progress (0.0 to 1.0)
|
||||
|
||||
// GDI+ resources
|
||||
ULONG_PTR m_gdiplusToken = 0; // GDI+ initialization token
|
||||
|
||||
friend class DwellIndicator;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Window procedure for the indicator window
|
||||
*
|
||||
* Handles window messages for the transparent indicator overlay:
|
||||
* - WM_PAINT: Triggers redraw of the progress arc
|
||||
* - WM_NCHITTEST: Returns HTTRANSPARENT to allow mouse events to pass through
|
||||
* - WM_DESTROY: Standard cleanup
|
||||
*
|
||||
* @param hWnd Window handle
|
||||
* @param message Windows message ID
|
||||
* @param wParam Message parameter
|
||||
* @param lParam Message parameter
|
||||
* @return Message handling result
|
||||
*/
|
||||
LRESULT CALLBACK DwellIndicatorImpl::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) noexcept
|
||||
{
|
||||
DwellIndicatorImpl* pThis = nullptr;
|
||||
|
||||
// Retrieve the instance pointer stored during window creation
|
||||
if (message == WM_NCCREATE)
|
||||
{
|
||||
// During window creation, extract the 'this' pointer from creation params
|
||||
CREATESTRUCT* pcs = reinterpret_cast<CREATESTRUCT*>(lParam);
|
||||
pThis = static_cast<DwellIndicatorImpl*>(pcs->lpCreateParams);
|
||||
SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pThis));
|
||||
}
|
||||
else
|
||||
{
|
||||
// For all other messages, retrieve the stored 'this' pointer
|
||||
pThis = reinterpret_cast<DwellIndicatorImpl*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
|
||||
}
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case WM_PAINT:
|
||||
// Redraw the indicator - this is where our visual progress arc gets drawn
|
||||
if (pThis)
|
||||
{
|
||||
PAINTSTRUCT ps;
|
||||
HDC hdc = BeginPaint(hWnd, &ps);
|
||||
pThis->DrawIndicator(hdc); // Draw the circular progress indicator
|
||||
EndPaint(hWnd, &ps);
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_NCHITTEST:
|
||||
// Restore transparent mouse behavior - allow clicks to pass through
|
||||
return HTTRANSPARENT;
|
||||
|
||||
case WM_DESTROY:
|
||||
// DO NOT call PostQuitMessage(0) for overlay windows!
|
||||
// This was interfering with PowerToys main message loop and causing settings menu issues
|
||||
// Just let the window be destroyed normally
|
||||
break;
|
||||
|
||||
default:
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the indicator system
|
||||
*
|
||||
* Sets up GDI+ graphics system and creates the indicator window.
|
||||
* This must be called before any Show/Update operations.
|
||||
*
|
||||
* @return true if initialization successful, false on failure
|
||||
*/
|
||||
bool DwellIndicatorImpl::Initialize()
|
||||
{
|
||||
m_hinstance = GetModuleHandle(NULL);
|
||||
|
||||
// Initialize GDI+ graphics system for smooth drawing
|
||||
GdiplusStartupInput gdiplusStartupInput;
|
||||
Gdiplus::Status status = GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
|
||||
if (status != Gdiplus::Ok)
|
||||
{
|
||||
// GDI+ initialization failed - no visual indicator will work
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the transparent overlay window
|
||||
bool windowCreated = CreateIndicatorWindow();
|
||||
if (!windowCreated)
|
||||
{
|
||||
// Clean up GDI+ if window creation failed
|
||||
if (m_gdiplusToken != 0)
|
||||
{
|
||||
GdiplusShutdown(m_gdiplusToken);
|
||||
m_gdiplusToken = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return windowCreated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create the transparent indicator window
|
||||
*
|
||||
* Creates a layered, transparent, topmost window that:
|
||||
* - Appears above all other windows
|
||||
* - Allows mouse events to pass through
|
||||
* - Has no border, title bar, or decorations
|
||||
* - Is positioned and sized later when shown
|
||||
*
|
||||
* @return true if window created successfully, false on failure
|
||||
*/
|
||||
bool DwellIndicatorImpl::CreateIndicatorWindow()
|
||||
{
|
||||
WNDCLASS wc{};
|
||||
|
||||
// Set DPI awareness for proper scaling on high-DPI displays
|
||||
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
|
||||
// Register window class only if not already registered
|
||||
if (!GetClassInfoW(m_hinstance, m_className, &wc))
|
||||
{
|
||||
wc.lpfnWndProc = WndProc; // Our window procedure
|
||||
wc.hInstance = m_hinstance; // Module instance
|
||||
wc.hIcon = LoadIcon(m_hinstance, IDI_APPLICATION); // Default icon
|
||||
wc.hCursor = LoadCursor(nullptr, IDC_ARROW); // Default cursor
|
||||
wc.hbrBackground = static_cast<HBRUSH>(GetStockObject(NULL_BRUSH)); // Transparent background
|
||||
wc.lpszClassName = m_className; // Class name for window
|
||||
|
||||
if (!RegisterClassW(&wc))
|
||||
{
|
||||
// Failed to register window class
|
||||
DWORD error = GetLastError();
|
||||
// Note: Can't use Logger here as it might not be available in all contexts
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create window with transparency and mouse pass-through restored
|
||||
DWORD exStyle = WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
|
||||
|
||||
m_hwnd = CreateWindowExW(
|
||||
exStyle, // Extended window styles with transparency restored
|
||||
m_className, // Window class name
|
||||
m_windowTitle, // Window title (not visible)
|
||||
WS_POPUP, // Window style - popup with no decorations
|
||||
0, 0, 100, 100, // Initial position and size (will be adjusted in Show())
|
||||
nullptr, // No parent window
|
||||
nullptr, // No menu
|
||||
m_hinstance, // Module instance
|
||||
this); // Pass 'this' pointer for WndProc to access
|
||||
|
||||
if (!m_hwnd)
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
// Note: Can't use Logger here as it might not be available in all contexts
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputDebugStringA("DwellIndicator: Created transparent layered window with mouse pass-through\n");
|
||||
}
|
||||
|
||||
return m_hwnd != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Show the indicator at specified cursor position
|
||||
*
|
||||
* Positions the window centered on the cursor location and makes it visible.
|
||||
* The window size is calculated based on indicator radius and DPI scaling.
|
||||
*
|
||||
* @param x Cursor X coordinate in screen pixels
|
||||
* @param y Cursor Y coordinate in screen pixels
|
||||
*/
|
||||
void DwellIndicatorImpl::Show(int x, int y)
|
||||
{
|
||||
// Check if window handle is valid before proceeding
|
||||
if (!m_hwnd)
|
||||
{
|
||||
OutputDebugStringA("DwellIndicator: ERROR - Window handle is NULL, cannot show indicator\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// **CRITICAL FIX: Reset progress state immediately when showing at new position**
|
||||
float oldProgress = m_progress;
|
||||
m_progress = 0.0f;
|
||||
|
||||
// Store current position for reference
|
||||
m_currentX = x;
|
||||
m_currentY = y;
|
||||
|
||||
// Calculate window size based on indicator radius and DPI scaling
|
||||
const float dpiScale = GetDpiScale();
|
||||
const int windowSize = static_cast<int>((kIndicatorRadius * 2 + kStrokeWidth * 2 + 10) * dpiScale);
|
||||
|
||||
// Calculate final window position (centered on cursor)
|
||||
const int windowX = x - windowSize / 2;
|
||||
const int windowY = y - windowSize / 2;
|
||||
|
||||
// Log detailed positioning information for debugging
|
||||
char debugMsg[512];
|
||||
sprintf_s(debugMsg, sizeof(debugMsg),
|
||||
"DwellIndicator: SHOW - Cursor:(%d,%d) Window:(%d,%d) Size:%dx%d DPI:%.2f Progress: %.3f->0.0\n",
|
||||
x, y, windowX, windowY, windowSize, windowSize, dpiScale, oldProgress);
|
||||
OutputDebugStringA(debugMsg);
|
||||
|
||||
// **GDI+ EXPERT FIX: Use UpdateLayeredWindow for proper transparency reset**
|
||||
// This is the correct way to handle layered windows with transparency
|
||||
|
||||
// First hide the window to ensure clean state
|
||||
if (m_isVisible)
|
||||
{
|
||||
ShowWindow(m_hwnd, SW_HIDE);
|
||||
m_isVisible = false;
|
||||
}
|
||||
|
||||
// Position window (while hidden for clean transition)
|
||||
BOOL setWindowPosResult = SetWindowPos(m_hwnd, HWND_TOPMOST,
|
||||
windowX, windowY, // Calculated position
|
||||
windowSize, windowSize, // Square window to contain circle
|
||||
SWP_NOACTIVATE | SWP_HIDEWINDOW); // Position but keep hidden for now
|
||||
|
||||
if (!setWindowPosResult)
|
||||
{
|
||||
DWORD error = GetLastError();
|
||||
sprintf_s(debugMsg, sizeof(debugMsg),
|
||||
"DwellIndicator: ERROR - SetWindowPos failed with error %lu\n", error);
|
||||
OutputDebugStringA(debugMsg);
|
||||
}
|
||||
|
||||
// **GDI+ EXPERT FIX: Create clean bitmap and use UpdateLayeredWindow**
|
||||
// This completely clears any previous drawing artifacts
|
||||
HDC screenDC = GetDC(NULL);
|
||||
HDC memoryDC = CreateCompatibleDC(screenDC);
|
||||
|
||||
// Create 32-bit bitmap with alpha channel for proper transparency
|
||||
BITMAPINFO bmi = {};
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = windowSize;
|
||||
bmi.bmiHeader.biHeight = -windowSize; // Top-down DIB
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 32; // 32-bit with alpha
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
void* pvBits = nullptr;
|
||||
HBITMAP hBitmap = CreateDIBSection(screenDC, &bmi, DIB_RGB_COLORS, &pvBits, NULL, 0);
|
||||
|
||||
if (hBitmap && memoryDC)
|
||||
{
|
||||
HBITMAP oldBitmap = static_cast<HBITMAP>(SelectObject(memoryDC, hBitmap));
|
||||
|
||||
// **CRITICAL: Clear the entire bitmap with transparent pixels**
|
||||
// This ensures no artifacts from previous drawings
|
||||
// Fix for C26451: Use safe arithmetic to prevent overflow
|
||||
const size_t bitmapSizeBytes = static_cast<size_t>(windowSize) * static_cast<size_t>(windowSize) * 4ULL;
|
||||
memset(pvBits, 0, bitmapSizeBytes); // Clear to transparent
|
||||
|
||||
// Create GDI+ Graphics object from memory DC
|
||||
Graphics graphics(memoryDC);
|
||||
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
|
||||
graphics.SetCompositingMode(CompositingModeSourceOver);
|
||||
graphics.SetCompositingQuality(CompositingQualityHighQuality);
|
||||
|
||||
// **GDI+ EXPERT: Use Graphics::Clear with transparent color**
|
||||
// This properly clears the alpha channel
|
||||
graphics.Clear(Color(0, 0, 0, 0)); // Fully transparent
|
||||
|
||||
// Draw only the background circle (no progress arc yet since progress = 0.0)
|
||||
const float centerX = windowSize / 2.0f;
|
||||
const float centerY = windowSize / 2.0f;
|
||||
const float radius = kIndicatorRadius * dpiScale;
|
||||
const float strokeWidth = kStrokeWidth * dpiScale;
|
||||
|
||||
// Get system accent color
|
||||
DWORD accentColor = 0;
|
||||
BOOL isOpaque = FALSE;
|
||||
Color backgroundCircleColor;
|
||||
|
||||
if (SUCCEEDED(DwmGetColorizationColor(&accentColor, &isOpaque)))
|
||||
{
|
||||
const BYTE r = (accentColor >> 16) & 0xFF;
|
||||
const BYTE g = (accentColor >> 8) & 0xFF;
|
||||
const BYTE b = accentColor & 0xFF;
|
||||
const BYTE bgR = static_cast<BYTE>((r + 128) / 2);
|
||||
const BYTE bgG = static_cast<BYTE>((g + 128) / 2);
|
||||
const BYTE bgB = static_cast<BYTE>((b + 128) / 2);
|
||||
backgroundCircleColor = Color(80, bgR, bgG, bgB);
|
||||
}
|
||||
else
|
||||
{
|
||||
backgroundCircleColor = Color(80, 160, 160, 160);
|
||||
}
|
||||
|
||||
// Draw background circle
|
||||
RectF ellipseRect(centerX - radius, centerY - radius, radius * 2, radius * 2);
|
||||
Pen bgPen(backgroundCircleColor, strokeWidth * 0.6f);
|
||||
graphics.DrawEllipse(&bgPen, ellipseRect);
|
||||
|
||||
// **GDI+ EXPERT: Use UpdateLayeredWindow for artifact-free display**
|
||||
POINT ptSrc = {0, 0};
|
||||
POINT ptDst = {windowX, windowY};
|
||||
SIZE size = {windowSize, windowSize};
|
||||
BLENDFUNCTION blend = {};
|
||||
blend.BlendOp = AC_SRC_OVER;
|
||||
blend.SourceConstantAlpha = 255;
|
||||
blend.AlphaFormat = AC_SRC_ALPHA; // Use per-pixel alpha
|
||||
|
||||
BOOL updateResult = UpdateLayeredWindow(m_hwnd, screenDC, &ptDst, &size,
|
||||
memoryDC, &ptSrc, 0, &blend, ULW_ALPHA);
|
||||
|
||||
// Cleanup
|
||||
SelectObject(memoryDC, oldBitmap);
|
||||
DeleteObject(hBitmap);
|
||||
|
||||
sprintf_s(debugMsg, sizeof(debugMsg),
|
||||
"DwellIndicator: UpdateLayeredWindow result: %s\n",
|
||||
updateResult ? "SUCCESS" : "FAILED");
|
||||
OutputDebugStringA(debugMsg);
|
||||
}
|
||||
|
||||
DeleteDC(memoryDC);
|
||||
ReleaseDC(NULL, screenDC);
|
||||
|
||||
// Now show the window with clean, artifact-free display
|
||||
ShowWindow(m_hwnd, SW_SHOWNOACTIVATE);
|
||||
m_isVisible = true;
|
||||
|
||||
OutputDebugStringA("DwellIndicator: SHOW Complete - Clean display with no artifacts\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the progress of the countdown indicator
|
||||
*
|
||||
* Updates the internal progress value and triggers a redraw.
|
||||
* Progress is clamped to [0.0, 1.0] range.
|
||||
*
|
||||
* @param progress Progress value from 0.0 (start) to 1.0 (complete)
|
||||
*/
|
||||
void DwellIndicatorImpl::UpdateProgress(float progress)
|
||||
{
|
||||
// Clamp progress to valid range [0.0, 1.0]
|
||||
if (progress < 0.0f) progress = 0.0f;
|
||||
if (progress > 1.0f) progress = 1.0f;
|
||||
|
||||
// Log progress updates for debugging
|
||||
char debugMsg[256];
|
||||
sprintf_s(debugMsg, sizeof(debugMsg),
|
||||
"DwellIndicator: UPDATE Progress %.3f -> %.3f - Window: %s, Visible: %s\n",
|
||||
m_progress, progress,
|
||||
m_hwnd ? "VALID" : "NULL",
|
||||
m_isVisible ? "TRUE" : "FALSE");
|
||||
OutputDebugStringA(debugMsg);
|
||||
|
||||
float oldProgress = m_progress;
|
||||
m_progress = progress;
|
||||
|
||||
// **GDI+ EXPERT FIX: Use UpdateLayeredWindow for artifact-free updates**
|
||||
if (m_hwnd && m_isVisible)
|
||||
{
|
||||
// Get window dimensions
|
||||
RECT rect;
|
||||
GetClientRect(m_hwnd, &rect);
|
||||
int windowSize = rect.right - rect.left;
|
||||
|
||||
// Create memory DC and bitmap for off-screen rendering
|
||||
HDC screenDC = GetDC(NULL);
|
||||
HDC memoryDC = CreateCompatibleDC(screenDC);
|
||||
|
||||
// Create 32-bit bitmap with alpha channel
|
||||
BITMAPINFO bmi = {};
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = windowSize;
|
||||
bmi.bmiHeader.biHeight = -windowSize; // Top-down DIB
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 32; // 32-bit with alpha
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
void* pvBits = nullptr;
|
||||
HBITMAP hBitmap = CreateDIBSection(screenDC, &bmi, DIB_RGB_COLORS, &pvBits, NULL, 0);
|
||||
|
||||
if (hBitmap && memoryDC)
|
||||
{
|
||||
HBITMAP oldBitmap = static_cast<HBITMAP>(SelectObject(memoryDC, hBitmap));
|
||||
|
||||
// **CRITICAL: Clear entire bitmap to transparent**
|
||||
const size_t bitmapSizeBytes = static_cast<size_t>(windowSize) * static_cast<size_t>(windowSize) * 4ULL;
|
||||
memset(pvBits, 0, bitmapSizeBytes);
|
||||
|
||||
// Create GDI+ Graphics object
|
||||
Graphics graphics(memoryDC);
|
||||
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
|
||||
graphics.SetCompositingMode(CompositingModeSourceOver);
|
||||
graphics.SetCompositingQuality(CompositingQualityHighQuality);
|
||||
|
||||
// **GDI+ EXPERT: Proper alpha channel clearing**
|
||||
graphics.Clear(Color(0, 0, 0, 0));
|
||||
|
||||
// Calculate drawing parameters
|
||||
const float dpiScale = GetDpiScale();
|
||||
const float centerX = windowSize / 2.0f;
|
||||
const float centerY = windowSize / 2.0f;
|
||||
const float radius = kIndicatorRadius * dpiScale;
|
||||
const float strokeWidth = kStrokeWidth * dpiScale;
|
||||
|
||||
// Get system colors
|
||||
DWORD accentColor = 0;
|
||||
BOOL isOpaque = FALSE;
|
||||
Color progressColor;
|
||||
Color backgroundCircleColor;
|
||||
|
||||
if (SUCCEEDED(DwmGetColorizationColor(&accentColor, &isOpaque)))
|
||||
{
|
||||
const BYTE a = 255;
|
||||
const BYTE r = (accentColor >> 16) & 0xFF;
|
||||
const BYTE g = (accentColor >> 8) & 0xFF;
|
||||
const BYTE b = accentColor & 0xFF;
|
||||
progressColor = Color(a, r, g, b);
|
||||
|
||||
const BYTE bgR = static_cast<BYTE>((r + 128) / 2);
|
||||
const BYTE bgG = static_cast<BYTE>((g + 128) / 2);
|
||||
const BYTE bgB = static_cast<BYTE>((b + 128) / 2);
|
||||
backgroundCircleColor = Color(80, bgR, bgG, bgB);
|
||||
}
|
||||
else
|
||||
{
|
||||
progressColor = Color(255, 0, 120, 215);
|
||||
backgroundCircleColor = Color(80, 160, 160, 160);
|
||||
}
|
||||
|
||||
// Create bounding rectangle
|
||||
RectF ellipseRect(centerX - radius, centerY - radius, radius * 2, radius * 2);
|
||||
|
||||
// Draw background circle
|
||||
Pen bgPen(backgroundCircleColor, strokeWidth * 0.6f);
|
||||
graphics.DrawEllipse(&bgPen, ellipseRect);
|
||||
|
||||
// Draw progress arc if we have progress
|
||||
if (m_progress > 0.0f)
|
||||
{
|
||||
Pen progressPen(progressColor, strokeWidth);
|
||||
progressPen.SetStartCap(LineCapRound);
|
||||
progressPen.SetEndCap(LineCapRound);
|
||||
|
||||
const float startAngle = -90.0f; // 12 o'clock
|
||||
const float sweepAngle = m_progress * 360.0f;
|
||||
|
||||
graphics.DrawArc(&progressPen, ellipseRect, startAngle, sweepAngle);
|
||||
|
||||
sprintf_s(debugMsg, sizeof(debugMsg),
|
||||
"DwellIndicator: Drew arc - Progress %.3f, SweepAngle %.1f degrees\n",
|
||||
m_progress, sweepAngle);
|
||||
OutputDebugStringA(debugMsg);
|
||||
}
|
||||
|
||||
// **GDI+ EXPERT: Update layered window with new content**
|
||||
POINT ptSrc = {0, 0};
|
||||
POINT ptDst = {m_currentX - windowSize/2, m_currentY - windowSize/2};
|
||||
SIZE size = {windowSize, windowSize};
|
||||
BLENDFUNCTION blend = {};
|
||||
blend.BlendOp = AC_SRC_OVER;
|
||||
blend.SourceConstantAlpha = 255;
|
||||
blend.AlphaFormat = AC_SRC_ALPHA;
|
||||
|
||||
BOOL updateResult = UpdateLayeredWindow(m_hwnd, screenDC, &ptDst, &size,
|
||||
memoryDC, &ptSrc, 0, &blend, ULW_ALPHA);
|
||||
|
||||
// Cleanup
|
||||
SelectObject(memoryDC, oldBitmap);
|
||||
DeleteObject(hBitmap);
|
||||
|
||||
sprintf_s(debugMsg, sizeof(debugMsg),
|
||||
"DwellIndicator: UPDATE Complete - UpdateLayeredWindow: %s (%.3f->%.3f)\n",
|
||||
updateResult ? "SUCCESS" : "FAILED", oldProgress, progress);
|
||||
OutputDebugStringA(debugMsg);
|
||||
}
|
||||
|
||||
DeleteDC(memoryDC);
|
||||
ReleaseDC(NULL, screenDC);
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputDebugStringA("DwellIndicator: UPDATE Skipped - window not ready or not visible\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Draw the circular progress indicator
|
||||
*
|
||||
* **NOTE: This method is now primarily for fallback WM_PAINT handling**
|
||||
* The main rendering is done through UpdateLayeredWindow in Show() and UpdateProgress()
|
||||
* for artifact-free display on layered windows.
|
||||
*
|
||||
* @param hdc Device context to draw into
|
||||
*/
|
||||
void DwellIndicatorImpl::DrawIndicator(HDC hdc)
|
||||
{
|
||||
// Log drawing calls for debugging
|
||||
char debugMsg[256];
|
||||
sprintf_s(debugMsg, sizeof(debugMsg),
|
||||
"DwellIndicator: DRAW (Fallback WM_PAINT) - Progress %.3f, Visible: %s\n",
|
||||
m_progress, m_isVisible ? "TRUE" : "FALSE");
|
||||
OutputDebugStringA(debugMsg);
|
||||
|
||||
// **GDI+ EXPERT: For WM_PAINT on layered windows, we need special handling**
|
||||
// Generally, UpdateLayeredWindow bypasses WM_PAINT, but this provides fallback
|
||||
|
||||
// Set up GDI+ graphics object with optimal settings for layered windows
|
||||
Graphics graphics(hdc);
|
||||
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
|
||||
graphics.SetCompositingMode(CompositingModeSourceOver);
|
||||
graphics.SetCompositingQuality(CompositingQualityHighQuality);
|
||||
graphics.SetPixelOffsetMode(PixelOffsetModeHighQuality);
|
||||
|
||||
// Get window client area dimensions
|
||||
RECT rect;
|
||||
GetClientRect(m_hwnd, &rect);
|
||||
const float centerX = (rect.right - rect.left) / 2.0f;
|
||||
const float centerY = (rect.bottom - rect.top) / 2.0f;
|
||||
|
||||
// **GDI+ EXPERT: Proper clearing for layered windows**
|
||||
// Use Graphics::Clear instead of FillRectangle for proper alpha handling
|
||||
graphics.Clear(Color(0, 0, 0, 0)); // Fully transparent background
|
||||
|
||||
// Apply DPI scaling for high-resolution displays
|
||||
const float dpiScale = GetDpiScale();
|
||||
const float radius = kIndicatorRadius * dpiScale;
|
||||
const float strokeWidth = kStrokeWidth * dpiScale;
|
||||
|
||||
// Get system accent color for theming consistency
|
||||
DWORD accentColor = 0;
|
||||
BOOL isOpaque = FALSE;
|
||||
Color progressColor;
|
||||
Color backgroundCircleColor;
|
||||
|
||||
if (SUCCEEDED(DwmGetColorizationColor(&accentColor, &isOpaque)))
|
||||
{
|
||||
// Extract RGB components from system accent color
|
||||
const BYTE a = 255;
|
||||
const BYTE r = (accentColor >> 16) & 0xFF;
|
||||
const BYTE g = (accentColor >> 8) & 0xFF;
|
||||
const BYTE b = accentColor & 0xFF;
|
||||
progressColor = Color(a, r, g, b);
|
||||
|
||||
// Create subtle background color
|
||||
const BYTE bgR = static_cast<BYTE>((r + 128) / 2);
|
||||
const BYTE bgG = static_cast<BYTE>((g + 128) / 2);
|
||||
const BYTE bgB = static_cast<BYTE>((b + 128) / 2);
|
||||
backgroundCircleColor = Color(80, bgR, bgG, bgB);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback colors
|
||||
progressColor = Color(255, 0, 120, 215);
|
||||
backgroundCircleColor = Color(80, 160, 160, 160);
|
||||
}
|
||||
|
||||
// Create bounding rectangle for the circle
|
||||
RectF ellipseRect(
|
||||
centerX - radius,
|
||||
centerY - radius,
|
||||
radius * 2,
|
||||
radius * 2
|
||||
);
|
||||
|
||||
// Draw background circle
|
||||
Pen bgPen(backgroundCircleColor, strokeWidth * 0.6f);
|
||||
graphics.DrawEllipse(&bgPen, ellipseRect);
|
||||
|
||||
// Draw progress arc only if we have measurable progress
|
||||
if (m_progress > 0.0f)
|
||||
{
|
||||
Pen progressPen(progressColor, strokeWidth);
|
||||
progressPen.SetStartCap(LineCapRound);
|
||||
progressPen.SetEndCap(LineCapRound);
|
||||
|
||||
const float startAngle = -90.0f; // 12 o'clock position
|
||||
const float sweepAngle = m_progress * 360.0f;
|
||||
|
||||
graphics.DrawArc(&progressPen, ellipseRect, startAngle, sweepAngle);
|
||||
|
||||
sprintf_s(debugMsg, sizeof(debugMsg),
|
||||
"DwellIndicator: DRAW Arc (Fallback) - Progress %.3f, SweepAngle %.1f degrees\n",
|
||||
m_progress, sweepAngle);
|
||||
OutputDebugStringA(debugMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputDebugStringA("DwellIndicator: DRAW (Fallback) - No arc (progress 0.0)\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Hide the indicator window
|
||||
*
|
||||
* Makes the window invisible but keeps it alive for potential re-showing.
|
||||
* Also resets the progress state to ensure clean restart on next show.
|
||||
*/
|
||||
void DwellIndicatorImpl::Hide()
|
||||
{
|
||||
if (m_hwnd && m_isVisible)
|
||||
{
|
||||
char debugMsg[256];
|
||||
sprintf_s(debugMsg, sizeof(debugMsg),
|
||||
"DwellIndicator: HIDE - Progress %.3f->0.0, Visible: %s->FALSE\n",
|
||||
m_progress, m_isVisible ? "TRUE" : "FALSE");
|
||||
OutputDebugStringA(debugMsg);
|
||||
|
||||
// **GDI+ EXPERT FIX: Proper layered window hiding**
|
||||
// Clear the layered window content before hiding to prevent artifacts
|
||||
RECT rect;
|
||||
GetClientRect(m_hwnd, &rect);
|
||||
int windowSize = rect.right - rect.left;
|
||||
|
||||
if (windowSize > 0)
|
||||
{
|
||||
HDC screenDC = GetDC(NULL);
|
||||
HDC memoryDC = CreateCompatibleDC(screenDC);
|
||||
|
||||
// Create transparent bitmap
|
||||
BITMAPINFO bmi = {};
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = windowSize;
|
||||
bmi.bmiHeader.biHeight = -windowSize;
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 32;
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
void* pvBits = nullptr;
|
||||
HBITMAP hBitmap = CreateDIBSection(screenDC, &bmi, DIB_RGB_COLORS, &pvBits, NULL, 0);
|
||||
|
||||
if (hBitmap && memoryDC)
|
||||
{
|
||||
HBITMAP oldBitmap = static_cast<HBITMAP>(SelectObject(memoryDC, hBitmap));
|
||||
|
||||
// Clear to fully transparent
|
||||
// Fix for C26451: Use safe arithmetic to prevent overflow
|
||||
const size_t bitmapSizeBytes = static_cast<size_t>(windowSize) * static_cast<size_t>(windowSize) * 4ULL;
|
||||
memset(pvBits, 0, bitmapSizeBytes);
|
||||
|
||||
// Update layered window with transparent content
|
||||
POINT ptSrc = {0, 0};
|
||||
POINT ptDst = {m_currentX - windowSize/2, m_currentY - windowSize/2};
|
||||
SIZE size = {windowSize, windowSize};
|
||||
BLENDFUNCTION blend = {};
|
||||
blend.BlendOp = AC_SRC_OVER;
|
||||
blend.SourceConstantAlpha = 0; // Make completely transparent
|
||||
blend.AlphaFormat = AC_SRC_ALPHA;
|
||||
|
||||
UpdateLayeredWindow(m_hwnd, screenDC, &ptDst, &size,
|
||||
memoryDC, &ptSrc, 0, &blend, ULW_ALPHA);
|
||||
|
||||
SelectObject(memoryDC, oldBitmap);
|
||||
DeleteObject(hBitmap);
|
||||
}
|
||||
|
||||
DeleteDC(memoryDC);
|
||||
ReleaseDC(NULL, screenDC);
|
||||
}
|
||||
|
||||
// Now hide the window
|
||||
ShowWindow(m_hwnd, SW_HIDE);
|
||||
m_isVisible = false;
|
||||
|
||||
// **CRITICAL: Reset progress when hiding to ensure clean state for next show**
|
||||
m_progress = 0.0f;
|
||||
|
||||
OutputDebugStringA("DwellIndicator: HIDE Complete - Layered window cleared and hidden\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputDebugStringA("DwellIndicator: HIDE Skipped - already hidden or invalid window\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clean up all indicator resources
|
||||
*
|
||||
* Hides and destroys the window, shuts down GDI+.
|
||||
* Called during module shutdown or when indicator is no longer needed.
|
||||
*/
|
||||
void DwellIndicatorImpl::Cleanup()
|
||||
{
|
||||
Hide(); // Hide window first
|
||||
|
||||
// Destroy the window and clean up Windows resources
|
||||
if (m_hwnd)
|
||||
{
|
||||
DestroyWindow(m_hwnd);
|
||||
m_hwnd = NULL;
|
||||
}
|
||||
|
||||
// Shutdown GDI+ graphics system
|
||||
if (m_gdiplusToken != 0)
|
||||
{
|
||||
GdiplusShutdown(m_gdiplusToken);
|
||||
m_gdiplusToken = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get DPI scaling factor for the current display
|
||||
*
|
||||
* @return DPI scale factor (1.0 = 96 DPI, 1.25 = 120 DPI, etc.)
|
||||
*/
|
||||
float DwellIndicatorImpl::GetDpiScale() const
|
||||
{
|
||||
if (!m_hwnd) return 1.0f; // Default scale if no window
|
||||
return static_cast<float>(GetDpiForWindow(m_hwnd)) / 96.0f;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DwellIndicator Public Interface Implementation
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Constructor - creates the implementation instance
|
||||
*/
|
||||
DwellIndicator::DwellIndicator() : m_impl(std::make_unique<DwellIndicatorImpl>())
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor - ensures cleanup of resources
|
||||
*/
|
||||
DwellIndicator::~DwellIndicator()
|
||||
{
|
||||
if (m_impl)
|
||||
{
|
||||
m_impl->Cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the indicator system
|
||||
* @return true if successful, false on failure
|
||||
*/
|
||||
bool DwellIndicator::Initialize()
|
||||
{
|
||||
return m_impl ? m_impl->Initialize() : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Show indicator at cursor position
|
||||
* @param x Cursor X coordinate
|
||||
* @param y Cursor Y coordinate
|
||||
*/
|
||||
void DwellIndicator::Show(int x, int y)
|
||||
{
|
||||
if (m_impl) m_impl->Show(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update countdown progress
|
||||
* @param progress Progress from 0.0 to 1.0
|
||||
*/
|
||||
void DwellIndicator::UpdateProgress(float progress)
|
||||
{
|
||||
if (m_impl) m_impl->UpdateProgress(progress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Hide the indicator
|
||||
*/
|
||||
void DwellIndicator::Hide()
|
||||
{
|
||||
if (m_impl) m_impl->Hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clean up all resources
|
||||
*/
|
||||
void DwellIndicator::Cleanup()
|
||||
{
|
||||
if (m_impl) m_impl->Cleanup();
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/**
|
||||
* @file DwellIndicator.h
|
||||
* @brief Visual countdown indicator for DwellCursor module
|
||||
*
|
||||
* This header defines the interface for the visual feedback system that shows
|
||||
* users when a dwell click is about to occur. The indicator appears as a
|
||||
* circular progress arc that fills clockwise during the countdown period.
|
||||
*
|
||||
* Key Features:
|
||||
* - Transparent overlay window that doesn't interfere with normal interaction
|
||||
* - System accent color theming for consistency with Windows
|
||||
* - DPI-aware rendering for high-resolution displays
|
||||
* - Smooth progress animation updated at 30 FPS
|
||||
* - Automatic positioning centered on cursor location
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <memory>
|
||||
|
||||
// Forward declaration to hide implementation details (Pimpl idiom)
|
||||
class DwellIndicatorImpl;
|
||||
|
||||
/**
|
||||
* @brief Visual countdown indicator for dwell cursor functionality
|
||||
*
|
||||
* This class provides a clean interface for showing a circular progress
|
||||
* indicator during dwell cursor countdown. It uses the Pimpl (Pointer to
|
||||
* Implementation) idiom to hide all Windows/GDI+ dependencies from the header.
|
||||
*
|
||||
* Usage Pattern:
|
||||
* 1. Create instance: DwellIndicator indicator;
|
||||
* 2. Initialize: indicator.Initialize();
|
||||
* 3. Show at cursor: indicator.Show(x, y);
|
||||
* 4. Update progress: indicator.UpdateProgress(0.5f); // 50% complete
|
||||
* 5. Hide when done: indicator.Hide();
|
||||
* 6. Cleanup: indicator.Cleanup(); // or let destructor handle it
|
||||
*
|
||||
* Thread Safety:
|
||||
* - All methods must be called from the same thread (UI thread)
|
||||
* - Progress updates can be called frequently (30ms intervals recommended)
|
||||
* - Hide/Show calls are safe to call multiple times
|
||||
*/
|
||||
class DwellIndicator
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor - creates implementation instance
|
||||
*
|
||||
* Note: This only creates the object, call Initialize() before use.
|
||||
*/
|
||||
DwellIndicator();
|
||||
|
||||
/**
|
||||
* @brief Destructor - ensures proper cleanup
|
||||
*
|
||||
* Automatically calls Cleanup() if not already called.
|
||||
*/
|
||||
~DwellIndicator();
|
||||
|
||||
/**
|
||||
* @brief Initialize the indicator system
|
||||
*
|
||||
* Must be called before any other operations. Sets up:
|
||||
* - GDI+ graphics system
|
||||
* - Transparent overlay window
|
||||
* - DPI awareness
|
||||
*
|
||||
* @return true if initialization successful, false on failure
|
||||
*/
|
||||
bool Initialize();
|
||||
|
||||
/**
|
||||
* @brief Show the indicator at specified screen coordinates
|
||||
*
|
||||
* Displays the circular indicator centered on the given position.
|
||||
* If already visible, moves to new position. Window is sized
|
||||
* automatically based on DPI and indicator radius.
|
||||
*
|
||||
* @param x Screen X coordinate in pixels
|
||||
* @param y Screen Y coordinate in pixels
|
||||
*/
|
||||
void Show(int x, int y);
|
||||
|
||||
/**
|
||||
* @brief Update the countdown progress
|
||||
*
|
||||
* Updates the progress arc to show how much of the dwell delay
|
||||
* has elapsed. Can be called frequently for smooth animation.
|
||||
*
|
||||
* @param progress Progress value from 0.0 (start) to 1.0 (complete)
|
||||
* Values outside this range are automatically clamped
|
||||
*/
|
||||
void UpdateProgress(float progress);
|
||||
|
||||
/**
|
||||
* @brief Hide the indicator
|
||||
*
|
||||
* Makes the indicator invisible but keeps resources allocated
|
||||
* for potential re-showing. Safe to call multiple times.
|
||||
*/
|
||||
void Hide();
|
||||
|
||||
/**
|
||||
* @brief Clean up all resources
|
||||
*
|
||||
* Destroys the window, shuts down GDI+, releases all resources.
|
||||
* Called automatically by destructor if not called explicitly.
|
||||
* After calling this, Initialize() must be called again before reuse.
|
||||
*/
|
||||
void Cleanup();
|
||||
|
||||
// Disable copy constructor and assignment operator
|
||||
// The indicator manages Windows resources that shouldn't be copied
|
||||
DwellIndicator(const DwellIndicator&) = delete;
|
||||
DwellIndicator& operator=(const DwellIndicator&) = delete;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Pointer to implementation (Pimpl idiom)
|
||||
*
|
||||
* This hides all Windows/GDI+ implementation details from the header,
|
||||
* reducing compile dependencies and keeping the interface clean.
|
||||
*/
|
||||
std::unique_ptr<DwellIndicatorImpl> m_impl;
|
||||
};
|
||||
@@ -1,595 +0,0 @@
|
||||
#include "pch.h"
|
||||
#include <common/SettingsAPI/settings_objects.h>
|
||||
#include <interface/powertoy_module_interface.h>
|
||||
#include "trace.h"
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include "DwellIndicator.h"
|
||||
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
|
||||
namespace
|
||||
{
|
||||
// JSON configuration keys for settings persistence
|
||||
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
|
||||
const wchar_t JSON_KEY_VALUE[] = L"value";
|
||||
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"activation_shortcut";
|
||||
const wchar_t JSON_KEY_DELAY_TIME_MS[] = L"delay_time_ms";
|
||||
const wchar_t JSON_KEY_SETTLE_TIME_SECONDS[] = L"settle_time_seconds";
|
||||
|
||||
// Update interval for the visual indicator (in milliseconds)
|
||||
// 30ms gives ~33 FPS for smooth animation without excessive CPU usage
|
||||
constexpr DWORD kIndicatorUpdateIntervalMs = 30;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a left mouse click via Windows input system
|
||||
*
|
||||
* Simulates a complete left click (down + up) at the current cursor position.
|
||||
* This is the core functionality that gets triggered after the dwell delay.
|
||||
*/
|
||||
static void SendLeftClick()
|
||||
{
|
||||
INPUT inputs[2]{};
|
||||
|
||||
// First input: Left mouse button down
|
||||
inputs[0].type = INPUT_MOUSE;
|
||||
inputs[0].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
|
||||
|
||||
// Second input: Left mouse button up
|
||||
inputs[1].type = INPUT_MOUSE;
|
||||
inputs[1].mi.dwFlags = MOUSEEVENTF_LEFTUP;
|
||||
|
||||
// Send both inputs to simulate a complete click
|
||||
SendInput(2, inputs, sizeof(INPUT));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main DwellCursor PowerToy module implementation
|
||||
*
|
||||
* This class implements the dwell cursor functionality:
|
||||
* - Monitors mouse movement continuously
|
||||
* - Detects when mouse becomes stationary
|
||||
* - Shows visual countdown indicator
|
||||
* - Triggers left click after configured delay
|
||||
* - Provides hotkey toggle for enable/disable
|
||||
*
|
||||
* State Management:
|
||||
* - m_enabled: Whether module is active (controlled by PowerToys settings)
|
||||
* - m_armed: Whether dwell clicking is currently armed (toggled by hotkey)
|
||||
* - firedForThisStationary: Prevents multiple clicks during one stationary period
|
||||
*/
|
||||
class DwellCursorModule : public PowertoyModuleIface
|
||||
{
|
||||
private:
|
||||
// Core module state - SINGLE DECLARATIONS ONLY
|
||||
bool m_enabled{ false }; // Module enabled/disabled state
|
||||
Hotkey m_activationHotkey{}; // Hotkey for toggling armed state
|
||||
|
||||
// Configuration settings - SINGLE DECLARATIONS ONLY
|
||||
std::atomic<int> m_delayMs{ 1000 }; // Dwell delay in milliseconds (500-10000ms)
|
||||
std::atomic<int> m_settleTimeSeconds{ 1 }; // Settle time in seconds (1-5s)
|
||||
|
||||
// Runtime state management - SINGLE DECLARATIONS ONLY
|
||||
std::atomic<bool> m_armed{ true }; // Whether dwell clicking is armed
|
||||
std::atomic<bool> m_stop{ false }; // Signal to stop the worker thread
|
||||
std::thread m_worker; // Background thread for mouse monitoring
|
||||
|
||||
// Visual feedback system
|
||||
std::unique_ptr<DwellIndicator> m_indicator;
|
||||
|
||||
// Progress tracking - Use member variable instead of static
|
||||
float m_lastProgress{ -1.0f }; // Last progress value for change detection
|
||||
|
||||
// Mouse movement sensitivity (pixels) - SINGLE DECLARATION ONLY
|
||||
// Movement within this threshold is considered "stationary"
|
||||
static constexpr int kMoveThresholdPx = 5;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor - Initialize the DwellCursor module
|
||||
*
|
||||
* Sets up logging, loads settings, configures default hotkey,
|
||||
* and creates the visual indicator instance.
|
||||
*/
|
||||
DwellCursorModule()
|
||||
{
|
||||
// Initialize logging system for debugging and telemetry
|
||||
LoggerHelpers::init_logger(L"DwellCursor", L"ModuleInterface", "dwell-cursor");
|
||||
Logger::trace(L"DwellCursor: Constructor called");
|
||||
|
||||
// Load saved settings from PowerToys configuration
|
||||
init_settings();
|
||||
|
||||
// Set default hotkey if not configured: Win+Alt+D
|
||||
if (m_activationHotkey.key == 0)
|
||||
{
|
||||
m_activationHotkey.win = true; // Windows key required
|
||||
m_activationHotkey.alt = true; // Alt key required
|
||||
m_activationHotkey.key = 'D'; // D key
|
||||
}
|
||||
|
||||
// Create visual indicator instance (but don't initialize yet)
|
||||
m_indicator = std::make_unique<DwellIndicator>();
|
||||
Logger::trace(L"DwellCursor: Constructor completed");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor cleanup
|
||||
*/
|
||||
virtual void destroy() override
|
||||
{
|
||||
disable(); // Stop all activity
|
||||
delete this;
|
||||
}
|
||||
|
||||
// PowerToy identification methods
|
||||
virtual const wchar_t* get_name() override { return L"DwellCursor"; }
|
||||
virtual const wchar_t* get_key() override { return L"DwellCursor"; }
|
||||
|
||||
/**
|
||||
* @brief Get module configuration for PowerToys settings UI
|
||||
*/
|
||||
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
|
||||
{
|
||||
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
|
||||
PowerToysSettings::Settings settings(hinstance, get_name());
|
||||
return settings.serialize_to_buffer(buffer, buffer_size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply new configuration from PowerToys settings UI
|
||||
*/
|
||||
virtual void set_config(const wchar_t* config) override
|
||||
{
|
||||
try
|
||||
{
|
||||
PowerToysSettings::PowerToyValues values = PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
|
||||
parse_settings(values);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Ignore configuration errors to prevent crashes
|
||||
}
|
||||
}
|
||||
|
||||
virtual void call_custom_action(const wchar_t* /*action*/) override {}
|
||||
|
||||
/**
|
||||
* @brief Enable the DwellCursor module
|
||||
*
|
||||
* This is called when:
|
||||
* 1. PowerToys starts up (if module is enabled in settings)
|
||||
* 2. User enables the module via PowerToys settings UI
|
||||
*
|
||||
* Actions performed:
|
||||
* 1. Initialize visual indicator system
|
||||
* 2. Start background mouse monitoring thread
|
||||
* 3. Begin dwell detection
|
||||
*/
|
||||
virtual void enable() override
|
||||
{
|
||||
if (m_enabled)
|
||||
{
|
||||
Logger::trace(L"DwellCursor: Already enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::trace(L"DwellCursor: Enabling module");
|
||||
m_enabled = true;
|
||||
m_stop = false;
|
||||
|
||||
// Initialize the visual indicator system (GDI+, window creation)
|
||||
if (m_indicator)
|
||||
{
|
||||
if (!m_indicator->Initialize())
|
||||
{
|
||||
Logger::trace(L"DwellCursor: Failed to initialize visual indicator");
|
||||
// Continue without visual indicator - core functionality still works
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::trace(L"DwellCursor: Visual indicator initialized successfully");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::trace(L"DwellCursor: No indicator instance available");
|
||||
}
|
||||
|
||||
// Start the mouse monitoring thread
|
||||
m_worker = std::thread([this]() { this->RunLoop(); });
|
||||
Logger::trace(L"DwellCursor: Module enabled and worker thread started");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disable the DwellCursor module
|
||||
*
|
||||
* This is called when:
|
||||
* 1. PowerToys shuts down
|
||||
* 2. User disables the module via PowerToys settings UI
|
||||
*
|
||||
* Actions performed:
|
||||
* 1. Stop mouse monitoring thread
|
||||
* 2. Hide any visible indicator
|
||||
* 3. Clean up visual indicator resources
|
||||
*/
|
||||
virtual void disable() override
|
||||
{
|
||||
if (!m_enabled)
|
||||
{
|
||||
Logger::trace(L"DwellCursor: Already disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::trace(L"DwellCursor: Disabling module");
|
||||
m_enabled = false;
|
||||
m_stop = true;
|
||||
|
||||
// Wait for worker thread to finish
|
||||
if (m_worker.joinable()) m_worker.join();
|
||||
|
||||
// Clean up visual indicator resources
|
||||
if (m_indicator)
|
||||
{
|
||||
m_indicator->Cleanup();
|
||||
}
|
||||
|
||||
Logger::trace(L"DwellCursor: Module disabled");
|
||||
}
|
||||
|
||||
virtual bool is_enabled() override { return m_enabled; }
|
||||
virtual bool is_enabled_by_default() const override { return false; } // User must explicitly enable
|
||||
|
||||
/**
|
||||
* @brief Report hotkeys to PowerToys for registration
|
||||
*/
|
||||
virtual size_t get_hotkeys(Hotkey* buffer, size_t buffer_size) override
|
||||
{
|
||||
if (buffer && buffer_size >= 1)
|
||||
{
|
||||
buffer[0] = m_activationHotkey;
|
||||
}
|
||||
return 1; // We have exactly one hotkey
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle hotkey press events
|
||||
*
|
||||
* The hotkey toggles the "armed" state:
|
||||
* - Armed: Dwell clicking is active, countdown indicator shows
|
||||
* - Disarmed: No dwell clicking, indicator hidden
|
||||
*
|
||||
* This allows users to temporarily disable dwell clicking without
|
||||
* going into settings (e.g., when typing or doing precise work).
|
||||
*
|
||||
* @param hotkeyId Index of the pressed hotkey (we only have one)
|
||||
* @return true if handled, false otherwise
|
||||
*/
|
||||
virtual bool on_hotkey(size_t hotkeyId) override
|
||||
{
|
||||
// Handle our single registered hotkey
|
||||
if (hotkeyId == 0)
|
||||
{
|
||||
// Toggle armed state (enabled/disabled functionality)
|
||||
m_armed = !m_armed.load();
|
||||
|
||||
// Hide indicator immediately when disarming or when module disabled
|
||||
if ((!m_armed || !m_enabled) && m_indicator)
|
||||
{
|
||||
m_indicator->Hide();
|
||||
}
|
||||
|
||||
Logger::trace(L"DwellCursor: Hotkey pressed, armed={}, enabled={}", m_armed.load(), m_enabled);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Load settings from PowerToys configuration files
|
||||
*/
|
||||
void init_settings()
|
||||
{
|
||||
try
|
||||
{
|
||||
PowerToysSettings::PowerToyValues settings = PowerToysSettings::PowerToyValues::load_from_settings_file(get_key());
|
||||
parse_settings(settings);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Use default settings if loading fails
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parse and apply settings from JSON configuration
|
||||
*
|
||||
* Extracts:
|
||||
* - Activation hotkey configuration
|
||||
* - Dwell delay time (with validation)
|
||||
*/
|
||||
void parse_settings(PowerToysSettings::PowerToyValues& settings)
|
||||
{
|
||||
auto obj = settings.get_raw_json();
|
||||
if (!obj.GetView().Size()) return;
|
||||
|
||||
// Parse hotkey configuration
|
||||
try
|
||||
{
|
||||
auto jsonHotkey = obj.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT);
|
||||
auto hk = PowerToysSettings::HotkeyObject::from_json(jsonHotkey);
|
||||
m_activationHotkey = {};
|
||||
m_activationHotkey.win = hk.win_pressed();
|
||||
m_activationHotkey.ctrl = hk.ctrl_pressed();
|
||||
m_activationHotkey.shift = hk.shift_pressed();
|
||||
m_activationHotkey.alt = hk.alt_pressed();
|
||||
m_activationHotkey.key = static_cast<unsigned char>(hk.get_code());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Keep default hotkey if parsing fails
|
||||
}
|
||||
|
||||
// Parse dwell delay setting
|
||||
try
|
||||
{
|
||||
auto jsonDelay = obj.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_DELAY_TIME_MS);
|
||||
int v = static_cast<int>(jsonDelay.GetNamedNumber(JSON_KEY_VALUE));
|
||||
|
||||
// Validate delay range: 0.5 seconds to 10 seconds
|
||||
if (v < 500) v = 500; // Minimum 0.5 seconds
|
||||
if (v > 10000) v = 10000; // Maximum 10 seconds
|
||||
|
||||
m_delayMs = v;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Keep default delay if parsing fails
|
||||
}
|
||||
|
||||
// Parse settle time setting
|
||||
try
|
||||
{
|
||||
auto jsonSettleTime = obj.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SETTLE_TIME_SECONDS);
|
||||
int v = static_cast<int>(jsonSettleTime.GetNamedNumber(JSON_KEY_VALUE));
|
||||
|
||||
// Validate settle time range: 1 to 5 seconds
|
||||
if (v < 1) v = 1; // Minimum 1 second
|
||||
if (v > 5) v = 5; // Maximum 5 seconds
|
||||
|
||||
m_settleTimeSeconds = v;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Keep default settle time if parsing fails
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if two points are within movement threshold
|
||||
*
|
||||
* @param a First coordinate
|
||||
* @param b Second coordinate
|
||||
* @param thr Threshold in pixels
|
||||
* @return true if coordinates are within threshold (considered "near")
|
||||
*/
|
||||
static bool Near(int a, int b, int thr) { return (abs(a - b) <= thr); }
|
||||
|
||||
/**
|
||||
* @brief Main mouse monitoring loop (runs in background thread)
|
||||
*
|
||||
* This is the core logic that runs continuously while the module is enabled:
|
||||
*
|
||||
* State Machine:
|
||||
* 1. Monitor mouse position every 50ms (20 Hz)
|
||||
* 2. If mouse moves > threshold: Reset timer, hide indicator
|
||||
* 3. If mouse stationary for SETTLE_TIME: Show indicator
|
||||
* 4. If mouse stationary for SETTLE_TIME + dwell delay: Send click
|
||||
*
|
||||
* CRITICAL: This method handles ALL progress reset logic
|
||||
*/
|
||||
void RunLoop()
|
||||
{
|
||||
constexpr DWORD ACTIVE_POLL_INTERVAL = 50; // 50ms = 20 Hz monitoring when active
|
||||
constexpr DWORD INACTIVE_POLL_INTERVAL = 200; // 200ms when disabled
|
||||
|
||||
// Initialize tracking variables
|
||||
POINT last{}; // Last recorded mouse position
|
||||
GetCursorPos(&last); // Get initial position
|
||||
DWORD lastMove = GetTickCount(); // Time of last movement
|
||||
bool firedForThisStationary = false; // Prevents multiple clicks during one stationary period
|
||||
bool indicatorShown = false; // Current indicator visibility state
|
||||
DWORD lastIndicatorUpdate = 0; // Last time we updated indicator progress
|
||||
|
||||
Logger::trace(L"DwellCursor: RunLoop started with 50ms polling and {}s configurable settle time, enabled={}, armed={}",
|
||||
m_settleTimeSeconds.load(), m_enabled, m_armed.load());
|
||||
|
||||
// Main monitoring loop - continues until module shutdown
|
||||
while (!m_stop)
|
||||
{
|
||||
// Performance optimization: When module disabled, sleep longer and skip processing
|
||||
if (!m_enabled)
|
||||
{
|
||||
// Hide any visible indicator when disabled
|
||||
if (indicatorShown && m_indicator)
|
||||
{
|
||||
m_indicator->Hide();
|
||||
indicatorShown = false;
|
||||
m_lastProgress = -1.0f; // Reset progress tracking
|
||||
}
|
||||
Sleep(INACTIVE_POLL_INTERVAL); // Sleep 200ms when disabled
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get current mouse position and time
|
||||
POINT p{};
|
||||
GetCursorPos(&p);
|
||||
DWORD currentTime = GetTickCount();
|
||||
|
||||
// Check if mouse has moved beyond our threshold
|
||||
if (!Near(p.x, last.x, kMoveThresholdPx) || !Near(p.y, last.y, kMoveThresholdPx))
|
||||
{
|
||||
// MOUSE MOVEMENT DETECTED - RESET ALL STATE
|
||||
|
||||
Logger::trace(L"DwellCursor: Mouse movement detected, resetting state");
|
||||
|
||||
// Update tracking variables
|
||||
last = p; // Record new position
|
||||
lastMove = currentTime; // Record movement time
|
||||
firedForThisStationary = false; // Re-arm for next stationary period
|
||||
|
||||
// CRITICAL: Hide indicator and reset progress immediately on movement
|
||||
if (indicatorShown && m_indicator)
|
||||
{
|
||||
m_indicator->Hide();
|
||||
indicatorShown = false;
|
||||
}
|
||||
// Reset progress tracking for next stationary period
|
||||
m_lastProgress = -1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// MOUSE IS STATIONARY - Process dwell logic
|
||||
|
||||
// Check if we should process dwell logic
|
||||
if (m_enabled && m_armed && !firedForThisStationary)
|
||||
{
|
||||
// Calculate how long mouse has been stationary
|
||||
DWORD elapsed = currentTime - lastMove;
|
||||
DWORD delayMs = static_cast<DWORD>(m_delayMs.load());
|
||||
DWORD settleTimeMs = static_cast<DWORD>(m_settleTimeSeconds.load() * 1000); // Convert seconds to milliseconds
|
||||
DWORD totalTimeRequired = settleTimeMs + delayMs; // Settle time + dwell delay
|
||||
|
||||
if (elapsed >= totalTimeRequired)
|
||||
{
|
||||
// SETTLE TIME + DWELL DELAY COMPLETED - TRIGGER CLICK
|
||||
|
||||
Logger::trace(L"DwellCursor: Triggering click after {}ms total ({}ms settle + {}ms dwell)", elapsed, settleTimeMs, delayMs);
|
||||
|
||||
// Hide indicator before clicking
|
||||
if (indicatorShown && m_indicator)
|
||||
{
|
||||
m_indicator->Hide();
|
||||
indicatorShown = false;
|
||||
}
|
||||
|
||||
// Reset progress tracking
|
||||
m_lastProgress = -1.0f;
|
||||
|
||||
SendLeftClick(); // Send the mouse click
|
||||
firedForThisStationary = true; // Prevent additional clicks
|
||||
}
|
||||
else if (elapsed >= settleTimeMs)
|
||||
{
|
||||
// SETTLE TIME COMPLETED - START/UPDATE COUNTDOWN INDICATOR
|
||||
|
||||
DWORD dwellElapsed = elapsed - settleTimeMs; // Time since settle completed
|
||||
|
||||
// Show indicator if not already visible
|
||||
if (!indicatorShown && m_indicator)
|
||||
{
|
||||
Logger::trace(L"DwellCursor: Settle time ({}ms) completed, showing NEW indicator at ({}, {}) - dwellElapsed={}ms, delayMs={}",
|
||||
settleTimeMs, p.x, p.y, dwellElapsed, delayMs);
|
||||
|
||||
// CRITICAL: Force complete reset before showing
|
||||
m_lastProgress = -1.0f;
|
||||
|
||||
m_indicator->Show(p.x, p.y); // This internally resets indicator progress to 0.0
|
||||
indicatorShown = true;
|
||||
lastIndicatorUpdate = currentTime; // Reset update timer when showing
|
||||
}
|
||||
|
||||
// Update indicator progress ONLY at throttled intervals
|
||||
if (indicatorShown && (currentTime - lastIndicatorUpdate >= ACTIVE_POLL_INTERVAL))
|
||||
{
|
||||
// Calculate progress as percentage: 0.0 = just started, 1.0 = almost complete
|
||||
float newProgress = static_cast<float>(dwellElapsed) / static_cast<float>(delayMs);
|
||||
if (newProgress > 1.0f) newProgress = 1.0f; // Clamp to prevent over-draw
|
||||
|
||||
// Only update if progress changed significantly (at least 3% or 0.03) OR forced reset
|
||||
if (abs(newProgress - m_lastProgress) >= 0.03f || m_lastProgress < 0.0f)
|
||||
{
|
||||
Logger::trace(L"DwellCursor: Updating progress from {:.2f} to {:.2f} (dwellElapsed={}ms)",
|
||||
m_lastProgress, newProgress, dwellElapsed);
|
||||
|
||||
if (m_indicator)
|
||||
{
|
||||
m_indicator->UpdateProgress(newProgress);
|
||||
}
|
||||
m_lastProgress = newProgress;
|
||||
lastIndicatorUpdate = currentTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
// else: Still in settle time, do nothing (no indicator shown)
|
||||
}
|
||||
else if (indicatorShown && m_indicator)
|
||||
{
|
||||
// STATIONARY BUT CONDITIONS NOT MET - HIDE INDICATOR
|
||||
// This happens when: not enabled, not armed, or already fired
|
||||
Logger::trace(L"DwellCursor: Hiding indicator - conditions not met (enabled={}, armed={}, fired={})",
|
||||
m_enabled, m_armed.load(), firedForThisStationary);
|
||||
|
||||
m_indicator->Hide();
|
||||
indicatorShown = false;
|
||||
// Reset progress tracking
|
||||
m_lastProgress = -1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Sleep appropriate interval based on activity (20 Hz when active)
|
||||
Sleep(ACTIVE_POLL_INTERVAL);
|
||||
}
|
||||
|
||||
// THREAD SHUTDOWN CLEANUP
|
||||
Logger::trace(L"DwellCursor: RunLoop shutdown - cleaning up");
|
||||
|
||||
// Hide indicator when stopping
|
||||
if (indicatorShown && m_indicator)
|
||||
{
|
||||
m_indicator->Hide();
|
||||
}
|
||||
|
||||
// Final progress reset
|
||||
m_lastProgress = -1.0f;
|
||||
|
||||
Logger::trace(L"DwellCursor: RunLoop ended");
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// DLL Entry Points
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief DLL entry point for Windows module loading
|
||||
*/
|
||||
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
Trace::RegisterProvider(); // Initialize ETW tracing
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
Trace::UnregisterProvider(); // Cleanup ETW tracing
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief PowerToys module factory function
|
||||
*
|
||||
* This is the entry point called by PowerToys runner to create an instance
|
||||
* of our module. The runner will call this once during startup.
|
||||
*
|
||||
* @return New instance of DwellCursorModule
|
||||
*/
|
||||
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
|
||||
{
|
||||
return new DwellCursorModule();
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
|
||||
</packages>
|
||||
@@ -1 +0,0 @@
|
||||
#include "pch.h"
|
||||
@@ -1,20 +0,0 @@
|
||||
#pragma once
|
||||
#define NOMINMAX
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <windowsx.h>
|
||||
#include <ShellScalingApi.h>
|
||||
#include <dwmapi.h>
|
||||
#include <stdint.h>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <common/SettingsAPI/settings_objects.h>
|
||||
#include <interface/powertoy_module_interface.h>
|
||||
#include <common/logger/logger.h>
|
||||
#include <common/utils/logger_helper.h>
|
||||
@@ -1,2 +0,0 @@
|
||||
#pragma once
|
||||
#define IDS_MODULE_NAME 1001
|
||||
@@ -1,2 +0,0 @@
|
||||
#include "pch.h"
|
||||
#include "trace.h"
|
||||
@@ -1,2 +0,0 @@
|
||||
#pragma once
|
||||
namespace Trace { inline void RegisterProvider(){} inline void UnregisterProvider(){} }
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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 Microsoft.CmdPal.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public class GalleryGridPropertiesViewModel : IGridPropertiesViewModel
|
||||
{
|
||||
private readonly ExtensionObject<IGalleryGridLayout> _model;
|
||||
|
||||
public GalleryGridPropertiesViewModel(IGalleryGridLayout galleryGridLayout)
|
||||
{
|
||||
_model = new(galleryGridLayout);
|
||||
}
|
||||
|
||||
public bool ShowTitle { get; set; }
|
||||
|
||||
public bool ShowSubtitle { get; set; }
|
||||
|
||||
public void InitializeProperties()
|
||||
{
|
||||
var model = _model.Unsafe;
|
||||
if (model is null)
|
||||
{
|
||||
return; // throw?
|
||||
}
|
||||
|
||||
ShowTitle = model.ShowTitle;
|
||||
ShowSubtitle = model.ShowSubtitle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// 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.
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public interface IGridPropertiesViewModel
|
||||
{
|
||||
void InitializeProperties();
|
||||
}
|
||||
@@ -45,6 +45,10 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
(!_isFetching) &&
|
||||
IsLoading == false;
|
||||
|
||||
public bool IsGridView { get; private set; }
|
||||
|
||||
public IGridPropertiesViewModel? GridProperties { get; private set; }
|
||||
|
||||
// Remember - "observable" properties from the model (via PropChanged)
|
||||
// cannot be marked [ObservableProperty]
|
||||
public bool ShowDetails { get; private set; }
|
||||
@@ -516,6 +520,13 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
|
||||
_isDynamic = model is IDynamicListPage;
|
||||
|
||||
IsGridView = model.GridProperties is not null;
|
||||
UpdateProperty(nameof(IsGridView));
|
||||
|
||||
GridProperties = LoadGridPropertiesViewModel(model.GridProperties);
|
||||
GridProperties?.InitializeProperties();
|
||||
UpdateProperty(nameof(GridProperties));
|
||||
|
||||
ShowDetails = model.ShowDetails;
|
||||
UpdateProperty(nameof(ShowDetails));
|
||||
|
||||
@@ -537,9 +548,27 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
model.ItemsChanged += Model_ItemsChanged;
|
||||
}
|
||||
|
||||
private IGridPropertiesViewModel? LoadGridPropertiesViewModel(IGridProperties? gridProperties)
|
||||
{
|
||||
if (gridProperties is IMediumGridLayout mediumGridLayout)
|
||||
{
|
||||
return new MediumGridPropertiesViewModel(mediumGridLayout);
|
||||
}
|
||||
else if (gridProperties is IGalleryGridLayout galleryGridLayout)
|
||||
{
|
||||
return new GalleryGridPropertiesViewModel(galleryGridLayout);
|
||||
}
|
||||
else if (gridProperties is ISmallGridLayout smallGridLayout)
|
||||
{
|
||||
return new SmallGridPropertiesViewModel(smallGridLayout);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void LoadMoreIfNeeded()
|
||||
{
|
||||
var model = this._model.Unsafe;
|
||||
var model = _model.Unsafe;
|
||||
if (model is null)
|
||||
{
|
||||
return;
|
||||
@@ -583,7 +612,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
{
|
||||
base.FetchProperty(propertyName);
|
||||
|
||||
var model = this._model.Unsafe;
|
||||
var model = _model.Unsafe;
|
||||
if (model is null)
|
||||
{
|
||||
return; // throw?
|
||||
@@ -591,14 +620,20 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
|
||||
switch (propertyName)
|
||||
{
|
||||
case nameof(GridProperties):
|
||||
IsGridView = model.GridProperties is not null;
|
||||
GridProperties = LoadGridPropertiesViewModel(model.GridProperties);
|
||||
GridProperties?.InitializeProperties();
|
||||
UpdateProperty(nameof(IsGridView));
|
||||
break;
|
||||
case nameof(ShowDetails):
|
||||
this.ShowDetails = model.ShowDetails;
|
||||
ShowDetails = model.ShowDetails;
|
||||
break;
|
||||
case nameof(PlaceholderText):
|
||||
this._modelPlaceholderText = model.PlaceholderText;
|
||||
_modelPlaceholderText = model.PlaceholderText;
|
||||
break;
|
||||
case nameof(SearchText):
|
||||
this.SearchText = model.SearchText;
|
||||
SearchText = model.SearchText;
|
||||
break;
|
||||
case nameof(EmptyContent):
|
||||
EmptyContent = new(new(model.EmptyContent), PageContext);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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 Microsoft.CmdPal.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public class MediumGridPropertiesViewModel : IGridPropertiesViewModel
|
||||
{
|
||||
private readonly ExtensionObject<IMediumGridLayout> _model;
|
||||
|
||||
public MediumGridPropertiesViewModel(IMediumGridLayout mediumGridLayout)
|
||||
{
|
||||
_model = new(mediumGridLayout);
|
||||
}
|
||||
|
||||
public bool ShowTitle { get; set; }
|
||||
|
||||
public void InitializeProperties()
|
||||
{
|
||||
var model = _model.Unsafe;
|
||||
if (model is null)
|
||||
{
|
||||
return; // throw?
|
||||
}
|
||||
|
||||
ShowTitle = model.ShowTitle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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 Microsoft.CmdPal.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public class SmallGridPropertiesViewModel : IGridPropertiesViewModel
|
||||
{
|
||||
private readonly ExtensionObject<ISmallGridLayout> _model;
|
||||
|
||||
public SmallGridPropertiesViewModel(ISmallGridLayout smallGridLayout)
|
||||
{
|
||||
_model = new(smallGridLayout);
|
||||
}
|
||||
|
||||
public void InitializeProperties()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,8 @@ public partial class SettingsModel : ObservableObject
|
||||
|
||||
public bool IgnoreShortcutWhenFullscreen { get; set; }
|
||||
|
||||
public bool AllowExternalReload { get; set; }
|
||||
|
||||
public Dictionary<string, ProviderSettings> ProviderSettings { get; set; } = [];
|
||||
|
||||
public Dictionary<string, CommandAlias> Aliases { get; set; } = [];
|
||||
|
||||
@@ -38,6 +38,16 @@ public partial class SettingsViewModel : INotifyPropertyChanged
|
||||
}
|
||||
}
|
||||
|
||||
public bool AllowExternalReload
|
||||
{
|
||||
get => _settings.AllowExternalReload;
|
||||
set
|
||||
{
|
||||
_settings.AllowExternalReload = value;
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowAppDetails
|
||||
{
|
||||
get => _settings.ShowAppDetails;
|
||||
|
||||
@@ -131,6 +131,11 @@ public sealed partial class SearchBar : UserControl,
|
||||
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(new OpenContextMenuMessage(null, null, null, ContextMenuFilterLocation.Bottom));
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (ctrlPressed && e.Key == VirtualKey.I)
|
||||
{
|
||||
// Today you learned that Ctrl+I in a TextBox will insert a tab
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.Key == VirtualKey.Escape)
|
||||
{
|
||||
if (string.IsNullOrEmpty(FilterBox.Text))
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// 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 Microsoft.CmdPal.Core.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.CmdPal.UI;
|
||||
|
||||
internal sealed partial class GridItemTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public IGridPropertiesViewModel? GridProperties { get; set; }
|
||||
|
||||
public DataTemplate? Small { get; set; }
|
||||
|
||||
public DataTemplate? Medium { get; set; }
|
||||
|
||||
public DataTemplate? Gallery { get; set; }
|
||||
|
||||
protected override DataTemplate? SelectTemplateCore(object item, DependencyObject dependencyObject)
|
||||
{
|
||||
DataTemplate? dataTemplate = Medium;
|
||||
|
||||
if (GridProperties is SmallGridPropertiesViewModel)
|
||||
{
|
||||
dataTemplate = Small;
|
||||
}
|
||||
else if (GridProperties is MediumGridPropertiesViewModel)
|
||||
{
|
||||
dataTemplate = Medium;
|
||||
}
|
||||
else if (GridProperties is GalleryGridPropertiesViewModel)
|
||||
{
|
||||
dataTemplate = Gallery;
|
||||
}
|
||||
|
||||
return dataTemplate;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
x:Class="Microsoft.CmdPal.UI.ListPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cmdpalUI="using:Microsoft.CmdPal.UI"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:coreViewModels="using:Microsoft.CmdPal.Core.ViewModels"
|
||||
@@ -11,8 +12,11 @@
|
||||
xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
|
||||
xmlns:local="using:Microsoft.CmdPal.UI"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
|
||||
x:Name="PageRoot"
|
||||
Background="Transparent"
|
||||
DataContext="{x:Bind ViewModel, Mode=OneWay}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
@@ -23,6 +27,7 @@
|
||||
IsSourceGrouped="True"
|
||||
Source="{x:Bind ViewModel.Items, Mode=OneWay}" />-->
|
||||
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:StringVisibilityConverter
|
||||
x:Key="StringVisibilityConverter"
|
||||
EmptyValue="Collapsed"
|
||||
@@ -39,6 +44,14 @@
|
||||
ToolTipService.ToolTip="{x:Bind ToolTip, Mode=OneWay}" />
|
||||
</DataTemplate>
|
||||
|
||||
<cmdpalUI:GridItemTemplateSelector
|
||||
x:Key="GridItemTemplateSelector"
|
||||
x:DataType="coreViewModels:ListItemViewModel"
|
||||
Gallery="{StaticResource GalleryGridItemViewModelTemplate}"
|
||||
GridProperties="{x:Bind ViewModel.GridProperties}"
|
||||
Medium="{StaticResource MediumGridItemViewModelTemplate}"
|
||||
Small="{StaticResource SmallGridItemViewModelTemplate}" />
|
||||
|
||||
<!-- https://learn.microsoft.com/windows/apps/design/controls/itemsview#specify-the-look-of-the-items -->
|
||||
<DataTemplate x:Key="ListItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
|
||||
<Grid
|
||||
@@ -102,6 +115,145 @@
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- Grid item templates for visual grid representation -->
|
||||
<DataTemplate x:Key="SmallGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
|
||||
<StackPanel
|
||||
Width="60"
|
||||
Height="60"
|
||||
Padding="8,16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="{x:Bind Title}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="8"
|
||||
Orientation="Vertical"
|
||||
ToolTipService.ToolTip="{x:Bind Title}">
|
||||
|
||||
<cpcontrols:IconBox
|
||||
x:Name="GridIconBorder"
|
||||
Width="28"
|
||||
Height="28"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorPrimary}"
|
||||
SourceKey="{x:Bind Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
|
||||
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="MediumGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
|
||||
<StackPanel
|
||||
Width="100"
|
||||
Height="100"
|
||||
Padding="8,16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="{x:Bind Title}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="8"
|
||||
Orientation="Vertical"
|
||||
ToolTipService.ToolTip="{x:Bind Title}">
|
||||
|
||||
<cpcontrols:IconBox
|
||||
x:Name="GridIconBorder"
|
||||
Width="36"
|
||||
Height="36"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="12"
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource TextFillColorPrimary}"
|
||||
SourceKey="{x:Bind Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
|
||||
|
||||
<TextBlock
|
||||
x:Name="TitleTextBlock"
|
||||
MaxHeight="40"
|
||||
Margin="0,8,0,4"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="12"
|
||||
FontSize="12"
|
||||
Text="{x:Bind Title}"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="WordEllipsis"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{Binding ElementName=PageRoot, Path=DataContext.GridProperties.ShowTitle, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="GalleryGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
|
||||
<StackPanel
|
||||
Width="160"
|
||||
Margin="4"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="{x:Bind Title}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="4"
|
||||
Orientation="Vertical"
|
||||
ToolTipService.ToolTip="{x:Bind Title}">
|
||||
|
||||
<Grid
|
||||
Width="160"
|
||||
Height="160"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Viewbox
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
Stretch="UniformToFill"
|
||||
StretchDirection="Both">
|
||||
<cpcontrols:IconBox
|
||||
CornerRadius="4"
|
||||
Foreground="{ThemeResource TextFillColorPrimary}"
|
||||
SourceKey="{x:Bind Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Padding="4" Orientation="Vertical">
|
||||
<TextBlock
|
||||
x:Name="TitleTextBlock"
|
||||
MaxWidth="152"
|
||||
MaxHeight="40"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="12"
|
||||
FontSize="14"
|
||||
Foreground="{ThemeResource TextFillColorPrimary}"
|
||||
Text="{x:Bind Title}"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="WordEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
<TextBlock
|
||||
x:Name="SubTitleTextBlock"
|
||||
MaxWidth="152"
|
||||
MaxHeight="40"
|
||||
Margin="0,4,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
CharacterSpacing="11"
|
||||
FontSize="11"
|
||||
Foreground="{ThemeResource TextFillColorTertiary}"
|
||||
Text="{x:Bind Subtitle}"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="WordEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid>
|
||||
@@ -110,66 +262,50 @@
|
||||
TargetType="x:Boolean"
|
||||
Value="{x:Bind ViewModel.ShowEmptyContent, Mode=OneWay}">
|
||||
<controls:Case Value="False">
|
||||
<ListView
|
||||
x:Name="ItemsList"
|
||||
Padding="0,2,0,0"
|
||||
ContextCanceled="ItemsList_OnContextCanceled"
|
||||
ContextRequested="ItemsList_OnContextRequested"
|
||||
DoubleTapped="ItemsList_DoubleTapped"
|
||||
IsDoubleTapEnabled="True"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="ItemsList_ItemClick"
|
||||
ItemTemplate="{StaticResource ListItemViewModelTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
|
||||
SelectionChanged="ItemsList_SelectionChanged">
|
||||
<ListView.ItemContainerTransitions>
|
||||
<TransitionCollection />
|
||||
</ListView.ItemContainerTransitions>
|
||||
<!--<ListView.GroupStyle>
|
||||
<GroupStyle HidesIfEmpty="True">
|
||||
<GroupStyle.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock
|
||||
Margin="0,16,0,0"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{Binding Key, Mode=OneWay}" />
|
||||
</DataTemplate>
|
||||
</GroupStyle.HeaderTemplate>
|
||||
</GroupStyle>
|
||||
</ListView.GroupStyle>-->
|
||||
</ListView>
|
||||
</controls:Case>
|
||||
<controls:Case Value="True">
|
||||
<StackPanel
|
||||
Margin="24"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Vertical"
|
||||
Spacing="4">
|
||||
<cpcontrols:IconBox
|
||||
x:Name="IconBorder"
|
||||
Width="48"
|
||||
Height="48"
|
||||
Margin="8"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
SourceKey="{x:Bind ViewModel.EmptyContent.Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
|
||||
<TextBlock
|
||||
Margin="0,4,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Text="{x:Bind ViewModel.EmptyContent.Title, Mode=OneWay}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind ViewModel.EmptyContent.Subtitle, Mode=OneWay}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<controls:SwitchPresenter
|
||||
HorizontalAlignment="Stretch"
|
||||
TargetType="x:Boolean"
|
||||
Value="{x:Bind ViewModel.IsGridView, Mode=OneWay}">
|
||||
<controls:Case Value="False">
|
||||
<ListView
|
||||
x:Name="ItemsList"
|
||||
Padding="0,2,0,0"
|
||||
ContextCanceled="Items_OnContextCanceled"
|
||||
ContextRequested="Items_OnContextRequested"
|
||||
DoubleTapped="Items_DoubleTapped"
|
||||
IsDoubleTapEnabled="True"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="Items_ItemClick"
|
||||
ItemTemplate="{StaticResource ListItemViewModelTemplate}"
|
||||
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
|
||||
RightTapped="Items_RightTapped"
|
||||
SelectionChanged="Items_SelectionChanged">
|
||||
<ListView.ItemContainerTransitions>
|
||||
<TransitionCollection />
|
||||
</ListView.ItemContainerTransitions>
|
||||
</ListView>
|
||||
</controls:Case>
|
||||
<controls:Case Value="True">
|
||||
<GridView
|
||||
x:Name="ItemsGrid"
|
||||
Padding="8"
|
||||
ContextCanceled="Items_OnContextCanceled"
|
||||
ContextRequested="Items_OnContextRequested"
|
||||
DoubleTapped="Items_DoubleTapped"
|
||||
IsDoubleTapEnabled="True"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="Items_ItemClick"
|
||||
ItemTemplateSelector="{StaticResource GridItemTemplateSelector}"
|
||||
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
|
||||
RightTapped="Items_RightTapped"
|
||||
SelectionChanged="Items_SelectionChanged">
|
||||
<GridView.ItemContainerTransitions>
|
||||
<TransitionCollection />
|
||||
</GridView.ItemContainerTransitions>
|
||||
</GridView>
|
||||
</controls:Case>
|
||||
</controls:SwitchPresenter>
|
||||
</controls:Case>
|
||||
</controls:SwitchPresenter>
|
||||
|
||||
</Grid>
|
||||
</Page>
|
||||
|
||||
@@ -13,6 +13,7 @@ using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
@@ -38,13 +39,21 @@ public sealed partial class ListPage : Page,
|
||||
public static readonly DependencyProperty ViewModelProperty =
|
||||
DependencyProperty.Register(nameof(ViewModel), typeof(ListViewModel), typeof(ListPage), new PropertyMetadata(null, OnViewModelChanged));
|
||||
|
||||
private ListViewBase ItemView
|
||||
{
|
||||
get
|
||||
{
|
||||
return ViewModel?.IsGridView == true ? ItemsGrid : ItemsList;
|
||||
}
|
||||
}
|
||||
|
||||
public ListPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.NavigationCacheMode = NavigationCacheMode.Disabled;
|
||||
this.ItemsList.Loaded += ItemsList_Loaded;
|
||||
this.ItemsList.PreviewKeyDown += ItemsList_PreviewKeyDown;
|
||||
this.ItemsList.PointerPressed += ItemsList_PointerPressed;
|
||||
this.ItemView.Loaded += Items_Loaded;
|
||||
this.ItemView.PreviewKeyDown += Items_PreviewKeyDown;
|
||||
this.ItemView.PointerPressed += Items_PointerPressed;
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
@@ -55,11 +64,11 @@ public sealed partial class ListPage : Page,
|
||||
}
|
||||
|
||||
if (e.NavigationMode == NavigationMode.Back
|
||||
|| (e.NavigationMode == NavigationMode.New && ItemsList.Items.Count > 0))
|
||||
|| (e.NavigationMode == NavigationMode.New && ItemView.Items.Count > 0))
|
||||
{
|
||||
// Upon navigating _back_ to this page, immediately select the
|
||||
// first item in the list
|
||||
ItemsList.SelectedIndex = 0;
|
||||
ItemView.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
// RegisterAll isn't AOT compatible
|
||||
@@ -90,7 +99,6 @@ public sealed partial class ListPage : Page,
|
||||
{
|
||||
ViewModel?.SafeCleanup();
|
||||
CleanupHelper.Cleanup(this);
|
||||
Bindings.StopTracking();
|
||||
}
|
||||
|
||||
// Clean-up event listeners
|
||||
@@ -100,7 +108,7 @@ public sealed partial class ListPage : Page,
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS is too aggressive at pruning methods bound in XAML")]
|
||||
private void ItemsList_ItemClick(object sender, ItemClickEventArgs e)
|
||||
private void Items_ItemClick(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
if (e.ClickedItem is ListItemViewModel item)
|
||||
{
|
||||
@@ -123,9 +131,9 @@ public sealed partial class ListPage : Page,
|
||||
}
|
||||
}
|
||||
|
||||
private void ItemsList_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
|
||||
private void Items_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
|
||||
{
|
||||
if (ItemsList.SelectedItem is ListItemViewModel vm)
|
||||
if (ItemView.SelectedItem is ListItemViewModel vm)
|
||||
{
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
if (!settings.SingleClickActivates)
|
||||
@@ -136,10 +144,10 @@ public sealed partial class ListPage : Page,
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS is too aggressive at pruning methods bound in XAML")]
|
||||
private void ItemsList_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
private void Items_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
var vm = ViewModel;
|
||||
var li = ItemsList.SelectedItem as ListItemViewModel;
|
||||
var li = ItemView.SelectedItem as ListItemViewModel;
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
vm?.UpdateSelectedItemCommand.Execute(li);
|
||||
@@ -154,12 +162,12 @@ public sealed partial class ListPage : Page,
|
||||
// here, then in Page_ItemsUpdated trying to select that cached item if
|
||||
// it's in the list (otherwise, clear the cache), but that seems
|
||||
// aggressively BODGY for something that mostly just works today.
|
||||
if (ItemsList.SelectedItem is not null)
|
||||
if (ItemView.SelectedItem is not null)
|
||||
{
|
||||
ItemsList.ScrollIntoView(ItemsList.SelectedItem);
|
||||
ItemView.ScrollIntoView(ItemView.SelectedItem);
|
||||
|
||||
// Automation notification for screen readers
|
||||
var listViewPeer = Microsoft.UI.Xaml.Automation.Peers.ListViewAutomationPeer.CreatePeerForElement(ItemsList);
|
||||
var listViewPeer = Microsoft.UI.Xaml.Automation.Peers.ListViewAutomationPeer.CreatePeerForElement(ItemView);
|
||||
if (listViewPeer is not null && li is not null)
|
||||
{
|
||||
var notificationText = li.Title;
|
||||
@@ -172,10 +180,37 @@ public sealed partial class ListPage : Page,
|
||||
}
|
||||
}
|
||||
|
||||
private void ItemsList_Loaded(object sender, RoutedEventArgs e)
|
||||
private void Items_RightTapped(object sender, RightTappedRoutedEventArgs e)
|
||||
{
|
||||
// Find the ScrollViewer in the ListView
|
||||
var listViewScrollViewer = FindScrollViewer(this.ItemsList);
|
||||
if (e.OriginalSource is FrameworkElement element &&
|
||||
element.DataContext is ListItemViewModel item)
|
||||
{
|
||||
if (ItemView.SelectedItem != item)
|
||||
{
|
||||
ItemView.SelectedItem = item;
|
||||
}
|
||||
|
||||
ViewModel?.UpdateSelectedItemCommand.Execute(item);
|
||||
|
||||
var pos = e.GetPosition(element);
|
||||
|
||||
_ = DispatcherQueue.TryEnqueue(
|
||||
() =>
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(
|
||||
new OpenContextMenuMessage(
|
||||
element,
|
||||
Microsoft.UI.Xaml.Controls.Primitives.FlyoutPlacementMode.BottomEdgeAlignedLeft,
|
||||
pos,
|
||||
ContextMenuFilterLocation.Top));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void Items_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Find the ScrollViewer in the ItemView (ItemsList or ItemsGrid)
|
||||
var listViewScrollViewer = FindScrollViewer(this.ItemView);
|
||||
|
||||
if (listViewScrollViewer is not null)
|
||||
{
|
||||
@@ -207,25 +242,25 @@ public sealed partial class ListPage : Page,
|
||||
// And then have these commands manipulate that state being bound to the UI instead
|
||||
// We may want to see how other non-list UIs need to behave to make this decision
|
||||
// At least it's decoupled from the SearchBox now :)
|
||||
if (ItemsList.SelectedIndex < ItemsList.Items.Count - 1)
|
||||
if (ItemView.SelectedIndex < ItemView.Items.Count - 1)
|
||||
{
|
||||
ItemsList.SelectedIndex++;
|
||||
ItemView.SelectedIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemsList.SelectedIndex = 0;
|
||||
ItemView.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(NavigatePreviousCommand message)
|
||||
{
|
||||
if (ItemsList.SelectedIndex > 0)
|
||||
if (ItemView.SelectedIndex > 0)
|
||||
{
|
||||
ItemsList.SelectedIndex--;
|
||||
ItemView.SelectedIndex--;
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemsList.SelectedIndex = ItemsList.Items.Count - 1;
|
||||
ItemView.SelectedIndex = ItemView.Items.Count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +270,7 @@ public sealed partial class ListPage : Page,
|
||||
{
|
||||
ViewModel?.InvokeItemCommand.Execute(null);
|
||||
}
|
||||
else if (ItemsList.SelectedItem is ListItemViewModel item)
|
||||
else if (ItemView.SelectedItem is ListItemViewModel item)
|
||||
{
|
||||
ViewModel?.InvokeItemCommand.Execute(item);
|
||||
}
|
||||
@@ -247,7 +282,7 @@ public sealed partial class ListPage : Page,
|
||||
{
|
||||
ViewModel?.InvokeSecondaryCommandCommand.Execute(null);
|
||||
}
|
||||
else if (ItemsList.SelectedItem is ListItemViewModel item)
|
||||
else if (ItemView.SelectedItem is ListItemViewModel item)
|
||||
{
|
||||
ViewModel?.InvokeSecondaryCommandCommand.Execute(item);
|
||||
}
|
||||
@@ -283,19 +318,19 @@ public sealed partial class ListPage : Page,
|
||||
//
|
||||
// It's important to do this here, because once there's no selection
|
||||
// (which can happen as the list updates) we won't get an
|
||||
// ItemsList_SelectionChanged again to give us another chance to change
|
||||
// ItemView_SelectionChanged again to give us another chance to change
|
||||
// the selection from null -> something. Better to just update the
|
||||
// selection once, at the end of all the updating.
|
||||
if (ItemsList.SelectedItem is null)
|
||||
if (ItemView.SelectedItem is null)
|
||||
{
|
||||
ItemsList.SelectedIndex = 0;
|
||||
ItemView.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
// Always reset the selected item when the top-level list page changes
|
||||
// its items
|
||||
if (!sender.IsNested)
|
||||
{
|
||||
ItemsList.SelectedIndex = 0;
|
||||
ItemView.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +339,7 @@ public sealed partial class ListPage : Page,
|
||||
var prop = e.PropertyName;
|
||||
if (prop == nameof(ViewModel.FilteredItems))
|
||||
{
|
||||
Debug.WriteLine($"ViewModel.FilteredItems {ItemsList.SelectedItem}");
|
||||
Debug.WriteLine($"ViewModel.FilteredItems {ItemView.SelectedItem}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,12 +363,12 @@ public sealed partial class ListPage : Page,
|
||||
return null;
|
||||
}
|
||||
|
||||
private void ItemsList_OnContextRequested(UIElement sender, ContextRequestedEventArgs e)
|
||||
private void Items_OnContextRequested(UIElement sender, ContextRequestedEventArgs e)
|
||||
{
|
||||
var (item, element) = e.OriginalSource switch
|
||||
{
|
||||
// caused by keyboard shortcut (e.g. Context menu key or Shift+F10)
|
||||
ListViewItem listViewItem => (ItemsList.ItemFromContainer(listViewItem) as ListItemViewModel, listViewItem),
|
||||
SelectorItem selectorItem => (ItemView.ItemFromContainer(selectorItem) as ListItemViewModel, selectorItem),
|
||||
|
||||
// caused by right-click on the ListViewItem
|
||||
FrameworkElement { DataContext: ListItemViewModel itemViewModel } frameworkElement => (itemViewModel, frameworkElement),
|
||||
@@ -346,9 +381,9 @@ public sealed partial class ListPage : Page,
|
||||
return;
|
||||
}
|
||||
|
||||
if (ItemsList.SelectedItem != item)
|
||||
if (ItemView.SelectedItem != item)
|
||||
{
|
||||
ItemsList.SelectedItem = item;
|
||||
ItemView.SelectedItem = item;
|
||||
}
|
||||
|
||||
ViewModel?.UpdateSelectedItemCommand.Execute(item);
|
||||
@@ -371,14 +406,14 @@ public sealed partial class ListPage : Page,
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ItemsList_OnContextCanceled(UIElement sender, RoutedEventArgs e)
|
||||
private void Items_OnContextCanceled(UIElement sender, RoutedEventArgs e)
|
||||
{
|
||||
_ = DispatcherQueue.TryEnqueue(() => WeakReferenceMessenger.Default.Send<CloseContextMenuMessage>());
|
||||
}
|
||||
|
||||
private void ItemsList_PointerPressed(object sender, PointerRoutedEventArgs e) => _lastInputSource = InputSource.Pointer;
|
||||
private void Items_PointerPressed(object sender, PointerRoutedEventArgs e) => _lastInputSource = InputSource.Pointer;
|
||||
|
||||
private void ItemsList_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
|
||||
private void Items_PreviewKeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key is VirtualKey.Enter or VirtualKey.Space)
|
||||
{
|
||||
|
||||
@@ -521,6 +521,21 @@ public sealed partial class MainWindow : WindowEx,
|
||||
WeakReferenceMessenger.Default.Send<OpenSettingsMessage>(new());
|
||||
return;
|
||||
}
|
||||
else if (uri.StartsWith("x-cmdpal://reload", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var settings = App.Current.Services.GetService<SettingsModel>();
|
||||
if (settings?.AllowExternalReload == true)
|
||||
{
|
||||
Logger.LogInfo("External Reload triggered");
|
||||
WeakReferenceMessenger.Default.Send<ReloadCommandsMessage>(new());
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInfo("External Reload is disabled");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
<RepositionThemeTransition IsStaggeringEnabled="False" />
|
||||
</StackPanel.ChildrenTransitions>-->
|
||||
|
||||
<!-- 'Activation' section -->
|
||||
|
||||
<TextBlock x:Uid="ActivationSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<controls:SettingsExpander
|
||||
@@ -66,6 +68,8 @@
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- 'Behavior' section -->
|
||||
|
||||
<TextBlock x:Uid="BehaviorSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_ShowAppDetails_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
@@ -84,7 +88,16 @@
|
||||
<ToggleSwitch IsOn="{x:Bind viewModel.ShowSystemTrayIcon, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- Example 'About' section -->
|
||||
<!-- 'For Developers' section -->
|
||||
|
||||
<TextBlock x:Uid="ForDevelopersSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_AllowExternalReload_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind viewModel.AllowExternalReload, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- 'About' section -->
|
||||
|
||||
<TextBlock x:Uid="AboutSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<controls:SettingsExpander x:Uid="Settings_GeneralPage_About_SettingsExpander" HeaderIcon="{ui:BitmapIcon Source=ms-appx:///Assets/StoreLogo.png}">
|
||||
|
||||
@@ -441,4 +441,13 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<data name="NavigationPaneOpened" xml:space="preserve">
|
||||
<value>Navigation page opened</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AllowExternalReload_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Enable external reload</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AllowExternalReload_SettingsCard.Description" xml:space="preserve">
|
||||
<value>Trigger reload of the extension externally with the x-cmdpal://reload command</value>
|
||||
</data>
|
||||
<data name="ForDevelopersSettingsHeader.Text" xml:space="preserve">
|
||||
<value>For Developers</value>
|
||||
</data>
|
||||
</root>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 316 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 584 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 613 KiB |
@@ -0,0 +1,66 @@
|
||||
// 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 Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace SamplePagesExtension;
|
||||
|
||||
internal sealed partial class SampleGalleryListPage : ListPage
|
||||
{
|
||||
public SampleGalleryListPage()
|
||||
{
|
||||
Icon = new IconInfo("\uE7C5");
|
||||
Name = "Sample Gallery List Page";
|
||||
GridProperties = new GalleryGridLayout();
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
return [
|
||||
new ListItem(new NoOpCommand())
|
||||
{
|
||||
Title = "Sample Title",
|
||||
Subtitle = "I don't do anything",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/RedRectangle.png"),
|
||||
},
|
||||
new ListItem(new NoOpCommand())
|
||||
{
|
||||
Title = "Another Title",
|
||||
Subtitle = "I don't do anything",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/Swirls.png"),
|
||||
},
|
||||
new ListItem(new NoOpCommand())
|
||||
{
|
||||
Title = "More Titles",
|
||||
Subtitle = "I don't do anything",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/Win-Digital.png"),
|
||||
},
|
||||
new ListItem(new NoOpCommand())
|
||||
{
|
||||
Title = "Stop With The Titles",
|
||||
Subtitle = "I don't do anything",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/RedRectangle.png"),
|
||||
},
|
||||
new ListItem(new NoOpCommand())
|
||||
{
|
||||
Title = "Another Title",
|
||||
Subtitle = "I don't do anything",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/Space.png"),
|
||||
},
|
||||
new ListItem(new NoOpCommand())
|
||||
{
|
||||
Title = "More Titles",
|
||||
Subtitle = "I don't do anything",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/Swirls.png"),
|
||||
},
|
||||
new ListItem(new NoOpCommand())
|
||||
{
|
||||
Title = "Stop With The Titles",
|
||||
Subtitle = "I don't do anything",
|
||||
Icon = IconHelpers.FromRelativePath("Assets/Images/Win-Digital.png"),
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,11 @@ public partial class SamplesListPage : ListPage
|
||||
Title = "Dynamic List Page Command",
|
||||
Subtitle = "Changes the list of items in response to the typed query",
|
||||
},
|
||||
new ListItem(new SampleGalleryListPage())
|
||||
{
|
||||
Title = "Gallery List Page Command",
|
||||
Subtitle = "Displays items as a gallery",
|
||||
},
|
||||
new ListItem(new OnLoadPage())
|
||||
{
|
||||
Title = "Demo of OnLoad/OnUnload",
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// 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.
|
||||
|
||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
public partial class GalleryGridLayout : BaseObservable, IGalleryGridLayout
|
||||
{
|
||||
public virtual bool ShowTitle
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
OnPropertyChanged(nameof(ShowTitle));
|
||||
}
|
||||
}
|
||||
|
||||
= true;
|
||||
|
||||
public virtual bool ShowSubtitle
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
OnPropertyChanged(nameof(ShowSubtitle));
|
||||
}
|
||||
}
|
||||
|
||||
= true;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// 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.
|
||||
|
||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
public partial class MediumGridLayout : BaseObservable, IMediumGridLayout
|
||||
{
|
||||
public virtual bool ShowTitle
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
OnPropertyChanged(nameof(ShowTitle));
|
||||
}
|
||||
}
|
||||
|
||||
= true;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// 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.
|
||||
|
||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
public partial class SmallGridLayout : BaseObservable, ISmallGridLayout
|
||||
{
|
||||
}
|
||||
@@ -273,10 +273,26 @@ namespace Microsoft.CommandPalette.Extensions
|
||||
String Section { get; };
|
||||
String TextToSuggest { get; };
|
||||
}
|
||||
|
||||
[uuid("50C6F080-1CBE-4CE4-B92F-DA2F116ED524")]
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
interface IGridProperties requires INotifyPropChanged { }
|
||||
|
||||
[uuid("05914D59-6ECB-4992-9CF2-5982B5120A26")]
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
interface ISmallGridLayout requires IGridProperties { }
|
||||
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
interface IGridProperties {
|
||||
Windows.Foundation.Size TileSize { get; };
|
||||
interface IMediumGridLayout requires IGridProperties
|
||||
{
|
||||
Boolean ShowTitle { get; };
|
||||
}
|
||||
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
interface IGalleryGridLayout requires IGridProperties
|
||||
{
|
||||
Boolean ShowTitle { get; };
|
||||
Boolean ShowSubtitle { get; };
|
||||
}
|
||||
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
|
||||
@@ -781,14 +781,14 @@ namespace PowerAccent.Core
|
||||
{
|
||||
return letter switch
|
||||
{
|
||||
LetterKey.VK_A => new[] { "â" },
|
||||
LetterKey.VK_E => new[] { "ê" },
|
||||
LetterKey.VK_I => new[] { "î" },
|
||||
LetterKey.VK_O => new[] { "ô" },
|
||||
LetterKey.VK_A => new[] { "â", "ä", "à", "á" },
|
||||
LetterKey.VK_E => new[] { "ê", "ë", "è", "é" },
|
||||
LetterKey.VK_I => new[] { "î", "ï", "ì", "í" },
|
||||
LetterKey.VK_O => new[] { "ô", "ö", "ò", "ó" },
|
||||
LetterKey.VK_P => new[] { "£" },
|
||||
LetterKey.VK_U => new[] { "û" },
|
||||
LetterKey.VK_Y => new[] { "ŷ" },
|
||||
LetterKey.VK_W => new[] { "ŵ" },
|
||||
LetterKey.VK_U => new[] { "û", "ü", "ù", "ú" },
|
||||
LetterKey.VK_Y => new[] { "ŷ", "ÿ", "ỳ", "ý" },
|
||||
LetterKey.VK_W => new[] { "ŵ", "ẅ", "ẁ", "ẃ" },
|
||||
_ => Array.Empty<string>(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -177,7 +177,6 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
|
||||
L"PowerToys.WorkspacesModuleInterface.dll",
|
||||
L"PowerToys.CmdPalModuleInterface.dll",
|
||||
L"PowerToys.ZoomItModuleInterface.dll",
|
||||
L"PowerToys.DwellCursor.dll",
|
||||
};
|
||||
|
||||
for (auto moduleSubdir : knownModules)
|
||||
|
||||
17
src/runner/runner.instructions.md
Normal file
17
src/runner/runner.instructions.md
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
applyTo: "**/*.cpp,**/*.c,**/*.h,**/*.hpp,**/*.rc"
|
||||
---
|
||||
# Runner – tray / host process guidance
|
||||
|
||||
Scope
|
||||
- Module bootstrap, hotkey management, settings bridge, update/elevation handling.
|
||||
|
||||
Guidelines
|
||||
- If IPC/JSON contracts change, mirror updates in `src/settings-ui/**`.
|
||||
- Keep module discovery in `src/runner/main.cpp` in sync when adding/removing modules.
|
||||
- Keep startup lean: avoid blocking/network calls in early init path.
|
||||
- Preserve GPO & elevation behaviors; confirm no regression in policy handling.
|
||||
- Ask before modifying update workflow or elevation logic.
|
||||
|
||||
Acceptance
|
||||
- Stable startup, consistent contracts, no unnecessary logging noise.
|
||||
@@ -1,42 +0,0 @@
|
||||
// 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.Text.Json.Serialization;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class DwellCursorSettings : BasePTModuleSettings, ISettingsConfig, IHotkeyConfig
|
||||
{
|
||||
public const string ModuleName = "DwellCursor";
|
||||
|
||||
[JsonPropertyName("properties")]
|
||||
public DwellCursorSettingsProperties Properties { get; set; } = new DwellCursorSettingsProperties();
|
||||
|
||||
public DwellCursorSettings()
|
||||
{
|
||||
Name = ModuleName;
|
||||
Version = "1.0";
|
||||
}
|
||||
|
||||
public string GetModuleName() => Name;
|
||||
|
||||
public ModuleType GetModuleType() => ModuleType.MouseJump; // grouped under Mouse Utils UI
|
||||
|
||||
public HotkeyAccessor[] GetAllHotkeyAccessors()
|
||||
{
|
||||
return new HotkeyAccessor[]
|
||||
{
|
||||
new HotkeyAccessor(
|
||||
() => Properties.ActivationShortcut,
|
||||
value => Properties.ActivationShortcut = value ?? DwellCursorSettingsProperties.DefaultActivationShortcut,
|
||||
"MouseUtils_DwellCursor_ActivationShortcut"),
|
||||
};
|
||||
}
|
||||
|
||||
public bool UpgradeSettingsConfiguration() => false;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
// 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.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class DwellCursorSettingsProperties
|
||||
{
|
||||
[JsonPropertyName("activation_shortcut")]
|
||||
public HotkeySettings ActivationShortcut { get; set; } = DefaultActivationShortcut;
|
||||
|
||||
public static HotkeySettings DefaultActivationShortcut => new HotkeySettings(true, false, true, false, 0x44); // Win + Alt + D
|
||||
|
||||
[JsonPropertyName("delay_time_ms")]
|
||||
public IntProperty DelayTimeMs { get; set; } = new IntProperty() { Value = 1000 }; // 0.5s-10s
|
||||
|
||||
[JsonPropertyName("settle_time_seconds")]
|
||||
public IntProperty SettleTimeSeconds { get; set; } = new IntProperty() { Value = 1 }; // 1-5 seconds
|
||||
}
|
||||
}
|
||||
@@ -282,22 +282,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
}
|
||||
}
|
||||
|
||||
private bool dwellCursor; // defaulting to off
|
||||
|
||||
[JsonPropertyName("DwellCursor")]
|
||||
public bool DwellCursor
|
||||
{
|
||||
get => dwellCursor;
|
||||
set
|
||||
{
|
||||
if (dwellCursor != value)
|
||||
{
|
||||
LogTelemetryEvent(value);
|
||||
dwellCursor = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool powerAccent; // defaulting to off
|
||||
|
||||
[JsonPropertyName("QuickAccent")]
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// 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.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class SndDwellCursorSettings
|
||||
{
|
||||
[JsonPropertyName("DwellCursor")]
|
||||
public DwellCursorSettings DwellCursor { get; set; }
|
||||
|
||||
public SndDwellCursorSettings()
|
||||
{
|
||||
}
|
||||
|
||||
public SndDwellCursorSettings(DwellCursorSettings s)
|
||||
{
|
||||
DwellCursor = s;
|
||||
}
|
||||
|
||||
public string ToJsonString() => System.Text.Json.JsonSerializer.Serialize(this);
|
||||
}
|
||||
}
|
||||
@@ -161,6 +161,9 @@
|
||||
<Page Update="SettingsXAML\Controls\KeyVisual\KeyCharPresenter.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Update="SettingsXAML\Controls\GPOInfoControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Update="SettingsXAML\Controls\Dashboard\ShortcutConflictControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Microsoft.PowerToys.Settings.UI.Controls">
|
||||
<Style TargetType="local:GPOInfoControl">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:GPOInfoControl">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" />
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
Margin="0,4,0,0"
|
||||
IsClosable="False"
|
||||
IsOpen="{TemplateBinding ShowWarning}"
|
||||
IsTabStop="{TemplateBinding ShowWarning}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
</StackPanel>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Documents;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Controls;
|
||||
|
||||
public sealed partial class GPOInfoControl : ContentControl
|
||||
{
|
||||
public static readonly DependencyProperty ShowWarningProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(ShowWarning),
|
||||
typeof(bool),
|
||||
typeof(GPOInfoControl),
|
||||
new PropertyMetadata(false, OnShowWarningPropertyChanged));
|
||||
|
||||
public bool ShowWarning
|
||||
{
|
||||
get => (bool)GetValue(ShowWarningProperty);
|
||||
set => SetValue(ShowWarningProperty, value);
|
||||
}
|
||||
|
||||
private static void OnShowWarningPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is GPOInfoControl gpoInfoControl)
|
||||
{
|
||||
if (gpoInfoControl.ShowWarning)
|
||||
{
|
||||
gpoInfoControl.IsEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GPOInfoControl()
|
||||
{
|
||||
DefaultStyleKey = typeof(GPOInfoControl);
|
||||
}
|
||||
}
|
||||
@@ -22,25 +22,14 @@
|
||||
</UserControl.Resources>
|
||||
|
||||
<controls:SettingsGroup x:Uid="MouseUtils_MouseJump">
|
||||
|
||||
<tkcontrols:SettingsCard
|
||||
x:Name="MouseUtilsEnableMouseJump"
|
||||
x:Uid="MouseUtils_Enable_MouseJump"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseJump.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsJumpEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsMouseJumpEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsJumpEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsJumpEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsJumpEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
x:Name="MouseUtilsEnableMouseJump"
|
||||
x:Uid="MouseUtils_Enable_MouseJump"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseJump.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsMouseJumpEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
|
||||
<tkcontrols:SettingsCard
|
||||
x:Name="MouseUtilsMouseJumpActivationShortcut"
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="ms-appx:///SettingsXAML/Controls/SettingsGroup/SettingsGroup.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///SettingsXAML/Controls/GPOInfoControl.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///SettingsXAML/Controls/IsEnabledTextBlock.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///SettingsXAML/Controls/FlyoutMenuButton.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary>
|
||||
@@ -46,23 +46,14 @@
|
||||
ChildrenTransitions="{StaticResource SettingsCardsAnimations}"
|
||||
Orientation="Vertical"
|
||||
Spacing="2">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="AdvancedPasteEnableToggleControlHeaderText"
|
||||
x:Uid="AdvancedPaste_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/AdvancedPaste.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="AdvancedPasteEnableToggleControlHeaderText"
|
||||
x:Uid="AdvancedPaste_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/AdvancedPaste.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<controls:SettingsGroup x:Uid="AdvancedPaste_EnableAISettingsGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<InfoBar
|
||||
x:Uid="GPO_AdvancedPasteAi_SettingIsManaged"
|
||||
@@ -108,23 +99,14 @@
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:SettingsGroup>
|
||||
<controls:SettingsGroup x:Uid="AdvancedPaste_BehaviorSettingsGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="AdvancedPasteClipboardHistoryEnabledSettingsCard"
|
||||
x:Uid="AdvancedPaste_Clipboard_History_Enabled_SettingsCard"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsEnabled="{x:Bind ViewModel.ClipboardHistoryDisabledByGPO, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.ClipboardHistoryEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.ShowClipboardHistoryIsGpoConfiguredInfoBar, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.ShowClipboardHistoryIsGpoConfiguredInfoBar, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.ShowClipboardHistoryIsGpoConfiguredInfoBar, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="AdvancedPasteClipboardHistoryEnabledSettingsCard"
|
||||
x:Uid="AdvancedPaste_Clipboard_History_Enabled_SettingsCard"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.ClipboardHistoryEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<tkcontrols:SettingsCard Name="AdvancedPasteCloseAfterLosingFocus" x:Uid="AdvancedPaste_CloseAfterLosingFocus">
|
||||
<tkcontrols:SettingsCard.HeaderIcon>
|
||||
<PathIcon Data="M 4 16.284 C 1 22.284 29 59.284 71 101.284 L 143 174.284 L 101 220.284 C 54 271.284 5 367.284 14 390.284 C 23 416.284 40 406.284 56 367.284 C 64 347.284 76 320.284 82 307.284 C 97 278.284 160 215.284 175 215.284 C 181 215.284 199 228.284 214 243.284 C 239 270.284 240 273.284 224 286.284 C 202 304.284 180 357.284 180 392.284 C 180 430.284 213 481.284 252 505.284 C 297 532.284 349 531.284 394 500.284 C 414 486.284 434 475.284 438 475.284 C 442 475.284 484 514.284 532 562.284 C 602 631.284 622 647.284 632 637.284 C 642 627.284 581 561.284 335 315.284 C 164 144.284 22 5.284 18 5.284 C 14 5.284 8 10.284 4 16.284 Z M 337 367.284 C 372 401.284 400 435.284 400 442.284 C 400 457.284 349 485.284 321 485.284 C 269 485.284 220 437.284 220 385.284 C 220 357.284 248 305.284 262 305.284 C 269 305.284 303 333.284 337 367.284 Z M 248 132.284 C 228 137.284 225 151.284 241 161.284 C 247 164.284 284 168.284 324 169.284 C 393 171.284 442 188.284 491 227.284 C 522 252.284 578 335.284 585 364.284 C 592 399.284 607 412.284 622 397.284 C 629 390.284 627 370.284 615 333.284 C 590 260.284 506 176.284 427 147.284 C 373 127.284 293 120.284 248 132.284 Z" />
|
||||
|
||||
@@ -17,25 +17,15 @@
|
||||
ModuleImageSource="ms-appx:///Assets/Settings/Modules/AlwaysOnTop.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="AlwaysOnTopEnableToggleControlHeaderText"
|
||||
x:Uid="AlwaysOnTop_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/AlwaysOnTop.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="AlwaysOnTopEnableToggleControlHeaderText"
|
||||
x:Uid="AlwaysOnTop_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/AlwaysOnTop.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<controls:SettingsGroup x:Uid="AlwaysOnTop_Activation_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
|
||||
<tkcontrols:SettingsExpander
|
||||
Name="AlwaysOnTopActivationShortcut"
|
||||
x:Uid="AlwaysOnTop_ActivationShortcut"
|
||||
|
||||
@@ -24,26 +24,17 @@
|
||||
ModuleImageSource="ms-appx:///Assets/Settings/Modules/Awake.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="AwakeEnableSettingsCard"
|
||||
x:Uid="Awake_EnableSettingsCard"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Awake.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="AwakeEnableSettingsCard"
|
||||
x:Uid="Awake_EnableSettingsCard"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Awake.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
|
||||
<controls:SettingsGroup x:Uid="Awake_BehaviorSettingsGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
|
||||
<tkcontrols:SettingsCard
|
||||
Name="AwakeModeSettingsCard"
|
||||
x:Uid="Awake_ModeSettingsCard"
|
||||
|
||||
@@ -14,20 +14,14 @@
|
||||
<controls:SettingsPageControl x:Uid="CmdPal" ModuleImageSource="ms-appx:///Assets/Settings/Modules/CmdPal.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="CmdPalEnableCmdPal"
|
||||
x:Uid="CmdPal_Enable_CmdPal"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CmdPal.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational" />
|
||||
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="CmdPalEnableCmdPal"
|
||||
x:Uid="CmdPal_Enable_CmdPal"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CmdPal.png}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<controls:SettingsGroup x:Uid="CmdPal_Activation_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="CmdPalActivationShortcut"
|
||||
|
||||
@@ -21,23 +21,11 @@
|
||||
x:Name="ColorPickerView"
|
||||
ChildrenTransitions="{StaticResource SettingsCardsAnimations}"
|
||||
Orientation="Vertical">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="ColorPickerEnableColorPicker"
|
||||
x:Uid="ColorPicker_EnableColorPicker"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ColorPicker.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard x:Uid="ColorPicker_EnableColorPicker" HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ColorPicker.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
|
||||
<controls:SettingsGroup x:Uid="Shortcut" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
|
||||
@@ -17,25 +17,15 @@
|
||||
ModuleImageSource="ms-appx:///Assets/Settings/Modules/CropAndLock.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="CropAndLockEnableToggleControlHeaderText"
|
||||
x:Uid="CropAndLock_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CropAndLock.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="CropAndLockEnableToggleControlHeaderText"
|
||||
x:Uid="CropAndLock_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/CropAndLock.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<controls:SettingsGroup x:Uid="CropAndLock_Activation_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
|
||||
<tkcontrols:SettingsCard
|
||||
Name="CropAndLockThumbnailActivationShortcut"
|
||||
x:Uid="CropAndLock_ThumbnailActivation_Shortcut"
|
||||
|
||||
@@ -13,23 +13,14 @@
|
||||
<controls:SettingsPageControl x:Uid="EnvironmentVariables" ModuleImageSource="ms-appx:///Assets/Settings/Modules/EnvironmentVariables.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="EnvironmentVariablesEnableToggleControlHeaderText"
|
||||
x:Uid="EnvironmentVariables_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/EnvironmentVariables.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="EnvironmentVariablesEnableToggleControlHeaderText"
|
||||
x:Uid="EnvironmentVariables_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/EnvironmentVariables.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<controls:SettingsGroup x:Uid="EnvironmentVariables_Activation_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="EnvironmentVariablesLaunchButtonControl"
|
||||
|
||||
@@ -14,28 +14,14 @@
|
||||
<controls:SettingsPageControl x:Uid="FancyZones" ModuleImageSource="ms-appx:///Assets/Settings/Modules/FancyZones.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="FancyZonesEnableToggleControlHeaderText"
|
||||
x:Uid="FancyZones_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FancyZones.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch
|
||||
x:Name="EnableFancyZonesToggleSwitch"
|
||||
x:Uid="ToggleSwitch"
|
||||
AutomationProperties.AutomationId="EnableFancyZonesToggleSwitch"
|
||||
IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="FancyZonesEnableToggleControlHeaderText"
|
||||
x:Uid="FancyZones_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FancyZones.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<controls:SettingsGroup x:Uid="FancyZones_Editor_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="FancyZonesLaunchEditorButtonControl"
|
||||
|
||||
@@ -14,24 +14,14 @@
|
||||
<controls:SettingsPageControl x:Uid="FileLocksmith" ModuleImageSource="ms-appx:///Assets/Settings/Modules/FileLocksmith.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="FileLocksmithEnableFileLocksmith"
|
||||
x:Uid="FileLocksmith_Enable_FileLocksmith"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FileLocksmith.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsFileLocksmithEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="FileLocksmithEnableFileLocksmith"
|
||||
x:Uid="FileLocksmith_Enable_FileLocksmith"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FileLocksmith.png}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsFileLocksmithEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
|
||||
<controls:SettingsGroup x:Uid="FileLocksmith_ShellIntegration" IsEnabled="{x:Bind ViewModel.IsFileLocksmithEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard Name="FileLocksmithToggleContextMenu" x:Uid="FileLocksmith_Toggle_ContextMenu">
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</StackPanel>
|
||||
|
||||
<Button
|
||||
x:Uid="GeneralPage_CheckForUpdates"
|
||||
HorizontalAlignment="Right"
|
||||
@@ -143,7 +142,6 @@
|
||||
</InfoBar.ActionButton>
|
||||
</InfoBar>
|
||||
|
||||
|
||||
<!-- Ready to install -->
|
||||
<InfoBar
|
||||
x:Uid="General_NewVersionReadyToInstall"
|
||||
@@ -277,24 +275,14 @@
|
||||
IsEnabled="{x:Bind ViewModel.IsRunAtStartupGPOManaged, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.Startup, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard x:Uid="ShowSystemTrayIcon">
|
||||
<ToggleSwitch
|
||||
x:Uid="ShowSystemTrayIcon_ToggleSwitch"
|
||||
IsOn="{x:Bind ViewModel.ShowSysTrayIcon, Mode=TwoWay}"
|
||||
Toggled="ShowSystemTrayIcon_Toggled" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsRunAtStartupGPOManaged, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsRunAtStartupGPOManaged, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsRunAtStartupGPOManaged, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard x:Uid="ShowSystemTrayIcon">
|
||||
<ToggleSwitch
|
||||
x:Uid="ShowSystemTrayIcon_ToggleSwitch"
|
||||
IsOn="{x:Bind ViewModel.ShowSysTrayIcon, Mode=TwoWay}"
|
||||
Toggled="ShowSystemTrayIcon_Toggled" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="General_SettingsBackupAndRestoreTitle" Visibility="Visible">
|
||||
@@ -405,25 +393,14 @@
|
||||
IsTabStop="{x:Bind ViewModel.SettingsBackupRestoreMessageVisible, Mode=OneWay}"
|
||||
Severity="{x:Bind ViewModel.BackupRestoreMessageSeverity, Converter={StaticResource StringToInfoBarSeverityConverter}}" />
|
||||
<controls:SettingsGroup x:Uid="General_Experimentation" Visibility="Visible">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="GeneralPageEnableExperimentation"
|
||||
x:Uid="GeneralPage_EnableExperimentation"
|
||||
IsEnabled="{x:Bind ViewModel.IsExperimentationGpoDisallowed, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<tkcontrols:SettingsCard.HeaderIcon>
|
||||
<PathIcon Data="M1859 1758q14 23 21 47t7 51q0 40-15 75t-41 61-61 41-75 15H354q-40 0-75-15t-61-41-41-61-15-75q0-27 6-51t21-47l569-992q10-14 10-34V128H640V0h768v128h-128v604q0 19 10 35l569 991zM896 732q0 53-27 99l-331 577h972l-331-577q-27-46-27-99V128H896v604zm799 1188q26 0 44-19t19-45q0-10-2-17t-8-16l-164-287H464l-165 287q-9 15-9 33 0 26 18 45t46 19h1341z" />
|
||||
</tkcontrols:SettingsCard.HeaderIcon>
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.EnableExperimentation, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsExperimentationGpoDisallowed, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsExperimentationGpoDisallowed, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsExperimentationGpoDisallowed, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard Name="GeneralPageEnableExperimentation" x:Uid="GeneralPage_EnableExperimentation">
|
||||
<tkcontrols:SettingsCard.HeaderIcon>
|
||||
<PathIcon Data="M1859 1758q14 23 21 47t7 51q0 40-15 75t-41 61-61 41-75 15H354q-40 0-75-15t-61-41-41-61-15-75q0-27 6-51t21-47l569-992q10-14 10-34V128H640V0h768v128h-128v604q0 19 10 35l569 991zM896 732q0 53-27 99l-331 577h972l-331-577q-27-46-27-99V128H896v604zm799 1188q26 0 44-19t19-45q0-10-2-17t-8-16l-164-287H464l-165 287q-9 15-9 33 0 26 18 45t46 19h1341z" />
|
||||
</tkcontrols:SettingsCard.HeaderIcon>
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.EnableExperimentation, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
</controls:SettingsGroup>
|
||||
<controls:SettingsGroup x:Uid="General_DiagnosticsAndFeedback">
|
||||
<tkcontrols:SettingsExpander
|
||||
@@ -478,30 +455,22 @@
|
||||
</InfoBar>
|
||||
</tkcontrols:SettingsExpander.ItemsFooter>
|
||||
</tkcontrols:SettingsExpander>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsDataDiagnosticsGPOManaged, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsDataDiagnosticsGPOManaged, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<tkcontrols:SettingsCard x:Uid="GeneralPage_ReportBugPackage" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button
|
||||
x:Uid="GeneralPageReportBugPackage"
|
||||
Click="BugReportToolClicked"
|
||||
Visibility="{x:Bind ViewModel.IsBugReportRunning, Converter={StaticResource ReverseBoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
<ProgressRing
|
||||
Width="24"
|
||||
Height="24"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{x:Bind ViewModel.IsBugReportRunning, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsCard>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsDataDiagnosticsGPOManaged, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard x:Uid="GeneralPage_ReportBugPackage" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button
|
||||
x:Uid="GeneralPageReportBugPackage"
|
||||
Click="BugReportToolClicked"
|
||||
Visibility="{x:Bind ViewModel.IsBugReportRunning, Converter={StaticResource ReverseBoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
<ProgressRing
|
||||
Width="24"
|
||||
Height="24"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{x:Bind ViewModel.IsBugReportRunning, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
</controls:SettingsGroup>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
@@ -13,23 +13,14 @@
|
||||
<controls:SettingsPageControl x:Uid="Hosts" ModuleImageSource="ms-appx:///Assets/Settings/Modules/HostsFileEditor.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="HostsEnableToggleControlHeaderText"
|
||||
x:Uid="Hosts_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Hosts.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="HostsEnableToggleControlHeaderText"
|
||||
x:Uid="Hosts_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Hosts.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<controls:SettingsGroup x:Uid="Hosts_Activation_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="HostsLaunchButtonControl"
|
||||
|
||||
@@ -31,25 +31,14 @@
|
||||
<controls:SettingsPageControl x:Uid="ImageResizer" ModuleImageSource="ms-appx:///Assets/Settings/Modules/ImageResizer.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="ImageResizerEnableToggle"
|
||||
x:Uid="ImageResizer_EnableToggle"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ImageResizer.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="ImageResizerEnableToggle"
|
||||
x:Uid="ImageResizer_EnableToggle"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ImageResizer.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<controls:SettingsGroup x:Uid="ImageResizer_CustomSizes" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="ImageResizerPresets"
|
||||
|
||||
@@ -56,28 +56,19 @@
|
||||
<controls:SettingsPageControl x:Uid="KeyboardManager" ModuleImageSource="ms-appx:///Assets/Settings/Modules/KBM.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="KeyboardManagerEnableToggle"
|
||||
x:Uid="KeyboardManager_EnableToggle"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/KeyboardManager.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.Enabled, Mode=TwoWay}" />
|
||||
<tkcontrols:SettingsCard.Description>
|
||||
<HyperlinkButton NavigateUri="https://aka.ms/powerToysCannotRemapKeys">
|
||||
<TextBlock x:Uid="KBM_KeysCannotBeRemapped" FontWeight="SemiBold" />
|
||||
</HyperlinkButton>
|
||||
</tkcontrols:SettingsCard.Description>
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="KeyboardManagerEnableToggle"
|
||||
x:Uid="KeyboardManager_EnableToggle"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/KeyboardManager.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.Enabled, Mode=TwoWay}" />
|
||||
<tkcontrols:SettingsCard.Description>
|
||||
<HyperlinkButton NavigateUri="https://aka.ms/powerToysCannotRemapKeys">
|
||||
<TextBlock x:Uid="KBM_KeysCannotBeRemapped" FontWeight="SemiBold" />
|
||||
</HyperlinkButton>
|
||||
</tkcontrols:SettingsCard.Description>
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
|
||||
<controls:SettingsGroup x:Uid="KeyboardManager_Keys" IsEnabled="{x:Bind ViewModel.Enabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
|
||||
@@ -14,29 +14,27 @@
|
||||
<controls:SettingsPageControl x:Uid="MeasureTool" ModuleImageSource="ms-appx:///Assets/Settings/Modules/ScreenRuler.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="MeasureToolEnableMeasureTool"
|
||||
x:Uid="MeasureTool_EnableMeasureTool"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ScreenRuler.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="MeasureToolEnableMeasureTool"
|
||||
x:Uid="MeasureTool_EnableMeasureTool"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ScreenRuler.png}">
|
||||
<ToggleSwitch
|
||||
x:Uid="ToggleSwitch"
|
||||
AutomationProperties.AutomationId="Toggle_ScreenRuler"
|
||||
IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
|
||||
<controls:SettingsGroup x:Uid="MeasureTool_ActivationSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="MeasureToolActivationShortcut"
|
||||
x:Uid="MeasureTool_ActivationShortcut"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.ActivationShortcut, Mode=TwoWay}" />
|
||||
<controls:ShortcutControl
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
AutomationProperties.AutomationId="Shortcut_ScreenRuler"
|
||||
HotkeySettings="{x:Bind Path=ViewModel.ActivationShortcut, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard Name="MeasureToolDefaultMeasureStyle" x:Uid="MeasureTool_DefaultMeasureStyle">
|
||||
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind Path=ViewModel.DefaultMeasureStyle, Mode=TwoWay}">
|
||||
|
||||
@@ -24,23 +24,14 @@
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<controls:SettingsGroup x:Uid="MouseUtils_FindMyMouse">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="MouseUtilsEnableFindMyMouse"
|
||||
x:Uid="MouseUtils_Enable_FindMyMouse"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FindMyMouse.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsFindMyMouseEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsFindMyMouseEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsFindMyMouseEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsFindMyMouseEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsFindMyMouseEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="MouseUtilsEnableFindMyMouse"
|
||||
x:Uid="MouseUtils_Enable_FindMyMouse"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/FindMyMouse.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsFindMyMouseEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<tkcontrols:SettingsExpander
|
||||
Name="MouseUtilsFindMyMouseActivationMethod"
|
||||
x:Uid="MouseUtils_FindMyMouse_ActivationMethod"
|
||||
@@ -169,7 +160,7 @@
|
||||
Severity="Informational"
|
||||
Visibility="{x:Bind ViewModel.IsAnimationEnabledBySystem, Mode=OneWay, Converter={StaticResource ReverseBoolToVisibilityConverter}}">
|
||||
<InfoBar.ActionButton>
|
||||
<HyperlinkButton x:Uid="OpenAnimationsSettings" Click="OpenAnimationsSettings_Click" />
|
||||
<HyperlinkButton x:Uid="OpenSettings" Click="OpenAnimationsSettings_Click" />
|
||||
</InfoBar.ActionButton>
|
||||
</InfoBar>
|
||||
<tkcontrols:SettingsExpander
|
||||
@@ -196,23 +187,14 @@
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="MouseUtils_MouseHighlighter">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="MouseUtilsEnableMouseHighlighter"
|
||||
x:Uid="MouseUtils_Enable_MouseHighlighter"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseHighlighter.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsHighlighterEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsMouseHighlighterEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsHighlighterEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsHighlighterEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsHighlighterEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="MouseUtilsEnableMouseHighlighter"
|
||||
x:Uid="MouseUtils_Enable_MouseHighlighter"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseHighlighter.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsMouseHighlighterEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<tkcontrols:SettingsExpander
|
||||
Name="MouseUtilsMouseHighlighterActivationShortcut"
|
||||
x:Uid="MouseUtils_MouseHighlighter_ActivationShortcut"
|
||||
@@ -284,23 +266,15 @@
|
||||
<panels:MouseJumpPanel x:Name="MouseUtils_MouseJump_Panel" x:Uid="MouseUtils_MouseJump_Panel" />
|
||||
|
||||
<controls:SettingsGroup x:Uid="MouseUtils_MousePointerCrosshairs">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="MouseUtilsEnableMousePointerCrosshairs"
|
||||
x:Uid="MouseUtils_Enable_MousePointerCrosshairs"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseCrosshairs.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsMousePointerCrosshairsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsMousePointerCrosshairsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsMousePointerCrosshairsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsMousePointerCrosshairsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsMousePointerCrosshairsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="MouseUtilsEnableMousePointerCrosshairs"
|
||||
x:Uid="MouseUtils_Enable_MousePointerCrosshairs"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseCrosshairs.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsMousePointerCrosshairsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
|
||||
<tkcontrols:SettingsExpander
|
||||
Name="MouseUtilsMousePointerCrosshairsActivationShortcut"
|
||||
x:Uid="MouseUtils_MousePointerCrosshairs_ActivationShortcut"
|
||||
@@ -413,42 +387,6 @@
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<!-- Dwell Cursor -->
|
||||
<controls:SettingsGroup x:Uid="MouseUtils_DwellCursor">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="MouseUtilsEnableDwellCursor"
|
||||
x:Uid="MouseUtils_Enable_DwellCursor"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseCrosshairs.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsDwellCursorEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
<tkcontrols:SettingsExpander
|
||||
Name="MouseUtilsDwellCursorActivationShortcut"
|
||||
x:Uid="MouseUtils_DwellCursor_ActivationShortcut"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsEnabled="{x:Bind ViewModel.IsDwellCursorEnabled, Mode=OneWay}"
|
||||
IsExpanded="True">
|
||||
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.DwellCursorActivationShortcut, Mode=TwoWay}" />
|
||||
<tkcontrols:SettingsExpander.Items>
|
||||
<tkcontrols:SettingsCard Name="MouseUtilsDwellCursorDelayMs" x:Uid="MouseUtils_DwellCursor_DelayMs">
|
||||
<Slider
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
Maximum="10"
|
||||
Minimum="1"
|
||||
Value="{x:Bind ViewModel.DwellCursorDelayTimeSeconds, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard Name="MouseUtilsDwellCursorSettleTimeSeconds" x:Uid="MouseUtils_DwellCursor_SettleTimeSeconds">
|
||||
<Slider
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
Maximum="5"
|
||||
Minimum="1"
|
||||
Value="{x:Bind ViewModel.DwellCursorSettleTimeSeconds, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</tkcontrols:SettingsExpander.Items>
|
||||
</tkcontrols:SettingsExpander>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
</StackPanel>
|
||||
</controls:SettingsPageControl.ModuleContent>
|
||||
<controls:SettingsPageControl.PrimaryLinks>
|
||||
|
||||
@@ -42,7 +42,6 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
SettingsRepository<MouseHighlighterSettings>.GetInstance(settingsUtils),
|
||||
SettingsRepository<MouseJumpSettings>.GetInstance(settingsUtils),
|
||||
SettingsRepository<MousePointerCrosshairsSettings>.GetInstance(settingsUtils),
|
||||
SettingsRepository<DwellCursorSettings>.GetInstance(settingsUtils),
|
||||
ShellPage.SendDefaultIPCMessage);
|
||||
|
||||
DataContext = ViewModel;
|
||||
|
||||
@@ -21,27 +21,18 @@
|
||||
<controls:SettingsPageControl x:Uid="MouseWithoutBorders" ModuleImageSource="ms-appx:///Assets/Settings/Modules/MouseWithoutBorders.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<controls:SettingsGroup x:Name="ActivationSettingsGroup" x:Uid="MouseWithoutBorders_ActivationSettings">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="MouseWithoutBordersToggleEnable"
|
||||
x:Uid="MouseWithoutBorders_Toggle_Enable"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseWithoutBorders.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch
|
||||
x:Uid="ToggleSwitch"
|
||||
IsEnabled="{x:Bind ViewModel.CanBeEnabled, Mode=OneWay}"
|
||||
IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:SettingsGroup x:Uid="MouseWithoutBorders_ActivationSettings">
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="MouseWithoutBordersToggleEnable"
|
||||
x:Uid="MouseWithoutBorders_Toggle_Enable"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/MouseWithoutBorders.png}">
|
||||
<ToggleSwitch
|
||||
x:Uid="ToggleSwitch"
|
||||
IsEnabled="{x:Bind ViewModel.CanBeEnabled, Mode=OneWay}"
|
||||
IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
</controls:SettingsGroup>
|
||||
<controls:SettingsGroup
|
||||
x:Name="KeySettingsGroup"
|
||||
@@ -179,16 +170,12 @@
|
||||
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
<InfoBar
|
||||
x:Uid="MouseWithoutBorders_CannotDragDropAsAdmin"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.ShowInfobarCannotDragDropAsAdmin, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.ShowInfobarCannotDragDropAsAdmin, Mode=OneWay}"
|
||||
Severity="Informational" />
|
||||
|
||||
|
||||
|
||||
<tkcontrols:SettingsCard Name="MouseWithoutBordersMatrixOneRow" x:Uid="MouseWithoutBorders_MatrixOneRow">
|
||||
<ToggleSwitch x:Uid="MouseWithoutBorders_MatrixOneRow_ToggleSwitch" IsOn="{x:Bind ViewModel.MatrixOneRow, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
@@ -197,34 +184,28 @@
|
||||
x:Name="ServiceSettingsGroup"
|
||||
x:Uid="MouseWithoutBorders_ServiceSettings"
|
||||
IsEnabled="{x:Bind ViewModel.CanToggleUseService, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="MouseWithoutBordersUseService"
|
||||
x:Uid="MouseWithoutBorders_UseService"
|
||||
IsEnabled="{x:Bind ViewModel.UseServiceSettingIsEnabled, Mode=OneWay}">
|
||||
<ToggleSwitch x:Uid="MouseWithoutBorders_UseService_ToggleSwitch" IsOn="{x:Bind ViewModel.UseService, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.ShowPolicyConfiguredInfoForServiceSettings, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.ShowPolicyConfiguredInfoForServiceSettings, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<InfoBar
|
||||
x:Uid="MouseWithoutBorders_RunAsAdminText"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.ShowInfobarRunAsAdminText, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.ShowInfobarRunAsAdminText, Mode=OneWay}"
|
||||
Severity="Informational" />
|
||||
<InfoBar
|
||||
x:Uid="MouseWithoutBorders_ServiceUserUninstallWarning"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabled, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabled, Mode=OneWay}"
|
||||
Severity="Warning" />
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.ShowPolicyConfiguredInfoForServiceSettings, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="MouseWithoutBordersUseService"
|
||||
x:Uid="MouseWithoutBorders_UseService"
|
||||
IsEnabled="{x:Bind ViewModel.UseServiceSettingIsEnabled, Mode=OneWay}">
|
||||
<ToggleSwitch x:Uid="MouseWithoutBorders_UseService_ToggleSwitch" IsOn="{x:Bind ViewModel.UseService, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<InfoBar
|
||||
x:Uid="MouseWithoutBorders_RunAsAdminText"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.ShowInfobarRunAsAdminText, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.ShowInfobarRunAsAdminText, Mode=OneWay}"
|
||||
Severity="Informational" />
|
||||
<InfoBar
|
||||
x:Uid="MouseWithoutBorders_ServiceUserUninstallWarning"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabled, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabled, Mode=OneWay}"
|
||||
Severity="Warning" />
|
||||
</StackPanel>
|
||||
<tkcontrols:SettingsCard
|
||||
Name="MouseWithoutBordersUninstallService"
|
||||
x:Uid="MouseWithoutBorders_UninstallService"
|
||||
@@ -497,22 +478,14 @@
|
||||
ActionIcon="{ui:FontIcon Glyph=}"
|
||||
Command="{x:Bind ViewModel.AddFirewallRuleEventHandler}"
|
||||
IsClickEnabled="True" />
|
||||
<tkcontrols:SettingsCard
|
||||
Name="MouseWithoutBordersShowOriginalUI"
|
||||
x:Uid="MouseWithoutBorders_ShowOriginalUI"
|
||||
IsEnabled="{x:Bind ViewModel.CardForOriginalUiSettingIsEnabled, Mode=OneWay}">
|
||||
<ToggleSwitch x:Uid="MouseWithoutBorders_ShowOriginalUI_ToggleSwitch" IsOn="{x:Bind ViewModel.ShowOriginalUI, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.ShowPolicyConfiguredInfoForOriginalUiSetting, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.ShowPolicyConfiguredInfoForOriginalUiSetting, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.ShowPolicyConfiguredInfoForOriginalUiSetting, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="MouseWithoutBordersShowOriginalUI"
|
||||
x:Uid="MouseWithoutBorders_ShowOriginalUI"
|
||||
IsEnabled="{x:Bind ViewModel.CardForOriginalUiSettingIsEnabled, Mode=OneWay}">
|
||||
<ToggleSwitch x:Uid="MouseWithoutBorders_ShowOriginalUI_ToggleSwitch" IsOn="{x:Bind ViewModel.ShowOriginalUI, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
</controls:SettingsGroup>
|
||||
</StackPanel>
|
||||
</controls:SettingsPageControl.ModuleContent>
|
||||
|
||||
@@ -18,24 +18,14 @@
|
||||
ChildrenTransitions="{StaticResource SettingsCardsAnimations}"
|
||||
Orientation="Vertical"
|
||||
Spacing="2">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="NewPlusEnableToggle"
|
||||
x:Uid="NewPlus_Enable_Toggle"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/NewPlus.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="NewPlusEnableToggle"
|
||||
x:Uid="NewPlus_Enable_Toggle"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/NewPlus.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<controls:SettingsGroup x:Uid="NewPlus_Templates" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="NewPlusTemplatesLocation"
|
||||
@@ -55,7 +45,6 @@
|
||||
<HyperlinkButton x:Uid="NewPlus_Templates_Location_Learn_More" NavigateUri="https://aka.ms/PowerToysOverview_NewPlus_TemplatesLocation" />
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsCard.Description>
|
||||
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
<InfoBar
|
||||
@@ -64,27 +53,18 @@
|
||||
IsOpen="{x:Bind ViewModel.IsEnabled, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabled, Mode=OneWay}"
|
||||
Severity="Informational" />
|
||||
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="NewPlus_Display_Options" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="NewPlusHideFileExtensionToggle"
|
||||
x:Uid="NewPlus_Hide_File_Extension_Toggle"
|
||||
IsEnabled="{x:Bind ViewModel.IsHideFileExtSettingsCardEnabled, Mode=OneWay}">
|
||||
<ToggleSwitch x:Uid="HideFileExtensionToggle" IsOn="{x:Bind ViewModel.HideFileExtension, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsHideFileExtSettingGPOConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsHideFileExtSettingGPOConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsHideFileExtSettingGPOConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="NewPlusHideFileExtensionToggle"
|
||||
x:Uid="NewPlus_Hide_File_Extension_Toggle"
|
||||
IsEnabled="{x:Bind ViewModel.IsHideFileExtSettingsCardEnabled, Mode=OneWay}">
|
||||
<ToggleSwitch x:Uid="HideFileExtensionToggle" IsOn="{x:Bind ViewModel.HideFileExtension, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<tkcontrols:SettingsCard Name="NewPlusHideStartingDigitsToggle" x:Uid="NewPlus_Hide_Starting_Digits_Toggle">
|
||||
|
||||
<ToggleSwitch x:Uid="HideStartingDigitsToggle" IsOn="{x:Bind ViewModel.HideStartingDigits, Mode=TwoWay}" />
|
||||
<tkcontrols:SettingsCard.Description>
|
||||
<TextBlock x:Uid="NewPlus_Hide_Starting_Digits_Description" />
|
||||
@@ -93,116 +73,111 @@
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="NewPlus_behavior" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="NewPlusBehaviourReplaceVariablesToggle"
|
||||
x:Uid="NewPlus_Behaviour_Replace_Variables_Toggle"
|
||||
IsEnabled="{x:Bind ViewModel.IsReplaceVariablesSettingsCardEnabled, Mode=OneWay}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<ToggleSwitch x:Uid="ReplaceVariablesToggle" IsOn="{x:Bind ViewModel.ReplaceVariables, Mode=TwoWay}" />
|
||||
<Button
|
||||
x:Uid="FileCreationButton"
|
||||
Width="28"
|
||||
Height="40"
|
||||
Margin="0,0,-4,0"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
Content=""
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}">
|
||||
<Button.Flyout>
|
||||
<Flyout x:Name="VariableExamplesFlyout" ShouldConstrainToRootBounds="False">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="62" />
|
||||
<ColumnDefinition Width="300" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsReplaceVariablesSettingGPOConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="NewPlusBehaviourReplaceVariablesToggle"
|
||||
x:Uid="NewPlus_Behaviour_Replace_Variables_Toggle"
|
||||
IsEnabled="{x:Bind ViewModel.IsReplaceVariablesSettingsCardEnabled, Mode=OneWay}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||
<ToggleSwitch x:Uid="ReplaceVariablesToggle" IsOn="{x:Bind ViewModel.ReplaceVariables, Mode=TwoWay}" />
|
||||
<Button
|
||||
x:Uid="FileCreationButton"
|
||||
Width="28"
|
||||
Height="40"
|
||||
Margin="0,0,-4,0"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
Content=""
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}">
|
||||
<Button.Flyout>
|
||||
<Flyout x:Name="VariableExamplesFlyout" ShouldConstrainToRootBounds="False">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="62" />
|
||||
<ColumnDefinition Width="300" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Text="$YYYY" />
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,5"
|
||||
TextWrapping="Wrap"><Run x:Uid="NewPlus_Year_YYYY_Variable_Description" /></TextBlock>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Text="$YYYY" />
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,5"
|
||||
TextWrapping="Wrap"><Run x:Uid="NewPlus_Year_YYYY_Variable_Description" /></TextBlock>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Text="$MM" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,5"
|
||||
TextWrapping="Wrap"><Run x:Uid="NewPlus_Month_MM_Variable_Description" /></TextBlock>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Text="$MM" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,5"
|
||||
TextWrapping="Wrap"><Run x:Uid="NewPlus_Month_MM_Variable_Description" /></TextBlock>
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Text="$DD" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,5"
|
||||
TextWrapping="Wrap"><Run x:Uid="NewPlus_Day_DD_Variable_Description" /></TextBlock>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Text="$DD" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,5"
|
||||
TextWrapping="Wrap"><Run x:Uid="NewPlus_Day_DD_Variable_Description" /></TextBlock>
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Text="$hh" />
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,5"
|
||||
TextWrapping="Wrap"><Run x:Uid="NewPlus_Hour_hh_Variable_Description" /></TextBlock>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Text="$hh" />
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,5"
|
||||
TextWrapping="Wrap"><Run x:Uid="NewPlus_Hour_hh_Variable_Description" /></TextBlock>
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Text="$mm" />
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,5"
|
||||
TextWrapping="Wrap"><Run x:Uid="NewPlus_Minute_mm_Variable_Description" /></TextBlock>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Text="$mm" />
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,5"
|
||||
TextWrapping="Wrap"><Run x:Uid="NewPlus_Minute_mm_Variable_Description" /></TextBlock>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
Text="$ss" />
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,0"
|
||||
TextWrapping="Wrap"><Run x:Uid="NewPlus_Second_ss_Variable_Description" /></TextBlock>
|
||||
</Grid>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<tkcontrols:SettingsCard.Description>
|
||||
<StackPanel>
|
||||
<HyperlinkButton x:Uid="NewPlus_Behaviour_Replace_Variables_Learn_More" NavigateUri="https://aka.ms/PowerToysOverview_NewPlus" />
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
Text="$ss" />
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,0"
|
||||
TextWrapping="Wrap"><Run x:Uid="NewPlus_Second_ss_Variable_Description" /></TextBlock>
|
||||
</Grid>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsCard.Description>
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsReplaceVariablesSettingGPOConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsReplaceVariablesSettingGPOConfigured, Mode=OneWay}"
|
||||
Severity="Informational" />
|
||||
<tkcontrols:SettingsCard.Description>
|
||||
<StackPanel>
|
||||
<HyperlinkButton x:Uid="NewPlus_Behaviour_Replace_Variables_Learn_More" NavigateUri="https://aka.ms/PowerToysOverview_NewPlus" />
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsCard.Description>
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
</controls:SettingsGroup>
|
||||
</StackPanel>
|
||||
</controls:SettingsPageControl.ModuleContent>
|
||||
|
||||
@@ -14,24 +14,14 @@
|
||||
<controls:SettingsPageControl x:Uid="Peek" ModuleImageSource="ms-appx:///Assets/Settings/Modules/Peek.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="PeekEnablePeek"
|
||||
x:Uid="Peek_EnablePeek"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Peek.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="PeekEnablePeek"
|
||||
x:Uid="Peek_EnablePeek"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Peek.png}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<controls:SettingsGroup x:Uid="Peek_Activation_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="ActivationShortcut"
|
||||
|
||||
@@ -27,25 +27,14 @@
|
||||
ModuleImageSource="ms-appx:///Assets/Settings/Modules/QuickAccent.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="QuickAccentEnableQuickAccent"
|
||||
x:Uid="QuickAccent_EnableQuickAccent"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/QuickAccent.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="QuickAccentEnableQuickAccent"
|
||||
x:Uid="QuickAccent_EnableQuickAccent"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/QuickAccent.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<controls:SettingsGroup x:Uid="QuickAccent_Activation_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsExpander
|
||||
Name="QuickAccentActivationShortcut"
|
||||
|
||||
@@ -308,7 +308,6 @@
|
||||
|
||||
<controls:SettingsPageControl x:Uid="PowerLauncher" ModuleImageSource="ms-appx:///Assets/Settings/Modules/Run.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<InfoBar
|
||||
x:Uid="Run_CheckOutCmdPal"
|
||||
@@ -322,24 +321,14 @@
|
||||
<HyperlinkButton x:Uid="Run_NavigateCmdPalSettings" Click="NavigateCmdPalSettings_Click" />
|
||||
</InfoBar.ActionButton>
|
||||
</InfoBar>
|
||||
<tkcontrols:SettingsCard
|
||||
Name="PowerLauncherEnablePowerLauncher"
|
||||
x:Uid="PowerLauncher_EnablePowerLauncher"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/PowerToysRun.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.EnablePowerLauncher, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="PowerLauncherEnablePowerLauncher"
|
||||
x:Uid="PowerLauncher_EnablePowerLauncher"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/PowerToysRun.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.EnablePowerLauncher, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<controls:SettingsGroup x:Uid="Shortcut" IsEnabled="{x:Bind ViewModel.EnablePowerLauncher, Mode=OneWay}">
|
||||
<tkcontrols:SettingsExpander
|
||||
Name="ActivationShortcut"
|
||||
@@ -358,7 +347,6 @@
|
||||
</tkcontrols:SettingsExpander>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
|
||||
<!--<Custom:HotkeySettingsControl x:Uid="PowerLauncher_OpenFileLocation"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="{StaticResource SmallTopMargin}"
|
||||
|
||||
@@ -24,23 +24,14 @@
|
||||
IsOpen="{x:Bind ViewModel.IsWin11OrGreater, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsWin11OrGreater, Mode=OneWay}"
|
||||
Severity="Informational" />
|
||||
<tkcontrols:SettingsCard
|
||||
Name="TextExtractorEnableToggleControlHeaderText"
|
||||
x:Uid="TextExtractor_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/TextExtractor.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="TextExtractorEnableToggleControlHeaderText"
|
||||
x:Uid="TextExtractor_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/TextExtractor.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<InfoBar
|
||||
x:Uid="TextExtractor_SupportedLanguages"
|
||||
IsClosable="False"
|
||||
|
||||
@@ -17,23 +17,14 @@
|
||||
x:Name="PowerRenameView"
|
||||
ChildrenTransitions="{StaticResource SettingsCardsAnimations}"
|
||||
Orientation="Vertical">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="PowerRenameToggleEnable"
|
||||
x:Uid="PowerRename_Toggle_Enable"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/PowerRename.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="PowerRenameToggleEnable"
|
||||
x:Uid="PowerRename_Toggle_Enable"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/PowerRename.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<controls:SettingsGroup x:Uid="PowerRename_ShellIntegration" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsExpander
|
||||
Name="PowerRenameToggleContextMenu"
|
||||
|
||||
@@ -14,25 +14,14 @@
|
||||
<controls:SettingsPageControl x:Uid="RegistryPreview" ModuleImageSource="ms-appx:///Assets/Settings/Modules/RegistryPreview.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="RegistryPreviewEnableRegistryPreview"
|
||||
x:Uid="RegistryPreview_Enable_RegistryPreview"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/RegistryPreview.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsRegistryPreviewEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="RegistryPreviewEnableRegistryPreview"
|
||||
x:Uid="RegistryPreview_Enable_RegistryPreview"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/RegistryPreview.png}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsRegistryPreviewEnabled, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<controls:SettingsGroup x:Uid="RegistryPreview_Launch_GroupSettings" IsEnabled="{x:Bind ViewModel.IsRegistryPreviewEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="RegistryPreviewLaunchButtonControl"
|
||||
@@ -49,10 +38,8 @@
|
||||
<ToggleSwitch x:Uid="RegistryPreview_DefaultRegApp_ToggleSwitch" IsOn="{x:Bind ViewModel.IsRegistryPreviewDefaultRegApp, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
</StackPanel>
|
||||
</controls:SettingsPageControl.ModuleContent>
|
||||
|
||||
<controls:SettingsPageControl.PrimaryLinks>
|
||||
<controls:PageLink x:Uid="LearnMore_RegistryPreview" Link="https://aka.ms/PowerToysOverview_RegistryPreview" />
|
||||
</controls:SettingsPageControl.PrimaryLinks>
|
||||
|
||||
@@ -212,7 +212,7 @@
|
||||
x:Name="MeasureToolNavigationItem"
|
||||
x:Uid="Shell_MeasureTool"
|
||||
helpers:NavHelper.NavigateTo="views:MeasureToolPage"
|
||||
AutomationProperties.AutomationId="MeasureToolNavItem"
|
||||
AutomationProperties.AutomationId="ScreenRulerNavItem"
|
||||
Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ScreenRuler.png}" />
|
||||
<NavigationViewItem
|
||||
x:Name="ShortcutGuideNavigationItem"
|
||||
|
||||
@@ -14,23 +14,14 @@
|
||||
<controls:SettingsPageControl x:Uid="ShortcutGuide" ModuleImageSource="ms-appx:///Assets/Settings/Modules/ShortcutGuide.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="ShortcutGuideEnable"
|
||||
x:Uid="ShortcutGuide_Enable"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ShortcutGuide.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="ShortcutGuideEnable"
|
||||
x:Uid="ShortcutGuide_Enable"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ShortcutGuide.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<controls:SettingsGroup x:Uid="Shortcut" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard Name="ShortcutGuideActivationMethod" x:Uid="ShortcutGuide_ActivationMethod">
|
||||
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind ViewModel.UseLegacyPressWinKeyBehavior, Mode=TwoWay, Converter={StaticResource BoolToComboBoxIndexConverter}}">
|
||||
|
||||
@@ -16,32 +16,21 @@
|
||||
ModuleImageSource="ms-appx:///Assets/Settings/Modules/Workspaces.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="WorkspacesEnableToggleControlHeaderText"
|
||||
x:Uid="Workspaces_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Workspaces.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="WorkspacesEnableToggleControlHeaderText"
|
||||
x:Uid="Workspaces_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/Workspaces.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
<controls:SettingsGroup x:Uid="Workspaces_Activation_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
|
||||
<tkcontrols:SettingsCard
|
||||
Name="WorkspacesActivationShortcut"
|
||||
x:Uid="Workspaces_ActivationShortcut"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.Hotkey, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
|
||||
<tkcontrols:SettingsCard
|
||||
Name="WorkspacesLaunchEditorButtonControl"
|
||||
x:Uid="Workspaces_LaunchEditorButtonControl"
|
||||
|
||||
@@ -30,23 +30,15 @@
|
||||
IsOpen="True"
|
||||
IsTabStop="True"
|
||||
Severity="Warning" />
|
||||
<tkcontrols:SettingsCard
|
||||
Name="ZoomItEnableToggleControlHeaderText"
|
||||
x:Uid="ZoomIt_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ZoomIt.png}"
|
||||
IsEnabled="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
<InfoBar.IconSource>
|
||||
<FontIconSource FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||
</InfoBar.IconSource>
|
||||
</InfoBar>
|
||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsEnabledGpoConfigured, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard
|
||||
Name="ZoomItEnableToggleControlHeaderText"
|
||||
x:Uid="ZoomIt_EnableToggleControl_HeaderText"
|
||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ZoomIt.png}">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
|
||||
<controls:SettingsGroup x:Uid="ZoomIt_BehaviorGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard Name="ZoomItToggleShowTrayIcon" x:Uid="ZoomIt_Toggle_ShowTrayIcon">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.ShowTrayIcon, Mode=TwoWay}" />
|
||||
|
||||
@@ -5299,33 +5299,4 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
||||
<data name="UtilitiesHeader.Title" xml:space="preserve">
|
||||
<value>Utilities</value>
|
||||
</data>
|
||||
<data name="MouseUtils_DwellCursor.Header" xml:space="preserve">
|
||||
<value>Dwell Cursor (placeholder)</value>
|
||||
<comment>Dwell Cursor description</comment>
|
||||
</data>
|
||||
<data name="MouseUtils_Enable_DwellCursor.Header" xml:space="preserve">
|
||||
<value>Enable Dwell Cursor</value>
|
||||
<comment>"Dwell Cursor" is the name of the utility.</comment>
|
||||
</data>
|
||||
<data name="MouseUtils_GlidingCursor_InitialSpeed.Header" xml:space="preserve">
|
||||
<value>Initial line speed</value>
|
||||
</data>
|
||||
<data name="MouseUtils_DwellCursor_DelayMs.Header" xml:space="preserve">
|
||||
<value>Delay time (in seconds)</value>
|
||||
</data>
|
||||
<data name="MouseUtils_DwellCursor_DelayMs.Description" xml:space="preserve">
|
||||
<value>Seconds to wait before automatically clicking when the cursor is still (1–10)</value>
|
||||
</data>
|
||||
<data name="MouseUtils_DwellCursor_ActivationShortcut.Header" xml:space="preserve">
|
||||
<value>Activation shortcut</value>
|
||||
</data>
|
||||
<data name="MouseUtils_DwellCursor_ActivationShortcut.Description" xml:space="preserve">
|
||||
<value>Customize the shortcut to turn on or off this mode</value>
|
||||
</data>
|
||||
<data name="MouseUtils_DwellCursor_SettleTimeSeconds.Header" xml:space="preserve">
|
||||
<value>Cursor settle time (seconds)</value>
|
||||
</data>
|
||||
<data name="MouseUtils_DwellCursor_SettleTimeSeconds.Description" xml:space="preserve">
|
||||
<value>Time the cursor must remain still before the countdown starts</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -29,22 +29,13 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
private MousePointerCrosshairsSettings MousePointerCrosshairsSettingsConfig { get; set; }
|
||||
|
||||
private DwellCursorSettings DwellCursorSettingsConfig { get; set; }
|
||||
|
||||
public MouseUtilsViewModel(
|
||||
ISettingsUtils settingsUtils,
|
||||
ISettingsRepository<GeneralSettings> settingsRepository,
|
||||
ISettingsRepository<FindMyMouseSettings> findMyMouseSettingsRepository,
|
||||
ISettingsRepository<MouseHighlighterSettings> mouseHighlighterSettingsRepository,
|
||||
ISettingsRepository<MouseJumpSettings> mouseJumpSettingsRepository,
|
||||
ISettingsRepository<MousePointerCrosshairsSettings> mousePointerCrosshairsSettingsRepository,
|
||||
ISettingsRepository<DwellCursorSettings> dwellCursorSettingsRepository,
|
||||
Func<string, int> ipcMSGCallBackFunc)
|
||||
public MouseUtilsViewModel(ISettingsUtils settingsUtils, ISettingsRepository<GeneralSettings> settingsRepository, ISettingsRepository<FindMyMouseSettings> findMyMouseSettingsRepository, ISettingsRepository<MouseHighlighterSettings> mouseHighlighterSettingsRepository, ISettingsRepository<MouseJumpSettings> mouseJumpSettingsRepository, ISettingsRepository<MousePointerCrosshairsSettings> mousePointerCrosshairsSettingsRepository, Func<string, int> ipcMSGCallBackFunc)
|
||||
{
|
||||
SettingsUtils = settingsUtils;
|
||||
|
||||
// To obtain the general settings configurations of PowerToys Settings.
|
||||
ArgumentNullException.ThrowIfNull(settingsRepository);
|
||||
|
||||
GeneralSettingsConfig = settingsRepository.SettingsConfig;
|
||||
|
||||
InitializeEnabledValues();
|
||||
@@ -52,6 +43,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
// To obtain the find my mouse settings, if the file exists.
|
||||
// If not, to create a file with the default settings and to return the default configurations.
|
||||
ArgumentNullException.ThrowIfNull(findMyMouseSettingsRepository);
|
||||
|
||||
FindMyMouseSettingsConfig = findMyMouseSettingsRepository.SettingsConfig;
|
||||
_findMyMouseActivationMethod = FindMyMouseSettingsConfig.Properties.ActivationMethod.Value < 4 ? FindMyMouseSettingsConfig.Properties.ActivationMethod.Value : 0;
|
||||
_findMyMouseIncludeWinKey = FindMyMouseSettingsConfig.Properties.IncludeWinKey.Value;
|
||||
@@ -73,6 +65,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
_findMyMouseShakingFactor = FindMyMouseSettingsConfig.Properties.ShakingFactor.Value;
|
||||
|
||||
ArgumentNullException.ThrowIfNull(mouseHighlighterSettingsRepository);
|
||||
|
||||
MouseHighlighterSettingsConfig = mouseHighlighterSettingsRepository.SettingsConfig;
|
||||
string leftClickColor = MouseHighlighterSettingsConfig.Properties.LeftButtonClickColor.Value;
|
||||
_highlighterLeftButtonClickColor = !string.IsNullOrEmpty(leftClickColor) ? leftClickColor : "#a6FFFF00";
|
||||
@@ -92,6 +85,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
this.InitializeMouseJumpSettings(mouseJumpSettingsRepository);
|
||||
|
||||
ArgumentNullException.ThrowIfNull(mousePointerCrosshairsSettingsRepository);
|
||||
|
||||
MousePointerCrosshairsSettingsConfig = mousePointerCrosshairsSettingsRepository.SettingsConfig;
|
||||
|
||||
string crosshairsColor = MousePointerCrosshairsSettingsConfig.Properties.CrosshairsColor.Value;
|
||||
@@ -109,13 +103,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
_mousePointerCrosshairsFixedLength = MousePointerCrosshairsSettingsConfig.Properties.CrosshairsFixedLength.Value;
|
||||
_mousePointerCrosshairsAutoActivate = MousePointerCrosshairsSettingsConfig.Properties.AutoActivate.Value;
|
||||
|
||||
// Dwell Cursor
|
||||
ArgumentNullException.ThrowIfNull(dwellCursorSettingsRepository);
|
||||
DwellCursorSettingsConfig = dwellCursorSettingsRepository.SettingsConfig;
|
||||
_dwellCursorDelayTimeSeconds = DwellCursorSettingsConfig.Properties.DelayTimeMs.Value / 1000;
|
||||
_dwellCursorSettleTimeSeconds = DwellCursorSettingsConfig.Properties.SettleTimeSeconds.Value;
|
||||
|
||||
int isEnabled = 0;
|
||||
|
||||
Utilities.NativeMethods.SystemParametersInfo(Utilities.NativeMethods.SPI_GETCLIENTAREAANIMATION, 0, ref isEnabled, 0);
|
||||
_isAnimationEnabledBySystem = isEnabled != 0;
|
||||
|
||||
@@ -162,9 +151,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
_isMousePointerCrosshairsEnabled = GeneralSettingsConfig.Enabled.MousePointerCrosshairs;
|
||||
}
|
||||
|
||||
// Dwell Cursor enabled state
|
||||
_isDwellCursorEnabled = GeneralSettingsConfig.Enabled.DwellCursor;
|
||||
}
|
||||
|
||||
public override Dictionary<string, HotkeySettings[]> GetAllHotkeySettings()
|
||||
@@ -177,7 +163,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
MousePointerCrosshairsActivationShortcut,
|
||||
GlidingCursorActivationShortcut],
|
||||
[MouseJumpSettings.ModuleName] = [MouseJumpActivationShortcut],
|
||||
[DwellCursorSettings.ModuleName] = [DwellCursorActivationShortcut],
|
||||
};
|
||||
|
||||
return hotkeysDict;
|
||||
@@ -974,84 +959,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
SettingsUtils.SaveSettings(MousePointerCrosshairsSettingsConfig.ToJsonString(), MousePointerCrosshairsSettings.ModuleName);
|
||||
}
|
||||
|
||||
// Dwell Cursor properties
|
||||
private bool _isDwellCursorEnabled;
|
||||
private int _dwellCursorDelayTimeSeconds;
|
||||
private int _dwellCursorSettleTimeSeconds;
|
||||
|
||||
public bool IsDwellCursorEnabled
|
||||
{
|
||||
get => _isDwellCursorEnabled;
|
||||
set
|
||||
{
|
||||
if (_isDwellCursorEnabled != value)
|
||||
{
|
||||
_isDwellCursorEnabled = value;
|
||||
|
||||
GeneralSettingsConfig.Enabled.DwellCursor = value;
|
||||
OnPropertyChanged(nameof(IsDwellCursorEnabled));
|
||||
|
||||
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
|
||||
SendConfigMSG(outgoing.ToString());
|
||||
|
||||
NotifyDwellCursorPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HotkeySettings DwellCursorActivationShortcut
|
||||
{
|
||||
get => DwellCursorSettingsConfig.Properties.ActivationShortcut;
|
||||
set
|
||||
{
|
||||
if (DwellCursorSettingsConfig.Properties.ActivationShortcut != value)
|
||||
{
|
||||
DwellCursorSettingsConfig.Properties.ActivationShortcut = value ?? DwellCursorSettingsProperties.DefaultActivationShortcut;
|
||||
NotifyDwellCursorPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int DwellCursorDelayTimeSeconds
|
||||
{
|
||||
get => _dwellCursorDelayTimeSeconds;
|
||||
set
|
||||
{
|
||||
if (value != _dwellCursorDelayTimeSeconds)
|
||||
{
|
||||
_dwellCursorDelayTimeSeconds = value;
|
||||
|
||||
// Convert seconds to milliseconds for storage
|
||||
DwellCursorSettingsConfig.Properties.DelayTimeMs.Value = value * 1000;
|
||||
NotifyDwellCursorPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int DwellCursorSettleTimeSeconds
|
||||
{
|
||||
get => _dwellCursorSettleTimeSeconds;
|
||||
set
|
||||
{
|
||||
if (value != _dwellCursorSettleTimeSeconds)
|
||||
{
|
||||
_dwellCursorSettleTimeSeconds = value;
|
||||
DwellCursorSettingsConfig.Properties.SettleTimeSeconds.Value = value;
|
||||
NotifyDwellCursorPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void NotifyDwellCursorPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
OnPropertyChanged(propertyName);
|
||||
|
||||
var outsettings = new SndDwellCursorSettings(DwellCursorSettingsConfig);
|
||||
var ipcMessage = new SndModuleSettings<SndDwellCursorSettings>(outsettings);
|
||||
SendConfigMSG(ipcMessage.ToJsonString());
|
||||
SettingsUtils.SaveSettings(DwellCursorSettingsConfig.ToJsonString(), DwellCursorSettings.ModuleName);
|
||||
}
|
||||
|
||||
public void RefreshEnabledState()
|
||||
{
|
||||
InitializeEnabledValues();
|
||||
@@ -1059,7 +966,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
OnPropertyChanged(nameof(IsMouseHighlighterEnabled));
|
||||
OnPropertyChanged(nameof(IsMouseJumpEnabled));
|
||||
OnPropertyChanged(nameof(IsMousePointerCrosshairsEnabled));
|
||||
OnPropertyChanged(nameof(IsDwellCursorEnabled));
|
||||
}
|
||||
|
||||
private Func<string, int> SendConfigMSG { get; }
|
||||
|
||||
17
src/settings-ui/settings-ui.instructions.md
Normal file
17
src/settings-ui/settings-ui.instructions.md
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
applyTo: "**/*.cs,**/*.xaml"
|
||||
---
|
||||
# Settings UI – configuration app guidance
|
||||
|
||||
Scope
|
||||
- WinUI/WPF UI, communicates with Runner over named pipes; manages persisted settings schema.
|
||||
|
||||
Guidelines
|
||||
- Don’t break settings schema silently; add migration when shape changes.
|
||||
- If IPC/JSON contracts change, align with `src/runner/**` implementation.
|
||||
- Keep UI responsive: marshal to UI thread for UI-bound operations.
|
||||
- Reuse existing styles/resources; avoid duplicate theme keys.
|
||||
- Add/adjust migration or serialization tests when changing persisted settings.
|
||||
|
||||
Acceptance
|
||||
- Schema integrity preserved, responsive UI, consistent contracts, no style duplication.
|
||||
Reference in New Issue
Block a user