[ColorPicker] Migrate from WPF to WinUI 3#49174
Draft
moooyo wants to merge 47 commits into
Draft
Conversation
…urces Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…builds Exclude Helpers\ColorConverterTest.cs, Helpers\ColorFormatConversionTest.cs, and Helpers\ColorRepresentationHelperTest.cs from compilation via <Compile Remove> in ColorPickerUI.UnitTests.csproj. These tests reference ColorPicker.Helpers types quarantined in the UI project during the WPF→WinUI 3 migration (Task 1). Files remain on disk for re-enablement in sub-project B. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…jection Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lection - git rm Common/ViewModelBase.cs and Common/RelayCommand.cs (dead WPF MVVM helpers) - Remove their now-obsolete <Compile Remove> lines from ColorPickerUI.csproj - Add RangeObservableCollectionTests (1 test: AddRange raises single Reset) - All 2 Foundation tests pass (ServiceProviderSmokeTests + new collection test) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ping Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… .resx designer Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, docs, manifest casing) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…he source token (A §12b carry-forward)
…lper) off System.Windows.Point Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
….Rect, drop WPF DpiScale
…tem) off MEF + DI singleton Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… §12a carry-forward) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ent contract Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sub-project D's NativeMethodsHelperTests is the first unit test to consume an internal ColorPicker type (internal NativeMethodsHelper). The production InternalsVisibleTo named "UnitTest-ColorPickerUI" but the test project's assembly is "ColorPickerUI.UnitTests" (no AssemblyName override), so the IVT did not apply and that test would fail to compile once the test project builds. Align the IVT to the actual test assembly name. Found by the whole-branch review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ows.UI.Color - Move the ~76-key string table from the dead Properties\Resources.resx to Strings\en-us\Resources.resw and delete the .resx; normalize the legacy "Lightest color" key (stray space) to "Lightest_color" for MRT. - ColorRepresentationHelper: System.Windows.Media.Color -> Windows.UI.Color; collapse the 42-case color-name switch to ResourceLoaderInstance.GetString; make ReplaceName resolve the color name lazily (only when the format contains the name placeholder) so numeric formats skip the resource lookup and the pure-conversion unit tests don't require the MRT .pri in the test host. - ColorHelper re-included as-is (no WPF deps). - HSVColor / ColorFormatModel / SerializationHelper: System.Windows.Media.Color -> Windows.UI.Color. - 5 value converters: IValueConverter(string language) signature + Microsoft.UI.Xaml namespaces. - Re-include the ported files past the csproj folder quarantines; un-quarantine the three color unit tests (378 color-logic test cases pass; build is clean). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…licate x:Class The quarantined root MainWindow.xaml/.cs (the WPF picking-overlay shell: AllowsTransparency, SizeToContent, Topmost, WindowStyle=None, MEF [Import] IMainViewModel, HwndSource window-handle registration) declared the same x:Class "ColorPicker.MainWindow" as the live WinUI 3 ColorPickerXAML\MainWindow.xaml, coexisting only because the build quarantined the root file. Delete it -- git history preserves the old WPF shell, and the sub-project C overlay port builds a fresh AppWindow-based window rather than porting the WPF Window line-by-line -- and drop the now-obsolete csproj quarantine entries, permanently removing the duplicate-partial-class hazard before overlay work begins. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… off WPF Re-include the window-independent input layer past the Mouse/Keyboard quarantines: - MouseHook: drop System.Windows.Input (MouseWheelEventArgs/InputManager/MouseDevice); the wheel event now carries the raw signed delta (high word of MSLLHOOKSTRUCT.mouseData), letting consumers derive zoom direction from its sign. - IMouseInfoProvider: System.Windows.Point -> Windows.Foundation.Point. - CursorManager, GlobalKeyboardHook, GlobalKeyboardHookEventArgs: re-included verbatim (already WPF-free Win32 / registry code). The stateful consumers (MouseInfoProvider, KeyboardMonitor) stay quarantined because they depend on AppStateHandler, which in turn depends on the not-yet-rebuilt overlay window. Build is clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ainView Build the picking-overlay window on the shared Common.UI.Controls TransparentWindow (introduced by PR #48176) instead of hand-rolling AppWindow transparency: - ColorPickerOverlayWindow : TransparentWindow hosts the tooltip and inherits the transparent TransparentTintBackdrop, frameless tool-window chrome (out of taskbar/ Alt-Tab, no Win11 phantom border), and a no-activate Show/Hide that never steals focus. - Port MainView (the tooltip content): clr-namespace -> using, DynamicResource -> ThemeResource, ApplicationBackgroundBrush -> LayerFillColorDefaultBrush, keep loose {Binding} (no MainViewModel compile dependency yet), declare the two visibility converters locally, and replace the WPF AutomationEvents.MenuOpened narrator hack with LiveRegionChanged (paired with the existing LiveSetting=Assertive). - Add a non-inverted BoolToVisibilityConverter. - Reference Common.UI.Controls and CommunityToolkit.WinUI.Animations (the XAML compiler needs the latter to resolve TransparentWindow's ShowAnimations/HideAnimations). - Narrow the Views compile/page quarantine to just the still-WPF editor and zoom views. Not yet wired into the App launch path (waits on the MainViewModel / AppStateHandler port). Build is clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port and un-quarantine IMainViewModel / IZoomViewModel / IColorEditorViewModel: - IMainViewModel: System.Windows.Media.Brush -> Microsoft.UI.Xaml.Media.Brush; RegisterWindowHandle(HwndSource) -> RegisterWindowHandle(IntPtr). - IZoomViewModel: System.Windows.Media.Imaging.BitmapSource -> Microsoft.UI.Xaml.Media.ImageSource. - IColorEditorViewModel: System.Windows.Media.Color -> Windows.UI.Color (ICommand kept -- System.Windows.Input.ICommand is the BCL type WinUI and CommunityToolkit.Mvvm already use). These unblock the view models that implement them. Build is clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…geSource) ZoomViewModel now derives from CommunityToolkit.Mvvm ObservableObject (the custom ViewModelBase was removed earlier) and exposes ZoomArea as Microsoft.UI.Xaml.Media.ImageSource instead of WPF BitmapSource; the MEF export is dropped (DI registration lands with the overlay wiring). Re-included past the ViewModels quarantine; ZoomWindow / ZoomWindowHelper stay quarantined pending the Win2D magnifier. Build is clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nd + FileSavePicker) - ObservableObject base (custom ViewModelBase removed); CommunityToolkit.Mvvm RelayCommand<object> / AsyncRelayCommand<object> replace the custom RelayCommand. - System.Windows.Media.Color -> Windows.UI.Color throughout (history + selected color). - Export now uses the WinRT FileSavePicker (async) instead of WPF SaveFileDialog; a new WindowHandle property lets the host editor window anchor the picker (desktop requirement). - MEF export dropped (DI registration lands with the overlay/editor wiring). Re-included past the ViewModels quarantine; the editor window/view/controls stay quarantined pending the XAML port. Build is clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nto App.xaml Port the shared resource dictionary off WPF (approach A: lean on native WinUI styling) and merge it into App.xaml so the editor controls can resolve their styles/converters: - Drop CustomAccentButtonStyle (consumers will use the built-in AccentButtonStyle). - SubtleButtonStyle / TitleBarCloseButtonStyle now derive from the native DefaultButtonStyle via BasedOn, inheriting proper Fluent visual states (the WPF red-on-hover close fill is dropped pending editor visual polish). - ColorShadeButtonStyle keeps a custom template, rewritten with the VisualStateManager and the built-in Control.CornerRadius -- the WPF-era ControlHelper.CornerRadius attached property is unnecessary in WinUI, so ControlHelper is not ported. - Declare the six value converters and IconFontFamily. App launches cleanly (startup smoke test passes; the merged dictionary loads at runtime without error). Build is clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… DI)
- DependencyProperties on Microsoft.UI.Xaml; Color -> Windows.UI.Color.
- Bootstrapper MEF lookup -> App.GetService<IThrottledActionInvoker>().
- Copy-indicator WPF DoubleAnimation/BeginAnimation -> a Storyboard (the Height leg opts into
EnableDependentAnimation); narrator MenuOpened -> LiveRegionChanged.
- {x:Static p:Resources.*} automation names / tooltips and the RelativeSource FindAncestor
help-text binding (no WinUI equivalent) move to code-behind via ResourceLoaderInstance.
- ReadonlyTextBoxStyle simplified to BasedOn DefaultTextBoxStyle (approach A: lean on native).
- Re-included past the Controls quarantine (HSVColor + ColorFormatControl are default-glob now;
only ColorPickerControl stays quarantined). Build is clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tyle + Flyout)
The largest editor control, ported with approach A (lean on native WinUI):
- Slider: drop the WPF Track/TickBar/RepeatButton custom template; use native WinUI Sliders
whose track theme brushes are overridden to Transparent so the gradient Borders behind
(HueGradientBorder + the Saturation/Value LinearGradientBrush stops) show through.
- Adjust-color Popup -> a Button.Flyout; Popup.IsOpen toggling -> Flyout open/Hide().
- WPF DoubleAnimation/ThicknessAnimation via BeginAnimation -> a Storyboard (Height as a
dependent animation; WinUI has no ThicknessAnimation, so the margin is set directly).
- Slider.ValueChanged RoutedPropertyChangedEventArgs<double> -> RangeBaseValueChangedEventArgs;
PreviewTextInput -> TextBox.BeforeTextChanging; GotKeyboardFocus -> GotFocus.
- Color -> Windows.UI.Color; brushes/GradientStop/Point -> Microsoft.UI.Xaml.Media /
Windows.Foundation; the custom automation peer derives from FrameworkElementAutomationPeer
(no UserControlAutomationPeer in WinUI) and is partial (CsWinRT ABI requirement).
- {x:Static p:Resources.*} automation names/tooltips resolved in code via ResourceLoaderInstance.
- Extract AppStateHandler.BlockEscapeKeyClosingColorPickerEditor into a small EditorState static
so the control no longer depends on the window-coupled AppStateHandler.
All Controls are now off the quarantine. Build is clean. The slider/flyout interaction needs
desktop verification.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…out, VSM)
- ListView.ContextMenu / MenuItem -> ListView.ContextFlyout / MenuFlyout(SubItem); the WPF
FindAncestor PlacementTarget.SelectedItems command parameter -> {Binding SelectedItems,
ElementName=HistoryColors}.
- The ListViewItem ControlTemplate triggers (custom selection ellipse) -> VisualStateManager
CommonStates / SelectionStates.
- VirtualizingStackPanel -> ItemsStackPanel; MouseWheel -> PointerWheelChanged with
ScrollViewer.ChangeView; DynamicResource -> ThemeResource (the SystemColors accent brush ->
AccentFillColorDefaultBrush).
- {x:Static p:Resources.*} labels / automation names / menu headers resolved in code-behind
via ResourceLoaderInstance.
Re-included past the Views quarantine (only ZoomView remains). Build is clean; the editor
list/menu interaction needs desktop verification.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port the mutually-dependent overlay/editor integration cluster off WPF + MEF and wire it into the WinUI 3 startup path. The module now launches, resolves its DI graph, creates the overlay, registers the named events, and stays alive (startup smoke test passes); the picking/editor flow is ready for desktop verification. - AppStateHandler: MEF -> DI; drives the picking overlay via the TransparentWindow-based ColorPickerOverlayWindow (App.Window) Show()/Hide() instead of Application.Current.MainWindow; hosts the editor via ColorEditorWindow; HwndSource -> IntPtr; reads EditorState for the Esc-block flag; cursor move via NativeMethodsHelper GetCursorPos/SetCursorPos. - ColorEditorWindow: WPF WindowChrome/DWM/custom-title-bar -> native WinUI title bar + a fixed OverlappedPresenter; AppWindow.Closing cancels and ends the session (hides), not destroys. - MouseInfoProvider: MEF -> DI; DispatcherTimer -> DispatcherQueueTimer; System.Windows.Point -> Windows.Foundation.Point; the wheel hook now carries a raw delta. - KeyboardMonitor: MEF -> DI; System.Windows.Input.Key/KeyInterop -> Windows.System.VirtualKey. - MainViewModel: MEF -> DI + ObservableObject; the three NativeEventWaiter loops drop the dispatcher arg (the primitive captures the DispatcherQueue itself); Application.Current.Shutdown -> cancel the exit token + Environment.Exit; Brush/Color -> Microsoft.UI.Xaml.Media / Windows.UI.Color; RegisterWindowHandle(IntPtr). - ZoomWindowHelper is a no-op placeholder (the Win2D magnifier is 7e-2). - App.OnLaunched builds ColorPickerOverlayWindow, resolves IMainViewModel, hosts it in MainView, and registers the HWND; AppServices registers the new singletons. Build is clean and the app starts. Hotkey-show / pick / editor behavior needs desktop verification; the zoom magnifier (7e-2) is not implemented yet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MouseInfoProvider samples the pixel under the cursor in its constructor (resolved during App.OnLaunched, before the overlay is shown). On a non-interactive / disconnected session the GDI CopyFromScreen can throw "the handle is invalid", which previously took down the whole app at startup. Swallow the failure in GetPixelColor and return black; the next sample succeeds once a real desktop is present. Found via the startup smoke test during 7e-1 verification. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e-2) Replace the no-op ZoomWindowHelper placeholder and the quarantined WPF ZoomWindow/ZoomView + GridShaderEffect with a Win2D implementation (方案1): - ZoomView hosts a single Win2D CanvasControl that draws the GDI-captured region scaled with nearest-neighbor filtering and overlays the pixel grid + center highlight (replacing the WPF ShaderEffect; the brightness-adaptive color + radius reveal from GridShader.fx are simplified for now and can be refined, or escalated to 方案2, after desktop verification). - ZoomWindow derives from the shared TransparentWindow (transparent, frameless, no-activate). - ZoomWindowHelper captures a 50x50 region around the cursor, converts the GDI bitmap to a CanvasBitmap, sizes + centers the magnifier on the cursor (physical pixels), and shows it; CopyFromScreen failures degrade gracefully. WindowCaptureExclusionHelper re-included as-is. - Add the Microsoft.Graphics.Win2D package; un-quarantine ZoomView/ZoomWindow. The WPF Behaviors\ and Shaders\ stay quarantined (intentionally dead; GridShader.fx kept as the spec). This was the last quarantined feature. Build is clean and the app starts; the magnifier's on-screen behavior needs desktop verification (and the dev box's flaky GDI screen capture means it can only be fully verified on a local interactive desktop). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Remove the per-language ColorPicker satellite component from Resources.wxs: WinUI 3 localizes via the .pri compiled from Strings\*\Resources.resw, so PowerToys.ColorPickerUI.resources.dll is no longer produced and referencing it broke the WiX harvest. - ESRPSigning: point PowerToys.ColorPickerUI.dll/.exe at the WinUI3Apps\ output (the WinUI 3 app now builds there), mirroring AdvancedPaste; the native PowerToys.ColorPicker.dll stays at root. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…igration The WPF->WinUI 3 migration is structurally complete, so delete the source that has no remaining consumer: - ColorPickerXAML\MainWindow.xaml(.cs): unused empty shell; App hosts ColorPickerOverlayWindow. - Behaviors\*.cs (7 files): replaced by TransparentWindow built-ins, services, the native ListView reorder, and Win2D - none were re-included in the build. - Shaders\GridShaderEffect.cs, Global.cs, GridShader.cso: the WPF pixel-grid shader, replaced by the Win2D ZoomView. GridShader.fx is kept as the reference spec for the alternative magnifier. - Helpers\ControlHelper.cs: a WPF (System.Windows) attached-property helper with no consumer; its CornerRadius attached property is unnecessary in WinUI (see Styles.xaml). It was already excluded from the build by the Helpers\ quarantine glob. Drop the now-empty Behaviors\ / Shaders\ Compile-Remove globs and refresh the csproj comment. Build: 0 warnings, 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (WinUI) The migration showed the picking overlay as a default ~1920x1030 window: the WPF MainWindow's SizeToContent + ChangeWindowPositionBehavior (cursor-follow) + re-asserted Topmost were never ported (a stale 7d TODO shipped through 7e-1, and the behavior lived in the quarantined Behaviors\). ColorPickerOverlayWindow now: - sizes the window to the MainView tooltip (WinUI has no SizeToContent), measured only on show / content change and cached otherwise; - follows the cursor via IMouseInfoProvider.MousePositionChanged, with the WPF behavior's monitor-edge dead-zone flip; it also positions on Show() since the event only fires on movement (a stationary-cursor show would otherwise leave the tooltip parked); - stays HWND_TOPMOST so the tooltip sits above the (non-top-most) zoom magnifier, matching the WPF helper that re-asserted MainWindow.Topmost after showing the zoom; - caches the DPI scale + monitor bounds per pick session and moves with a single SetWindowPos per cursor tick (no per-tick layout pass), keeping following light. App.OnLaunched wires the shared IMouseInfoProvider singleton into the overlay. Desktop-verified: the small tooltip follows the cursor, the color readout updates, pick-in-magnifier works, and the card sits above the magnifier and returns after zoom-out. Build: 0 warnings, 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… HSV sliders, clean history) Desktop-verification polish for the ported editor (Approach A - native WinUI chrome): - ColorEditorWindow: set the title-bar/taskbar icon to the ColorPicker app icon (was the generic WinUI placeholder), and add a Mica SystemBackdrop so the window's uncovered header no longer renders as an opaque black bar against the content card. - ColorPickerControl: grow the H/S/V gradient bars from 8px to 16px (CornerRadius 8) so the native Slider thumb sits on them naturally instead of dwarfing a thin strip. - ColorEditorView: hide the color-history horizontal scrollbar - WinUI's persistent Fluent scroll indicator read as a stray gray bar under the swatches; the mouse wheel still scrolls the history via the existing PointerWheelChanged handler. Build: 0 warnings, 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Run xstyler over the WinUI 3 ColorPicker XAML so it conforms to src/Settings.XamlStyler, fixing the "Verify XAML formatting" CI step. Formatting-only: add UTF-8 BOM, reorder x:FieldModifier, and split multi-line comment delimiters. No semantic changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The WinUI3Apps asset-conflict audit (.pipelines/verifyPossibleAssetConflicts.ps1) rejects any *.xbf in the shared WinUI3Apps output root. ColorEditorWindow, ColorPickerOverlayWindow and ZoomWindow lived at the project root, so their compiled .xbf landed directly in WinUI3Apps\ and failed the audit. Move all three .xaml + .xaml.cs into the existing ColorPickerXAML\ subfolder (alongside App.xaml), matching every other WinUI3 app (AdvancedPaste/PowerDisplay/ Hosts/MeasureTool). No csproj change (default SDK globs) and no code change (x:Class/namespace are folder-independent); the .xbf now lands under WinUI3Apps\ColorPickerXAML\. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…installer root The "Verify all binaries are signed and versioned" stage failed on BaseApplicationsFiles_File_PowerToys.ColorPickerUI.exe (Not Signed). A SelfContained Exe->WinExe ProjectReference (ColorPickerUI.UnitTests -> ColorPickerUI) makes MSBuild copy the WinExe apphost (.exe/.deps.json/ .runtimeconfig.json) into the ROOT output dir as a side effect. The installer's BaseApplications harvest picks up that leaked root PowerToys.ColorPickerUI.exe, which ESRP never signs (it only signs the WinUI3Apps copy). Because the signed and unsigned copies differ in hash, the dedup step kept both and the MSI shipped the unsigned root one. Mirror the existing ImageResizer leak workaround: strip the ColorPickerUI apphost trio from BaseApplications. The dedup loop then keeps the signed WinUI3Apps copy (which the native module launches anyway). The native root PowerToys.ColorPicker.dll is unaffected (the UI suffix disambiguates). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two small WPF behaviors were dropped during the WinUI 3 migration and are restored here: - ColorEditorWindow now calls CenterOnScreen() (ports WindowStartupLocation="CenterScreen"); it previously opened at the OS default position. - The picking overlay fades in over 250ms (CubicEase) on Show(), porting the WPF AppearAnimationBehavior that was lost when the merged TransparentWindow refactor removed ShowAnimations. Window has no Opacity, so the hosted MainView content is animated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ports the WPF ResizeBehavior feel (grow easeOutSine / shrink easeInQuad, 200ms) that the WinUI 3 magnifier lost. ALT-1 (the WPF structural model): the zoom window is now a constant max-size host (50*8+30 = 430px) re-centered on the cursor each step, and only the inner card animates its Microsoft.UI.Composition Scale (CenterPoint pinned to card center). A fixed window that always exceeds the centered card means the briefly-larger card during a shrink never clips, so there is no per-step window resize and no shrink-trim bookkeeping. Win2D still draws at the final factor; the compositor scales that crisp texture during the tween. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A thorough review of the WinUI 3 migration (vs origin/main) surfaced several issues; this applies the fixes. Builds 0/0 on ColorPickerUI and ColorPickerUI.UnitTests. Correctness: - Editor Export was broken: ColorEditorViewModel.WindowHandle was never assigned, so the FileSavePicker was initialized with IntPtr.Zero. Add WindowHandle to IColorEditorViewModel and set it from AppStateHandler once the editor window exists. - Installer harvested ColorPicker assets from the pre-WinUI3 root path; repoint generateAllFileComponents.ps1 + ColorPicker.wxs to WinUI3Apps so colorPicker.cur / icon.ico ship in the package. - Restore the system cursors on process exit (AppDomain.ProcessExit): the Environment.Exit paths bypassed MouseInfoProvider.DisposeHook, leaking the crosshair as the system cursor (parity with WPF OnExit). - Overlay tooltip re-queries the cursor monitor's DPI per move instead of caching it once, fixing sizing/offset on mixed-DPI multi-monitor. - Dispose the Win2D CanvasBitmap per zoom session (ZoomWindowHelper + ZoomView.ClearBitmap) to stop leaking a GPU surface on every pick. - Re-add the ETWTrace diagnostic session dropped during the migration (matches the WinUI 3 sibling modules). Cleanup: - Collapse the ~100-line csproj Compile-Remove + re-include quarantine (it excluded nothing) back to the default SDK glob; DefineConstants now appends so inherited DEBUG/TRACE survive. - Delete dead code: ViewLocator (+test), ZoomViewModel/IZoomViewModel (+DI registration + ZoomWindowHelper wiring), AppStateHandler.SetTopMost, NativeMethods.SetToolWindowStyle (+orphaned interop), and App's ExitToken / RunnerPid + stale comments. App.Dispose now actually runs (via ProcessExit), so its IDisposable is no longer dead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
方案2 magnifier grid (replaces the flat 方案1 grid): - Grid line color now adapts to each cell's source-pixel brightness (dark over light areas, light over dark) so it stays visible on very light and very dark regions; grid alpha fades toward the magnifier edge (the shader's radius reveal); the center cursor-pixel highlight picks dark/light from its own brightness so it shows on a white pixel too. Source pixels are cached once per capture (ZoomView) instead of read back from the GPU on every draw. Dead-code cleanup (all pre-existing, dead before the migration): - ColorRepresentationHelper: drop 15 never-called ColorTo* methods (keep ColorToHex). - Delete unused ColorToBrushConverter / ColorToStringConverter (+ their Styles.xaml registrations) and the duplicate converter keys in MainView.xaml. - Remove ColorPickerControl.GetChildOfType<T>, HSVColor.GetSpectrum, ColorEditorViewModel.HideColorFormatCommand, the orphaned TitleBarCloseButtonStyle, the unused MainBorder x:Name, a stale using in SerializationHelper, and a duplicate IOException catch in UserSettings. - Delete the orphaned Shaders/GridShader.fx (方案2 supersedes it as the reference). Polish: - Overlay GetMonitorBounds: when no monitors are enumerated, log a warning and fall back to the primary screen bounds instead of an unbounded rect, so edge-flipping still works. - Overlay Show(): present the card transparent, then start the fade-in + initial positioning at Low priority after base.Show() so the 250ms fade begins when the window is actually on screen (no early/partial fade) and without a flash. Builds 0/0 on ColorPickerUI + UnitTests. Magnifier look + fade verified on the real desktop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The WinUI 3 magnifier re-centered its window on the cursor on every mouse-wheel step, so moving the mouse and scrolling again dragged the magnifier to the new cursor location instead of zooming the region it was first opened over. This regressed from the WPF version, which only positions the window on first appearance (while it is still transparent, Opacity < 0.5). Move the window sizing and positioning into the first-appearance branch, so the magnifier is centered on the cursor once per zoom session and then stays put, only animating the card between zoom levels. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
6 tasks
niels9001
reviewed
Jul 7, 2026
| /// The color editor window. Approach A: lean on the native WinUI title bar + a fixed-size | ||
| /// OverlappedPresenter instead of the WPF WindowChrome / DWM / custom-title-bar machinery. | ||
| /// </summary> | ||
| public sealed partial class ColorEditorWindow : Window |
Collaborator
There was a problem hiding this comment.
Let's use WinUIEX so we can do more stuff in XAML directly vs code behind (see the Workspaces Editor PR).
Apparently agents aren't picking up on that.. should we call it out specifically in our migration skills?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary of the Pull Request
Migrates the ColorPicker module from WPF to WinUI 3 (Windows App SDK). The whole module — the picking overlay, the color editor, settings, the zoom magnifier, telemetry, and the Win32 input hooks — is ported off
System.WindowsontoMicrosoft.UI.Xaml, MEF is replaced byMicrosoft.Extensions.DependencyInjection, and the legacy WPF sources are removed once each piece is ported.demo:
Video.Project.43.mp4
PR Checklist
Detailed Description of the Pull Request / Additional comments
High-level areas ported (see the commit history for the step-by-step breakdown):
App/MainWindow, aMicrosoft.Extensions.DependencyInjectioncontainer replacing MEF, and aViewLocatorreplacing the implicit WPFDataTemplatemapping.TransparentWindow-based overlay (cursor-following tooltip) plusColorEditorView/ColorPickerControl/ColorFormatControlported off WPF (ContextMenu→MenuFlyout, Visual State Manager, native WinUI styles merged intoApp.xaml).CanvasControlthat redraws the captured region with a brightness-adaptive pixel grid + center highlight (replacing the WPFShaderEffect), and a Composition-based resize animation between zoom levels.Windows.UI.Color,MonitorResolutionHelperonWindows.Foundation.Rect,ThrottledActionInvokeronDispatcherQueueTimer,ClipboardHelperon the WinRTDataPackage, and the Win32 mouse/keyboard hooks off WPF input types..reswscaffold +ResourceLoaderInstancereplacing the old.resxdesigner.ColorPickerUIflipped to WinUI 3, launched fromWinUI3Apps\; installer satellite + signing paths updated; leftover WPF sources removed.Most recent fix in this branch: the zoom magnifier no longer follows the cursor on every mouse-wheel step. It is now centered on the cursor once, when the zoom session first starts, and then stays put while the wheel keeps zooming the region it opened over — restoring the original WPF behavior. (A separate, still-open item: aligning the zoom animation step/feel with the WPF original — under investigation.)
Validation Steps Performed
ColorPickerUI.csprojbuilds clean (x64 Debug, exit code 0).ColorPickerUI.UnitTestscovers settings, helpers (throttling, native methods, color conversion, clipboard), and DI smoke / frozen-contract tests.