Skip to content

[Quick Accent] Migrate UI to WinUI 3 - #48891

Merged
Yu Leng (moooyo) merged 38 commits into
mainfrom
yuleng/m/qa/1
Jul 3, 2026
Merged

Yu Leng (moooyo) merged 38 commits into
mainfrom
yuleng/m/qa/1

Conversation

@moooyo

@moooyo Yu Leng (moooyo) commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary of the Pull Request

Migrates the Quick Accent (PowerAccent) module's UI from WPF (System.Windows.*) to WinUI 3 (Windows App SDK), following the pattern used by other migrated modules (ImageResizer, PowerDisplay). The accent selector is now a self-contained WinUI 3 app (PowerToys.PowerAccent.exe) shipped under WinUI3Apps, and PowerAccent.Core is UI-framework-agnostic.

demo:
https://github.com/user-attachments/assets/400c33ee-0fc0-491e-841b-a546438edf91

PR Checklist

  • Closes: Migrate quick accent to WinUI3 #48889
  • Communication: Tracked task (Migrate quick accent to WinUI3 #48889) agreed with core contributors
  • Tests: Added/updated and all pass — new PowerAccent.Core.UnitTests (21 tests for the positioning / DPI math); existing PowerAccent.Common.UnitTests unaffected
  • Localization: No new localizable end-user strings — new accessibility metadata uses non-localized AutomationProperties.AutomationId, and the window title is the brand name "Quick Accent" (literal, matching ColorPicker)
  • Dev docs: Updated doc/devdocs/modules/quickaccent.md
  • New binaries: Added on the required places
    • JSON for signing: the WinUI3Apps\ PowerAccent payloads are listed in ESRPSigning_core.json
    • WXS for installer: no manual Product.wxs entry — the self-contained WinUI3Apps output (exe + .pri + Windows App SDK runtime) is harvested by the WinUI3ApplicationsFiles glob (same as ImageResizer)
    • YML for CI pipeline: no change needed — PowerAccent.Core.UnitTests is discovered by the existing **\*UnitTest*.dll VSTest glob
    • YML for signed pipeline: covered by ESRPSigning_core.json; no release.yml change needed
  • Documentation updated: N/A — internal UI-framework migration with no user-facing behavior change

Detailed Description of the Pull Request / Additional comments

The migration spans three areas (tracked in #48889):

UI (WPF → WinUI 3)

  • PowerAccent.UI is now a WinUI 3 app shell (custom Program.Main, WindowsPackageType=None, WindowsAppSDKSelfContained=true).
  • The accent selector is a non-activating TransparentWindow overlay shown with SW_SHOWNA (never steals focus). It is made always-on-top only while shown — the WinUIEx WindowEx.IsAlwaysOnTop property is toggled true on show / false on hide in OnChangeDisplay (matching the WPF original's Topmost = isActive), so the dormant, never-destroyed overlay does not pin a discrete GPU awake on hybrid-graphics laptops (issue Power Toys Quick Accent Stops the GPU #34849 / PR [QuickAccent] Update Topmost logic to attempt to fix hybrid graphics issues #41044).
  • The accent "pill" selection visual is reproduced with VisualStateManager (WinUI 3 has no Style.Triggers).
  • WinUI 3 gotcha: x:Bind on a Window-rooted XAML initializes only on Window.Activated, which never fires for this SW_SHOWNA overlay — so the selector calls Bindings.Update() after InitializeComponent(); without it the ListView renders empty.
  • Theme: the long-lived, never-activated process follows the system app theme automatically — App.xaml leaves Application.RequestedTheme unset, so WinUI re-resolves the {ThemeResource} brushes (and retints the acrylic) on a live light/dark switch with no manual ThemeListener needed.
  • Layout parity with the WPF original: the bar width hugs its content (itemCount × 48, clamped to the monitor width — computed, not measured, to avoid a racy ListView measure), and each cell pins MinWidth=48 (WinUI's ListViewItem defaults to 88, which would otherwise leave wide gaps).
  • Accessibility: UIA window name + AutomationIds on the character list and description.

Dependency

  • PowerAccent.Core no longer depends on WPF — it raises events and takes an injected UI-thread marshaller.
  • WinForms SendKeys → SendInput (CsWin32 P/Invoke); WPF-UI (Lepo) removed; language data moved to the UI-/WinRT-agnostic PowerAccent.Common.
  • MVVM via CommunityToolkit.Mvvm with [ObservableProperty] partial properties (WinRT-correct, clears MVVMTK0045).

CI / Build / Installer

  • Signing config, WinUI3Apps glob harvest, and the new unit-test project — see the checklist above.

Validation Steps Performed

  • Build: x64 Debug builds with 0 warnings / 0 errors.
  • Unit tests: PowerAccent.Core.UnitTests — 21/21 pass (9 anchor positions × DPI 1.0/1.5/2.0, the offset and negative-origin monitors, caret centering + edge clamping + flip-below).
  • XamlStyler: PowerAccentXAML/MainWindow.xaml passes the passive format check (CI mode).
  • Manual (single monitor, Top-center, light theme):
    • Accent popup appears with the full accent list rendered.
    • Bar hugs the characters and is centered; cell spacing matches the WPF original.
    • Switching the system theme (light/dark) is followed live by the popup.
    • With show_description enabled, the description row is wide (≥600px) and readable, with the accent bar centered above it.
  • Remaining manual validation (tracked in Migrate quick accent to WinUI3 #48889): multi-monitor, per-monitor DPI, all 9 toolbar_position values, and high-contrast theme.

Yu Leng and others added 21 commits June 25, 2026 14:08
- Add WindowsFunctions.SendArrowKey(bool left) using PInvoke.SendInput
- Replace SendKeys.SendWait("{LEFT/RIGHT}") with SendArrowKey calls
- Remove dead System.Drawing.Point ctor and implicit operator from Point.cs
- Remove dead System.Drawing.Size implicit operator from Size.cs
- Remove <UseWindowsForms> from PowerAccent.Core.csproj; <UseWPF> stays for Task 2

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cy from Core

- Add `Action<Action> _runOnUiThread` field to `PowerAccent` and require it in the constructor
- Replace all three `System.Windows.Application.Current.Dispatcher.Invoke` calls in `SetEvents` with `_runOnUiThread(…)`
- Remove `<UseWPF>true</UseWPF>` from `PowerAccent.Core.csproj`
- Adapt `Selector.xaml.cs` to pass a WPF dispatcher delegate (throwaway adapter until Task 4 replaces that file)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… arbitrary anchors

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ntWindow)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…inalize

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SelectorViewModel.cs: four [ObservableProperty] fields (Characters,
SelectedIndex, Description, ShowDescription) on ObservableObject.
Selector.xaml: replace placeholder TextBlock with RootContent Grid,
CharactersList ListView (horizontal StackPanel, accent-pill brushes),
and a description row (BoolToVisibilityConverter via converters: xmlns).
Selector.xaml.cs: add public SelectorViewModel ViewModel { get; } = new().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…xplicit

Re-add the Blend design-time namespace (xmlns:d) to Selector.xaml's root
element; mc:Ignorable="d" referenced an undeclared prefix, which is a XAML
parse error. Also make the character DataTemplate's bare {x:Bind} explicit
as Mode=OneTime (items are immutable strings) per the project's
always-state-x:Bind-mode constraint.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hor positioning

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nalize

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…staller hand-off

Add WinUI3Apps\ prefix to the four self-contained PowerAccent artifacts
(PowerAccent.Core.dll, PowerAccent.Common.dll, PowerToys.PowerAccent.dll,
PowerToys.PowerAccent.exe) to match their new output location in the
WinUI 3 self-contained build. The C++ module interface DLL remains
unprefixed (root output). PowerToys.PowerAccentKeyboardService.dll is
left unprefixed pending confirmation of its post-build output path; see
docs/superpowers/quick-accent-winui3-build-checklist.md for the
validation steps required in CI.

WiX harvest investigation confirmed: no static PowerAccent.wxs exists or
is needed. File components are generated at build time by
generateAllFileComponents.ps1 scanning the build output directories.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Zero references to Windows.Win32 or PInvoke.* exist in any .cs file
under PowerAccent.UI (grep-verified; obj/bin excluded). Positioning and
window management go through FlyoutWindowHelper/AppWindow. The
NativeMethods.txt entries (SetWindowPos, GetSystemMetrics) were
generating unreachable stubs. Cleanup is build-verified on VS 2026.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ose Selector on exit

FINDING 1 (runtime-fatal): the relocated PowerToys.PowerAccent.exe lives in
WinUI3Apps\ but PowerAccentKeyboardService still emitted its DLL/.winmd to the
repo root, so in-proc reg-free WinRT activation of KeyboardListener failed and
the popup never appeared. Change the service vcxproj <OutDir> to WinUI3Apps\ so
the impl DLL and .winmd land next to the exe (mirrors MeasureToolCore and the
original co-located layout). Reduce PowerAccent.UI <CsWinRTIncludes> to just
PowerToys.GPOWrapper (Program.cs calls GPO directly); the keyboard service is
used only by PowerAccent.Core, which projects it transitively, matching the
original WPF UI that had no CsWinRTIncludes. Prefix the ESRP signing entry with
WinUI3Apps\; keep the module-interface DLL at root.

FINDING 3 (regression): dispose App.Window (Selector) in Program.Terminate's
enqueued lambda so Selector.SaveUsageInfo + Core.PowerAccent.Dispose run on the
UI thread before exit; otherwise usage-frequency learning was lost each session.

FINDING 5: remove the unused CorePoint alias in Selector.xaml.cs.

Build-verify: holding a vowel must show the popup (proves the service WinRT
class activates from WinUI3Apps\); confirm the UI's transitive CsWinRT
projection of the keyboard service still compiles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ml, CsWinRT, x:Bind)

First successful compile of the WPF -> WinUI 3 migration. Five build breaks,
all from the static (never-compiled) migration commits:

1. CS0029 in PowerAccent.Core: GetActiveDisplay returned `rcWork.Size`
   (System.Drawing.Size) relying on the implicit Size operator that Task 1
   removed as "dead". Construct Core.Size from the RECT edges instead, dropping
   the System.Drawing dependency entirely (more faithful to the decoupling goal).

2. WMC1012 (more than one ApplicationXaml): App.xaml sits at the project root,
   where the WinUI SDK's default glob already adds it as ApplicationDefinition;
   the csproj added it a second time. Set EnableDefaultApplicationDefinition=false
   so the explicit item is the only one (matches QuickAccess.UI; siblings such as
   Hosts instead nest App.xaml in a subfolder).

3. CS0246 ETWTrace not found: the rewritten App.xaml.cs dropped
   `using Microsoft.PowerToys.Telemetry;`. Restored.

4. CS0436 GpoRuleConfigured/GPOWrapper defined twice (235x): PowerAccent.Core
   already projects PowerToys.GPOWrapper and the UI references Core, so the
   projection arrives transitively; the UI's CsWinRTIncludes projected it a
   second time. Remove CsWinRTIncludes from the UI (the original WPF UI had
   none) - GPOWrapper and the keyboard service both come transitively from Core.

5. CS1503 Selector -> FrameworkElement: a {StaticResource} converter inside an
   x:Bind needs a FrameworkElement binding root, but Selector's XAML root is a
   Window (TransparentWindow). Expose Visibility DescriptionVisibility on the
   view model and bind it directly, removing BoolToVisibilityConverter.

Build-verified: x64 Debug `0 Error(s)`; exe + pri + keyboard-service dll/winmd
co-locate in WinUI3Apps\; smoke-launch starts without COMException (proves the
keyboard-service WinRT class activates from WinUI3Apps\). Two MVVMTK0045 AOT
warnings remain (ObservableProperty-on-field) - follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ll visuals

Runtime fixes found by build+run verification (the migration had never been run):

- Always-on-top: the popup was shown correctly sized and positioned but BEHIND the
  foreground app, so it was effectively invisible. TransparentWindow.Show uses
  SW_SHOWNA (no activation); the WPF original set Window.Topmost on show. Set the
  OverlappedPresenter's IsAlwaysOnTop on the Selector.

- Deterministic sizing: the show path measured RootContent.DesiredSize before the
  window was shown. WinUI does not lay out hidden windows, and a ListView's
  ScrollViewer does not report content size, so the measure intermittently returned
  a tiny/zero size -> a blank or clipped bar. Replace measurement with a
  deterministic one-row accent bar (monitor max width x 52 DIP, plus a description
  row when enabled); the ListView scrolls horizontally and ScrollIntoView brings the
  selected glyph into view once its containers realize. An _active guard drops the
  deferred scroll if the key was already released.

Visual parity with the WPF original:

- Selection: replace the flat full-cell accent fill (a plain blue square) with the
  original inset (7px), rounded (4px), accent-filled pill plus on-accent glyph
  foreground, driven by VisualStateManager (Style/ControlTemplate triggers are not
  available in WinUI 3).
- Hide the horizontal scrollbar and zero the ListView/item padding so the bar hugs a
  single row, removing the empty acrylic strip below the glyphs.

Verified by running the built exe: holding a vowel + Space/arrow shows the acrylic
bar over the active app, navigation highlights the pill, and releasing inserts the
selected accent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The migrated Selector is a TransparentWindow (Window-rooted XAML) shown with
SW_SHOWNA and never activated. Its root x:Bind bindings initialize only on
Window.Activated, which never fires, so ItemsSource/Description stayed unset and
the popup rendered empty. Force Bindings.Update() after InitializeComponent.

Also restore visual parity with the WPF original:
- Size the bar to its content width (clamp(count*48, 48, GetDisplayMaxWidth()))
  instead of always filling the monitor, so it hugs the glyphs and centers.
- Override ListViewItem MinWidth to 48 (WinUI defaults it to 88; WPF ListBoxItem
  was 0) so accent cells are tight instead of widely spaced.
- Follow the system app theme via ThemeListener -> Card.RequestedTheme, since a
  never-activated window does not get WinUI's automatic runtime theme switching.
- Give the overlay a UIA window name and AutomationIds for accessibility/testing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Convert SelectorViewModel's [ObservableProperty] fields to partial properties so
the CsWinRT generators emit correct WinRT marshalling for the WinUI 3 app (fixes
4x MVVMTK0045). Partial properties cannot carry field initializers, so initial
values move to the constructor. This requires LangVersion=preview on the UI
project, matching the sibling WinUI 3 module PowerDisplay.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add PowerAccent.Core.UnitTests covering Calculation.GetRawCoordinatesFromPosition
(9 anchors across DPI 1.0/1.5/2.0, the offset, and negative-origin monitors) and
GetRawCoordinatesFromCaret (centering, left/right edge clamping, flip-below). The
project references PowerAccent.Core without CsWinRTIncludes (Core projects the
WinRT components transitively, avoiding CS0436). Expose the internal Calculation
helper via InternalsVisibleTo and register the project in the solution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With content-hugging width, a short accent bar was too narrow to show a readable
Unicode description line once that row is enabled. Restore the WPF original's
behavior by widening the window to at least 600px (DescriptionMinWidthDip) while
the description row is shown; the accent bar itself stays centered within it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ViewModel.SelectedIndex was only ever written, never read or bound - the actual
selection is driven by CharactersList.SelectedIndex (the ListView's own property).
Remove the dead property and its two assignments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Refresh doc/devdocs/modules/quickaccent.md to match the current module:
- List all five projects (add PowerAccent.Common) and describe its role
- Note PowerAccent.UI is now a self-contained WinUI 3 app (migrated from WPF)
- Note Core is UI-framework-agnostic and reference the new unit-test projects
- Add a "WinUI 3 Selector Notes" section capturing the non-obvious patterns
  (Bindings.Update on the never-activated window, ListViewItem MinWidth=48,
  deterministic width)
- Drop the stale "~78 errors" WPF-era debugging note

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the Product-Quick Accent Refers to the Quick Accent PowerToy label Jun 26, 2026
@moooyo

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines could not run because the pipeline triggers exclude this branch/path.

Niels Laute (niels9001) and others added 4 commits June 26, 2026 17:25
Split the reusable transparent-overlay infrastructure into a pure host
window and a self-animating acrylic surface:

- TransparentWindow is now animation-agnostic; it raises Showing/Hiding
  events (with a Hiding deferral so the HWND stays visible until the
  out-animation finishes).
- TransientSurface (renamed from TransparentCard) owns all chrome
  (shadow, always-active acrylic, border, corners) and its own show/hide
  slide animations. SlideFrom selects the edge; None (default) plays no
  animation at all.
- Expose AcrylicKind on TransientSurface (bound to the backdrop via
  TemplateBinding), defaulting to thin acrylic. AlwaysActiveDesktopAcrylicBackdrop
  gains a matching Kind property.
- Migrate CmdPal ToastWindow to the new pattern.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Migrates the Selector overlay off the now-removed built-in TransparentWindow
card/animation API onto the new TransientSurface control:

- Wrap the selector content in <controls:TransientSurface x:Name="Surface">,
  which owns the border, acrylic, corners and shadow. SlideFrom defaults to
  None, preserving the instant (no slide/fade) show/hide the typing aid needs.
- Subscribe the surface to the window via Surface.SubscribeTo(this) instead of
  assigning empty window-level ShowAnimations/HideAnimations.
- Drive theming through Surface.RequestedTheme (the surface is now the window's
  root content) in place of the old Card.RequestedTheme.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…w feedback

Rename SlideDirection to Transition and replace the single SlideFrom property with independent ShowTransition and HideTransition properties, so a surface can, e.g., slide in from the bottom and pop out. Add a new Pop transition (fast fade + subtle 96 to 100 percent center scale) following Fluent motion guidance.

Also address PR #48915 review feedback:
- Abandon a pending hide when Show() interrupts it, so the outstanding Hiding deferral is never completed for the interrupted cycle (avoids a stale AppWindow.Hide later).
- Drop the unused PART_BackdropElement template name (AcrylicKind flows via TemplateBinding; there is no code-behind wiring).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Yu Leng (from Dev Box) and others added 3 commits June 28, 2026 20:38
Quick Accent now relies on WinUI 3's built-in theme following instead of the ManagedCommon.ThemeListener: App.xaml leaves Application.RequestedTheme unset, so the framework follows the system app theme and re-resolves the {ThemeResource} brushes on a live light/dark switch. Verified empirically that the never-activated SW_SHOWNA accent popup follows the runtime theme in all cases (visible, hidden, and never-shown-before-the-change), including the ActualThemeChanged that retints the acrylic - so the manual ThemeListener + ApplyTheme are redundant and are removed.

Quick Accent was the only new driver of the ThemeListener Native-AOT rewrite, so revert ManagedCommon.ThemeListener (and the System.Management package reference) to main's WMI ManagementEventWatcher version to keep this PR focused on PowerAccent. Peek.UI (the other, pre-existing consumer) keeps working; it is not AOT-published.

Verified: ManagedCommon, PowerAccent.UI and Peek.UI build 0 warn / 0 err; the full Native AOT publish of PowerAccent.UI still succeeds end-to-end (ThemeListener is trimmed, so no System.Management / IL3052).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The WinUI 3 selector set IsAlwaysOnTop once at construction and left it on
for the life of the (hidden but never-destroyed) process, reintroducing the
hybrid-graphics regression that PR #41044 fixed for the WPF original by
toggling Topmost = isActive (issue #34849, still open): a permanently
always-on-top overlay can keep a discrete GPU awake.

Toggle the WinUIEx WindowEx.IsAlwaysOnTop property in PowerAccent_OnChangeDisplay
(true on show, false on hide) instead, matching the sibling PowerDisplay's use
of the same property. This drops the cached presenter field, the constructor
null-check/warning and the SetAlwaysOnTop helper (WindowEx.IsAlwaysOnTop is
null-safe internally), and the now-unused ManagedCommon using.

Also fix the dev-doc "WinUI 3 Selector Notes" to reference the renamed
PowerAccentXAML/MainWindow.xaml(.cs) instead of the removed Selector.xaml(.cs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the "WinUI 3 Selector Notes" section; the rationale it captured now
lives in the code comments in PowerAccentXAML/MainWindow.xaml(.cs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@moooyo
Yu Leng (moooyo) marked this pull request as ready for review June 29, 2026 03:58
@moooyo
Yu Leng (moooyo) requested a review from a team as a code owner June 29, 2026 03:58
@github-actions

Copy link
Copy Markdown

Thank you for contributing to PowerToys. We've detected that this PR might include a new or modified telemetry event. Please ensure the following before merging:

@moooyo

Copy link
Copy Markdown
Contributor Author

I've done a simple test on my Win11 PC, looks good overall.
I think we can push into PR review stage, any idea?

Niels Laute (@niels9001) Dave Rayment (@daverayment)

@moooyo

Yu Leng (moooyo) commented Jun 29, 2026 •

Copy link
Copy Markdown
Contributor Author

You can get the installer in pipeline. Niels Laute (@niels9001)

@Jay-o-Way Jay (Jay-o-Way) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General advice when somebody is going to change framework: start from scratch.

…accent

Replace the custom accent "pill" ControlTemplate with the default ListViewItemPresenter selection, keeping only the layout setters (48px centered cells). Override just the selected-state brushes to the accent color via ThemeDictionaries so the selection retints correctly on a live light/dark theme switch. Addresses PR review feedback to let the control handle the selection UI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@moooyo

Yu Leng (moooyo) commented Jul 2, 2026 •

Copy link
Copy Markdown
Contributor Author

General advice when somebody is going to change framework: start from scratch.

yeah, fully agree. But to be honest, it would take more dev cost.

the short goal is migrate all WPF/WinForms modules to WinUI3. AI can easily rewrite it with the same UI and logic. But if we want to re-design, it would so difficult.

Replace the magic "FontSize=12" on the Unicode description TextBlock with the native CaptionTextBlockStyle so the caption follows the system type ramp. The explicit secondary foreground is kept (the style does not set a foreground).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…clean up

- Restore the custom accent "pill" selection (revert the stock ListViewItem chrome) per review feedback: keep the existing design-team UI, no UX redesign.

- Move the selector content into a SelectorControl UserControl so x:Bind initializes on the control Loading pass; the SW_SHOWNA overlay never activates, which removes the manual Bindings.Update().

- Remove unused Point/Size/Rect operators and redundant/dead constructors, drop an unused using, fix an outdated WPF comment, and trim verbose/duplicated comments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Clint Rutkas (crutkas) added a commit that referenced this pull request Jul 3, 2026
## Summary of the Pull Request

Adds an opt-in **press-and-hold** activation mode to Quick Accent, like
iOS / macOS: hold an accent-capable letter (e.g. `a`) and after a short,
configurable delay the accent picker opens automatically — no separate
trigger key (Space/arrows) required.

This is purely additive. The existing trigger-key modes (`Left/Right
arrow`, `Space`, `Both`) and all serialized settings values are
unchanged.


https://github.com/user-attachments/assets/faec298c-e42c-4fd1-84bd-6e74d1b481a0


### What it does

- Holding a letter types the base letter immediately, then arms the
picker. After the **Hold duration** (default **500 ms**) the toolbar
appears.
- Navigate the options with the arrow keys / Space, then **release the
letter** to insert the selected accent (it replaces the base letter).
- A quick tap types just the letter. Holding and releasing without
selecting leaves the base letter as-is.
- `Ctrl` / `Alt` / `AltGr` / `Win` + letter combinations are left
untouched, so shortcuts like `Ctrl+A` still work.

## PR Checklist

- [ ] **Closes:** N/A — feature enhancement (happy to link a tracking
issue if one is preferred)
- [x] **Communication:** Discussed direction with maintainers;
coordinated with #48891 (see below)
- [ ] **Tests:** No automated tests added — the activation decision
lives in the C++ low-level keyboard hook and isn't reachable from the
existing managed unit-test project. Validated manually (steps below).
Open to guidance on the preferred test surface.
- [x] **Localization:** All new end-user strings are in
`Settings.UI/Strings/en-us/Resources.resw` with translator comments.
- [x] **Dev docs:** `doc/devdocs/modules/quickaccent.md` updated with
the new mode.
- [x] **New binaries:** None.
- [x] **Documentation updated:** Dev docs updated; public Learn docs can
follow once shipped.

## Detailed Description of the Pull Request / Additional comments

- **`PowerAccentKeyboardService` (C++ hook):**
- Append `PressAndHold` to the internal `PowerAccentActivationKey` enum
(value `3`, appended to preserve serialized `0/1/2`).
- Add a `holdDuration` setting and `UpdateHoldDuration(Int32)` to the
WinRT projection (`.idl`).
- In `OnKeyDown`, arm the picker on the held letter itself; the base
letter still types on first press and auto-repeat is swallowed (reuses
the existing `m_toolbarVisible` repeat guard from #36853).
- In `OnKeyUp`, use the hold duration as the minimum-hold release
threshold for this mode (trigger modes keep using `inputTime`).
  - Modifier guard: Ctrl/Alt/AltGr/Win do not arm the mode.
- **Settings model (`Settings.UI.Library`):** append `PressAndHold` to
`PowerAccentActivationKey`; add `hold_duration_ms` (`IntProperty`,
default 500). Existing `settings.json` without the field falls back to
the 500 ms default.
- **`PowerAccent.Core`:** read and forward the hold duration to the
hook, and use it as the popup delay when `PressAndHold` is active.
- **Settings UI:** add the **"Press and hold the letter"** activation
option and a **"Hold duration (ms)"** control that is shown only when
that mode is selected.

### Enum sync note

`PowerAccentActivationKey` exists in both the C++ hook and the managed
settings library and is kept in sync by integer value. `PressAndHold`
was **appended** (never reordered) so existing serialized settings
(`0/1/2`) keep their meaning.

### Coordination with #48891 (Quick Accent WinUI migration)

This lands as its own atomic change on `main`. The overlap with the
in-progress WinUI migration (#48891) is tiny: only `PowerAccent.cs`'s
mode-aware popup delay (a single `Task.Delay` line). The C++ hook,
settings enum/model, and Settings UI are not touched by #48891, so it
can rebase onto this with minimal effort.

## Validation Steps Performed

- Built the full chain in `Debug|x64`:
- `PowerAccent.UI.csproj` → rebuilds the C++
`PowerAccentKeyboardService` projection (incl. `UpdateHoldDuration`) +
`PowerAccent.Core` + `Settings.UI.Library`. **0 errors.**
- `PowerToys.Settings.csproj` → Settings UI XAML / ViewModel / `.resw`
(XamlIndexBuilder search index regenerated). **0 errors.**
- Manual trial of the running module (`PowerToys.PowerAccent.exe`) with
`activation_key = 3`:
- Hold `a` → base letter types immediately; picker opens after ~500 ms;
arrows/Space navigate; releasing inserts the accent (replacing the base
letter).
- Quick tap → base letter only. Hold + release without selecting → base
letter remains.
  - `Ctrl+A` / `Alt`+letter unaffected.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@moooyo
Yu Leng (moooyo) merged commit 9039451 into main Jul 3, 2026
15 checks passed
@moooyo
Yu Leng (moooyo) deleted the yuleng/m/qa/1 branch July 3, 2026 08:58
@moooyo

Copy link
Copy Markdown
Contributor Author

we have discussed offline. Merge it first to avoid more conflict and can easily get test installer.

if anyone have any idea on this changes, please ping me. I will follow up the next fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Product-Quick Accent Refers to the Quick Accent PowerToy

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate quick accent to WinUI3

5 participants