mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-07-07 19:09:43 +02:00
Compare commits
47 Commits
main
...
yuleng/m/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbc1b8d4e8 | ||
|
|
56f09af496 | ||
|
|
3c159ec93a | ||
|
|
b17e91276d | ||
|
|
6d5b786fec | ||
|
|
70f9ee13a5 | ||
|
|
7526abe367 | ||
|
|
e6d0cf450e | ||
|
|
b532b8854f | ||
|
|
ce8569bc0c | ||
|
|
8e7ccad936 | ||
|
|
6ba3b25a0b | ||
|
|
adee12a08d | ||
|
|
28d7a21d4c | ||
|
|
b89ecf5e51 | ||
|
|
e1c2a4bd01 | ||
|
|
20256f3898 | ||
|
|
cb9103a0a8 | ||
|
|
f247e021d3 | ||
|
|
1443f340d3 | ||
|
|
bb81d9b408 | ||
|
|
cc639237e1 | ||
|
|
7183772e47 | ||
|
|
39b576dae4 | ||
|
|
3d32896542 | ||
|
|
79c1a82a2e | ||
|
|
277efe593a | ||
|
|
679192d1ad | ||
|
|
b641f536dc | ||
|
|
3938ebd96f | ||
|
|
230cbcfa03 | ||
|
|
3936ac7a78 | ||
|
|
27462c49a1 | ||
|
|
de2e076b2f | ||
|
|
905f9fc2d5 | ||
|
|
f317b6a027 | ||
|
|
c5565f62a3 | ||
|
|
6da9743f26 | ||
|
|
0569391c57 | ||
|
|
5bfe386117 | ||
|
|
7eac5f9890 | ||
|
|
63008d264c | ||
|
|
8844f61ae8 | ||
|
|
4e72726b0a | ||
|
|
a065e04124 | ||
|
|
b1187982d5 | ||
|
|
69057330fd |
@@ -38,8 +38,8 @@
|
||||
|
||||
|
||||
"PowerToys.ColorPicker.dll",
|
||||
"PowerToys.ColorPickerUI.dll",
|
||||
"PowerToys.ColorPickerUI.exe",
|
||||
"WinUI3Apps\\PowerToys.ColorPickerUI.dll",
|
||||
"WinUI3Apps\\PowerToys.ColorPickerUI.exe",
|
||||
|
||||
"PowerToys.CropAndLockModuleInterface.dll",
|
||||
"PowerToys.CropAndLock.exe",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<?include $(sys.CURRENTDIR)\Common.wxi?>
|
||||
|
||||
<?define ColorPickerAssetsFiles=?>
|
||||
<?define ColorPickerAssetsFilesPath=$(var.BinDir)Assets\ColorPicker\?>
|
||||
<?define ColorPickerAssetsFilesPath=$(var.BinDir)WinUI3Apps\Assets\ColorPicker\?>
|
||||
|
||||
<Fragment>
|
||||
<!-- Color Picker -->
|
||||
|
||||
@@ -171,12 +171,9 @@
|
||||
</RegistryKey>
|
||||
<File Id="FancyZonesEditor_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.FancyZonesEditor.resources.dll" />
|
||||
</Component>
|
||||
<Component Id="ColorPicker_$(var.IdSafeLanguage)_Component" Directory="Resource$(var.IdSafeLanguage)INSTALLFOLDER" Guid="$(var.CompGUIDPrefix)03">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="ColorPicker_$(var.IdSafeLanguage)_Component" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<File Id="ColorPicker_$(var.IdSafeLanguage)_File" Source="$(var.BinDir)\$(var.Language)\PowerToys.ColorPickerUI.resources.dll" />
|
||||
</Component>
|
||||
<!-- ColorPicker satellite resources removed during the WinUI 3 migration: the WinUI 3
|
||||
ColorPicker localizes via the .pri compiled from Strings\*\Resources.resw, so no
|
||||
PowerToys.ColorPickerUI.resources.dll is produced (referencing it broke the harvest). -->
|
||||
<Component Id="MarkdownPreviewHandler_$(var.IdSafeLanguage)_Component" Directory="Resource$(var.IdSafeLanguage)INSTALLFOLDER" Guid="$(var.CompGUIDPrefix)04">
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="MarkdownPreviewHandler_$(var.IdSafeLanguage)_Component" Value="" KeyPath="yes" />
|
||||
|
||||
@@ -142,20 +142,27 @@ if ($platform -ceq "arm64") {
|
||||
}
|
||||
|
||||
#BaseApplications
|
||||
# WORKAROUND: Exclude ImageResizer files that leak into the root output directory.
|
||||
# ImageResizerCLI (Exe, SelfContained) has a ProjectReference to ImageResizerUI (WinExe, SelfContained).
|
||||
# MSBuild copies the referenced WinExe's apphost (.exe, .deps.json, .runtimeconfig.json) to the root
|
||||
# output directory as a side effect. These files are incomplete (missing the managed .dll) and should
|
||||
# not be included in the installer. The complete ImageResizer files are in WinUI3Apps/ and are handled
|
||||
# by WinUI3ApplicationsFiles. TODO: Refactor ImageResizer to use a shared Library project instead.
|
||||
# WORKAROUND: Exclude app-host files that leak into the root output directory.
|
||||
# A SelfContained Exe with a ProjectReference to a SelfContained WinExe makes MSBuild copy the
|
||||
# referenced WinExe's apphost (.exe, .deps.json, .runtimeconfig.json) to the root output directory
|
||||
# as a side effect. These files are incomplete (missing the managed .dll) and should not be in the
|
||||
# installer; the complete app is in WinUI3Apps/ and is handled by WinUI3ApplicationsFiles.
|
||||
# - ImageResizer: ImageResizerCLI (Exe) references ImageResizerUI (WinExe).
|
||||
# - ColorPicker: ColorPickerUI.UnitTests (Exe) references ColorPickerUI (WinExe). The leaked root
|
||||
# PowerToys.ColorPickerUI.exe is also unsigned (ESRP only signs the WinUI3Apps copy), so it must
|
||||
# be stripped or the "Verify all binaries are signed and versioned" stage fails.
|
||||
# TODO: Refactor these to use a shared Library project instead.
|
||||
Generate-FileList -fileDepsJson "" -fileListName BaseApplicationsFiles -wxsFilePath $PSScriptRoot\BaseApplications.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release"
|
||||
|
||||
# Remove leaked ImageResizer artifacts from BaseApplications
|
||||
# Remove leaked app-host artifacts from BaseApplications
|
||||
$baseAppWxsPath = "$PSScriptRoot\BaseApplications.wxs"
|
||||
$baseAppWxs = Get-Content $baseAppWxsPath -Raw
|
||||
$baseAppWxs = $baseAppWxs -replace 'PowerToys\.ImageResizer\.exe;?', ''
|
||||
$baseAppWxs = $baseAppWxs -replace 'PowerToys\.ImageResizer\.deps\.json;?', ''
|
||||
$baseAppWxs = $baseAppWxs -replace 'PowerToys\.ImageResizer\.runtimeconfig\.json;?', ''
|
||||
$baseAppWxs = $baseAppWxs -replace 'PowerToys\.ColorPickerUI\.exe;?', ''
|
||||
$baseAppWxs = $baseAppWxs -replace 'PowerToys\.ColorPickerUI\.deps\.json;?', ''
|
||||
$baseAppWxs = $baseAppWxs -replace 'PowerToys\.ColorPickerUI\.runtimeconfig\.json;?', ''
|
||||
# Clean up trailing/double semicolons left after removal
|
||||
$baseAppWxs = $baseAppWxs -replace ';;+', ';'
|
||||
$baseAppWxs = $baseAppWxs -replace '=;', '='
|
||||
@@ -237,7 +244,7 @@ Generate-FileList -fileDepsJson "" -fileListName AwakeImagesFiles -wxsFilePath $
|
||||
Generate-FileComponents -fileListName "AwakeImagesFiles" -wxsFilePath $PSScriptRoot\Awake.wxs
|
||||
|
||||
#ColorPicker
|
||||
Generate-FileList -fileDepsJson "" -fileListName ColorPickerAssetsFiles -wxsFilePath $PSScriptRoot\ColorPicker.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\ColorPicker"
|
||||
Generate-FileList -fileDepsJson "" -fileListName ColorPickerAssetsFiles -wxsFilePath $PSScriptRoot\ColorPicker.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\ColorPicker"
|
||||
Generate-FileComponents -fileListName "ColorPickerAssetsFiles" -wxsFilePath $PSScriptRoot\ColorPicker.wxs
|
||||
|
||||
#Environment Variables
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
|
||||
<Style x:Key="DefaultDockItemControlStyle" TargetType="local:DockItemControl">
|
||||
<Style.Setters>
|
||||
<Setter Property="AutomationProperties.LiveSetting" Value="Off" />
|
||||
<Setter Property="Background" Value="{ThemeResource DockItemBackground}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource DockItemBorderBrush}" />
|
||||
<Setter Property="Padding" Value="{StaticResource DockItemPadding}" />
|
||||
|
||||
@@ -10,6 +10,7 @@ using CommunityToolkit.Mvvm.Messaging;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Common.Helpers;
|
||||
using Microsoft.CmdPal.Common.Messages;
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.CmdPal.UI.Controls;
|
||||
using Microsoft.CmdPal.UI.Dock;
|
||||
using Microsoft.CmdPal.UI.Events;
|
||||
@@ -662,8 +663,6 @@ public sealed partial class MainWindow : WindowEx,
|
||||
var borderColor = showFrame ? DWMWA_COLOR_DEFAULT : DWMWA_COLOR_NONE;
|
||||
PInvoke.DwmSetWindowAttribute(_hwnd, DWMWINDOWATTRIBUTE.DWMWA_BORDER_COLOR, &borderColor, sizeof(uint));
|
||||
}
|
||||
|
||||
RedrawWindow(_hwnd);
|
||||
}
|
||||
|
||||
private void InitializeBackdropSupport()
|
||||
@@ -728,7 +727,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
var positionWindowForAnchor = (HWND hwnd) =>
|
||||
{
|
||||
PInvoke.GetWindowRect(hwnd, out var bounds);
|
||||
var swpFlags = SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE | SET_WINDOW_POS_FLAGS.SWP_NOZORDER | SET_WINDOW_POS_FLAGS.SWP_FRAMECHANGED;
|
||||
var swpFlags = SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE | SET_WINDOW_POS_FLAGS.SWP_NOZORDER;
|
||||
switch (anchorCorner)
|
||||
{
|
||||
case AnchorPoint.TopLeft:
|
||||
@@ -828,33 +827,17 @@ public sealed partial class MainWindow : WindowEx,
|
||||
// topmost status when we hide the window (because we cloak it instead
|
||||
// of hiding it).
|
||||
//
|
||||
// SWP_FRAMECHANGED re-sends WM_NCCALCSIZE so the OS recomputes the
|
||||
// (zero-width) frame. But on this cloak->show path it doesn't reliably
|
||||
// *repaint* the non-client area, so the WS_THICKFRAME border that was
|
||||
// painted earlier survives on the top/left/right edges (the bottom is
|
||||
// clipped away by the card region). A real resize fixes it because it
|
||||
// forces that NC repaint - so we do the same explicitly below.
|
||||
// SWP_FRAMECHANGED is load-bearing for the borderless look on a cold
|
||||
// start. Asking for SWP_FRAMECHANGED here re-sends WM_NCCALCSIZE and
|
||||
// forces the NC repaint every time we show, so the frame is gone from
|
||||
// the very first summon.
|
||||
PInvoke.SetWindowPos(hwnd, HWND.HWND_TOPMOST, 0, 0, 0, 0, SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_FRAMECHANGED);
|
||||
|
||||
// Force the non-client frame to actually redraw (RDW_FRAME) and the
|
||||
// client to repaint over wherever it used to be. Without this the stale
|
||||
// border lingers until the user resizes the window.
|
||||
RedrawWindow(hwnd);
|
||||
|
||||
// Treat the overall show/hide lifecycle as the authoritative
|
||||
// visibility transition, not the lower-level cloak/uncloak helpers.
|
||||
SetIsVisibleToUser(true);
|
||||
}
|
||||
|
||||
private static void RedrawWindow(HWND hwnd)
|
||||
{
|
||||
const uint RDW_INVALIDATE = 0x0001;
|
||||
const uint RDW_UPDATENOW = 0x0100;
|
||||
const uint RDW_ALLCHILDREN = 0x0080;
|
||||
const uint RDW_FRAME = 0x0400;
|
||||
_ = RedrawWindow(hwnd, IntPtr.Zero, IntPtr.Zero, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN | RDW_FRAME);
|
||||
}
|
||||
|
||||
private static DisplayArea GetScreen(HWND currentHwnd, MonitorBehavior target)
|
||||
{
|
||||
// Leaving a note here, in case we ever need it:
|
||||
@@ -1319,10 +1302,6 @@ public sealed partial class MainWindow : WindowEx,
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool DeleteObject(IntPtr hObject);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, uint flags);
|
||||
|
||||
internal void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
|
||||
{
|
||||
if (!_themeServiceInitialized && args.WindowActivationState != WindowActivationState.Deactivated)
|
||||
@@ -1689,17 +1668,6 @@ public sealed partial class MainWindow : WindowEx,
|
||||
case PInvoke.WM_NCCALCSIZE when wParam.Value != 0 && _hwndFrameVisible != true:
|
||||
return (LRESULT)0;
|
||||
|
||||
// On every activation change the OS repaints the non-client frame to
|
||||
// flip between the active / inactive caption. With WS_THICKFRAME still
|
||||
// present that paints the OS border back over our borderless window each
|
||||
// time we lose (or gain) focus - which is the frame that reappears when
|
||||
// another window steals focus, and the one that shows up shortly after
|
||||
// startup (the first activation flip). Passing -1 as the update-region
|
||||
// (lParam) to DefWindowProc keeps the correct activation result while
|
||||
// telling it to skip the non-client repaint entirely.
|
||||
case PInvoke.WM_NCACTIVATE when _hwndFrameVisible != true:
|
||||
return PInvoke.CallWindowProc(_originalWndProc, hwnd, uMsg, wParam, new LPARAM(-1));
|
||||
|
||||
case PInvoke.WM_HOTKEY:
|
||||
{
|
||||
var hotkeyIndex = (int)wParam.Value;
|
||||
|
||||
@@ -105,7 +105,6 @@ WM_ACTIVATE
|
||||
WM_ACTIVATEAPP
|
||||
WA_INACTIVE
|
||||
WM_DISPLAYCHANGE
|
||||
WM_NCACTIVATE
|
||||
WM_SYSCOMMAND
|
||||
WM_SETTINGCHANGE
|
||||
WM_WINDOWPOSCHANGING
|
||||
|
||||
@@ -121,9 +121,6 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
|
||||
public bool ExpandedMode { get; set; }
|
||||
|
||||
// Item keybindings act on the selected item, which is hidden while collapsed — only honor them when expanded.
|
||||
private bool ItemActionsAllowed => !_compactMode || ExpandedMode;
|
||||
|
||||
public ShellPage()
|
||||
{
|
||||
_settingsService = App.Current.Services.GetRequiredService<ISettingsService>();
|
||||
@@ -952,74 +949,47 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
case VirtualKey.F when modifiers.OnlyAlt: // Alt+F: toggle filter focus
|
||||
((ShellPage)sender).ToggleFilterFocus();
|
||||
e.Handled = true;
|
||||
break;
|
||||
case VirtualKey.Down when modifiers.None:
|
||||
case VirtualKey.Tab when modifiers.None:
|
||||
// In a collapsed compact palette, Down/Tab reveals the top-level items so the
|
||||
// user can browse and discover them. Only swallow the key when we actually
|
||||
// expand; otherwise let it fall through to normal list navigation / focus move.
|
||||
if (((ShellPage)sender).TryExpandCollapsedCompact())
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
{
|
||||
// The CommandBar handles item keybindings; skip them while collapsed so a chord can't hit the hidden selection.
|
||||
if (((ShellPage)sender).ItemActionsAllowed)
|
||||
{
|
||||
TryCommandKeybindingMessage msg = new(modifiers.Ctrl, modifiers.Alt, modifiers.Shift, modifiers.Win, e.Key);
|
||||
WeakReferenceMessenger.Default.Send(msg);
|
||||
e.Handled = msg.Handled;
|
||||
}
|
||||
|
||||
// The CommandBar is responsible for handling all the item keybindings,
|
||||
// since the bound context item may need to then show another
|
||||
// context menu
|
||||
TryCommandKeybindingMessage msg = new(modifiers.Ctrl, modifiers.Alt, modifiers.Shift, modifiers.Win, e.Key);
|
||||
WeakReferenceMessenger.Default.Send(msg);
|
||||
e.Handled = msg.Handled;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ShellPage_OnKeyDown(object sender, KeyRoutedEventArgs e)
|
||||
private static void ShellPage_OnKeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (ItemActionsAllowed && TryHandleItemAction(e))
|
||||
var mods = KeyModifiers.GetCurrent();
|
||||
if (mods.Ctrl && e.Key == VirtualKey.Enter)
|
||||
{
|
||||
return;
|
||||
// ctrl+enter
|
||||
WeakReferenceMessenger.Default.Send<ActivateSecondaryCommandMessage>();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
if (e.Key == VirtualKey.Escape)
|
||||
else if (e.Key == VirtualKey.Enter)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<ActivateSelectedListItemMessage>();
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (mods.Ctrl && e.Key == VirtualKey.K)
|
||||
{
|
||||
// ctrl+k
|
||||
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(new OpenContextMenuMessage(null, null, null, ContextMenuFilterLocation.Bottom));
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.Key == VirtualKey.Escape)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new());
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryHandleItemAction(KeyRoutedEventArgs e)
|
||||
{
|
||||
var mods = KeyModifiers.GetCurrent();
|
||||
switch (e.Key)
|
||||
{
|
||||
// Ctrl+Enter
|
||||
case VirtualKey.Enter when mods.OnlyCtrl:
|
||||
WeakReferenceMessenger.Default.Send<ActivateSecondaryCommandMessage>();
|
||||
break;
|
||||
|
||||
// Enter
|
||||
case VirtualKey.Enter when mods.None:
|
||||
WeakReferenceMessenger.Default.Send<ActivateSelectedListItemMessage>();
|
||||
break;
|
||||
|
||||
// Ctrl+K
|
||||
case VirtualKey.K when mods.OnlyCtrl:
|
||||
WeakReferenceMessenger.Default.Send<OpenContextMenuMessage>(new(null, null, null, ContextMenuFilterLocation.Bottom));
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ShellPage_OnPointerPressed(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -1085,23 +1055,6 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
PropertyChanged?.Invoke(this, new(nameof(ExpandedMode)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expands a collapsed compact palette on demand (via Down/Tab) so the user can browse the
|
||||
/// top-level items. Returns <see langword="false"/> and does nothing unless compact mode is
|
||||
/// on and the palette is currently collapsed, letting the caller keep the key's normal
|
||||
/// meaning (list navigation / focus traversal) in every other case.
|
||||
/// </summary>
|
||||
private bool TryExpandCollapsedCompact()
|
||||
{
|
||||
if (!_compactMode || ExpandedMode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HandleExpandCompactOnUiThread(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces the shell into its compact (collapsed) layout and flushes layout so the host can
|
||||
/// read the resulting card height. Only has an effect when compact mode is enabled.
|
||||
|
||||
@@ -13,8 +13,8 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
[TestClass]
|
||||
public class AvailableResultsListTests
|
||||
{
|
||||
private CultureInfo originalCulture = null!;
|
||||
private CultureInfo originalUiCulture = null!;
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
|
||||
@@ -12,8 +12,8 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
[TestClass]
|
||||
public class FallbackTimeDateItemTests
|
||||
{
|
||||
private CultureInfo originalCulture = null!;
|
||||
private CultureInfo originalUiCulture = null!;
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.TimeDate.UnitTests</RootNamespace>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\tests\</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
|
||||
@@ -1,160 +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;
|
||||
using System.Globalization;
|
||||
using Microsoft.CmdPal.Ext.TimeDate;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("IDisposable", "CA1001:Types that own disposable fields should be disposable", Justification = "Disposed in TestCleanup")]
|
||||
public class NowDockBandTests
|
||||
{
|
||||
private static readonly DateTime FixedTime = new DateTime(2025, 7, 1, 14, 5, 32);
|
||||
|
||||
private CultureInfo _originalCulture = null!;
|
||||
private CultureInfo _originalUiCulture = null!;
|
||||
private NowDockBand? _band;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
_originalCulture = CultureInfo.CurrentCulture;
|
||||
_originalUiCulture = CultureInfo.CurrentUICulture;
|
||||
CultureInfo.CurrentCulture = new CultureInfo("en-US", false);
|
||||
CultureInfo.CurrentUICulture = new CultureInfo("en-US", false);
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
_band?.Dispose();
|
||||
_band = null;
|
||||
CultureInfo.CurrentCulture = _originalCulture;
|
||||
CultureInfo.CurrentUICulture = _originalUiCulture;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Constructor_TitleIsSetImmediately()
|
||||
{
|
||||
_band = new NowDockBand(clock: () => FixedTime);
|
||||
|
||||
Assert.AreEqual("2:05 PM", _band.Title);
|
||||
Assert.IsFalse(string.IsNullOrEmpty(_band.Subtitle));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateText_LongTimeFormat_TitleContainsSeconds()
|
||||
{
|
||||
_band = new NowDockBand(timeWithSeconds: true, clock: () => FixedTime);
|
||||
|
||||
_band.UpdateText();
|
||||
|
||||
Assert.AreEqual("2:05:32 PM", _band.Title);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateText_ShortDateFormat_SubtitleIsShortDate()
|
||||
{
|
||||
_band = new NowDockBand(clock: () => FixedTime);
|
||||
|
||||
_band.UpdateText();
|
||||
|
||||
Assert.AreEqual("7/1/2025", _band.Subtitle);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateText_FiresOnUpdatedCallback()
|
||||
{
|
||||
var callbackFired = false;
|
||||
_band = new NowDockBand(onUpdated: () => callbackFired = true, clock: () => FixedTime);
|
||||
|
||||
callbackFired = false; // reset — constructor already fired it once during synchronous UpdateText()
|
||||
|
||||
_band.UpdateText();
|
||||
|
||||
Assert.IsTrue(callbackFired);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateText_CallbackFiredAfterAssignments()
|
||||
{
|
||||
var titleAtCallback = string.Empty;
|
||||
_band = new NowDockBand(
|
||||
onUpdated: () => titleAtCallback = _band?.Title ?? string.Empty,
|
||||
clock: () => FixedTime);
|
||||
|
||||
titleAtCallback = string.Empty; // reset after construction callback
|
||||
|
||||
_band.UpdateText();
|
||||
|
||||
Assert.IsFalse(string.IsNullOrEmpty(titleAtCallback), "Title should be assigned before callback fires");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateText_CopyCommandsUpdated()
|
||||
{
|
||||
_band = new NowDockBand(clock: () => FixedTime);
|
||||
|
||||
_band.UpdateText();
|
||||
|
||||
Assert.AreEqual(_band.Title, _band.CopyTimeCommand.Text);
|
||||
Assert.AreEqual(_band.Subtitle, _band.CopyDateCommand.Text);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("de-DE")]
|
||||
[DataRow("fr-FR")]
|
||||
[DataRow("ar-SA")]
|
||||
public void UpdateText_CultureSmoke_TitleNonEmpty(string cultureName)
|
||||
{
|
||||
// Culture MUST be set before construction — constructor calls UpdateText() synchronously
|
||||
CultureInfo.CurrentCulture = new CultureInfo(cultureName, false);
|
||||
CultureInfo.CurrentUICulture = new CultureInfo(cultureName, false);
|
||||
|
||||
_band = new NowDockBand(clock: () => FixedTime);
|
||||
|
||||
Assert.IsFalse(string.IsNullOrEmpty(_band.Title), $"Title should be non-empty for culture '{cultureName}'");
|
||||
Assert.IsFalse(string.IsNullOrEmpty(_band.Subtitle), $"Subtitle should be non-empty for culture '{cultureName}'");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateSettings_EnablingSeconds_TitleIncludesSeconds()
|
||||
{
|
||||
_band = new NowDockBand(timeWithSeconds: false, clock: () => FixedTime);
|
||||
|
||||
Assert.AreEqual("2:05 PM", _band.Title, "Precondition: seconds hidden by default");
|
||||
|
||||
_band.UpdateSettings(timeWithSeconds: true);
|
||||
|
||||
Assert.AreEqual("2:05:32 PM", _band.Title, "Title should update live to include seconds");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateSettings_DisablingSeconds_TitleDropsSeconds()
|
||||
{
|
||||
_band = new NowDockBand(timeWithSeconds: true, clock: () => FixedTime);
|
||||
|
||||
Assert.AreEqual("2:05:32 PM", _band.Title, "Precondition: seconds shown");
|
||||
|
||||
_band.UpdateSettings(timeWithSeconds: false);
|
||||
|
||||
Assert.AreEqual("2:05 PM", _band.Title, "Title should update live to drop seconds");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateSettings_NoChange_FiresNoCallback()
|
||||
{
|
||||
var callbackCount = 0;
|
||||
_band = new NowDockBand(timeWithSeconds: false, onUpdated: () => callbackCount++, clock: () => FixedTime);
|
||||
|
||||
callbackCount = 0; // reset after construction callback
|
||||
|
||||
_band.UpdateSettings(timeWithSeconds: false);
|
||||
|
||||
Assert.AreEqual(0, callbackCount, "A no-op settings change should not refresh the band");
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
[TestClass]
|
||||
public class QueryTests : CommandPaletteUnitTestBase
|
||||
{
|
||||
private CultureInfo originalCulture = null!;
|
||||
private CultureInfo originalUiCulture = null!;
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
@@ -166,7 +166,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
Assert.IsTrue(results.Length > 0, $"Query '{query}' should return at least one result");
|
||||
|
||||
var firstItem = results.FirstOrDefault();
|
||||
Assert.IsTrue(firstItem!.Title.StartsWith("Error: Invalid input", StringComparison.CurrentCulture), $"Query '{query}' should return an error result for invalid input");
|
||||
Assert.IsTrue(firstItem.Title.StartsWith("Error: Invalid input", StringComparison.CurrentCulture), $"Query '{query}' should return an error result for invalid input");
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
@@ -201,7 +201,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
// Assert
|
||||
Assert.IsNotNull(results);
|
||||
var firstResult = results.FirstOrDefault();
|
||||
Assert.IsTrue(firstResult!.Subtitle.StartsWith(expectedSubtitle, StringComparison.CurrentCulture), $"Could not find result with subtitle starting with '{expectedSubtitle}' for query '{query}'");
|
||||
Assert.IsTrue(firstResult.Subtitle.StartsWith(expectedSubtitle, StringComparison.CurrentCulture), $"Could not find result with subtitle starting with '{expectedSubtitle}' for query '{query}'");
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
@@ -218,7 +218,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
// Assert
|
||||
Assert.IsNotNull(resultsList);
|
||||
var firstResult = resultsList.FirstOrDefault();
|
||||
Assert.IsTrue(firstResult!.Title.Contains(expectedResult, StringComparison.CurrentCulture), $"Delimiter query '{query}' result not match {expectedResult} current result {firstResult.Title}");
|
||||
Assert.IsTrue(firstResult.Title.Contains(expectedResult, StringComparison.CurrentCulture), $"Delimiter query '{query}' result not match {expectedResult} current result {firstResult.Title}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
@@ -12,8 +12,8 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
[TestClass]
|
||||
public class ResultHelperTests
|
||||
{
|
||||
private CultureInfo originalCulture = null!;
|
||||
private CultureInfo originalUiCulture = null!;
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
@@ -41,8 +41,6 @@ public class ResultHelperTests
|
||||
{
|
||||
Label = "Test Label",
|
||||
Value = "Test Value",
|
||||
AlternativeSearchTag = string.Empty,
|
||||
IconType = ResultIconType.Time,
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -57,10 +55,10 @@ public class ResultHelperTests
|
||||
[TestMethod]
|
||||
public void ResultHelper_CreateListItem_HandlesNullInput()
|
||||
{
|
||||
AvailableResult? availableResult = null;
|
||||
AvailableResult availableResult = null;
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsException<System.NullReferenceException>(() => availableResult!.ToListItem());
|
||||
Assert.ThrowsException<System.NullReferenceException>(() => availableResult.ToListItem());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -71,8 +69,6 @@ public class ResultHelperTests
|
||||
{
|
||||
Label = string.Empty,
|
||||
Value = string.Empty,
|
||||
AlternativeSearchTag = string.Empty,
|
||||
IconType = ResultIconType.Time,
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -92,7 +88,6 @@ public class ResultHelperTests
|
||||
{
|
||||
Label = "Test Label",
|
||||
Value = "Test Value",
|
||||
AlternativeSearchTag = string.Empty,
|
||||
IconType = ResultIconType.Date,
|
||||
};
|
||||
|
||||
@@ -115,8 +110,6 @@ public class ResultHelperTests
|
||||
{
|
||||
Label = longText,
|
||||
Value = longText,
|
||||
AlternativeSearchTag = string.Empty,
|
||||
IconType = ResultIconType.Time,
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -137,8 +130,6 @@ public class ResultHelperTests
|
||||
{
|
||||
Label = specialText,
|
||||
Value = specialText,
|
||||
AlternativeSearchTag = string.Empty,
|
||||
IconType = ResultIconType.Time,
|
||||
};
|
||||
|
||||
// Act
|
||||
|
||||
@@ -13,7 +13,6 @@ public class Settings : ISettingsInterface
|
||||
private readonly int firstDayOfWeek;
|
||||
private readonly bool enableFallbackItems;
|
||||
private readonly bool timeWithSecond;
|
||||
private readonly bool dockClockWithSecond;
|
||||
private readonly bool dateWithWeekday;
|
||||
private readonly List<string> customFormats;
|
||||
|
||||
@@ -22,15 +21,13 @@ public class Settings : ISettingsInterface
|
||||
int firstDayOfWeek = -1,
|
||||
bool enableFallbackItems = true,
|
||||
bool timeWithSecond = false,
|
||||
bool dockClockWithSecond = false,
|
||||
bool dateWithWeekday = false,
|
||||
List<string>? customFormats = null)
|
||||
List<string> customFormats = null)
|
||||
{
|
||||
this.firstWeekOfYear = firstWeekOfYear;
|
||||
this.firstDayOfWeek = firstDayOfWeek;
|
||||
this.enableFallbackItems = enableFallbackItems;
|
||||
this.timeWithSecond = timeWithSecond;
|
||||
this.dockClockWithSecond = dockClockWithSecond;
|
||||
this.dateWithWeekday = dateWithWeekday;
|
||||
this.customFormats = customFormats ?? new List<string>();
|
||||
}
|
||||
@@ -43,8 +40,6 @@ public class Settings : ISettingsInterface
|
||||
|
||||
public bool TimeWithSecond => timeWithSecond;
|
||||
|
||||
public bool DockClockWithSecond => dockClockWithSecond;
|
||||
|
||||
public bool DateWithWeekday => dateWithWeekday;
|
||||
|
||||
public List<string> CustomFormats => customFormats;
|
||||
|
||||
@@ -12,8 +12,8 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
[TestClass]
|
||||
public class StringParserTests
|
||||
{
|
||||
private CultureInfo originalCulture = null!;
|
||||
private CultureInfo originalUiCulture = null!;
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
|
||||
@@ -12,8 +12,8 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
[TestClass]
|
||||
public class TimeAndDateHelperTests
|
||||
{
|
||||
private CultureInfo originalCulture = null!;
|
||||
private CultureInfo originalUiCulture = null!;
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
|
||||
@@ -15,8 +15,8 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
[TestClass]
|
||||
public class TimeDateCalculatorTests
|
||||
{
|
||||
private CultureInfo originalCulture = null!;
|
||||
private CultureInfo originalUiCulture = null!;
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
@@ -69,7 +69,7 @@ public class TimeDateCalculatorTests
|
||||
var settings = new SettingsManager();
|
||||
|
||||
// Act
|
||||
var results = TimeDateCalculator.ExecuteSearch(settings, null!);
|
||||
var results = TimeDateCalculator.ExecuteSearch(settings, null);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(results);
|
||||
|
||||
@@ -11,8 +11,8 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests
|
||||
[TestClass]
|
||||
public class TimeDateCommandsProviderTests
|
||||
{
|
||||
private CultureInfo originalCulture = null!;
|
||||
private CultureInfo originalUiCulture = null!;
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
@@ -90,27 +90,5 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests
|
||||
// Assert
|
||||
Assert.IsFalse(string.IsNullOrEmpty(displayName));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetDockBands_ReturnsNonEmptyArray()
|
||||
{
|
||||
var provider = new TimeDateCommandsProvider();
|
||||
|
||||
var bands = provider.GetDockBands();
|
||||
|
||||
Assert.IsTrue(bands.Length > 0, "GetDockBands should return at least one item");
|
||||
Assert.IsNotNull(bands[0], "First dock band should not be null");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetDockBands_NotificationCenterBandDoesNotSetDockIcon()
|
||||
{
|
||||
var provider = new TimeDateCommandsProvider();
|
||||
|
||||
var bands = provider.GetDockBands();
|
||||
|
||||
Assert.IsTrue(bands.Length > 1, "Expected notification center band to be present");
|
||||
Assert.IsNull(bands[1].Icon, "Notification center band should not set a dock icon");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,16 +29,16 @@ internal sealed partial class FallbackTimeDateItem : FallbackCommandItem
|
||||
|
||||
_validOptions = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagDate", CultureInfo.CurrentCulture)!,
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagDateNow", CultureInfo.CurrentCulture)!,
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagDate", CultureInfo.CurrentCulture),
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagDateNow", CultureInfo.CurrentCulture),
|
||||
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagTime", CultureInfo.CurrentCulture)!,
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagTimeNow", CultureInfo.CurrentCulture)!,
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagTime", CultureInfo.CurrentCulture),
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagTimeNow", CultureInfo.CurrentCulture),
|
||||
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagFormat", CultureInfo.CurrentCulture)!,
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagFormatNow", CultureInfo.CurrentCulture)!,
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagFormat", CultureInfo.CurrentCulture),
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagFormatNow", CultureInfo.CurrentCulture),
|
||||
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagWeek", CultureInfo.CurrentCulture)!,
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagWeek", CultureInfo.CurrentCulture),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ internal sealed partial class FallbackTimeDateItem : FallbackCommandItem
|
||||
}
|
||||
|
||||
var availableResults = AvailableResultsList.GetList(false, _settingsManager, timestamp: _timestamp);
|
||||
ListItem? result = null;
|
||||
ListItem result = null;
|
||||
var maxScore = 0;
|
||||
|
||||
foreach (var f in availableResults)
|
||||
|
||||
@@ -10,22 +10,22 @@ internal sealed class AvailableResult
|
||||
/// <summary>
|
||||
/// Gets or sets the time/date value
|
||||
/// </summary>
|
||||
internal required string Value { get; set; }
|
||||
internal string Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text used for the subtitle and as search term
|
||||
/// </summary>
|
||||
internal required string Label { get; set; }
|
||||
internal string Label { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an alternative search tag that will be evaluated if label doesn't match. For example we like to show the era on searches for 'year' too.
|
||||
/// </summary>
|
||||
internal required string AlternativeSearchTag { get; set; }
|
||||
internal string AlternativeSearchTag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the type of result
|
||||
/// </summary>
|
||||
internal required ResultIconType IconType { get; set; }
|
||||
internal ResultIconType IconType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value to show additional error details
|
||||
@@ -37,7 +37,7 @@ internal sealed class AvailableResult
|
||||
/// </summary>
|
||||
/// <param name="theme">Theme</param>
|
||||
/// <returns>Path</returns>
|
||||
public IconInfo? GetIconInfo()
|
||||
public IconInfo GetIconInfo()
|
||||
{
|
||||
return IconType switch
|
||||
{
|
||||
|
||||
@@ -20,8 +20,6 @@ public interface ISettingsInterface
|
||||
|
||||
public bool TimeWithSecond { get; }
|
||||
|
||||
public bool DockClockWithSecond { get; }
|
||||
|
||||
public bool DateWithWeekday { get; }
|
||||
|
||||
public List<string> CustomFormats { get; }
|
||||
|
||||
@@ -17,7 +17,7 @@ internal static class ResultHelper
|
||||
/// <param name="stringId">Id of the string. (Example: `MyString` for `MyString` and `MyStringNow`)</param>
|
||||
/// <param name="stringIdNow">Optional string id for now case</param>
|
||||
/// <returns>The string from the resource file, or <see cref="string.Empty"/> otherwise.</returns>
|
||||
internal static string SelectStringFromResources(bool isSystemTimeDate, string stringId, string? stringIdNow = default)
|
||||
internal static string SelectStringFromResources(bool isSystemTimeDate, string stringId, string stringIdNow = default)
|
||||
{
|
||||
return !isSystemTimeDate
|
||||
? Resources.ResourceManager.GetString(stringId, CultureInfo.CurrentUICulture) ?? string.Empty
|
||||
|
||||
@@ -87,12 +87,6 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
Resources.Microsoft_plugin_timedate_SettingTimeWithSeconds_Description,
|
||||
false); // TODO -- double check default value
|
||||
|
||||
private readonly ToggleSetting _dockClockWithSeconds = new(
|
||||
Namespaced(nameof(DockClockWithSecond)),
|
||||
Resources.Microsoft_plugin_timedate_SettingDockClockWithSeconds,
|
||||
Resources.Microsoft_plugin_timedate_SettingDockClockWithSeconds_Description,
|
||||
false);
|
||||
|
||||
private readonly ToggleSetting _dateWithWeekday = new(
|
||||
Namespaced(nameof(DateWithWeekday)),
|
||||
Resources.Microsoft_plugin_timedate_SettingDateWithWeekday,
|
||||
@@ -149,11 +143,9 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
|
||||
public bool TimeWithSecond => _timeWithSeconds.Value;
|
||||
|
||||
public bool DockClockWithSecond => _dockClockWithSeconds.Value;
|
||||
|
||||
public bool DateWithWeekday => _dateWithWeekday.Value;
|
||||
|
||||
public List<string> CustomFormats => (_customFormats.Value ?? string.Empty).Split(TEXTBOXNEWLINE).ToList();
|
||||
public List<string> CustomFormats => _customFormats.Value.Split(TEXTBOXNEWLINE).ToList();
|
||||
|
||||
internal static string SettingsJsonPath()
|
||||
{
|
||||
@@ -169,7 +161,6 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
|
||||
Settings.Add(_enableFallbackItems);
|
||||
Settings.Add(_timeWithSeconds);
|
||||
Settings.Add(_dockClockWithSeconds);
|
||||
Settings.Add(_dateWithWeekday);
|
||||
Settings.Add(_firstWeekOfYear);
|
||||
Settings.Add(_firstDayOfWeek);
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.TimeDate</RootNamespace>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
|
||||
@@ -1,134 +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;
|
||||
using System.Globalization;
|
||||
using Microsoft.CmdPal.Ext.TimeDate.Helpers;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.TimeDate;
|
||||
|
||||
internal sealed partial class NowDockBand : ListItem, IDisposable
|
||||
{
|
||||
private static readonly TimeSpan PerSecondUpdateInterval = TimeSpan.FromSeconds(1);
|
||||
private static readonly TimeSpan PerMinuteUpdateInterval = TimeSpan.FromMinutes(1);
|
||||
|
||||
private readonly System.Timers.Timer _timer;
|
||||
private readonly Action? _onUpdated;
|
||||
private readonly Func<DateTime> _clock;
|
||||
private bool _timeWithSeconds;
|
||||
|
||||
private CopyTextCommand _copyTimeCommand;
|
||||
private CopyTextCommand _copyDateCommand;
|
||||
|
||||
internal CopyTextCommand CopyTimeCommand => _copyTimeCommand;
|
||||
|
||||
internal CopyTextCommand CopyDateCommand => _copyDateCommand;
|
||||
|
||||
internal NowDockBand(bool timeWithSeconds = false, Action? onUpdated = null, Func<DateTime>? clock = null)
|
||||
{
|
||||
_timeWithSeconds = timeWithSeconds;
|
||||
_onUpdated = onUpdated;
|
||||
_clock = clock ?? (() => DateTime.Now);
|
||||
|
||||
Command = new OpenUrlCommand("ms-actioncenter:")
|
||||
{
|
||||
Id = "com.microsoft.cmdpal.timedate.dockBand",
|
||||
Name = Resources.timedate_show_notification_center_command_name,
|
||||
Result = CommandResult.Dismiss(),
|
||||
};
|
||||
_copyTimeCommand = new CopyTextCommand(string.Empty) { Name = Resources.timedate_copy_time_command_name };
|
||||
_copyDateCommand = new CopyTextCommand(string.Empty) { Name = Resources.timedate_copy_date_command_name };
|
||||
MoreCommands =
|
||||
[
|
||||
new CommandContextItem(_copyTimeCommand),
|
||||
new CommandContextItem(_copyDateCommand),
|
||||
];
|
||||
|
||||
UpdateText();
|
||||
|
||||
_timer = new System.Timers.Timer() { AutoReset = true };
|
||||
ConfigureTimer();
|
||||
}
|
||||
|
||||
// Reads the current "show seconds" preference and, if it changed, reconfigures
|
||||
// the timer cadence and refreshes the displayed text. Safe to call at any time
|
||||
// (e.g. from a settings-changed handler) so the dock clock stays in sync without
|
||||
// requiring the app to restart.
|
||||
internal void UpdateSettings(bool timeWithSeconds)
|
||||
{
|
||||
if (_timeWithSeconds == timeWithSeconds)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_timeWithSeconds = timeWithSeconds;
|
||||
ConfigureTimer();
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
private void ConfigureTimer()
|
||||
{
|
||||
_timer.Stop();
|
||||
_timer.Elapsed -= Timer_Elapsed;
|
||||
_timer.Elapsed -= Timer_ElapsedFirstMinuteTick;
|
||||
|
||||
if (_timeWithSeconds)
|
||||
{
|
||||
_timer.Interval = PerSecondUpdateInterval.TotalMilliseconds;
|
||||
_timer.Elapsed += Timer_Elapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Align the first tick to the next minute boundary so the clock flips
|
||||
// exactly when the system clock does, then fall back to a per-minute cadence.
|
||||
var now = _clock();
|
||||
_timer.Interval = PerMinuteUpdateInterval.TotalMilliseconds - ((now.Second * 1000) + now.Millisecond);
|
||||
_timer.Elapsed += Timer_ElapsedFirstMinuteTick;
|
||||
}
|
||||
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
private void Timer_ElapsedFirstMinuteTick(object? sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
if (sender is System.Timers.Timer timer)
|
||||
{
|
||||
timer.Interval = PerMinuteUpdateInterval.TotalMilliseconds;
|
||||
timer.Elapsed -= Timer_ElapsedFirstMinuteTick;
|
||||
timer.Elapsed += Timer_Elapsed;
|
||||
}
|
||||
|
||||
Timer_Elapsed(sender, e);
|
||||
}
|
||||
|
||||
private void Timer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
internal void UpdateText()
|
||||
{
|
||||
var now = _clock();
|
||||
var timeString = now.ToString(
|
||||
TimeAndDateHelper.GetStringFormat(FormatStringType.Time, _timeWithSeconds, false),
|
||||
CultureInfo.CurrentCulture);
|
||||
var dateString = now.ToString(
|
||||
TimeAndDateHelper.GetStringFormat(FormatStringType.Date, false, false),
|
||||
CultureInfo.CurrentCulture);
|
||||
|
||||
Title = timeString;
|
||||
Subtitle = dateString;
|
||||
_copyTimeCommand.Text = timeString;
|
||||
_copyDateCommand.Text = dateString;
|
||||
|
||||
_onUpdated?.Invoke(); // Must remain last — ViewModel reads Title/Subtitle via GetItems() on callback
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_timer.Stop();
|
||||
_timer.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -834,24 +834,6 @@ namespace Microsoft.CmdPal.Ext.TimeDate {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show seconds on the dock clock.
|
||||
/// </summary>
|
||||
public static string Microsoft_plugin_timedate_SettingDockClockWithSeconds {
|
||||
get {
|
||||
return ResourceManager.GetString("Microsoft_plugin_timedate_SettingDockClockWithSeconds", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to This setting applies to the clock shown on the Command Palette dock..
|
||||
/// </summary>
|
||||
public static string Microsoft_plugin_timedate_SettingDockClockWithSeconds_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("Microsoft_plugin_timedate_SettingDockClockWithSeconds_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Select for more details..
|
||||
/// </summary>
|
||||
|
||||
@@ -267,12 +267,6 @@
|
||||
<data name="Microsoft_plugin_timedate_SettingTimeWithSeconds_Description" xml:space="preserve">
|
||||
<value>This setting applies to the 'Time' and 'Now' result.</value>
|
||||
</data>
|
||||
<data name="Microsoft_plugin_timedate_SettingDockClockWithSeconds" xml:space="preserve">
|
||||
<value>Show seconds on the dock clock</value>
|
||||
</data>
|
||||
<data name="Microsoft_plugin_timedate_SettingDockClockWithSeconds_Description" xml:space="preserve">
|
||||
<value>This setting applies to the clock shown on the Command Palette dock.</value>
|
||||
</data>
|
||||
<data name="Microsoft_plugin_timedate_SubTitleNote" xml:space="preserve">
|
||||
<value>Select or press Ctrl+C to copy</value>
|
||||
<comment>'Ctrl+C' is a shortcut</comment>
|
||||
|
||||
@@ -20,11 +20,8 @@ public sealed partial class TimeDateCommandsProvider : CommandProvider
|
||||
private static readonly TimeDateExtensionPage _timeDateExtensionPage = new(_settingsManager);
|
||||
private readonly FallbackTimeDateItem _fallbackTimeDateItem = new(_settingsManager);
|
||||
|
||||
private readonly WrappedDockItem _bandItem;
|
||||
private readonly WrappedDockItem _notificationCenterBandItem;
|
||||
|
||||
// Keep a reference to the band so we can dispose it when the provider is disposed.
|
||||
private NowDockBand? _nowDockBand;
|
||||
private readonly ListItem _bandItem;
|
||||
private readonly ListItem _notificationCenterBandItem;
|
||||
|
||||
public TimeDateCommandsProvider()
|
||||
{
|
||||
@@ -40,40 +37,8 @@ public sealed partial class TimeDateCommandsProvider : CommandProvider
|
||||
Icon = _timeDateExtensionPage.Icon;
|
||||
Settings = _settingsManager.Settings;
|
||||
|
||||
WrappedDockItem? wrappedBand = null;
|
||||
|
||||
// During NowDockBand construction, UpdateText() runs synchronously.
|
||||
// At that point wrappedBand is still null so the callback is a no-op.
|
||||
// On subsequent timer ticks, wrappedBand is non-null and SetItems fires
|
||||
// RaiseItemsChanged - the framework marshals to the UI thread in
|
||||
// DockBandViewModel.InitializeFromList via DoOnUiThread.
|
||||
_nowDockBand = new NowDockBand(_settingsManager.DockClockWithSecond, onUpdated: () =>
|
||||
{
|
||||
if (wrappedBand is not null)
|
||||
{
|
||||
wrappedBand.Items = [_nowDockBand!];
|
||||
}
|
||||
});
|
||||
|
||||
// Re-read the dock clock preference whenever settings change so the band updates
|
||||
// live (no app restart required). The band ignores no-op changes internally.
|
||||
_settingsManager.Settings.SettingsChanged += OnSettingsChanged;
|
||||
|
||||
wrappedBand = new WrappedDockItem(
|
||||
[_nowDockBand],
|
||||
"com.microsoft.cmdpal.timedate.dockBand",
|
||||
Resources.Microsoft_plugin_timedate_dock_band_title)
|
||||
{
|
||||
Icon = Icons.TimeDateExtIcon,
|
||||
};
|
||||
|
||||
_bandItem = wrappedBand;
|
||||
|
||||
var notificationCenterBand = new NotificationCenterDockBand();
|
||||
_notificationCenterBandItem = new WrappedDockItem(
|
||||
[notificationCenterBand],
|
||||
"com.microsoft.cmdpal.timedate.notificationCenterBand",
|
||||
Resources.timedate_notification_center_band_title);
|
||||
_bandItem = new NowDockBand();
|
||||
_notificationCenterBandItem = new NotificationCenterDockBand();
|
||||
}
|
||||
|
||||
private string GetTranslatedPluginDescription()
|
||||
@@ -91,26 +56,97 @@ public sealed partial class TimeDateCommandsProvider : CommandProvider
|
||||
|
||||
public override ICommandItem[] GetDockBands()
|
||||
{
|
||||
return [_bandItem, _notificationCenterBandItem];
|
||||
}
|
||||
var clockBand = new WrappedDockItem(
|
||||
[_bandItem],
|
||||
"com.microsoft.cmdpal.timedate.dockBand",
|
||||
Resources.Microsoft_plugin_timedate_dock_band_title)
|
||||
{
|
||||
Icon = Icons.TimeDateExtIcon,
|
||||
};
|
||||
|
||||
private void OnSettingsChanged(object sender, Settings args)
|
||||
{
|
||||
_nowDockBand?.UpdateSettings(_settingsManager.DockClockWithSecond);
|
||||
}
|
||||
var notificationBand = new WrappedDockItem(
|
||||
[_notificationCenterBandItem],
|
||||
"com.microsoft.cmdpal.timedate.notificationCenterBand",
|
||||
Resources.timedate_notification_center_band_title)
|
||||
{
|
||||
Icon = Icons.NotificationCenterIcon,
|
||||
};
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_settingsManager.Settings.SettingsChanged -= OnSettingsChanged;
|
||||
_nowDockBand?.Dispose();
|
||||
_nowDockBand = null;
|
||||
GC.SuppressFinalize(this);
|
||||
base.Dispose();
|
||||
return new ICommandItem[] { clockBand, notificationBand };
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable SA1402 // File may only contain a single type
|
||||
|
||||
internal sealed partial class NowDockBand : ListItem
|
||||
{
|
||||
private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(60);
|
||||
private CopyTextCommand _copyTimeCommand;
|
||||
private CopyTextCommand _copyDateCommand;
|
||||
|
||||
public NowDockBand()
|
||||
{
|
||||
// Open Notification Center on click
|
||||
Command = new OpenUrlCommand("ms-actioncenter:") { Id = "com.microsoft.cmdpal.timedate.dockBand", Name = Resources.timedate_show_notification_center_command_name, Result = CommandResult.Dismiss(), Icon = null };
|
||||
_copyTimeCommand = new CopyTextCommand(string.Empty) { Name = Resources.timedate_copy_time_command_name };
|
||||
_copyDateCommand = new CopyTextCommand(string.Empty) { Name = Resources.timedate_copy_date_command_name };
|
||||
MoreCommands = [
|
||||
new CommandContextItem(_copyTimeCommand),
|
||||
new CommandContextItem(_copyDateCommand)
|
||||
];
|
||||
UpdateText();
|
||||
|
||||
// Create a timer to update the time every minute
|
||||
System.Timers.Timer timer = new(UpdateInterval.TotalMilliseconds);
|
||||
|
||||
// but we want it to tick on the minute, so calculate the initial delay
|
||||
var now = DateTime.Now;
|
||||
timer.Interval = UpdateInterval.TotalMilliseconds - ((now.Second * 1000) + now.Millisecond);
|
||||
|
||||
// then after the first tick, set it to 60 seconds
|
||||
timer.Elapsed += Timer_ElapsedFirst;
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
private void Timer_ElapsedFirst(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
// After the first tick, set the interval to 60 seconds
|
||||
if (sender is System.Timers.Timer timer)
|
||||
{
|
||||
timer.Interval = UpdateInterval.TotalMilliseconds;
|
||||
timer.Elapsed -= Timer_ElapsedFirst;
|
||||
timer.Elapsed += Timer_Elapsed;
|
||||
|
||||
// Still call the callback, so that we update the clock
|
||||
Timer_Elapsed(sender, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
private void UpdateText()
|
||||
{
|
||||
var timeExtended = false; // timeLongFormat ?? settings.TimeWithSecond;
|
||||
var dateExtended = false; // dateLongFormat ?? settings.DateWithWeekday;
|
||||
var dateTimeNow = DateTime.Now;
|
||||
|
||||
var timeString = dateTimeNow.ToString(
|
||||
TimeAndDateHelper.GetStringFormat(FormatStringType.Time, timeExtended, dateExtended),
|
||||
CultureInfo.CurrentCulture);
|
||||
var dateString = dateTimeNow.ToString(
|
||||
TimeAndDateHelper.GetStringFormat(FormatStringType.Date, timeExtended, dateExtended),
|
||||
CultureInfo.CurrentCulture);
|
||||
|
||||
Title = timeString;
|
||||
Subtitle = dateString;
|
||||
|
||||
_copyDateCommand.Text = dateString;
|
||||
_copyTimeCommand.Text = timeString;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed partial class NotificationCenterDockBand : ListItem
|
||||
{
|
||||
public NotificationCenterDockBand()
|
||||
|
||||
@@ -119,7 +119,7 @@ private:
|
||||
|
||||
SHELLEXECUTEINFOW sei{ sizeof(sei) };
|
||||
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
|
||||
sei.lpFile = L"PowerToys.ColorPickerUI.exe";
|
||||
sei.lpFile = L"WinUI3Apps\\PowerToys.ColorPickerUI.exe";
|
||||
sei.nShow = SW_SHOWNORMAL;
|
||||
sei.lpParameters = executable_args.data();
|
||||
if (ShellExecuteExW(&sei))
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Win32.SystemEvents">
|
||||
<!-- This package is a dependency of System.Drawing.Common, but we need to set it here so we can exclude the assets, so it doesn't conflict with the 8.0.1 dll coming from .NET SDK. -->
|
||||
<ExcludeAssets>runtime</ExcludeAssets> <!-- Should already be present on .net sdk runtime, so we avoid the conflicting runtime version from nuget -->
|
||||
@@ -35,7 +36,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
<ProjectReference Include="..\ColorPickerUI\ColorPickerUI.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// 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.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ColorPicker.UnitTests.Foundation
|
||||
{
|
||||
[TestClass]
|
||||
public class FrozenContractTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Show_event_name_is_frozen()
|
||||
{
|
||||
Assert.AreEqual(
|
||||
@"Local\ShowColorPickerEvent-8c46be2a-3e05-4186-b56b-4ae986ef2525",
|
||||
PowerToys.Interop.Constants.ShowColorPickerSharedEvent());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Terminate_event_name_is_frozen()
|
||||
{
|
||||
Assert.AreEqual(
|
||||
@"Local\TerminateColorPickerEvent-3d676258-c4d5-424e-a87a-4be22020e813",
|
||||
PowerToys.Interop.Constants.TerminateColorPickerSharedEvent());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SendSettingsTelemetry_event_name_is_frozen()
|
||||
{
|
||||
Assert.AreEqual(
|
||||
@"Local\ColorPickerSettingsTelemetryEvent-6c7071d8-4014-46ec-b687-913bd8a422f1",
|
||||
PowerToys.Interop.Constants.ColorPickerSendSettingsTelemetryEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 System.Collections.Specialized;
|
||||
using ColorPicker.Common;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ColorPicker.UnitTests.Foundation
|
||||
{
|
||||
[TestClass]
|
||||
public class RangeObservableCollectionTests
|
||||
{
|
||||
private static readonly int[] _inputItems = new[] { 1, 2, 3 };
|
||||
private static readonly int[] _expectedItems = new[] { 1, 2, 3 };
|
||||
|
||||
[TestMethod]
|
||||
public void AddRange_adds_all_items_and_raises_a_single_reset()
|
||||
{
|
||||
var sut = new RangeObservableCollection<int>();
|
||||
int notifications = 0;
|
||||
sut.CollectionChanged += (_, e) =>
|
||||
{
|
||||
notifications++;
|
||||
Assert.AreEqual(NotifyCollectionChangedAction.Reset, e.Action);
|
||||
};
|
||||
|
||||
sut.AddRange(_inputItems);
|
||||
|
||||
CollectionAssert.AreEqual(_expectedItems, sut);
|
||||
Assert.AreEqual(1, notifications);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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 ColorPicker.Foundation;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ColorPicker.UnitTests.Foundation
|
||||
{
|
||||
[TestClass]
|
||||
public class ServiceProviderSmokeTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Container_resolves_the_exit_token_source_as_a_singleton()
|
||||
{
|
||||
var provider = AppServices.Configure();
|
||||
|
||||
var a = provider.GetRequiredService<System.Threading.CancellationTokenSource>();
|
||||
var b = provider.GetRequiredService<System.Threading.CancellationTokenSource>();
|
||||
|
||||
Assert.IsNotNull(a);
|
||||
Assert.AreSame(a, b);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Container_resolves_the_exit_token_as_the_sources_token()
|
||||
{
|
||||
var provider = AppServices.Configure();
|
||||
|
||||
var source = provider.GetRequiredService<System.Threading.CancellationTokenSource>();
|
||||
var token = provider.GetRequiredService<System.Threading.CancellationToken>();
|
||||
|
||||
// CancellationToken is a struct; equality compares the underlying source.
|
||||
Assert.AreEqual(source.Token, token);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Resolved_token_observes_source_cancellation()
|
||||
{
|
||||
var provider = AppServices.Configure();
|
||||
|
||||
var source = provider.GetRequiredService<System.Threading.CancellationTokenSource>();
|
||||
source.Cancel();
|
||||
|
||||
var token = provider.GetRequiredService<System.Threading.CancellationToken>();
|
||||
Assert.IsTrue(token.IsCancellationRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// 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 ColorPicker.Helpers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ColorPicker.UnitTests.Helpers
|
||||
{
|
||||
[TestClass]
|
||||
public class ClipboardHelperTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void CopyToClipboard_null_is_a_no_op()
|
||||
{
|
||||
ClipboardHelper.CopyToClipboard(null); // must not throw, must not touch the clipboard
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CopyToClipboard_empty_is_a_no_op()
|
||||
{
|
||||
ClipboardHelper.CopyToClipboard(string.Empty); // must not throw
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// 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.Linq;
|
||||
using ColorPicker.Helpers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ColorPicker.UnitTests.Helpers
|
||||
{
|
||||
[TestClass]
|
||||
public class MonitorResolutionHelperTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void AllMonitors_has_at_least_one_named_monitor()
|
||||
{
|
||||
Assert.IsTrue(MonitorResolutionHelper.AllMonitors.Any());
|
||||
Assert.IsFalse(string.IsNullOrEmpty(MonitorResolutionHelper.AllMonitors.First().Name));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Exactly_one_primary_monitor()
|
||||
{
|
||||
Assert.AreEqual(1, MonitorResolutionHelper.AllMonitors.Count(m => m.IsPrimary));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Primary_bounds_is_a_positive_windows_foundation_rect()
|
||||
{
|
||||
var b = MonitorResolutionHelper.AllMonitors.First(m => m.IsPrimary).Bounds;
|
||||
Assert.IsInstanceOfType(b, typeof(Windows.Foundation.Rect));
|
||||
Assert.IsTrue(b.Width > 0 && b.Height > 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void HasMultipleMonitors_matches_count()
|
||||
{
|
||||
Assert.AreEqual(MonitorResolutionHelper.AllMonitors.Count() > 1, MonitorResolutionHelper.HasMultipleMonitors());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using ColorPicker.Helpers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ColorPicker.UnitTests.Helpers
|
||||
{
|
||||
[TestClass]
|
||||
public class NativeEventWaiterTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void WaitForEventLoop_without_a_dispatcher_queue_is_a_safe_no_op()
|
||||
{
|
||||
// MSTest worker thread has no DispatcherQueue: the guard logs + returns,
|
||||
// and signalling the event must NOT invoke the callback.
|
||||
var eventName = @"Local\PT_CP_Test_" + Guid.NewGuid().ToString("N");
|
||||
var fired = new ManualResetEventSlim(false);
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
NativeEventWaiter.WaitForEventLoop(eventName, () => fired.Set(), cts.Token);
|
||||
|
||||
using var signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
|
||||
signal.Set();
|
||||
|
||||
Assert.IsFalse(fired.Wait(300), "callback must not fire when there is no DispatcherQueue to marshal onto");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
using ColorPicker.Helpers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ColorPicker.UnitTests.Helpers
|
||||
{
|
||||
[TestClass]
|
||||
public class NativeMethodsHelperTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void GetCursorPos_after_SetCursorPos_round_trips()
|
||||
{
|
||||
// A safe coordinate inside any primary monitor.
|
||||
Assert.IsTrue(NativeMethodsHelper.SetCursorPos(100, 120));
|
||||
Assert.IsTrue(NativeMethodsHelper.GetCursorPos(out NativeMethodsHelper.POINT p));
|
||||
Assert.AreEqual(100, p.X);
|
||||
Assert.AreEqual(120, p.Y);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void POINT_implicit_operator_yields_Windows_Foundation_Point()
|
||||
{
|
||||
var p = new NativeMethodsHelper.POINT { X = 7, Y = 9 };
|
||||
Windows.Foundation.Point wf = p; // exercises the retargeted implicit operator
|
||||
Assert.AreEqual(7.0, wf.X);
|
||||
Assert.AreEqual(9.0, wf.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 ColorPicker.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Enumerations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ColorPicker.UnitTests.Helpers
|
||||
{
|
||||
[TestClass]
|
||||
public class SessionEventHelperTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Start_sets_Event_with_StartedAs()
|
||||
{
|
||||
SessionEventHelper.Start(ColorPickerActivationAction.OpenColorPicker);
|
||||
|
||||
Assert.IsNotNull(SessionEventHelper.Event);
|
||||
Assert.AreEqual(ColorPickerActivationAction.OpenColorPicker.ToString(), SessionEventHelper.Event.StartedAs);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void End_after_Start_finalizes_a_nonnegative_duration()
|
||||
{
|
||||
SessionEventHelper.Start(ColorPickerActivationAction.OpenColorPicker);
|
||||
SessionEventHelper.End();
|
||||
|
||||
// End() does not clear Event; it finalizes Duration and writes the telemetry event.
|
||||
Assert.IsNotNull(SessionEventHelper.Event);
|
||||
Assert.IsTrue(SessionEventHelper.Event.Duration >= 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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 ColorPicker.Helpers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ColorPicker.UnitTests.Helpers
|
||||
{
|
||||
[TestClass]
|
||||
public class ThrottledActionInvokerTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Ctor_throws_without_a_dispatcher_queue()
|
||||
{
|
||||
// MSTest runs on a worker thread that has no DispatcherQueue, so the
|
||||
// ctor's GetForCurrentThread() guard must throw. This also proves the
|
||||
// timer is bound at construction time (not lazily).
|
||||
Assert.ThrowsException<InvalidOperationException>(() => new ThrottledActionInvoker());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// 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.Text.Json;
|
||||
using ColorPicker.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ColorPicker.UnitTests.Settings
|
||||
{
|
||||
[TestClass]
|
||||
public class UserSettingsTests
|
||||
{
|
||||
private sealed class SyncThrottledActionInvoker : IThrottledActionInvoker
|
||||
{
|
||||
public void ScheduleAction(Action action, int milliseconds) => action();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Default_settings_version_is_2_1()
|
||||
{
|
||||
Assert.AreEqual("2.1", new ColorPickerSettings().Version);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Properties_serialize_with_lowercase_keys()
|
||||
{
|
||||
var json = JsonSerializer.Serialize(new ColorPickerProperties());
|
||||
StringAssert.Contains(json, "\"showcolorname\"");
|
||||
StringAssert.Contains(json, "\"copiedcolorrepresentation\"");
|
||||
StringAssert.Contains(json, "\"changecursor\"");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Color_history_entry_is_four_pipe_parts()
|
||||
{
|
||||
// The frozen A|R|G|B contract written by MainViewModel.GetColorString and
|
||||
// parsed by ColorPickerService.GetSavedColorsAsync (CmdPal).
|
||||
Assert.AreEqual(4, "255|16|32|48".Split('|').Length);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UserSettings_constructs_with_a_synchronous_invoker()
|
||||
{
|
||||
// Smoke: ctor loads defaults + registers the file watcher without a DispatcherQueue.
|
||||
var settings = new ColorPicker.Settings.UserSettings(new SyncThrottledActionInvoker());
|
||||
Assert.IsNotNull(settings.CopiedColorRepresentation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,121 +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;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
|
||||
using ColorPicker.Mouse;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
|
||||
namespace ColorPickerUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application, IDisposable
|
||||
{
|
||||
public ETWTrace EtwTrace { get; private set; } = new ETWTrace();
|
||||
|
||||
private Mutex _instanceMutex;
|
||||
private static string[] _args;
|
||||
private int _powerToysRunnerPid;
|
||||
private bool disposedValue;
|
||||
|
||||
private CancellationTokenSource NativeThreadCTS { get; set; }
|
||||
|
||||
[Export]
|
||||
private static CancellationToken ExitToken { get; set; }
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
string appLanguage = LanguageHelper.LoadLanguage();
|
||||
if (!string.IsNullOrEmpty(appLanguage))
|
||||
{
|
||||
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(appLanguage);
|
||||
}
|
||||
}
|
||||
catch (CultureNotFoundException ex)
|
||||
{
|
||||
Logger.LogError("CultureNotFoundException: " + ex.Message);
|
||||
}
|
||||
|
||||
NativeThreadCTS = new CancellationTokenSource();
|
||||
ExitToken = NativeThreadCTS.Token;
|
||||
|
||||
_args = e?.Args;
|
||||
|
||||
// allow only one instance of color picker
|
||||
_instanceMutex = new Mutex(true, @"Local\PowerToys_ColorPicker_InstanceMutex", out bool createdNew);
|
||||
if (!createdNew)
|
||||
{
|
||||
Logger.LogWarning("There is ColorPicker instance running. Exiting Color Picker");
|
||||
_instanceMutex = null;
|
||||
Shutdown(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_args?.Length > 0)
|
||||
{
|
||||
_ = int.TryParse(_args[0], out _powerToysRunnerPid);
|
||||
|
||||
Logger.LogInfo($"Color Picker started from the PowerToys Runner. Runner pid={_powerToysRunnerPid}");
|
||||
RunnerHelper.WaitForPowerToysRunner(_powerToysRunnerPid, () =>
|
||||
{
|
||||
Logger.LogInfo("PowerToys Runner exited. Exiting ColorPicker");
|
||||
NativeThreadCTS.Cancel();
|
||||
Dispatcher.Invoke(Shutdown);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_powerToysRunnerPid = -1;
|
||||
}
|
||||
|
||||
base.OnStartup(e);
|
||||
}
|
||||
|
||||
protected override void OnExit(ExitEventArgs e)
|
||||
{
|
||||
if (_instanceMutex != null)
|
||||
{
|
||||
_instanceMutex.ReleaseMutex();
|
||||
}
|
||||
|
||||
CursorManager.RestoreOriginalCursors();
|
||||
base.OnExit(e);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_instanceMutex?.Dispose();
|
||||
EtwTrace?.Dispose();
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public bool IsRunningDetachedFromPowerToys()
|
||||
{
|
||||
return _powerToysRunnerPid == -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +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;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
using Microsoft.Xaml.Behaviors;
|
||||
|
||||
namespace ColorPicker.Behaviors
|
||||
{
|
||||
public class AppearAnimationBehavior : Behavior<Window>
|
||||
{
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
AssociatedObject.Loaded += AssociatedObject_Loaded;
|
||||
}
|
||||
|
||||
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AssociatedObject.IsVisibleChanged += AssociatedObject_IsVisibleChanged;
|
||||
}
|
||||
|
||||
private void AssociatedObject_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (AssociatedObject.IsVisible)
|
||||
{
|
||||
Appear();
|
||||
}
|
||||
else
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private void Appear()
|
||||
{
|
||||
var duration = new Duration(TimeSpan.FromMilliseconds(250));
|
||||
|
||||
var opacityAppear = new DoubleAnimation(0d, 1d, duration)
|
||||
{
|
||||
EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseInOut },
|
||||
};
|
||||
|
||||
AssociatedObject.BeginAnimation(UIElement.OpacityProperty, opacityAppear);
|
||||
|
||||
// force re-evaluation of tooltip size
|
||||
AssociatedObject.SizeToContent = SizeToContent.Manual;
|
||||
AssociatedObject.SizeToContent = SizeToContent.WidthAndHeight;
|
||||
}
|
||||
|
||||
private void Hide()
|
||||
{
|
||||
var duration = new Duration(TimeSpan.FromMilliseconds(1));
|
||||
|
||||
var opacityAppear = new DoubleAnimation(0d, duration);
|
||||
|
||||
AssociatedObject.BeginAnimation(UIElement.OpacityProperty, opacityAppear);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +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.Windows;
|
||||
|
||||
using ColorPicker.Helpers;
|
||||
using ColorPicker.Mouse;
|
||||
using ManagedCommon;
|
||||
using Microsoft.Xaml.Behaviors;
|
||||
|
||||
namespace ColorPicker.Behaviors
|
||||
{
|
||||
public class ChangeWindowPositionBehavior : Behavior<Window>
|
||||
{
|
||||
// color window should not get into these zones, only mouse to avoid window getting outsize of monitor
|
||||
private const int MonitorRightSideDeadZone = 285;
|
||||
private const int MonitorBottomSideDeadZone = 90;
|
||||
private const int WindowOffsetWhenInDeadZone = 10;
|
||||
|
||||
private const int YOffset = 10;
|
||||
private const int XOffset = 5;
|
||||
|
||||
private Point _lastMousePosition;
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
AssociatedObject.Loaded += AssociatedObject_Loaded;
|
||||
}
|
||||
|
||||
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mouseInfoProvider = Bootstrapper.Container.GetExportedValue<IMouseInfoProvider>();
|
||||
|
||||
SetWindowPosition(mouseInfoProvider.CurrentPosition);
|
||||
mouseInfoProvider.MousePositionChanged += (s, mousePosition) =>
|
||||
{
|
||||
SetWindowPosition(mousePosition);
|
||||
};
|
||||
|
||||
AssociatedObject.IsVisibleChanged += AssociatedObject_IsVisibleChanged;
|
||||
}
|
||||
|
||||
private void AssociatedObject_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if ((bool)e.NewValue)
|
||||
{
|
||||
SetWindowPosition(_lastMousePosition);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetWindowPosition(Point mousePosition)
|
||||
{
|
||||
_lastMousePosition = mousePosition;
|
||||
var dpi = MonitorResolutionHelper.GetCurrentMonitorDpi();
|
||||
var mousePositionScaled = new Point(mousePosition.X / dpi.DpiScaleX, mousePosition.Y / dpi.DpiScaleX);
|
||||
|
||||
var monitorBounds = GetBoundsOfMonitorWithMouseIn(mousePosition);
|
||||
|
||||
var windowLeft = mousePositionScaled.X + XOffset;
|
||||
var windowTop = mousePositionScaled.Y + YOffset;
|
||||
|
||||
if ((windowLeft + MonitorRightSideDeadZone) > monitorBounds.Right / dpi.DpiScaleX)
|
||||
{
|
||||
windowLeft -= AssociatedObject.Width + WindowOffsetWhenInDeadZone;
|
||||
}
|
||||
|
||||
if ((windowTop + MonitorBottomSideDeadZone) > monitorBounds.Bottom / dpi.DpiScaleX)
|
||||
{
|
||||
windowTop -= AssociatedObject.Height + WindowOffsetWhenInDeadZone;
|
||||
}
|
||||
|
||||
AssociatedObject.Left = windowLeft;
|
||||
AssociatedObject.Top = windowTop;
|
||||
}
|
||||
|
||||
private static Rect GetBoundsOfMonitorWithMouseIn(Point mousePosition)
|
||||
{
|
||||
foreach (var monitor in MonitorResolutionHelper.AllMonitors)
|
||||
{
|
||||
if (monitor.Bounds.Contains(new Point(mousePosition.X, mousePosition.Y)))
|
||||
{
|
||||
return monitor.Bounds;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogWarning("Failed to get monitor bounds for mouse position" + mousePosition.X + "," + mousePosition.Y);
|
||||
return new Rect(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.Windows;
|
||||
|
||||
using ColorPicker.Helpers;
|
||||
using Microsoft.Xaml.Behaviors;
|
||||
|
||||
namespace ColorPicker.Behaviors
|
||||
{
|
||||
public class CloseZoomWindowBehavior : Behavior<Window>
|
||||
{
|
||||
private ZoomWindowHelper _zoomWindowHelper;
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
AssociatedObject.Loaded += AssociatedObject_Loaded;
|
||||
}
|
||||
|
||||
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AssociatedObject.PreviewKeyDown += AssociatedObject_PreviewKeyDown;
|
||||
AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
|
||||
_zoomWindowHelper = Bootstrapper.Container.GetExportedValue<ZoomWindowHelper>();
|
||||
}
|
||||
|
||||
private void AssociatedObject_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
_zoomWindowHelper.CloseZoomWindow();
|
||||
}
|
||||
|
||||
private void AssociatedObject_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == System.Windows.Input.Key.Escape)
|
||||
{
|
||||
_zoomWindowHelper.CloseZoomWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +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;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
using ColorPicker.Models;
|
||||
using Microsoft.Xaml.Behaviors;
|
||||
|
||||
namespace ColorPicker.Behaviors
|
||||
{
|
||||
public class DragAndDropReorderBehavior : Behavior<ItemsControl>
|
||||
{
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
|
||||
var style = new Style(typeof(ContentPresenter));
|
||||
style.Setters.Add(
|
||||
new EventSetter(
|
||||
FrameworkElement.PreviewMouseMoveEvent,
|
||||
new MouseEventHandler(ItemPreviewMouseMove)));
|
||||
style.Setters.Add(
|
||||
new EventSetter(
|
||||
FrameworkElement.DropEvent,
|
||||
new DragEventHandler(ItemDrop)));
|
||||
AssociatedObject.ItemContainerStyle = style;
|
||||
}
|
||||
|
||||
private void ItemPreviewMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
Task.Run(
|
||||
new Action(() =>
|
||||
{
|
||||
Dispatcher.BeginInvoke(
|
||||
new Action(() =>
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
var data = new DataObject();
|
||||
data.SetData("Source", (sender as FrameworkElement).DataContext);
|
||||
DragDrop.DoDragDrop(sender as DependencyObject, data, DragDropEffects.Move);
|
||||
e.Handled = true;
|
||||
}
|
||||
}),
|
||||
null);
|
||||
}),
|
||||
CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
private void ItemDrop(object sender, DragEventArgs e)
|
||||
{
|
||||
var source = e.Data.GetData("Source") as ColorFormatModel;
|
||||
if (source != null)
|
||||
{
|
||||
int newIndex = AssociatedObject.Items.IndexOf((sender as FrameworkElement).DataContext);
|
||||
var list = AssociatedObject.ItemsSource as ObservableCollection<ColorFormatModel>;
|
||||
list.RemoveAt(list.IndexOf(source));
|
||||
list.Insert(newIndex, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +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.Windows;
|
||||
|
||||
using Microsoft.Xaml.Behaviors;
|
||||
|
||||
namespace ColorPicker.Behaviors
|
||||
{
|
||||
public class DragWindowBehavior : Behavior<FrameworkElement>
|
||||
{
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
AssociatedObject.Loaded += AssociatedObject_Loaded;
|
||||
}
|
||||
|
||||
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
|
||||
}
|
||||
|
||||
private void AssociatedObject_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
var parentWindow = Window.GetWindow(AssociatedObject);
|
||||
if (parentWindow != null)
|
||||
{
|
||||
parentWindow.DragMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +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.Windows;
|
||||
|
||||
using ColorPicker.Shaders;
|
||||
using Microsoft.Xaml.Behaviors;
|
||||
|
||||
namespace ColorPicker.Behaviors
|
||||
{
|
||||
public class GridEffectBehavior : Behavior<FrameworkElement>
|
||||
{
|
||||
private static double _baseZoomImageSizeInPx = 50;
|
||||
|
||||
public static readonly DependencyProperty EffectProperty = DependencyProperty.Register("Effect", typeof(GridShaderEffect), typeof(GridEffectBehavior));
|
||||
|
||||
public static readonly DependencyProperty ZoomFactorProperty = DependencyProperty.Register("ZoomFactor", typeof(double), typeof(GridEffectBehavior));
|
||||
|
||||
public GridShaderEffect Effect
|
||||
{
|
||||
get { return (GridShaderEffect)GetValue(EffectProperty); }
|
||||
set { SetValue(EffectProperty, value); }
|
||||
}
|
||||
|
||||
public double ZoomFactor
|
||||
{
|
||||
get { return (double)GetValue(ZoomFactorProperty); }
|
||||
set { SetValue(ZoomFactorProperty, value); }
|
||||
}
|
||||
|
||||
protected override void OnAttached()
|
||||
{
|
||||
AssociatedObject.Loaded += AssociatedObject_Loaded;
|
||||
}
|
||||
|
||||
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AssociatedObject.MouseMove += AssociatedObject_MouseMove;
|
||||
}
|
||||
|
||||
private void AssociatedObject_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
|
||||
{
|
||||
var position = e.GetPosition(AssociatedObject);
|
||||
|
||||
var relativeX = position.X / AssociatedObject.ActualWidth;
|
||||
var relativeY = position.Y / AssociatedObject.Height;
|
||||
Effect.MousePosition = new Point(relativeX, relativeY);
|
||||
|
||||
if (ZoomFactor >= 4)
|
||||
{
|
||||
Effect.Radius = 0.04;
|
||||
Effect.SquareSize = ZoomFactor;
|
||||
Effect.TextureSize = _baseZoomImageSizeInPx * ZoomFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
// don't show grid, too small pixels
|
||||
Effect.Radius = 0.0;
|
||||
Effect.SquareSize = 0;
|
||||
Effect.TextureSize = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,106 +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;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
using Microsoft.Xaml.Behaviors;
|
||||
|
||||
namespace ColorPicker.Behaviors
|
||||
{
|
||||
public class ResizeBehavior : Behavior<FrameworkElement>
|
||||
{
|
||||
// animation behavior variables
|
||||
// used when size is getting bigger
|
||||
private static readonly TimeSpan _animationTime = TimeSpan.FromMilliseconds(200);
|
||||
private static readonly IEasingFunction _easeFunction = new SineEase() { EasingMode = EasingMode.EaseOut };
|
||||
|
||||
// used when size is getting smaller
|
||||
private static readonly TimeSpan _animationTimeSmaller = _animationTime;
|
||||
private static readonly IEasingFunction _easeFunctionSmaller = new QuadraticEase() { EasingMode = EasingMode.EaseIn };
|
||||
|
||||
private static void CustomAnimation(DependencyProperty prop, FrameworkElement sender, double fromValue, double toValue)
|
||||
{
|
||||
// if the animation is to/from a value of 0, it will cancel the current animation
|
||||
DoubleAnimation move = null;
|
||||
if (toValue > 0 && fromValue > 0)
|
||||
{
|
||||
// if getting bigger
|
||||
if (fromValue < toValue)
|
||||
{
|
||||
move = new DoubleAnimation(fromValue, toValue, new Duration(_animationTime), FillBehavior.Stop)
|
||||
{
|
||||
EasingFunction = _easeFunction,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
move = new DoubleAnimation(fromValue, toValue, new Duration(_animationTimeSmaller), FillBehavior.Stop)
|
||||
{
|
||||
EasingFunction = _easeFunctionSmaller,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// HandoffBehavior must be SnapshotAndReplace
|
||||
// Compose does not allow cancellation
|
||||
sender.BeginAnimation(prop, move, HandoffBehavior.SnapshotAndReplace);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty WidthProperty = DependencyProperty.Register("Width", typeof(double), typeof(ResizeBehavior), new PropertyMetadata(new PropertyChangedCallback(WidthPropertyChanged)));
|
||||
|
||||
private static void WidthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var sender = ((ResizeBehavior)d).AssociatedObject;
|
||||
|
||||
var fromValue = sender.Width;
|
||||
var toValue = (double)e.NewValue;
|
||||
|
||||
// setting Width before animation prevents jumping
|
||||
sender.Width = toValue;
|
||||
CustomAnimation(FrameworkElement.WidthProperty, sender, fromValue, toValue);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty HeightProperty = DependencyProperty.Register("Height", typeof(double), typeof(ResizeBehavior), new PropertyMetadata(new PropertyChangedCallback(HeightPropertyChanged)));
|
||||
|
||||
private static void HeightPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var sender = ((ResizeBehavior)d).AssociatedObject;
|
||||
|
||||
var fromValue = sender.Height;
|
||||
var toValue = (double)e.NewValue;
|
||||
|
||||
// setting Height before animation prevents jumping
|
||||
sender.Height = toValue;
|
||||
CustomAnimation(FrameworkElement.HeightProperty, sender, fromValue, toValue);
|
||||
}
|
||||
|
||||
public double Width
|
||||
{
|
||||
get
|
||||
{
|
||||
return (double)GetValue(WidthProperty);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetValue(WidthProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
public double Height
|
||||
{
|
||||
get
|
||||
{
|
||||
return (double)GetValue(HeightProperty);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
SetValue(HeightProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +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.ComponentModel.Composition;
|
||||
using System.ComponentModel.Composition.Hosting;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("UnitTest-ColorPickerUI")]
|
||||
|
||||
namespace ColorPicker
|
||||
{
|
||||
public static class Bootstrapper
|
||||
{
|
||||
public static CompositionContainer Container { get; private set; }
|
||||
|
||||
public static void InitializeContainer(object initPoint)
|
||||
{
|
||||
var catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly());
|
||||
Container = new CompositionContainer(catalog);
|
||||
|
||||
Container.SatisfyImportsOnce(initPoint);
|
||||
}
|
||||
|
||||
public static void Dispose()
|
||||
=> Container.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<Window
|
||||
x:Class="ColorPicker.ColorEditorWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:behaviors="clr-namespace:ColorPicker.Behaviors"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:e="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:p="clr-namespace:ColorPicker.Properties"
|
||||
Width="440"
|
||||
Height="380"
|
||||
AutomationProperties.Name="{x:Static p:Resources.cp_editor}"
|
||||
ResizeMode="NoResize"
|
||||
Topmost="True"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
<e:Interaction.Behaviors>
|
||||
<behaviors:CloseZoomWindowBehavior />
|
||||
</e:Interaction.Behaviors>
|
||||
<Border x:Name="MainBorder" BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<DockPanel
|
||||
Grid.Row="0"
|
||||
Background="Transparent"
|
||||
MouseLeftButtonDown="TitleBar_MouseDown"
|
||||
MouseRightButtonUp="TitleBar_MouseRightClick">
|
||||
<Image
|
||||
Width="20"
|
||||
Height="20"
|
||||
Margin="10,5,5,8"
|
||||
Source="pack://application:,,,/Assets/ColorPicker/icon.ico" />
|
||||
<TextBlock
|
||||
Margin="10"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Static p:Resources.CP_Title}" />
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
<Button
|
||||
Width="50"
|
||||
Height="30"
|
||||
Margin="10,-10,-2,1"
|
||||
Click="Close_Click"
|
||||
Content=""
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Style="{StaticResource TitleBarCloseButtonStyle}" />
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
<ContentPresenter
|
||||
x:Name="contentPresenter"
|
||||
Grid.Row="1"
|
||||
Content="{Binding Content}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -1,183 +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;
|
||||
using System.Drawing.Printing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Shell;
|
||||
using ColorPicker.Helpers;
|
||||
using ControlzEx.Theming;
|
||||
using ManagedCommon;
|
||||
using Microsoft.Diagnostics.Tracing.Parsers.ClrPrivate;
|
||||
using Microsoft.Win32;
|
||||
using Windows.Graphics;
|
||||
|
||||
namespace ColorPicker
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ColorEditorWindow.xaml
|
||||
/// </summary>
|
||||
public partial class ColorEditorWindow : Window
|
||||
{
|
||||
private readonly AppStateHandler _appStateHandler;
|
||||
|
||||
public ColorEditorWindow(AppStateHandler appStateHandler)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_appStateHandler = appStateHandler;
|
||||
Closing += ColorEditorWindow_Closing;
|
||||
}
|
||||
|
||||
private void ColorEditorWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
e.Cancel = true;
|
||||
_appStateHandler.EndUserSession();
|
||||
}
|
||||
|
||||
protected override void OnSourceInitialized(EventArgs e)
|
||||
{
|
||||
WindowChrome.SetWindowChrome(
|
||||
this,
|
||||
new WindowChrome
|
||||
{
|
||||
CaptionHeight = 0,
|
||||
CornerRadius = default,
|
||||
GlassFrameThickness = new Thickness(-1),
|
||||
ResizeBorderThickness = ResizeMode == ResizeMode.NoResize ? default : new Thickness(4),
|
||||
UseAeroCaptionButtons = false,
|
||||
});
|
||||
if (OSVersionHelper.IsWindows11())
|
||||
{
|
||||
// ResizeMode="NoResize" removes rounded corners. So force them to rounded.
|
||||
IntPtr hWnd = new WindowInteropHelper(GetWindow(this)).EnsureHandle();
|
||||
DWMWINDOWATTRIBUTE attribute = DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE;
|
||||
DWM_WINDOW_CORNER_PREFERENCE preference = DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUND;
|
||||
DwmSetWindowAttribute(hWnd, attribute, ref preference, sizeof(uint));
|
||||
}
|
||||
else
|
||||
{
|
||||
// On Windows10 ResizeMode="NoResize" removes the border so we add a new one.
|
||||
MainBorder.BorderThickness = new System.Windows.Thickness(0.5);
|
||||
}
|
||||
|
||||
// Hide then Show with WindowStyle="None" will remove the Mica effect. So manually remove the titlebar.
|
||||
RemoveWindowTitlebarContents();
|
||||
|
||||
base.OnSourceInitialized(e);
|
||||
}
|
||||
|
||||
public void RemoveWindowTitlebarContents()
|
||||
{
|
||||
IntPtr handle = new WindowInteropHelper(GetWindow(this)).EnsureHandle();
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int windowStyleLong = GetWindowLong(handle, GWLSTYLE);
|
||||
windowStyleLong &= ~(int)WindowStyles.WS_SYSMENU;
|
||||
|
||||
IntPtr result = SetWindowLong(handle, GWLSTYLE, windowStyleLong);
|
||||
if (result.ToInt64() == 0)
|
||||
{
|
||||
int error = Marshal.GetLastWin32Error();
|
||||
Logger.LogError($"SetWindowLong error {error}");
|
||||
}
|
||||
}
|
||||
|
||||
private void Close_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left)
|
||||
{
|
||||
this.DragMove();
|
||||
}
|
||||
}
|
||||
|
||||
private void TitleBar_MouseRightClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
IntPtr hwnd = new WindowInteropHelper(this).Handle;
|
||||
|
||||
// Get the mouse position relative to the screen
|
||||
Point mousePosition = e.GetPosition(this);
|
||||
|
||||
Point screenPoint = PointToScreen(mousePosition);
|
||||
|
||||
// Display the system menu at the current mouse position
|
||||
IntPtr hMenu = GetSystemMenu(hwnd, false);
|
||||
if (hMenu != IntPtr.Zero)
|
||||
{
|
||||
int command = TrackPopupMenu(
|
||||
hMenu,
|
||||
TPMLEFTALIGN | TPMRETURNCMD,
|
||||
(int)screenPoint.X,
|
||||
(int)screenPoint.Y,
|
||||
0,
|
||||
hwnd,
|
||||
IntPtr.Zero);
|
||||
if (command > 0)
|
||||
{
|
||||
SendMessage(hwnd, WMSYSCOMMAND, new IntPtr(command), IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const int WMSYSCOMMAND = 0x0112;
|
||||
private const int TPMLEFTALIGN = 0x0000;
|
||||
private const int TPMRETURNCMD = 0x0100;
|
||||
|
||||
// The enum flag for DwmSetWindowAttribute's second parameter, which tells the function what attribute to set.
|
||||
public enum DWMWINDOWATTRIBUTE
|
||||
{
|
||||
DWMWA_WINDOW_CORNER_PREFERENCE = 33,
|
||||
}
|
||||
|
||||
public enum DWM_WINDOW_CORNER_PREFERENCE
|
||||
{
|
||||
DWMWCP_DEFAULT = 0,
|
||||
DWMWCP_DONOTROUND = 1,
|
||||
DWMWCP_ROUND = 2,
|
||||
DWMWCP_ROUNDSMALL = 3,
|
||||
}
|
||||
|
||||
// Import dwmapi.dll and define DwmSetWindowAttribute in C# corresponding to the native function.
|
||||
[DllImport("dwmapi.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
|
||||
internal static extern void DwmSetWindowAttribute(
|
||||
IntPtr hwnd,
|
||||
DWMWINDOWATTRIBUTE attribute,
|
||||
ref DWM_WINDOW_CORNER_PREFERENCE pvAttribute,
|
||||
uint cbAttribute);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int TrackPopupMenu(IntPtr hMenu, int uFlags, int x, int y, int nReserved, IntPtr hWnd, IntPtr prcRect);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
public const int GWLSTYLE = -16;
|
||||
|
||||
[Flags]
|
||||
public enum WindowStyles : uint
|
||||
{
|
||||
WS_SYSMENU = 0x00080000, // System menu (close/maximize/minimize button area)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +1,90 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="$(RepoRoot)src\Common.SelfContained.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyTitle>PowerToys.ColorPickerUI</AssemblyTitle>
|
||||
<AssemblyDescription>PowerToys ColorPicker</AssemblyDescription>
|
||||
<OutputPath>$(RepoRoot)$(Platform)\$(Configuration)</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
|
||||
<UseWPF>true</UseWPF>
|
||||
<StartupObject>ColorPicker.Program</StartupObject>
|
||||
<ProjectGuid>{BA58206B-1493-4C75-BFEA-A85768A1E156}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>ColorPicker</RootNamespace>
|
||||
<AssemblyName>PowerToys.ColorPickerUI</AssemblyName>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>Assets\ColorPicker\icon.ico</ApplicationIcon>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<ApplicationManifest>App.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\ColorPicker\icon.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Assets\ColorPicker\icon.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Shaders\GridShader.cso" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.ComponentModel.Composition" />
|
||||
<PackageReference Include="System.IO.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
<None Update="Assets\ColorPicker\colorPicker.cur">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\GPOWrapperProjection\GPOWrapperProjection.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
|
||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Shaders\GridShader.fx" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<SubType>Designer</SubType>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="$(RepoRoot)src\Common.SelfContained.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyTitle>PowerToys.ColorPickerUI</AssemblyTitle>
|
||||
<AssemblyDescription>PowerToys ColorPicker</AssemblyDescription>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<OutputPath>$(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps</OutputPath>
|
||||
<RootNamespace>ColorPicker</RootNamespace>
|
||||
<AssemblyName>PowerToys.ColorPickerUI</AssemblyName>
|
||||
<ApplicationManifest>App.manifest</ApplicationManifest>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<ApplicationIcon>Assets\ColorPicker\icon.ico</ApplicationIcon>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<!-- MRT searches for a pri file with the module name before defaulting to resources.pri -->
|
||||
<ProjectPriFileName>PowerToys.ColorPickerUI.pri</ProjectPriFileName>
|
||||
<!-- Custom Main entry point (append so inherited DEBUG/TRACE constants survive) -->
|
||||
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Remove="ColorPickerXAML\App.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="ColorPickerXAML\App.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- See https://learn.microsoft.com/windows/apps/develop/platform/csharp-winrt/net-projection-from-cppwinrt-component -->
|
||||
<PropertyGroup>
|
||||
<CsWinRTIncludes>PowerToys.GPOWrapper</CsWinRTIncludes>
|
||||
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
|
||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Allow the test project to access internal types (moved from Bootstrapper.cs) -->
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="ColorPickerUI.UnitTests" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Assets\ColorPicker\icon.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<None Update="Assets\ColorPicker\colorPicker.cur">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||
<!-- Required by the XAML compiler so it can resolve TransparentWindow's animation-set
|
||||
properties (ShowAnimations/HideAnimations) when ColorPickerOverlayWindow.xaml derives
|
||||
from the Common.UI.Controls TransparentWindow base. -->
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Animations" />
|
||||
<!-- Win2D for the zoom magnifier's pixel-grid CanvasControl. -->
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" />
|
||||
<PackageReference Include="System.IO.Abstractions" />
|
||||
<PackageReference Include="WinUIEx" />
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\Common.UI.Controls\Common.UI.Controls.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\GPOWrapper\GPOWrapper.vcxproj" />
|
||||
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
|
||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
<Application
|
||||
x:Class="ColorPickerUI.App"
|
||||
<Application
|
||||
x:Class="ColorPicker.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
StartupUri="MainWindow.xaml"
|
||||
ThemeMode="System">
|
||||
xmlns:local="using:ColorPicker">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Resources/Styles.xaml" />
|
||||
<ResourceDictionary Source="Resources/ViewModelViewMappings.xaml" />
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<ResourceDictionary Source="ms-appx:///Resources/Styles.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
@@ -0,0 +1,152 @@
|
||||
// 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.Globalization;
|
||||
using System.Threading;
|
||||
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace ColorPicker
|
||||
{
|
||||
public partial class App : Application, IDisposable
|
||||
{
|
||||
private readonly string[] _args;
|
||||
private Mutex _instanceMutex;
|
||||
private int _powerToysRunnerPid;
|
||||
private bool _disposed;
|
||||
|
||||
private static IServiceProvider _serviceProvider;
|
||||
|
||||
public static Window Window { get; private set; }
|
||||
|
||||
// ETW diagnostic trace session (matches the WPF original and the WinUI 3 sibling modules,
|
||||
// e.g. EnvironmentVariables/AdvancedPaste). Constructed once; the session is torn down on
|
||||
// process exit, so no explicit Dispose is required (siblings follow the same pattern).
|
||||
public ETWTrace EtwTrace { get; } = new ETWTrace();
|
||||
|
||||
public App(string[] args)
|
||||
{
|
||||
_args = args ?? Array.Empty<string>();
|
||||
|
||||
try
|
||||
{
|
||||
string appLanguage = LanguageHelper.LoadLanguage();
|
||||
if (!string.IsNullOrEmpty(appLanguage))
|
||||
{
|
||||
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
|
||||
Thread.CurrentThread.CurrentUICulture = new CultureInfo(appLanguage);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Language initialization error: " + ex.Message);
|
||||
}
|
||||
|
||||
_serviceProvider = ConfigureServices();
|
||||
|
||||
// Environment.Exit on the runner-exit / terminate / single-instance paths raises no
|
||||
// Window.Closed, so the cursor-restore in MouseInfoProvider.DisposeHook never runs.
|
||||
// Restore the system cursors on any process exit (idempotent) so an abnormal exit while
|
||||
// a pick is active with ChangeCursor enabled does not leave the crosshair as the
|
||||
// system-wide cursor — parity with the WPF App.OnExit.
|
||||
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
|
||||
|
||||
InitializeComponent();
|
||||
UnhandledException += App_UnhandledException;
|
||||
}
|
||||
|
||||
public bool IsRunningDetachedFromPowerToys() => _powerToysRunnerPid == -1;
|
||||
|
||||
public static T GetService<T>()
|
||||
where T : class
|
||||
=> _serviceProvider.GetService(typeof(T)) as T;
|
||||
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
// Allow only one instance of Color Picker.
|
||||
// Single-instance guard. The mutex is intentionally released by OS process
|
||||
// exit (this is an unpackaged single-process app); no explicit ReleaseMutex.
|
||||
_instanceMutex = new Mutex(true, @"Local\PowerToys_ColorPicker_InstanceMutex", out bool createdNew);
|
||||
if (!createdNew)
|
||||
{
|
||||
Logger.LogWarning("There is a ColorPicker instance running. Exiting Color Picker");
|
||||
_instanceMutex = null;
|
||||
Environment.Exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_args.Length > 0 && int.TryParse(_args[0], out _powerToysRunnerPid))
|
||||
{
|
||||
Logger.LogInfo($"Color Picker started from the PowerToys Runner. Runner pid={_powerToysRunnerPid}");
|
||||
RunnerHelper.WaitForPowerToysRunner(_powerToysRunnerPid, () =>
|
||||
{
|
||||
Logger.LogInfo("PowerToys Runner exited. Exiting ColorPicker");
|
||||
GetService<CancellationTokenSource>()?.Cancel();
|
||||
Environment.Exit(0);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_powerToysRunnerPid = -1;
|
||||
}
|
||||
|
||||
// Build the picking overlay first so the DI graph (AppStateHandler etc.) can read
|
||||
// App.Window, then resolve the main view model (which wires the named events + input)
|
||||
// and host it in the overlay's MainView. The overlay starts hidden (TransparentWindow)
|
||||
// and is shown on the hotkey / ShowColorPicker event.
|
||||
var overlay = new ColorPickerOverlayWindow();
|
||||
Window = overlay;
|
||||
|
||||
var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(overlay);
|
||||
var mainViewModel = GetService<ColorPicker.ViewModelContracts.IMainViewModel>();
|
||||
overlay.MainViewControl.DataContext = mainViewModel;
|
||||
mainViewModel.RegisterWindowHandle(hwnd);
|
||||
|
||||
// Make the overlay size to the tooltip and follow the cursor while shown (the
|
||||
// IMouseInfoProvider singleton is the same one the MainViewModel reads).
|
||||
overlay.InitializeCursorFollow(GetService<ColorPicker.Mouse.IMouseInfoProvider>());
|
||||
}
|
||||
|
||||
private static IServiceProvider ConfigureServices()
|
||||
=> ColorPicker.Foundation.AppServices.Configure();
|
||||
|
||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
Logger.LogError("Unhandled exception", e.Exception);
|
||||
}
|
||||
|
||||
private void OnProcessExit(object sender, EventArgs e)
|
||||
{
|
||||
// Environment.Exit and normal shutdown both raise ProcessExit; run the deterministic
|
||||
// cleanup here since the WinUI Application model never calls Dispose() on the App.
|
||||
Mouse.CursorManager.RestoreOriginalCursors();
|
||||
Dispose();
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_instanceMutex?.Dispose();
|
||||
_instanceMutex = null;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Window
|
||||
x:Class="ColorPicker.ColorEditorWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
<ContentControl
|
||||
x:Name="ContentPresenter"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
x:FieldModifier="public" />
|
||||
</Window>
|
||||
@@ -0,0 +1,73 @@
|
||||
// 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 ColorPicker.Helpers;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using WinUIEx;
|
||||
|
||||
namespace ColorPicker
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
{
|
||||
private readonly AppStateHandler _appStateHandler;
|
||||
|
||||
public ColorEditorWindow(AppStateHandler appStateHandler)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_appStateHandler = appStateHandler;
|
||||
|
||||
var title = ResourceLoaderInstance.GetString("CP_Title");
|
||||
Title = title;
|
||||
AppWindow.Title = title;
|
||||
|
||||
// The native title bar / taskbar otherwise shows the generic WinUI placeholder icon;
|
||||
// point it at the ColorPicker app icon (copied next to the exe by the csproj).
|
||||
AppWindow.SetIcon("Assets/ColorPicker/icon.ico");
|
||||
|
||||
// Without a backdrop the window's uncovered areas (the header above the content card)
|
||||
// render black, which reads as a bold black bar at the header/content divider. Mica
|
||||
// gives the whole window the standard WinUI material so the header is no longer black.
|
||||
SystemBackdrop = new MicaBackdrop();
|
||||
|
||||
this.SetWindowSize(440, 380);
|
||||
|
||||
// Port of the WPF WindowStartupLocation="CenterScreen": WinUI windows open at the OS
|
||||
// default (cascade near the top-left), so center the fixed-size editor on the display.
|
||||
this.CenterOnScreen();
|
||||
|
||||
if (AppWindow.Presenter is OverlappedPresenter presenter)
|
||||
{
|
||||
presenter.IsResizable = false;
|
||||
presenter.IsMaximizable = false;
|
||||
presenter.IsMinimizable = false;
|
||||
presenter.IsAlwaysOnTop = true;
|
||||
}
|
||||
|
||||
AppWindow.Closing += AppWindow_Closing;
|
||||
Activated += ColorEditorWindow_Activated;
|
||||
}
|
||||
|
||||
public bool IsActiveWindow { get; private set; }
|
||||
|
||||
private void ColorEditorWindow_Activated(object sender, WindowActivatedEventArgs args)
|
||||
{
|
||||
IsActiveWindow = args.WindowActivationState != WindowActivationState.Deactivated;
|
||||
}
|
||||
|
||||
// Match the WPF behavior: closing the editor ends the user session (which hides it) rather
|
||||
// than destroying the window, so it can be reopened on the next activation.
|
||||
private void AppWindow_Closing(AppWindow sender, AppWindowClosingEventArgs args)
|
||||
{
|
||||
args.Cancel = true;
|
||||
_appStateHandler.EndUserSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<common:TransparentWindow
|
||||
x:Class="ColorPicker.ColorPickerOverlayWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:common="using:Microsoft.PowerToys.Common.UI.Controls.Window"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:views="using:ColorPicker.Views"
|
||||
mc:Ignorable="d">
|
||||
<views:MainView x:Name="MainViewControl" x:FieldModifier="public" />
|
||||
</common:TransparentWindow>
|
||||
@@ -0,0 +1,284 @@
|
||||
// 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.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using ColorPicker.Helpers;
|
||||
using ColorPicker.Mouse;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Common.UI.Controls.Window;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace ColorPicker
|
||||
{
|
||||
/// <summary>
|
||||
/// The picking-overlay tooltip window: hosts <see cref="Views.MainView"/> inside the
|
||||
/// shared <see cref="TransparentWindow"/> (transparent <c>TransparentTintBackdrop</c>,
|
||||
/// frameless, tool-window so it stays out of the taskbar/Alt-Tab, and a no-activate
|
||||
/// <c>Show()</c> so it never steals foreground from the user's work).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This replaces the deleted WPF root MainWindow (AllowsTransparency + WindowStyle=None +
|
||||
/// SizeToContent + Topmost). The WPF window auto-sized to the small color tooltip, sat above
|
||||
/// the zoom magnifier (Topmost, re-asserted by the zoom helper), and was re-positioned next to
|
||||
/// the cursor by the <c>ChangeWindowPositionBehavior</c>. <see cref="InitializeCursorFollow"/>
|
||||
/// ports all three: WinUI has no <c>SizeToContent</c>, the behavior was WPF-only, and the
|
||||
/// window is kept top-most so the tooltip stays visible over the magnifier. Per-tick work is a
|
||||
/// single top-most <c>SetWindowPos</c> move plus a cheap per-monitor DPI lookup (so a tooltip
|
||||
/// dragged across mixed-DPI monitors re-sizes to the monitor under the cursor); the tooltip size
|
||||
/// and monitor list are cached and only recomputed on first show / content change.
|
||||
/// </remarks>
|
||||
public sealed partial class ColorPickerOverlayWindow : TransparentWindow
|
||||
{
|
||||
// Cursor-follow offsets + monitor-edge handling (ported from the WPF
|
||||
// ChangeWindowPositionBehavior; logical px, scaled to physical at the point of use).
|
||||
private const double XOffset = 5;
|
||||
private const double YOffset = 10;
|
||||
|
||||
private const int HwndTopmost = -1;
|
||||
private const uint SwpNoSize = 0x0001;
|
||||
private const uint SwpNoActivate = 0x0010;
|
||||
|
||||
private const uint MonitorDefaultToNearest = 0x00000002;
|
||||
private const int MdtEffectiveDpi = 0;
|
||||
|
||||
private const int SmCxScreen = 0;
|
||||
private const int SmCyScreen = 1;
|
||||
|
||||
private readonly nint _hwnd;
|
||||
|
||||
private IMouseInfoProvider _mouseInfoProvider;
|
||||
private Storyboard _appearStoryboard;
|
||||
private Point _lastCursor;
|
||||
private double _scale = 1.0;
|
||||
private Rect[] _monitors = Array.Empty<Rect>();
|
||||
private int _width = -1;
|
||||
private int _height = -1;
|
||||
|
||||
public ColorPickerOverlayWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
_hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes the overlay size itself to the <see cref="Views.MainView"/> tooltip and follow
|
||||
/// the cursor (kept above the zoom magnifier) while it is shown — the WinUI replacement for
|
||||
/// the WPF <c>SizeToContent="WidthAndHeight"</c> + <c>ChangeWindowPositionBehavior</c> +
|
||||
/// re-asserted <c>Topmost</c> that the migration had not yet ported.
|
||||
/// </summary>
|
||||
public void InitializeCursorFollow(IMouseInfoProvider mouseInfoProvider)
|
||||
{
|
||||
_mouseInfoProvider = mouseInfoProvider;
|
||||
|
||||
// The provider polls on the UI thread (its DispatcherQueueTimer), so this fires on the
|
||||
// UI thread and can touch XAML/the window directly. It only fires when the cursor moves.
|
||||
_mouseInfoProvider.MousePositionChanged += (s, position) => MoveToCursor(position, resize: false);
|
||||
|
||||
// The tooltip resizes only when its content changes (e.g. a longer color string or the
|
||||
// color-name line). Re-cache the size then and re-apply it; the per-tick path stays a
|
||||
// pure move so following is not throttled by a layout pass every cursor tick.
|
||||
if (Content is FrameworkElement card)
|
||||
{
|
||||
card.SizeChanged += (s, e) => MoveToCursor(_lastCursor, resize: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides <see cref="TransparentWindow.Show"/> so the overlay snaps to the cursor as it
|
||||
/// appears. <see cref="IMouseInfoProvider.MousePositionChanged"/> only fires when the
|
||||
/// cursor moves, so a stationary-cursor show would otherwise leave the tooltip parked at
|
||||
/// the default window position. Positioning is queued after <c>base.Show()</c> so the card
|
||||
/// is laid out (a collapsed element measures to 0) before sizing.
|
||||
/// </summary>
|
||||
public new void Show()
|
||||
{
|
||||
// Present the card transparent first (so there is no flash), then start the 250ms fade
|
||||
// and the initial positioning together at Low priority — after base TransparentWindow.Show()
|
||||
// has pumped its deferred SW_SHOWNA, so the fade clock begins when the window is actually
|
||||
// on screen and the card is laid out before it is sized to the cursor.
|
||||
MainViewControl.Opacity = 0;
|
||||
base.Show();
|
||||
|
||||
DispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
PlayAppearAnimation();
|
||||
|
||||
if (_mouseInfoProvider != null)
|
||||
{
|
||||
// Cache the monitor bounds for this pick session (the DPI scale is re-queried per
|
||||
// move so a tooltip crossing into a different-DPI monitor re-sizes correctly).
|
||||
RefreshEnvironment();
|
||||
MoveToCursor(_mouseInfoProvider.CurrentPosition, resize: true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Port of the WPF <c>AppearAnimationBehavior</c>: fade the tooltip in over 250ms
|
||||
/// (CubicEase) every time the overlay is shown. WPF animated <c>Window.Opacity</c>; a WinUI
|
||||
/// <see cref="Window"/> has no Opacity, so the hosted content element is animated instead —
|
||||
/// visually identical since it fills the whole tooltip. (WPF's 1ms hide fade was effectively
|
||||
/// instant, so no hide animation is needed.)
|
||||
/// </summary>
|
||||
private void PlayAppearAnimation()
|
||||
{
|
||||
if (_appearStoryboard == null)
|
||||
{
|
||||
var fadeIn = new DoubleAnimation
|
||||
{
|
||||
From = 0,
|
||||
To = 1,
|
||||
Duration = new Duration(TimeSpan.FromMilliseconds(250)),
|
||||
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut },
|
||||
};
|
||||
Storyboard.SetTarget(fadeIn, MainViewControl);
|
||||
Storyboard.SetTargetProperty(fadeIn, "Opacity");
|
||||
_appearStoryboard = new Storyboard();
|
||||
_appearStoryboard.Children.Add(fadeIn);
|
||||
}
|
||||
|
||||
_appearStoryboard.Stop();
|
||||
MainViewControl.Opacity = 0;
|
||||
_appearStoryboard.Begin();
|
||||
}
|
||||
|
||||
private void RefreshEnvironment()
|
||||
{
|
||||
_scale = GetDpiForWindow(_hwnd) / 96.0;
|
||||
_monitors = MonitorResolutionHelper.AllMonitors.Select(m => m.Bounds).ToArray();
|
||||
}
|
||||
|
||||
private void MoveToCursor(Point cursorPhysical, bool resize)
|
||||
{
|
||||
_lastCursor = cursorPhysical;
|
||||
|
||||
if (_monitors.Length == 0)
|
||||
{
|
||||
RefreshEnvironment();
|
||||
}
|
||||
|
||||
// Re-query the DPI of the monitor under the cursor. On mixed-DPI multi-monitor setups
|
||||
// the scale changes as the cursor crosses monitors; a value frozen at Show() would
|
||||
// mis-size and mis-offset the tooltip (the WPF behavior re-queried DPI per move).
|
||||
double scale = GetScaleForCursor(cursorPhysical);
|
||||
if (scale > 0 && scale != _scale)
|
||||
{
|
||||
_scale = scale;
|
||||
resize = true; // re-measure the tooltip card at the new monitor's scale
|
||||
}
|
||||
|
||||
// WinUI has no SizeToContent: size the window to the tooltip card. Only measured when a
|
||||
// resize is requested (first show / content change), so the cursor tick stays a move.
|
||||
if (resize || _width <= 0 || _height <= 0)
|
||||
{
|
||||
if (Content is not FrameworkElement card)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
card.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
int width = (int)Math.Ceiling(card.DesiredSize.Width * _scale);
|
||||
int height = (int)Math.Ceiling(card.DesiredSize.Height * _scale);
|
||||
if (width <= 0 || height <= 0)
|
||||
{
|
||||
// Card still collapsed/unmeasured (fired before the show completes); a later
|
||||
// tick or the post-Show() queue will size and position it.
|
||||
return;
|
||||
}
|
||||
|
||||
_width = width;
|
||||
_height = height;
|
||||
}
|
||||
|
||||
// Place next to the cursor, flipping away from the monitor's right/bottom edge so the
|
||||
// tooltip never spills off-screen. Everything here is in physical pixels.
|
||||
var bounds = GetMonitorBounds(cursorPhysical);
|
||||
int xOffset = (int)(XOffset * _scale);
|
||||
int yOffset = (int)(YOffset * _scale);
|
||||
int left = (int)cursorPhysical.X + xOffset;
|
||||
int top = (int)cursorPhysical.Y + yOffset;
|
||||
|
||||
if (left + _width > bounds.X + bounds.Width)
|
||||
{
|
||||
left = (int)cursorPhysical.X - _width - xOffset;
|
||||
}
|
||||
|
||||
if (top + _height > bounds.Y + bounds.Height)
|
||||
{
|
||||
top = (int)cursorPhysical.Y - _height - yOffset;
|
||||
}
|
||||
|
||||
// Insert at HWND_TOPMOST so the tooltip stays above the (non-top-most) zoom magnifier,
|
||||
// matching the WPF helper that re-asserted MainWindow.Topmost after showing the zoom.
|
||||
uint flags = SwpNoActivate | (resize ? 0u : SwpNoSize);
|
||||
_ = SetWindowPos(_hwnd, HwndTopmost, left, top, _width, _height, flags);
|
||||
}
|
||||
|
||||
private Rect GetMonitorBounds(Point cursorPhysical)
|
||||
{
|
||||
foreach (var bounds in _monitors)
|
||||
{
|
||||
if (bounds.Contains(cursorPhysical))
|
||||
{
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
|
||||
if (_monitors.Length > 0)
|
||||
{
|
||||
// Cursor not inside any enumerated monitor (rare; e.g. just outside the work area):
|
||||
// fall back to the first monitor so the edge-flip math still applies.
|
||||
return _monitors[0];
|
||||
}
|
||||
|
||||
// No monitors enumerated at all (degenerate, e.g. a transient no-display state). Log it
|
||||
// and fall back to the primary screen bounds so the tooltip still edge-flips instead of
|
||||
// being placed against an unbounded rect.
|
||||
Logger.LogWarning("ColorPicker overlay: no monitors enumerated; falling back to primary screen bounds.");
|
||||
return new Rect(0, 0, GetSystemMetrics(SmCxScreen), GetSystemMetrics(SmCyScreen));
|
||||
}
|
||||
|
||||
// Effective DPI scale of the monitor the cursor is on (1.0 == 96 DPI). Falls back to the
|
||||
// last known scale if the lookup fails.
|
||||
private double GetScaleForCursor(Point cursorPhysical)
|
||||
{
|
||||
var monitor = MonitorFromPoint(new POINT { X = (int)cursorPhysical.X, Y = (int)cursorPhysical.Y }, MonitorDefaultToNearest);
|
||||
if (monitor != IntPtr.Zero && GetDpiForMonitor(monitor, MdtEffectiveDpi, out uint dpiX, out _) == 0)
|
||||
{
|
||||
return dpiX / 96.0;
|
||||
}
|
||||
|
||||
return _scale;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct POINT
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr MonitorFromPoint(POINT pt, uint dwFlags);
|
||||
|
||||
[DllImport("shcore.dll")]
|
||||
private static extern int GetDpiForMonitor(IntPtr hmonitor, int dpiType, out uint dpiX, out uint dpiY);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetSystemMetrics(int nIndex);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern uint GetDpiForWindow(nint hwnd);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool SetWindowPos(nint hWnd, nint hWndInsertAfter, int x, int y, int cx, int cy, uint flags);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<common:TransparentWindow
|
||||
x:Class="ColorPicker.ZoomWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:common="using:Microsoft.PowerToys.Common.UI.Controls.Window"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:views="using:ColorPicker.Views"
|
||||
mc:Ignorable="d">
|
||||
<views:ZoomView x:Name="ZoomViewControl" x:FieldModifier="public" />
|
||||
</common:TransparentWindow>
|
||||
@@ -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.
|
||||
|
||||
using Microsoft.PowerToys.Common.UI.Controls.Window;
|
||||
|
||||
namespace ColorPicker
|
||||
{
|
||||
/// <summary>
|
||||
/// The zoom magnifier window: a transparent, frameless, no-activate
|
||||
/// <see cref="TransparentWindow"/> hosting the Win2D-backed <see cref="Views.ZoomView"/>.
|
||||
/// </summary>
|
||||
public sealed partial class ZoomWindow : TransparentWindow
|
||||
{
|
||||
public ZoomWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace ColorPicker.Common
|
||||
{
|
||||
public class RelayCommand : ICommand
|
||||
{
|
||||
private Predicate<object> _canExecute;
|
||||
private Action<object> _execute;
|
||||
|
||||
public RelayCommand(Action execute)
|
||||
{
|
||||
_canExecute = x => true;
|
||||
_execute = x => { execute.Invoke(); };
|
||||
}
|
||||
|
||||
public RelayCommand(Action<object> execute)
|
||||
: this(x => true, execute)
|
||||
{
|
||||
}
|
||||
|
||||
public RelayCommand(Predicate<object> canExecute, Action<object> execute)
|
||||
{
|
||||
_canExecute = canExecute;
|
||||
_execute = execute;
|
||||
}
|
||||
|
||||
public event EventHandler CanExecuteChanged
|
||||
{
|
||||
add { CommandManager.RequerySuggested += value; }
|
||||
remove { CommandManager.RequerySuggested -= value; }
|
||||
}
|
||||
|
||||
public bool CanExecute(object parameter) => _canExecute(parameter);
|
||||
|
||||
public void Execute(object parameter) => _execute(parameter);
|
||||
}
|
||||
}
|
||||
@@ -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.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace ColorPicker.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Base for view models to provide property changed notifications
|
||||
/// </summary>
|
||||
public abstract class ViewModelBase : INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,134 +3,29 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:ColorPicker"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:p="clr-namespace:ColorPicker.Properties"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<Style x:Key="ReadonlyTextBoxStyle" TargetType="{x:Type TextBox}">
|
||||
<!-- Universal WPF UI focus -->
|
||||
<Setter Property="FocusVisualStyle" Value="{DynamicResource DefaultControlFocusVisualStyle}" />
|
||||
<!-- Universal WPF UI focus -->
|
||||
<!-- Universal WPF UI ContextMenu -->
|
||||
<Setter Property="ContextMenu" Value="{DynamicResource DefaultControlContextMenu}" />
|
||||
<!-- Universal WPF UI ContextMenu -->
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="CaretBrush" Value="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="Transparent" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="{StaticResource TextBoxBorderThemeThickness}" />
|
||||
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="ScrollViewer.CanContentScroll" Value="False" />
|
||||
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden" />
|
||||
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden" />
|
||||
<Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="False" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Top" />
|
||||
<Setter Property="MinHeight" Value="{DynamicResource TextControlThemeMinHeight}" />
|
||||
<Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}" />
|
||||
<Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" />
|
||||
<Setter Property="Border.CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
|
||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type TextBox}">
|
||||
<Grid HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}">
|
||||
<Border
|
||||
x:Name="ContentBorder"
|
||||
MinWidth="{TemplateBinding MinWidth}"
|
||||
MinHeight="{TemplateBinding MinHeight}"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding Border.CornerRadius}">
|
||||
<Grid
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
|
||||
<ScrollViewer
|
||||
x:Name="PART_ContentHost"
|
||||
VerticalAlignment="Center"
|
||||
CanContentScroll="{TemplateBinding ScrollViewer.CanContentScroll}"
|
||||
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
|
||||
IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
|
||||
IsTabStop="{TemplateBinding ScrollViewer.IsTabStop}"
|
||||
TextElement.Foreground="{TemplateBinding Foreground}"
|
||||
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<!-- The Accent Border is a separate element so that changes to the border thickness do not affect the position of the element -->
|
||||
<Border
|
||||
x:Name="AccentBorder"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderThickness="{StaticResource TextBoxAccentBorderThemeThickness}"
|
||||
CornerRadius="{TemplateBinding Border.CornerRadius}">
|
||||
<Border.BorderBrush>
|
||||
<SolidColorBrush Color="Transparent" />
|
||||
</Border.BorderBrush>
|
||||
</Border>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsFocused" Value="True">
|
||||
<Setter TargetName="AccentBorder" Property="BorderThickness" Value="0,0,0,0" />
|
||||
<Setter TargetName="AccentBorder" Property="BorderBrush">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="Transparent" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter TargetName="ContentBorder" Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="Transparent" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsEnabled" Value="True" />
|
||||
<Condition Property="IsMouseOver" Value="True" />
|
||||
<Condition Property="IsFocused" Value="False" />
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter TargetName="ContentBorder" Property="Background">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="Transparent" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</MultiTrigger>
|
||||
<Trigger Property="IsEnabled" Value="True">
|
||||
<Setter Property="Cursor" Value="IBeam" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="ContentBorder" Property="Background" Value="Transparent" />
|
||||
<Setter TargetName="ContentBorder" Property="BorderBrush" Value="Transparent" />
|
||||
<Setter TargetName="AccentBorder" Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextFillColorDisabledBrush}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<!-- A label-like read-only text box: lean on the native template, just drop the chrome. -->
|
||||
<Style
|
||||
x:Key="ReadonlyTextBoxStyle"
|
||||
BasedOn="{StaticResource DefaultTextBoxStyle}"
|
||||
TargetType="TextBox">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="IsReadOnly" Value="True" />
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
<Border
|
||||
x:Name="MainBorder"
|
||||
Height="48"
|
||||
Margin="0,0,0,8"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{DynamicResource ControlCornerRadius}">
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<Grid Margin="12,0,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="48" />
|
||||
@@ -140,7 +35,7 @@
|
||||
<TextBlock
|
||||
x:Name="FormatNameTextBlock"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
|
||||
@@ -149,10 +44,6 @@
|
||||
Grid.Column="1"
|
||||
Margin="0,-2,0,0"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="{x:Static p:Resources.Color_Code}"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
IsTabStop="False"
|
||||
Style="{StaticResource ReadonlyTextBoxStyle}" />
|
||||
@@ -164,12 +55,9 @@
|
||||
Height="36"
|
||||
Margin="0,0,4,0"
|
||||
Padding="0"
|
||||
AutomationProperties.HelpText="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:Controls.ColorFormatControl}}, Path=SelectedColorCopyHelperText}"
|
||||
AutomationProperties.Name="{x:Static p:Resources.Copy_to_clipboard}"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
Style="{StaticResource SubtleButtonStyle}"
|
||||
ToolTipService.ToolTip="{x:Static p:Resources.Copy_to_clipboard}">
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<Button.Content>
|
||||
<TextBlock FontFamily="{StaticResource IconFontFamily}" Text="" />
|
||||
</Button.Content>
|
||||
|
||||
@@ -1,67 +1,74 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Automation.Peers;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
using ColorPicker.Helpers;
|
||||
using ColorPicker.Models;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Automation;
|
||||
using Microsoft.UI.Xaml.Automation.Peers;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Windows.UI;
|
||||
|
||||
namespace ColorPicker.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ColorFormatControl.xaml
|
||||
/// </summary>
|
||||
public partial class ColorFormatControl : UserControl
|
||||
public sealed partial class ColorFormatControl : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty ColorFormatModelProperty = DependencyProperty.Register("ColorFormatModel", typeof(ColorFormatModel), typeof(ColorFormatControl), new PropertyMetadata(ColorFormatModelPropertyChanged));
|
||||
public static readonly DependencyProperty ColorFormatModelProperty = DependencyProperty.Register(nameof(ColorFormatModel), typeof(ColorFormatModel), typeof(ColorFormatControl), new PropertyMetadata(null, ColorFormatModelPropertyChanged));
|
||||
|
||||
public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register("SelectedColor", typeof(Color), typeof(ColorFormatControl), new PropertyMetadata(SelectedColorPropertyChanged));
|
||||
public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register(nameof(SelectedColor), typeof(Color), typeof(ColorFormatControl), new PropertyMetadata(default(Color), SelectedColorPropertyChanged));
|
||||
|
||||
public static readonly DependencyProperty SelectedColorCopyHelperTextProperty = DependencyProperty.Register("SelectedColorCopyHelperText", typeof(string), typeof(ColorFormatControl));
|
||||
public static readonly DependencyProperty SelectedColorCopyHelperTextProperty = DependencyProperty.Register(nameof(SelectedColorCopyHelperText), typeof(string), typeof(ColorFormatControl), new PropertyMetadata(null));
|
||||
|
||||
public static readonly DependencyProperty ColorCopiedNotificationBorderProperty = DependencyProperty.Register("ColorCopiedNotificationBorder", typeof(FrameworkElement), typeof(ColorFormatControl), new PropertyMetadata(ColorCopiedBorderPropertyChanged));
|
||||
public static readonly DependencyProperty ColorCopiedNotificationBorderProperty = DependencyProperty.Register(nameof(ColorCopiedNotificationBorder), typeof(FrameworkElement), typeof(ColorFormatControl), new PropertyMetadata(null));
|
||||
|
||||
private const int CopyIndicatorStayTimeInMs = 3000;
|
||||
private IThrottledActionInvoker _actionInvoker;
|
||||
private readonly IThrottledActionInvoker _actionInvoker;
|
||||
private bool _copyIndicatorVisible;
|
||||
|
||||
public Color SelectedColor
|
||||
{
|
||||
get { return (Color)GetValue(SelectedColorProperty); }
|
||||
set { SetValue(SelectedColorProperty, value); }
|
||||
}
|
||||
|
||||
public ColorFormatModel ColorFormatModel
|
||||
{
|
||||
get { return (ColorFormatModel)GetValue(ColorFormatModelProperty); }
|
||||
set { SetValue(ColorFormatModelProperty, value); }
|
||||
}
|
||||
|
||||
public FrameworkElement ColorCopiedNotificationBorder
|
||||
{
|
||||
get { return (FrameworkElement)GetValue(ColorCopiedNotificationBorderProperty); }
|
||||
set { SetValue(ColorCopiedNotificationBorderProperty, value); }
|
||||
}
|
||||
|
||||
public string SelectedColorCopyHelperText
|
||||
{
|
||||
get { return (string)GetValue(SelectedColorCopyHelperTextProperty); }
|
||||
set { SetValue(SelectedColorCopyHelperTextProperty, value); }
|
||||
}
|
||||
|
||||
public ColorFormatControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
_actionInvoker = Bootstrapper.Container.GetExportedValue<IThrottledActionInvoker>();
|
||||
_actionInvoker = App.GetService<IThrottledActionInvoker>();
|
||||
CopyToClipboardButton.Click += CopyToClipboardButton_Click;
|
||||
|
||||
// AutomationProperties.Name/ToolTip used WPF's {x:Static p:Resources.*}; resolve the
|
||||
// localized strings through the resource loader instead.
|
||||
AutomationProperties.SetName(ColorTextRepresentationTextBlock, ResourceLoaderInstance.GetString("Color_Code"));
|
||||
AutomationProperties.SetName(CopyToClipboardButton, ResourceLoaderInstance.GetString("Copy_to_clipboard"));
|
||||
ToolTipService.SetToolTip(CopyToClipboardButton, ResourceLoaderInstance.GetString("Copy_to_clipboard"));
|
||||
}
|
||||
|
||||
public Color SelectedColor
|
||||
{
|
||||
get => (Color)GetValue(SelectedColorProperty);
|
||||
set => SetValue(SelectedColorProperty, value);
|
||||
}
|
||||
|
||||
public ColorFormatModel ColorFormatModel
|
||||
{
|
||||
get => (ColorFormatModel)GetValue(ColorFormatModelProperty);
|
||||
set => SetValue(ColorFormatModelProperty, value);
|
||||
}
|
||||
|
||||
public FrameworkElement ColorCopiedNotificationBorder
|
||||
{
|
||||
get => (FrameworkElement)GetValue(ColorCopiedNotificationBorderProperty);
|
||||
set => SetValue(ColorCopiedNotificationBorderProperty, value);
|
||||
}
|
||||
|
||||
public string SelectedColorCopyHelperText
|
||||
{
|
||||
get => (string)GetValue(SelectedColorCopyHelperTextProperty);
|
||||
set => SetValue(SelectedColorCopyHelperTextProperty, value);
|
||||
}
|
||||
|
||||
private void CopyToClipboardButton_Click(object sender, RoutedEventArgs e)
|
||||
@@ -79,60 +86,89 @@ namespace ColorPicker.Controls
|
||||
private static void SelectedColorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var self = (ColorFormatControl)d;
|
||||
if (self.ColorFormatModel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var colorText = self.ColorFormatModel.GetColorText((Color)e.NewValue);
|
||||
self.ColorTextRepresentationTextBlock.Text = colorText;
|
||||
self.ColorTextRepresentationTextBlock.ToolTip = colorText;
|
||||
ToolTipService.SetToolTip(self.ColorTextRepresentationTextBlock, colorText);
|
||||
self.SelectedColorCopyHelperText = string.Format(CultureInfo.InvariantCulture, "{0} {1}", self.ColorFormatModel.FormatName, colorText);
|
||||
|
||||
// WPF used a RelativeSource FindAncestor binding for this; WinUI {Binding} has no
|
||||
// FindAncestor, so push the help text onto the button directly.
|
||||
AutomationProperties.SetHelpText(self.CopyToClipboardButton, self.SelectedColorCopyHelperText);
|
||||
}
|
||||
|
||||
private static void ColorFormatModelPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((ColorFormatControl)d).FormatNameTextBlock.Text = ((ColorFormatModel)e.NewValue).FormatName;
|
||||
((ColorFormatControl)d).FormatNameTextBlock.ToolTip = ((ColorFormatModel)e.NewValue).FormatName;
|
||||
}
|
||||
|
||||
private static void ColorCopiedBorderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((ColorFormatControl)d).ColorCopiedNotificationBorder = (FrameworkElement)e.NewValue;
|
||||
var self = (ColorFormatControl)d;
|
||||
var model = (ColorFormatModel)e.NewValue;
|
||||
self.FormatNameTextBlock.Text = model.FormatName;
|
||||
ToolTipService.SetToolTip(self.FormatNameTextBlock, model.FormatName);
|
||||
}
|
||||
|
||||
private void AppearCopiedIndicator()
|
||||
{
|
||||
_copyIndicatorVisible = true;
|
||||
var opacityAppear = new DoubleAnimation(1.0, new Duration(TimeSpan.FromMilliseconds(300)));
|
||||
opacityAppear.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseInOut };
|
||||
var resize = new DoubleAnimation(56, new Duration(TimeSpan.FromMilliseconds(300)));
|
||||
AnimateCopiedIndicator(1.0, 56);
|
||||
|
||||
resize.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseInOut };
|
||||
ColorCopiedNotificationBorder.BeginAnimation(Border.OpacityProperty, opacityAppear);
|
||||
ColorCopiedNotificationBorder.BeginAnimation(Border.HeightProperty, resize);
|
||||
|
||||
var clipboardNotification = ((Decorator)ColorCopiedNotificationBorder).Child;
|
||||
if (clipboardNotification == null)
|
||||
if (ColorCopiedNotificationBorder is not Border border || border.Child is not StackPanel panel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var innerTextBlock = ((StackPanel)clipboardNotification).Children.OfType<TextBlock>().FirstOrDefault();
|
||||
var peer = UIElementAutomationPeer.FromElement(innerTextBlock);
|
||||
if (peer == null)
|
||||
var innerTextBlock = panel.Children.OfType<TextBlock>().FirstOrDefault();
|
||||
if (innerTextBlock == null)
|
||||
{
|
||||
peer = UIElementAutomationPeer.CreatePeerForElement(innerTextBlock);
|
||||
return;
|
||||
}
|
||||
|
||||
peer.RaiseAutomationEvent(AutomationEvents.MenuOpened);
|
||||
var peer = FrameworkElementAutomationPeer.FromElement(innerTextBlock)
|
||||
?? FrameworkElementAutomationPeer.CreatePeerForElement(innerTextBlock);
|
||||
peer?.RaiseAutomationEvent(AutomationEvents.LiveRegionChanged);
|
||||
}
|
||||
|
||||
private void HideCopiedIndicator()
|
||||
{
|
||||
_copyIndicatorVisible = false;
|
||||
var opacityDisappear = new DoubleAnimation(0, new Duration(TimeSpan.FromMilliseconds(300)));
|
||||
opacityDisappear.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseInOut };
|
||||
var resize = new DoubleAnimation(0, new Duration(TimeSpan.FromMilliseconds(300)));
|
||||
AnimateCopiedIndicator(0, 0);
|
||||
}
|
||||
|
||||
resize.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseInOut };
|
||||
ColorCopiedNotificationBorder.BeginAnimation(Border.OpacityProperty, opacityDisappear);
|
||||
ColorCopiedNotificationBorder.BeginAnimation(Border.HeightProperty, resize);
|
||||
private void AnimateCopiedIndicator(double opacity, double height)
|
||||
{
|
||||
if (ColorCopiedNotificationBorder == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var storyboard = new Storyboard();
|
||||
|
||||
var opacityAnimation = new DoubleAnimation
|
||||
{
|
||||
To = opacity,
|
||||
Duration = new Duration(TimeSpan.FromMilliseconds(300)),
|
||||
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut },
|
||||
};
|
||||
Storyboard.SetTarget(opacityAnimation, ColorCopiedNotificationBorder);
|
||||
Storyboard.SetTargetProperty(opacityAnimation, "Opacity");
|
||||
storyboard.Children.Add(opacityAnimation);
|
||||
|
||||
var heightAnimation = new DoubleAnimation
|
||||
{
|
||||
To = height,
|
||||
Duration = new Duration(TimeSpan.FromMilliseconds(300)),
|
||||
|
||||
// Height is a layout property, so the animation must opt in to dependent animation.
|
||||
EnableDependentAnimation = true,
|
||||
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut },
|
||||
};
|
||||
Storyboard.SetTarget(heightAnimation, ColorCopiedNotificationBorder);
|
||||
Storyboard.SetTargetProperty(heightAnimation, "Height");
|
||||
storyboard.Children.Add(heightAnimation);
|
||||
|
||||
storyboard.Begin();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,159 +3,31 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:helpers="clr-namespace:ColorPicker.Helpers"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:p="clr-namespace:ColorPicker.Properties"
|
||||
AutomationProperties.Name="{x:Static p:Resources.Color_Palette}"
|
||||
FocusManager.IsFocusScope="True"
|
||||
KeyboardNavigation.TabNavigation="Once"
|
||||
TabIndex="3"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<Style TargetType="{x:Type Slider}">
|
||||
<Setter Property="MinWidth" Value="104" />
|
||||
<Setter Property="MinHeight" Value="21" />
|
||||
<Setter Property="FocusVisualStyle" Value="{DynamicResource DefaultControlFocusVisualStyle}" />
|
||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Slider}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" MinHeight="{TemplateBinding MinHeight}" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TickBar
|
||||
x:Name="TopTick"
|
||||
Grid.Row="0"
|
||||
Height="6"
|
||||
Fill="Transparent"
|
||||
Placement="Top"
|
||||
SnapsToDevicePixels="True"
|
||||
Visibility="Collapsed" />
|
||||
<Border
|
||||
x:Name="TrackBackground"
|
||||
Grid.Row="1"
|
||||
Height="8"
|
||||
Margin="0"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="4" />
|
||||
<Track x:Name="PART_Track" Grid.Row="1">
|
||||
<Track.DecreaseRepeatButton>
|
||||
<RepeatButton Command="Slider.DecreaseLarge">
|
||||
<RepeatButton.Style>
|
||||
<Style TargetType="{x:Type RepeatButton}">
|
||||
<Setter Property="IsTabStop" Value="False" />
|
||||
<Setter Property="Focusable" Value="False" />
|
||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type RepeatButton}">
|
||||
<Border Background="Transparent" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</RepeatButton.Style>
|
||||
</RepeatButton>
|
||||
</Track.DecreaseRepeatButton>
|
||||
<Track.Thumb>
|
||||
<Thumb x:Name="Thumb">
|
||||
<Thumb.Style>
|
||||
<Style TargetType="{x:Type Thumb}">
|
||||
<Setter Property="Height" Value="20" />
|
||||
<Setter Property="Width" Value="20" />
|
||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource ControlElevationBorderBrush}" />
|
||||
<Setter Property="Foreground" Value="Black" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Background" Value="{DynamicResource SliderOuterThumbBackground}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Thumb}">
|
||||
<Border
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="16">
|
||||
<Ellipse
|
||||
x:Name="Ellipse"
|
||||
Width="12"
|
||||
Height="12"
|
||||
Fill="{TemplateBinding Foreground}"
|
||||
Stroke="Transparent"
|
||||
StrokeThickness="0" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Thumb.Style>
|
||||
</Thumb>
|
||||
</Track.Thumb>
|
||||
<Track.IncreaseRepeatButton>
|
||||
<RepeatButton Command="Slider.IncreaseLarge">
|
||||
<RepeatButton.Style>
|
||||
<Style TargetType="{x:Type RepeatButton}">
|
||||
<Setter Property="IsTabStop" Value="False" />
|
||||
<Setter Property="Focusable" Value="False" />
|
||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type RepeatButton}">
|
||||
<Border Background="Transparent" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</RepeatButton.Style>
|
||||
</RepeatButton>
|
||||
|
||||
</Track.IncreaseRepeatButton>
|
||||
</Track>
|
||||
<TickBar
|
||||
x:Name="BottomTick"
|
||||
Grid.Row="2"
|
||||
Height="6"
|
||||
Fill="Transparent"
|
||||
Placement="Bottom"
|
||||
SnapsToDevicePixels="True"
|
||||
Visibility="Collapsed" />
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="TickPlacement" Value="TopLeft">
|
||||
<Setter TargetName="TopTick" Property="Visibility" Value="Visible" />
|
||||
</Trigger>
|
||||
<Trigger Property="TickPlacement" Value="BottomRight">
|
||||
<Setter TargetName="BottomTick" Property="Visibility" Value="Visible" />
|
||||
</Trigger>
|
||||
<Trigger Property="TickPlacement" Value="Both">
|
||||
<Setter TargetName="TopTick" Property="Visibility" Value="Visible" />
|
||||
<Setter TargetName="BottomTick" Property="Visibility" Value="Visible" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True" />
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<!--
|
||||
Approach A: keep the native WinUI Slider, but make its track transparent so the
|
||||
gradient Border behind each slider shows through. These keyed brushes override the
|
||||
default slider track theme brushes for every Slider inside this control.
|
||||
-->
|
||||
<SolidColorBrush x:Key="SliderTrackFill" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="SliderTrackFillPointerOver" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="SliderTrackFillPressed" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="SliderTrackFillDisabled" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="SliderTrackValueFill" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="SliderTrackValueFillPointerOver" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="SliderTrackValueFillPressed" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="SliderTrackValueFillDisabled" Color="Transparent" />
|
||||
</UserControl.Resources>
|
||||
<Border
|
||||
BorderBrush="{DynamicResource SurfaceStrokeColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource SurfaceStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{DynamicResource ControlCornerRadius}">
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<Grid
|
||||
x:Name="PickerPanel"
|
||||
Height="248"
|
||||
HorizontalAlignment="Stretch"
|
||||
ClipToBounds="True"
|
||||
Opacity="1">
|
||||
HorizontalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="36" />
|
||||
<RowDefinition Height="36" />
|
||||
@@ -166,39 +38,30 @@
|
||||
|
||||
<Button
|
||||
x:Name="colorVariation1Button"
|
||||
Grid.Column="0"
|
||||
helpers:ControlHelper.CornerRadius="4,4,0,0"
|
||||
AutomationProperties.Name="{x:Static p:Resources.Lightest_color}"
|
||||
Grid.Row="0"
|
||||
Background="LightPink"
|
||||
Click="ColorVariationButton_Click"
|
||||
Style="{DynamicResource ColorShadeButtonStyle}"
|
||||
TabIndex="3"
|
||||
ToolTipService.ToolTip="{x:Static p:Resources.Select_color}" />
|
||||
CornerRadius="4,4,0,0"
|
||||
Style="{StaticResource ColorShadeButtonStyle}" />
|
||||
<Button
|
||||
x:Name="colorVariation2Button"
|
||||
Grid.Row="1"
|
||||
AutomationProperties.Name="{x:Static p:Resources.Lighter_color}"
|
||||
Background="LightPink"
|
||||
Click="ColorVariationButton_Click"
|
||||
Style="{DynamicResource ColorShadeButtonStyle}"
|
||||
ToolTipService.ToolTip="{x:Static p:Resources.Select_color}" />
|
||||
Style="{StaticResource ColorShadeButtonStyle}" />
|
||||
<Button
|
||||
x:Name="colorVariation3Button"
|
||||
Grid.Row="3"
|
||||
AutomationProperties.Name="{x:Static p:Resources.Darker_color}"
|
||||
Background="LightPink"
|
||||
Click="ColorVariationButton_Click"
|
||||
Style="{DynamicResource ColorShadeButtonStyle}"
|
||||
ToolTipService.ToolTip="{x:Static p:Resources.Select_color}" />
|
||||
Style="{StaticResource ColorShadeButtonStyle}" />
|
||||
<Button
|
||||
x:Name="colorVariation4Button"
|
||||
Grid.Row="4"
|
||||
helpers:ControlHelper.CornerRadius="0,0,4,4"
|
||||
AutomationProperties.Name="{x:Static p:Resources.Darkest_color}"
|
||||
Background="LightPink"
|
||||
Click="ColorVariationButton_Click"
|
||||
Style="{DynamicResource ColorShadeButtonStyle}"
|
||||
ToolTipService.ToolTip="{x:Static p:Resources.Select_color}" />
|
||||
CornerRadius="0,0,4,4"
|
||||
Style="{StaticResource ColorShadeButtonStyle}" />
|
||||
|
||||
<Button
|
||||
x:Name="CurrentColorButton"
|
||||
@@ -206,27 +69,15 @@
|
||||
Height="104"
|
||||
Margin="0,72,0,72"
|
||||
VerticalAlignment="Top"
|
||||
AutomationProperties.HelpText="{x:Static p:Resources.Selected_color_helptext}"
|
||||
AutomationProperties.Name="{x:Static p:Resources.Selected_color}"
|
||||
Click="CurrentColorButton_Click"
|
||||
Style="{DynamicResource ColorShadeButtonStyle}"
|
||||
ToolTipService.ToolTip="{x:Static p:Resources.Selected_color_tooltip}">
|
||||
|
||||
<Popup
|
||||
x:Name="DetailsFlyout"
|
||||
Margin="24,0,0,0"
|
||||
AllowsTransparency="True"
|
||||
Closed="DetailsFlyout_Closed"
|
||||
Opened="DetailsFlyout_Opened"
|
||||
Placement="Top"
|
||||
StaysOpen="False">
|
||||
<Border
|
||||
Padding="15"
|
||||
Background="{DynamicResource ApplicationBackgroundBrush}"
|
||||
BorderBrush="{DynamicResource SurfaceStrokeColorFlyoutBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="16">
|
||||
<Grid x:Name="detailsGrid" KeyboardNavigation.TabNavigation="Contained">
|
||||
Style="{StaticResource ColorShadeButtonStyle}">
|
||||
<Button.Flyout>
|
||||
<Flyout
|
||||
x:Name="DetailsFlyout"
|
||||
Closed="DetailsFlyout_Closed"
|
||||
Opened="DetailsFlyout_Opened"
|
||||
Placement="Top">
|
||||
<Grid x:Name="detailsGrid" Width="316">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="44" />
|
||||
<ColumnDefinition Width="86" />
|
||||
@@ -248,111 +99,128 @@
|
||||
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="H"
|
||||
TextAlignment="Center" />
|
||||
|
||||
<Slider
|
||||
x:Name="HueGradientSlider"
|
||||
<Grid
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="3"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.Name="{x:Static p:Resources.Hue_slider}"
|
||||
IsMoveToPointEnabled="True"
|
||||
Maximum="289"
|
||||
Minimum="0"
|
||||
ValueChanged="HueGradientSlider_ValueChanged" />
|
||||
VerticalAlignment="Center">
|
||||
<Border
|
||||
x:Name="HueGradientBorder"
|
||||
Height="16"
|
||||
Margin="8,0"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="8" />
|
||||
<Slider
|
||||
x:Name="HueGradientSlider"
|
||||
VerticalAlignment="Center"
|
||||
Maximum="289"
|
||||
Minimum="0"
|
||||
ValueChanged="HueGradientSlider_ValueChanged" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="S"
|
||||
TextAlignment="Center" />
|
||||
|
||||
<Slider
|
||||
x:Name="SaturationGradientSlider"
|
||||
<Grid
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="3"
|
||||
AutomationProperties.Name="{x:Static p:Resources.Saturation_slider}"
|
||||
IsMoveToPointEnabled="True"
|
||||
Maximum="289"
|
||||
Minimum="0"
|
||||
ValueChanged="SaturationGradientSlider_ValueChanged">
|
||||
<Slider.Background>
|
||||
<LinearGradientBrush StartPoint="0, 0.5" EndPoint="1,0.5">
|
||||
<GradientStop x:Name="SaturationStartColor" Color="Black" />
|
||||
<GradientStop x:Name="SaturationStopColor" Offset="1" Color="Red" />
|
||||
</LinearGradientBrush>
|
||||
</Slider.Background>
|
||||
</Slider>
|
||||
VerticalAlignment="Center">
|
||||
<Border
|
||||
Height="16"
|
||||
Margin="8,0"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="8">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
|
||||
<GradientStop x:Name="SaturationStartColor" Color="Black" />
|
||||
<GradientStop x:Name="SaturationStopColor" Offset="1" Color="Red" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
</Border>
|
||||
<Slider
|
||||
x:Name="SaturationGradientSlider"
|
||||
VerticalAlignment="Center"
|
||||
Maximum="289"
|
||||
Minimum="0"
|
||||
ValueChanged="SaturationGradientSlider_ValueChanged" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="V"
|
||||
TextAlignment="Center" />
|
||||
|
||||
<Slider
|
||||
x:Name="ValueGradientSlider"
|
||||
<Grid
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="3"
|
||||
AutomationProperties.Name="{x:Static p:Resources.Value_slider}"
|
||||
IsMoveToPointEnabled="True"
|
||||
Maximum="289"
|
||||
Minimum="0"
|
||||
ValueChanged="ValueGradientSlider_ValueChanged">
|
||||
<Slider.Background>
|
||||
<LinearGradientBrush StartPoint="0, 0.5" EndPoint="1,0.5">
|
||||
<GradientStop x:Name="ValueStartColor" Color="Black" />
|
||||
<GradientStop x:Name="ValueStopColor" Offset="1" Color="Red" />
|
||||
</LinearGradientBrush>
|
||||
</Slider.Background>
|
||||
</Slider>
|
||||
VerticalAlignment="Center">
|
||||
<Border
|
||||
Height="16"
|
||||
Margin="8,0"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="8">
|
||||
<Border.Background>
|
||||
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
|
||||
<GradientStop x:Name="ValueStartColor" Color="Black" />
|
||||
<GradientStop x:Name="ValueStopColor" Offset="1" Color="Red" />
|
||||
</LinearGradientBrush>
|
||||
</Border.Background>
|
||||
</Border>
|
||||
<Slider
|
||||
x:Name="ValueGradientSlider"
|
||||
VerticalAlignment="Center"
|
||||
Maximum="289"
|
||||
Minimum="0"
|
||||
ValueChanged="ValueGradientSlider_ValueChanged" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="6"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="RGB"
|
||||
TextAlignment="Center" />
|
||||
|
||||
<TextBox
|
||||
x:Name="RNumberBox"
|
||||
Grid.Row="6"
|
||||
Grid.Column="1"
|
||||
AutomationProperties.Name="{x:Static p:Resources.Red_value}"
|
||||
PreviewTextInput="TextBox_PreviewTextInput"
|
||||
TextBoxBase.TextChanged="RGBNumberBox_TextChanged" />
|
||||
BeforeTextChanging="NumberBox_BeforeTextChanging"
|
||||
MaxLength="3"
|
||||
TextChanged="RGBNumberBox_TextChanged" />
|
||||
<TextBox
|
||||
x:Name="GNumberBox"
|
||||
Grid.Row="6"
|
||||
Grid.Column="2"
|
||||
Margin="4,0,0,0"
|
||||
AutomationProperties.Name="{x:Static p:Resources.Green_value}"
|
||||
PreviewTextInput="TextBox_PreviewTextInput"
|
||||
TextBoxBase.TextChanged="RGBNumberBox_TextChanged" />
|
||||
BeforeTextChanging="NumberBox_BeforeTextChanging"
|
||||
MaxLength="3"
|
||||
TextChanged="RGBNumberBox_TextChanged" />
|
||||
<TextBox
|
||||
x:Name="BNumberBox"
|
||||
Grid.Row="6"
|
||||
Grid.Column="3"
|
||||
Margin="4,0,0,0"
|
||||
AutomationProperties.Name="{x:Static p:Resources.Blue_value}"
|
||||
PreviewTextInput="TextBox_PreviewTextInput"
|
||||
TextBoxBase.TextChanged="RGBNumberBox_TextChanged" />
|
||||
BeforeTextChanging="NumberBox_BeforeTextChanging"
|
||||
MaxLength="3"
|
||||
TextChanged="RGBNumberBox_TextChanged" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="8"
|
||||
Width="38"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="HEX"
|
||||
TextAlignment="Center" />
|
||||
@@ -362,12 +230,9 @@
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="3"
|
||||
HorizontalAlignment="Stretch"
|
||||
AutomationProperties.Name="{x:Static p:Resources.Hex_value}"
|
||||
CharacterCasing="Lower"
|
||||
GotKeyboardFocus="HexCode_GotKeyboardFocus"
|
||||
GotFocus="HexCode_GotFocus"
|
||||
MaxLength="7"
|
||||
TextChanged="HexCode_TextChanged"
|
||||
TextWrapping="Wrap" />
|
||||
TextChanged="HexCode_TextChanged" />
|
||||
|
||||
<Button
|
||||
x:Name="OKButton"
|
||||
@@ -375,20 +240,12 @@
|
||||
Grid.ColumnSpan="4"
|
||||
Margin="0,32,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
AutomationProperties.Name="{x:Static p:Resources.Select}"
|
||||
Click="OKButton_Click"
|
||||
Content="{x:Static p:Resources.Select}"
|
||||
Style="{StaticResource CustomAccentButtonStyle}" />
|
||||
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
|
||||
|
||||
|
||||
|
||||
</Grid>
|
||||
|
||||
</Border>
|
||||
</UserControl>
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using System.Windows.Automation.Peers;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
using ColorPicker.Helpers;
|
||||
using ManagedCommon;
|
||||
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Automation;
|
||||
using Microsoft.UI.Xaml.Automation.Peers;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI;
|
||||
|
||||
namespace ColorPicker.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ColorPickerControl.xaml
|
||||
/// </summary>
|
||||
public partial class ColorPickerControl : UserControl
|
||||
public sealed partial class ColorPickerControl : UserControl
|
||||
{
|
||||
private double _currH = 360;
|
||||
private double _currS = 1;
|
||||
@@ -34,15 +36,34 @@ namespace ColorPicker.Controls
|
||||
private Color _originalColor;
|
||||
private Color _currentColor;
|
||||
|
||||
public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register("SelectedColor", typeof(Color), typeof(ColorPickerControl), new PropertyMetadata(SelectedColorPropertyChanged));
|
||||
public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register(nameof(SelectedColor), typeof(Color), typeof(ColorPickerControl), new PropertyMetadata(default(Color), SelectedColorPropertyChanged));
|
||||
|
||||
public static readonly DependencyProperty SelectedColorChangeCommandProperty = DependencyProperty.Register("SelectedColorChangedCommand", typeof(ICommand), typeof(ColorPickerControl));
|
||||
public static readonly DependencyProperty SelectedColorChangeCommandProperty = DependencyProperty.Register(nameof(SelectedColorChangedCommand), typeof(ICommand), typeof(ColorPickerControl), new PropertyMetadata(null));
|
||||
|
||||
public ColorPickerControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
UpdateHueGradient(1, 1);
|
||||
|
||||
// WPF set these via {x:Static p:Resources.*}; resolve through the resource loader instead.
|
||||
AutomationProperties.SetName(this, ResourceLoaderInstance.GetString("Color_Palette"));
|
||||
AutomationProperties.SetName(HueGradientSlider, ResourceLoaderInstance.GetString("Hue_slider"));
|
||||
AutomationProperties.SetName(SaturationGradientSlider, ResourceLoaderInstance.GetString("Saturation_slider"));
|
||||
AutomationProperties.SetName(ValueGradientSlider, ResourceLoaderInstance.GetString("Value_slider"));
|
||||
AutomationProperties.SetName(RNumberBox, ResourceLoaderInstance.GetString("Red_value"));
|
||||
AutomationProperties.SetName(GNumberBox, ResourceLoaderInstance.GetString("Green_value"));
|
||||
AutomationProperties.SetName(BNumberBox, ResourceLoaderInstance.GetString("Blue_value"));
|
||||
AutomationProperties.SetName(HexCode, ResourceLoaderInstance.GetString("Hex_value"));
|
||||
AutomationProperties.SetName(colorVariation1Button, ResourceLoaderInstance.GetString("Lightest_color"));
|
||||
AutomationProperties.SetName(colorVariation2Button, ResourceLoaderInstance.GetString("Lighter_color"));
|
||||
AutomationProperties.SetName(colorVariation3Button, ResourceLoaderInstance.GetString("Darker_color"));
|
||||
AutomationProperties.SetName(colorVariation4Button, ResourceLoaderInstance.GetString("Darkest_color"));
|
||||
AutomationProperties.SetName(CurrentColorButton, ResourceLoaderInstance.GetString("Selected_color"));
|
||||
AutomationProperties.SetHelpText(CurrentColorButton, ResourceLoaderInstance.GetString("Selected_color_helptext"));
|
||||
AutomationProperties.SetName(OKButton, ResourceLoaderInstance.GetString("Select"));
|
||||
ToolTipService.SetToolTip(CurrentColorButton, ResourceLoaderInstance.GetString("Selected_color_tooltip"));
|
||||
OKButton.Content = ResourceLoaderInstance.GetString("Select");
|
||||
}
|
||||
|
||||
protected override AutomationPeer OnCreateAutomationPeer()
|
||||
@@ -52,14 +73,14 @@ namespace ColorPicker.Controls
|
||||
|
||||
public Color SelectedColor
|
||||
{
|
||||
get { return (Color)GetValue(SelectedColorProperty); }
|
||||
set { SetValue(SelectedColorProperty, value); }
|
||||
get => (Color)GetValue(SelectedColorProperty);
|
||||
set => SetValue(SelectedColorProperty, value);
|
||||
}
|
||||
|
||||
public ICommand SelectedColorChangedCommand
|
||||
{
|
||||
get { return (ICommand)GetValue(SelectedColorChangeCommandProperty); }
|
||||
set { SetValue(SelectedColorChangeCommandProperty, value); }
|
||||
get => (ICommand)GetValue(SelectedColorChangeCommandProperty);
|
||||
set => SetValue(SelectedColorChangeCommandProperty, value);
|
||||
}
|
||||
|
||||
private static void SelectedColorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
@@ -68,8 +89,7 @@ namespace ColorPicker.Controls
|
||||
var newColor = (Color)e.NewValue;
|
||||
|
||||
control._originalColor = control._currentColor = newColor;
|
||||
var newColorBackground = new SolidColorBrush(newColor);
|
||||
control.CurrentColorButton.Background = newColorBackground;
|
||||
control.CurrentColorButton.Background = new SolidColorBrush(newColor);
|
||||
|
||||
control._ignoreHexChanges = true;
|
||||
control._ignoreRGBChanges = true;
|
||||
@@ -92,16 +112,17 @@ namespace ColorPicker.Controls
|
||||
{
|
||||
var g6 = HSVColor.HueSpectrum(saturation, value);
|
||||
|
||||
var gradientBrush = new LinearGradientBrush();
|
||||
gradientBrush.StartPoint = new Point(0, 0);
|
||||
gradientBrush.EndPoint = new Point(1, 0);
|
||||
var gradientBrush = new LinearGradientBrush
|
||||
{
|
||||
StartPoint = new Point(0, 0),
|
||||
EndPoint = new Point(1, 0),
|
||||
};
|
||||
for (int i = 0; i < g6.Length; i++)
|
||||
{
|
||||
var stop = new GradientStop(g6[i], i * 0.16);
|
||||
gradientBrush.GradientStops.Add(stop);
|
||||
gradientBrush.GradientStops.Add(new GradientStop { Color = g6[i], Offset = i * 0.16 });
|
||||
}
|
||||
|
||||
HueGradientSlider.Background = gradientBrush;
|
||||
HueGradientBorder.Background = gradientBrush;
|
||||
}
|
||||
|
||||
private static void SetColorVariationsForCurrentColor(DependencyObject d, (double Hue, double Saturation, double Value) hsv)
|
||||
@@ -194,17 +215,11 @@ namespace ColorPicker.Controls
|
||||
{
|
||||
_isCollapsed = false;
|
||||
|
||||
var resizeColor = new DoubleAnimation(256, new Duration(TimeSpan.FromMilliseconds(250)));
|
||||
resizeColor.EasingFunction = new ExponentialEase() { EasingMode = EasingMode.EaseInOut };
|
||||
|
||||
var moveColor = new ThicknessAnimation(new Thickness(0), new Duration(TimeSpan.FromMilliseconds(250)));
|
||||
moveColor.EasingFunction = new ExponentialEase() { EasingMode = EasingMode.EaseInOut };
|
||||
|
||||
CurrentColorButton.BeginAnimation(System.Windows.Controls.Button.HeightProperty, resizeColor);
|
||||
CurrentColorButton.BeginAnimation(System.Windows.Controls.Button.MarginProperty, moveColor);
|
||||
// The flyout opens automatically via Button.Flyout; animate the swatch to its
|
||||
// expanded pose alongside it.
|
||||
AnimateCurrentColorButton(256, new Thickness(0), 250);
|
||||
CurrentColorButton.IsEnabled = false;
|
||||
SessionEventHelper.Event.EditorAdjustColorOpened = true;
|
||||
DetailsFlyout.IsOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,50 +229,61 @@ namespace ColorPicker.Controls
|
||||
{
|
||||
_isCollapsed = true;
|
||||
|
||||
var resizeColor = new DoubleAnimation(165, new Duration(TimeSpan.FromMilliseconds(150)));
|
||||
resizeColor.EasingFunction = new ExponentialEase() { EasingMode = EasingMode.EaseInOut };
|
||||
|
||||
var moveColor = new ThicknessAnimation(new Thickness(0, 72, 0, 72), new Duration(TimeSpan.FromMilliseconds(150)));
|
||||
moveColor.EasingFunction = new ExponentialEase() { EasingMode = EasingMode.EaseInOut };
|
||||
|
||||
CurrentColorButton.BeginAnimation(System.Windows.Controls.Button.HeightProperty, resizeColor);
|
||||
CurrentColorButton.BeginAnimation(System.Windows.Controls.Button.MarginProperty, moveColor);
|
||||
AnimateCurrentColorButton(165, new Thickness(0, 72, 0, 72), 150);
|
||||
CurrentColorButton.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void AnimateCurrentColorButton(double height, Thickness margin, int durationMs)
|
||||
{
|
||||
// WinUI 3 has no ThicknessAnimation, so set the margin directly and animate only the
|
||||
// (dependent) Height; the swatch still grows/shrinks as the flyout opens/closes.
|
||||
CurrentColorButton.Margin = margin;
|
||||
|
||||
var storyboard = new Storyboard();
|
||||
var heightAnimation = new DoubleAnimation
|
||||
{
|
||||
To = height,
|
||||
Duration = new Duration(TimeSpan.FromMilliseconds(durationMs)),
|
||||
EnableDependentAnimation = true,
|
||||
EasingFunction = new ExponentialEase { EasingMode = EasingMode.EaseInOut },
|
||||
};
|
||||
Storyboard.SetTarget(heightAnimation, CurrentColorButton);
|
||||
Storyboard.SetTargetProperty(heightAnimation, "Height");
|
||||
storyboard.Children.Add(heightAnimation);
|
||||
storyboard.Begin();
|
||||
}
|
||||
|
||||
private void OKButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SelectedColorChangedCommand.Execute(_currentColor);
|
||||
SessionEventHelper.Event.EditorColorAdjusted = true;
|
||||
DetailsFlyout.IsOpen = false;
|
||||
DetailsFlyout.Hide();
|
||||
}
|
||||
|
||||
private void DetailsFlyout_Closed(object sender, object e)
|
||||
{
|
||||
HideDetails();
|
||||
AppStateHandler.BlockEscapeKeyClosingColorPickerEditor = false;
|
||||
EditorState.BlockEscapeKeyClosingColorPickerEditor = false;
|
||||
|
||||
// Revert to original color
|
||||
var originalColorBackground = new SolidColorBrush(_originalColor);
|
||||
CurrentColorButton.Background = originalColorBackground;
|
||||
|
||||
CurrentColorButton.Background = new SolidColorBrush(_originalColor);
|
||||
HexCode.Text = ColorToHex(_originalColor);
|
||||
}
|
||||
|
||||
private void DetailsFlyout_Opened(object sender, object e)
|
||||
{
|
||||
AppStateHandler.BlockEscapeKeyClosingColorPickerEditor = true;
|
||||
EditorState.BlockEscapeKeyClosingColorPickerEditor = true;
|
||||
}
|
||||
|
||||
private void ColorVariationButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var selectedColor = ((SolidColorBrush)((System.Windows.Controls.Button)sender).Background).Color;
|
||||
var selectedColor = ((SolidColorBrush)((Button)sender).Background).Color;
|
||||
SelectedColorChangedCommand.Execute(selectedColor);
|
||||
SessionEventHelper.Event.EditorSimilarColorPicked = true;
|
||||
}
|
||||
|
||||
private void SaturationGradientSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
private void SaturationGradientSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
|
||||
{
|
||||
UpdateSaturationColorGradient((sender as Slider).Value);
|
||||
_ignoreGradientsChanges = true;
|
||||
@@ -265,7 +291,7 @@ namespace ColorPicker.Controls
|
||||
_ignoreGradientsChanges = false;
|
||||
}
|
||||
|
||||
private void HueGradientSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
private void HueGradientSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
|
||||
{
|
||||
UpdateHueColorGradient((sender as Slider).Value);
|
||||
_ignoreGradientsChanges = true;
|
||||
@@ -273,7 +299,7 @@ namespace ColorPicker.Controls
|
||||
_ignoreGradientsChanges = false;
|
||||
}
|
||||
|
||||
private void ValueGradientSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
private void ValueGradientSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
|
||||
{
|
||||
UpdateValueColorGradient((sender as Slider).Value);
|
||||
_ignoreGradientsChanges = true;
|
||||
@@ -283,7 +309,7 @@ namespace ColorPicker.Controls
|
||||
|
||||
private void HexCode_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
var newValue = (sender as System.Windows.Controls.TextBox).Text;
|
||||
var newValue = (sender as TextBox).Text;
|
||||
|
||||
// support hex with 3 and 6 characters and optional with hashtag
|
||||
var reg = new Regex("^#?([0-9A-Fa-f]{3}){1,2}$");
|
||||
@@ -319,7 +345,7 @@ namespace ColorPicker.Controls
|
||||
UpdateValueColorGradient(valuePosition);
|
||||
}
|
||||
|
||||
UpdateTextBoxesAndCurrentColor(Color.FromRgb(color.R, color.G, color.B));
|
||||
UpdateTextBoxesAndCurrentColor(new Color { A = 255, R = color.R, G = color.G, B = color.B });
|
||||
}
|
||||
|
||||
private static string ColorToHex(Color color, string oldValue = "")
|
||||
@@ -333,7 +359,8 @@ namespace ColorPicker.Controls
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the hex code string to be accepted by <see cref="ConvertFromString()"/> of <see cref="ColorConverter.ColorConverter"/>. We are adding hashtag at the beginning if needed and convert from three characters to six characters code.
|
||||
/// Formats the hex code string to be accepted by the <see cref="System.Drawing.ColorConverter"/>.
|
||||
/// We add a hashtag at the beginning if needed and convert from three to six characters.
|
||||
/// </summary>
|
||||
/// <param name="hexCodeText">The string we read from the hex text box.</param>
|
||||
/// <returns>Formatted string with hashtag and six characters of hex code.</returns>
|
||||
@@ -351,14 +378,15 @@ namespace ColorPicker.Controls
|
||||
}
|
||||
}
|
||||
|
||||
private void HexCode_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
|
||||
private void HexCode_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
(sender as System.Windows.Controls.TextBox).SelectAll();
|
||||
(sender as TextBox).SelectAll();
|
||||
}
|
||||
|
||||
private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
|
||||
private void NumberBox_BeforeTextChanging(TextBox sender, TextBoxBeforeTextChangingEventArgs args)
|
||||
{
|
||||
e.Handled = !System.Text.RegularExpressions.Regex.IsMatch(e.Text, "^[0-9]+$");
|
||||
// WinUI has no PreviewTextInput; reject any non-digit edit before it is applied.
|
||||
args.Cancel = !Regex.IsMatch(args.NewText, "^[0-9]*$");
|
||||
}
|
||||
|
||||
private void RGBNumberBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
@@ -376,12 +404,6 @@ namespace ColorPicker.Controls
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NumberBox provides value only after it has been validated - happens after pressing enter or leaving this control.
|
||||
/// However, we need to get value immediately after the underlying textbox value changes
|
||||
/// </summary>
|
||||
/// <param name="numberBox">numberBox control which value we want to get</param>
|
||||
/// <returns>Validated value as per numberbox conditions, if content is invalid it returns previous value</returns>
|
||||
private static byte GetValueFromNumberBox(TextBox numberBox, byte previousValue)
|
||||
{
|
||||
int minimum = 0;
|
||||
@@ -411,32 +433,10 @@ namespace ColorPicker.Controls
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static T GetChildOfType<T>(DependencyObject depObj)
|
||||
where T : DependencyObject
|
||||
{
|
||||
if (depObj == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(depObj, i);
|
||||
|
||||
var result = (child as T) ?? GetChildOfType<T>(child);
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable SA1402 // File may only contain a single type
|
||||
public class ColorPickerAutomationPeer : UserControlAutomationPeer
|
||||
public sealed partial class ColorPickerAutomationPeer : FrameworkElementAutomationPeer
|
||||
#pragma warning restore SA1402 // File may only contain a single type
|
||||
{
|
||||
public ColorPickerAutomationPeer(ColorPickerControl owner)
|
||||
@@ -446,7 +446,7 @@ namespace ColorPicker.Controls
|
||||
|
||||
protected override string GetLocalizedControlTypeCore()
|
||||
{
|
||||
return ColorPicker.Properties.Resources.Color_Picker_Control;
|
||||
return ResourceLoaderInstance.GetString("Color_Picker_Control");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,14 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
|
||||
using Windows.UI;
|
||||
|
||||
namespace ColorPicker.Controls
|
||||
{
|
||||
public static class HSVColor
|
||||
{
|
||||
public static Color[] GetSpectrum()
|
||||
{
|
||||
var rgbs = new Color[360];
|
||||
|
||||
for (int h = 0; h < 360; h++)
|
||||
{
|
||||
rgbs[h] = RGBFromHSV(h, 1f, 1f);
|
||||
}
|
||||
|
||||
return rgbs;
|
||||
}
|
||||
private static Color FromRgb(byte r, byte g, byte b) => new() { A = 255, R = r, G = g, B = b };
|
||||
|
||||
public static Color[] HueSpectrum(double saturation, double value)
|
||||
{
|
||||
@@ -37,7 +28,7 @@ namespace ColorPicker.Controls
|
||||
{
|
||||
if (h > 360 || h < 0 || s > 1 || s < 0 || v > 1 || v < 0)
|
||||
{
|
||||
return Color.FromRgb(0, 0, 0);
|
||||
return FromRgb(0, 0, 0);
|
||||
}
|
||||
|
||||
double c = v * s;
|
||||
@@ -77,7 +68,7 @@ namespace ColorPicker.Controls
|
||||
b = x;
|
||||
}
|
||||
|
||||
return Color.FromRgb((byte)((r + m) * 255), (byte)((g + m) * 255), (byte)((b + m) * 255));
|
||||
return FromRgb((byte)((r + m) * 255), (byte)((g + m) * 255), (byte)((b + m) * 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace ColorPicker.Converters
|
||||
{
|
||||
public class BoolToInvertedVisibilityConverter : IValueConverter
|
||||
public partial class BoolToInvertedVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if ((bool)value)
|
||||
{
|
||||
@@ -21,7 +21,7 @@ namespace ColorPicker.Converters
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.Globalization;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace ColorPicker.Converters
|
||||
{
|
||||
public class ColorToStringConverter : IValueConverter
|
||||
public partial class BoolToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
var color = (Color)value;
|
||||
return "RGB(" + color.R + "," + color.G + "," + color.B + ")";
|
||||
if ((bool)value)
|
||||
{
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -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;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace ColorPicker.Converters
|
||||
{
|
||||
public class ColorToBrushConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
var color = (Color)value;
|
||||
return new SolidColorBrush(color);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace ColorPicker.Converters
|
||||
{
|
||||
public class NumberToInvertedVisibilityConverter : IValueConverter
|
||||
public partial class NumberToInvertedVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if ((int)value <= 0)
|
||||
{
|
||||
@@ -21,7 +21,7 @@ namespace ColorPicker.Converters
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace ColorPicker.Converters
|
||||
{
|
||||
public class NumberToVisibilityConverter : IValueConverter
|
||||
public partial class NumberToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if ((int)value <= 0)
|
||||
{
|
||||
@@ -21,7 +21,7 @@ namespace ColorPicker.Converters
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// 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.Threading;
|
||||
|
||||
using ColorPicker.Helpers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ColorPicker.Foundation
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds the application's dependency-injection container.
|
||||
/// Sub-projects B (editor), C (overlay), and D (infrastructure) add their
|
||||
/// own registrations to <see cref="Register"/> as each service is migrated.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Public (not internal) so the unit-test project can call <see cref="Configure"/>
|
||||
/// without depending on an <c>InternalsVisibleTo</c> assembly-name match.
|
||||
/// </remarks>
|
||||
public static class AppServices
|
||||
{
|
||||
public static IServiceProvider Configure()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
Register(services);
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
private static void Register(IServiceCollection services)
|
||||
{
|
||||
// Replaces the WPF App's [Export] ExitToken: a single source whose
|
||||
// token is cancelled when the runner exits / the app shuts down.
|
||||
services.AddSingleton<CancellationTokenSource>();
|
||||
services.AddSingleton(typeof(CancellationToken), sp =>
|
||||
(object)sp.GetRequiredService<CancellationTokenSource>().Token);
|
||||
|
||||
// D6 infrastructure singletons.
|
||||
// SINGLETON: replaces the WPF [Export(typeof(IThrottledActionInvoker))]; holds a single
|
||||
// UI-thread DispatcherQueueTimer. Resolve only on the UI thread (ctor binds the timer).
|
||||
services.AddSingleton<IThrottledActionInvoker, ThrottledActionInvoker>();
|
||||
|
||||
// D7 settings singleton: holds the live settings.json FileSystemWatcher + in-memory
|
||||
// ColorHistory. SINGLETON, registered AFTER IThrottledActionInvoker (its ctor dep).
|
||||
services.AddSingleton<ColorPicker.Settings.IUserSettings, ColorPicker.Settings.UserSettings>();
|
||||
|
||||
// Sub-project C overlay + editor wiring (7e-1). All singletons; the graph is acyclic:
|
||||
// MainViewModel -> {MouseInfoProvider, ZoomWindowHelper, AppStateHandler, KeyboardMonitor},
|
||||
// and AppStateHandler -> IColorEditorViewModel. AppStateHandler reads App.Window, which
|
||||
// App.OnLaunched assigns before resolving IMainViewModel.
|
||||
services.AddSingleton<ColorPicker.ViewModelContracts.IColorEditorViewModel, ColorPicker.ViewModels.ColorEditorViewModel>();
|
||||
services.AddSingleton<AppStateHandler>();
|
||||
services.AddSingleton<ZoomWindowHelper>();
|
||||
services.AddSingleton<ColorPicker.Mouse.IMouseInfoProvider, ColorPicker.Mouse.MouseInfoProvider>();
|
||||
services.AddSingleton<ColorPicker.Keyboard.KeyboardMonitor>();
|
||||
services.AddSingleton<ColorPicker.ViewModelContracts.IMainViewModel, ColorPicker.ViewModels.MainViewModel>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,37 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.ComponentModel.Composition;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
|
||||
using ColorPicker.Settings;
|
||||
using ColorPicker.ViewModelContracts;
|
||||
using Common.UI;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Enumerations;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
using static ColorPicker.Helpers.NativeMethodsHelper;
|
||||
|
||||
namespace ColorPicker.Helpers
|
||||
{
|
||||
[Export(typeof(AppStateHandler))]
|
||||
public class AppStateHandler
|
||||
{
|
||||
private readonly IColorEditorViewModel _colorEditorViewModel;
|
||||
private readonly IUserSettings _userSettings;
|
||||
private readonly object _colorPickerVisibilityLock = new object();
|
||||
private ColorEditorWindow _colorEditorWindow;
|
||||
private bool _colorPickerShown;
|
||||
private Lock _colorPickerVisibilityLock = new Lock();
|
||||
private IntPtr _mainWindowHandle;
|
||||
|
||||
private HwndSource _hwndSource;
|
||||
private const int _globalHotKeyId = 0x0001;
|
||||
|
||||
// Blocks using the escape key to close the color picker editor when the adjust color flyout is open.
|
||||
public static bool BlockEscapeKeyClosingColorPickerEditor { get; set; }
|
||||
|
||||
[ImportingConstructor]
|
||||
public AppStateHandler(IColorEditorViewModel colorEditorViewModel, IUserSettings userSettings)
|
||||
{
|
||||
Application.Current.MainWindow.Closed += MainWindow_Closed;
|
||||
// App.Window (the picking overlay) is created and assigned before the DI graph that
|
||||
// resolves this handler, so it is available here.
|
||||
if (App.Window != null)
|
||||
{
|
||||
App.Window.Closed += MainWindow_Closed;
|
||||
}
|
||||
|
||||
_colorEditorViewModel = colorEditorViewModel;
|
||||
_userSettings = userSettings;
|
||||
}
|
||||
@@ -71,7 +67,7 @@ namespace ColorPicker.Helpers
|
||||
ShowColorPicker();
|
||||
}
|
||||
|
||||
if (!(System.Windows.Application.Current as ColorPickerUI.App).IsRunningDetachedFromPowerToys())
|
||||
if (!((App)Application.Current).IsRunningDetachedFromPowerToys())
|
||||
{
|
||||
UserSessionStarted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
@@ -93,7 +89,7 @@ namespace ColorPicker.Helpers
|
||||
HideColorPicker();
|
||||
}
|
||||
|
||||
if (!(System.Windows.Application.Current as ColorPickerUI.App).IsRunningDetachedFromPowerToys())
|
||||
if (!((App)Application.Current).IsRunningDetachedFromPowerToys())
|
||||
{
|
||||
UserSessionEnded?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
@@ -117,19 +113,12 @@ namespace ColorPicker.Helpers
|
||||
ShowColorPickerEditor();
|
||||
}
|
||||
|
||||
public static void SetTopMost()
|
||||
{
|
||||
Application.Current.MainWindow.Topmost = false;
|
||||
Application.Current.MainWindow.Topmost = true;
|
||||
}
|
||||
|
||||
private void ShowColorPicker()
|
||||
{
|
||||
if (!_colorPickerShown)
|
||||
{
|
||||
AppShown?.Invoke(this, EventArgs.Empty);
|
||||
Application.Current.MainWindow.Opacity = 0;
|
||||
Application.Current.MainWindow.Visibility = Visibility.Visible;
|
||||
(App.Window as ColorPickerOverlayWindow)?.Show();
|
||||
_colorPickerShown = true;
|
||||
}
|
||||
}
|
||||
@@ -138,8 +127,7 @@ namespace ColorPicker.Helpers
|
||||
{
|
||||
if (_colorPickerShown)
|
||||
{
|
||||
Application.Current.MainWindow.Opacity = 0;
|
||||
Application.Current.MainWindow.Visibility = Visibility.Collapsed;
|
||||
(App.Window as ColorPickerOverlayWindow)?.Hide();
|
||||
AppHidden?.Invoke(this, EventArgs.Empty);
|
||||
_colorPickerShown = false;
|
||||
}
|
||||
@@ -150,7 +138,11 @@ namespace ColorPicker.Helpers
|
||||
if (_colorEditorWindow == null)
|
||||
{
|
||||
_colorEditorWindow = new ColorEditorWindow(this);
|
||||
_colorEditorWindow.contentPresenter.Content = _colorEditorViewModel;
|
||||
|
||||
// The export commands' FileSavePicker needs the editor window's HWND
|
||||
// (InitializeWithWindow); assign it here, once the window exists.
|
||||
_colorEditorViewModel.WindowHandle = WinRT.Interop.WindowNative.GetWindowHandle(_colorEditorWindow);
|
||||
_colorEditorWindow.ContentPresenter.Content = new Views.ColorEditorView { DataContext = _colorEditorViewModel };
|
||||
_colorEditorViewModel.OpenColorPickerRequested += ColorEditorViewModel_OpenColorPickerRequested;
|
||||
_colorEditorViewModel.OpenSettingsRequested += ColorEditorViewModel_OpenSettingsRequested;
|
||||
_colorEditorViewModel.OpenColorPickerRequested += (object sender, EventArgs e) =>
|
||||
@@ -160,27 +152,19 @@ namespace ColorPicker.Helpers
|
||||
}
|
||||
|
||||
_colorEditorViewModel.Initialize();
|
||||
_colorEditorWindow.Show();
|
||||
_colorEditorWindow.AppWindow.Show();
|
||||
_colorEditorWindow.Activate();
|
||||
SessionEventHelper.Event.EditorOpened = true;
|
||||
}
|
||||
|
||||
private void HideColorPickerEditor()
|
||||
{
|
||||
if (_colorEditorWindow != null)
|
||||
{
|
||||
_colorEditorWindow.Hide();
|
||||
}
|
||||
_colorEditorWindow?.AppWindow.Hide();
|
||||
}
|
||||
|
||||
public bool IsColorPickerEditorVisible()
|
||||
{
|
||||
if (_colorEditorWindow != null)
|
||||
{
|
||||
// Check if we are visible and on top. Using focus producing unreliable results the first time the picker is opened.
|
||||
return _colorEditorWindow.Topmost && _colorEditorWindow.IsVisible;
|
||||
}
|
||||
|
||||
return false;
|
||||
return _colorEditorWindow != null && _colorEditorWindow.AppWindow.IsVisible;
|
||||
}
|
||||
|
||||
public bool IsColorPickerVisible()
|
||||
@@ -188,7 +172,7 @@ namespace ColorPicker.Helpers
|
||||
return _colorPickerShown;
|
||||
}
|
||||
|
||||
private void MainWindow_Closed(object sender, EventArgs e)
|
||||
private void MainWindow_Closed(object sender, WindowEventArgs e)
|
||||
{
|
||||
AppClosed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
@@ -200,7 +184,7 @@ namespace ColorPicker.Helpers
|
||||
ShowColorPicker();
|
||||
}
|
||||
|
||||
_colorEditorWindow.Hide();
|
||||
_colorEditorWindow?.AppWindow.Hide();
|
||||
}
|
||||
|
||||
private void ColorEditorViewModel_OpenSettingsRequested(object sender, EventArgs e)
|
||||
@@ -208,9 +192,9 @@ namespace ColorPicker.Helpers
|
||||
SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.ColorPicker);
|
||||
}
|
||||
|
||||
internal void RegisterWindowHandle(System.Windows.Interop.HwndSource hwndSource)
|
||||
internal void RegisterWindowHandle(IntPtr hwnd)
|
||||
{
|
||||
_hwndSource = hwndSource;
|
||||
_mainWindowHandle = hwnd;
|
||||
}
|
||||
|
||||
public bool HandleEnterPressed()
|
||||
@@ -226,8 +210,8 @@ namespace ColorPicker.Helpers
|
||||
|
||||
public bool HandleEscPressed()
|
||||
{
|
||||
if (!BlockEscapeKeyClosingColorPickerEditor
|
||||
&& (_colorPickerShown || (_colorEditorWindow != null && _colorEditorWindow.IsActive)))
|
||||
if (!EditorState.BlockEscapeKeyClosingColorPickerEditor
|
||||
&& (_colorPickerShown || (_colorEditorWindow != null && _colorEditorWindow.IsActiveWindow)))
|
||||
{
|
||||
return EndUserSession();
|
||||
}
|
||||
@@ -237,13 +221,12 @@ namespace ColorPicker.Helpers
|
||||
|
||||
internal void MoveCursor(int xOffset, int yOffset)
|
||||
{
|
||||
POINT lpPoint;
|
||||
GetCursorPos(out lpPoint);
|
||||
GetCursorPos(out POINT lpPoint);
|
||||
lpPoint.X += xOffset;
|
||||
lpPoint.Y += yOffset;
|
||||
SetCursorPos(lpPoint.X, lpPoint.Y);
|
||||
}
|
||||
|
||||
internal IntPtr GetMainWindowHandle() => _hwndSource?.Handle ?? IntPtr.Zero;
|
||||
internal IntPtr GetMainWindowHandle() => _mainWindowHandle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
|
||||
using ManagedCommon;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
|
||||
using static ColorPicker.NativeMethods;
|
||||
|
||||
@@ -21,35 +20,40 @@ namespace ColorPicker.Helpers
|
||||
|
||||
public static void CopyToClipboard(string colorRepresentationToCopy)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(colorRepresentationToCopy))
|
||||
if (string.IsNullOrEmpty(colorRepresentationToCopy))
|
||||
{
|
||||
// nasty hack - sometimes clipboard can be in use and it will raise and exception
|
||||
for (int i = 0; i < 10; i++)
|
||||
return;
|
||||
}
|
||||
|
||||
// nasty hack - sometimes clipboard can be in use and it will raise and exception
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetDataObject(colorRepresentationToCopy);
|
||||
break;
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
var hwnd = GetOpenClipboardWindow();
|
||||
var sb = new StringBuilder(501);
|
||||
_ = GetWindowText(hwnd.ToInt32(), sb, 500);
|
||||
var applicationUsingClipboard = sb.ToString();
|
||||
|
||||
if ((uint)ex.ErrorCode != ErrorCodeClipboardCantOpen)
|
||||
{
|
||||
Logger.LogError("Failed to set text into clipboard", ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogError("Failed to set text into clipboard, application that is locking clipboard - " + applicationUsingClipboard, ex);
|
||||
}
|
||||
}
|
||||
|
||||
System.Threading.Thread.Sleep(10);
|
||||
var data = new DataPackage();
|
||||
data.SetText(colorRepresentationToCopy);
|
||||
Clipboard.SetContent(data);
|
||||
Clipboard.Flush(); // persist after this process exits (Color Picker may close immediately)
|
||||
break;
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
var hwnd = GetOpenClipboardWindow();
|
||||
var sb = new StringBuilder(501);
|
||||
_ = GetWindowText(hwnd.ToInt32(), sb, 500);
|
||||
var applicationUsingClipboard = sb.ToString();
|
||||
|
||||
if ((uint)ex.ErrorCode != ErrorCodeClipboardCantOpen)
|
||||
{
|
||||
Logger.LogError("Failed to set text into clipboard", ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogError("Failed to set text into clipboard, application that is locking clipboard - " + applicationUsingClipboard, ex);
|
||||
}
|
||||
}
|
||||
|
||||
System.Threading.Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.
|
||||
|
||||
@@ -6,7 +6,6 @@ using System;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
|
||||
using ColorPicker.Properties;
|
||||
using ManagedCommon;
|
||||
|
||||
namespace ColorPicker.Helpers
|
||||
@@ -22,7 +21,7 @@ namespace ColorPicker.Helpers
|
||||
/// <param name="color">The <see cref="Color"/> for the presentation</param>
|
||||
/// <param name="colorRepresentationType">The type of the representation</param>
|
||||
/// <returns>A <see cref="string"/> representation of a color</returns>
|
||||
internal static string GetStringRepresentationFromMediaColor(System.Windows.Media.Color color, string colorRepresentationType)
|
||||
internal static string GetStringRepresentationFromMediaColor(Windows.UI.Color color, string colorRepresentationType)
|
||||
{
|
||||
var drawingcolor = Color.FromArgb(color.A, color.R, color.G, color.B);
|
||||
return GetStringRepresentation(drawingcolor, colorRepresentationType, string.Empty);
|
||||
@@ -47,26 +46,6 @@ namespace ColorPicker.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a <see cref="string"/> representation of a CMYK color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> for the CMYK color presentation</param>
|
||||
/// <returns>A <see cref="string"/> representation of a CMYK color</returns>
|
||||
private static string ColorToCMYK(Color color)
|
||||
{
|
||||
var (cyan, magenta, yellow, blackKey) = ColorFormatHelper.ConvertToCMYKColor(color);
|
||||
|
||||
cyan = Math.Round(cyan * 100);
|
||||
magenta = Math.Round(magenta * 100);
|
||||
yellow = Math.Round(yellow * 100);
|
||||
blackKey = Math.Round(blackKey * 100);
|
||||
|
||||
return $"cmyk({cyan.ToString(CultureInfo.InvariantCulture)}%"
|
||||
+ $", {magenta.ToString(CultureInfo.InvariantCulture)}%"
|
||||
+ $", {yellow.ToString(CultureInfo.InvariantCulture)}%"
|
||||
+ $", {blackKey.ToString(CultureInfo.InvariantCulture)}%)";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a hexadecimal <see cref="string"/> representation of a RGB color
|
||||
/// </summary>
|
||||
@@ -81,288 +60,30 @@ namespace ColorPicker.Helpers
|
||||
+ $"{color.B.ToString(hexFormat, CultureInfo.InvariantCulture)}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a <see cref="string"/> representation of a HSB color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> for the HSB color presentation</param>
|
||||
/// <returns>A <see cref="string"/> representation of a HSB color</returns>
|
||||
private static string ColorToHSB(Color color)
|
||||
{
|
||||
var (hue, saturation, brightness) = ColorFormatHelper.ConvertToHSBColor(color);
|
||||
|
||||
hue = Math.Round(hue);
|
||||
saturation = Math.Round(saturation * 100);
|
||||
brightness = Math.Round(brightness * 100);
|
||||
|
||||
return $"hsb({hue.ToString(CultureInfo.InvariantCulture)}"
|
||||
+ $", {saturation.ToString(CultureInfo.InvariantCulture)}%"
|
||||
+ $", {brightness.ToString(CultureInfo.InvariantCulture)}%)";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a <see cref="string"/> representation float color styling(0.1f, 0.1f, 0.1f)
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> to convert</param>
|
||||
/// <returns>a string value (0.1f, 0.1f, 0.1f)</returns>
|
||||
private static string ColorToFloat(Color color)
|
||||
{
|
||||
var (red, green, blue) = ColorHelper.ConvertToDouble(color);
|
||||
const int precision = 2;
|
||||
const string floatFormat = "0.##";
|
||||
|
||||
return $"({Math.Round(red, precision).ToString(floatFormat, CultureInfo.InvariantCulture)}f"
|
||||
+ $", {Math.Round(green, precision).ToString(floatFormat, CultureInfo.InvariantCulture)}f"
|
||||
+ $", {Math.Round(blue, precision).ToString(floatFormat, CultureInfo.InvariantCulture)}f, 1f)";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a <see cref="string"/> representation decimal color value
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> to convert</param>
|
||||
/// <returns>a string value number</returns>
|
||||
private static string ColorToDecimal(Color color)
|
||||
{
|
||||
return $"{(color.R * 65536) + (color.G * 256) + color.B}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a <see cref="string"/> representation of a HSI color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> for the HSI color presentation</param>
|
||||
/// <returns>A <see cref="string"/> representation of a HSI color</returns>
|
||||
private static string ColorToHSI(Color color)
|
||||
{
|
||||
var (hue, saturation, intensity) = ColorFormatHelper.ConvertToHSIColor(color);
|
||||
|
||||
hue = Math.Round(hue);
|
||||
saturation = Math.Round(saturation * 100);
|
||||
intensity = Math.Round(intensity * 100);
|
||||
|
||||
return $"hsi({hue.ToString(CultureInfo.InvariantCulture)}"
|
||||
+ $", {saturation.ToString(CultureInfo.InvariantCulture)}%"
|
||||
+ $", {intensity.ToString(CultureInfo.InvariantCulture)}%)";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a <see cref="string"/> representation of a HSL color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> for the HSL color presentation</param>
|
||||
/// <returns>A <see cref="string"/> representation of a HSL color</returns>
|
||||
private static string ColorToHSL(Color color)
|
||||
{
|
||||
var (hue, saturation, lightness) = ColorFormatHelper.ConvertToHSLColor(color);
|
||||
|
||||
hue = Math.Round(hue);
|
||||
saturation = Math.Round(saturation * 100);
|
||||
lightness = Math.Round(lightness * 100);
|
||||
|
||||
// Using InvariantCulture since this is used for color representation
|
||||
return $"hsl({hue.ToString(CultureInfo.InvariantCulture)}"
|
||||
+ $", {saturation.ToString(CultureInfo.InvariantCulture)}%"
|
||||
+ $", {lightness.ToString(CultureInfo.InvariantCulture)}%)";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a <see cref="string"/> representation of a HSV color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> for the HSV color presentation</param>
|
||||
/// <returns>A <see cref="string"/> representation of a HSV color</returns>
|
||||
private static string ColorToHSV(Color color)
|
||||
{
|
||||
var (hue, saturation, value) = ColorFormatHelper.ConvertToHSVColor(color);
|
||||
|
||||
hue = Math.Round(hue);
|
||||
saturation = Math.Round(saturation * 100);
|
||||
value = Math.Round(value * 100);
|
||||
|
||||
// Using InvariantCulture since this is used for color representation
|
||||
return $"hsv({hue.ToString(CultureInfo.InvariantCulture)}"
|
||||
+ $", {saturation.ToString(CultureInfo.InvariantCulture)}%"
|
||||
+ $", {value.ToString(CultureInfo.InvariantCulture)}%)";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a <see cref="string"/> representation of a HWB color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> for the HWB color presentation</param>
|
||||
/// <returns>A <see cref="string"/> representation of a HWB color</returns>
|
||||
private static string ColorToHWB(Color color)
|
||||
{
|
||||
var (hue, whiteness, blackness) = ColorFormatHelper.ConvertToHWBColor(color);
|
||||
|
||||
hue = Math.Round(hue);
|
||||
whiteness = Math.Round(whiteness * 100);
|
||||
blackness = Math.Round(blackness * 100);
|
||||
|
||||
return $"hwb({hue.ToString(CultureInfo.InvariantCulture)}"
|
||||
+ $", {whiteness.ToString(CultureInfo.InvariantCulture)}%"
|
||||
+ $", {blackness.ToString(CultureInfo.InvariantCulture)}%)";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a <see cref="string"/> representation of a natural color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> for the natural color presentation</param>
|
||||
/// <returns>A <see cref="string"/> representation of a natural color</returns>
|
||||
private static string ColorToNCol(Color color)
|
||||
{
|
||||
var (hue, whiteness, blackness) = ColorFormatHelper.ConvertToNaturalColor(color);
|
||||
|
||||
whiteness = Math.Round(whiteness * 100);
|
||||
blackness = Math.Round(blackness * 100);
|
||||
|
||||
return $"{hue}"
|
||||
+ $", {whiteness.ToString(CultureInfo.InvariantCulture)}%"
|
||||
+ $", {blackness.ToString(CultureInfo.InvariantCulture)}%";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a <see cref="string"/> representation of a RGB color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> for the RGB color presentation</param>
|
||||
/// <returns>A <see cref="string"/> representation of a RGB color</returns>
|
||||
private static string ColorToRGB(Color color)
|
||||
=> $"rgb({color.R.ToString(CultureInfo.InvariantCulture)}"
|
||||
+ $", {color.G.ToString(CultureInfo.InvariantCulture)}"
|
||||
+ $", {color.B.ToString(CultureInfo.InvariantCulture)})";
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="string"/> representation of a CIE LAB color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> for the CIE LAB color presentation</param>
|
||||
/// <returns>A <see cref="string"/> representation of a CIE LAB color</returns>
|
||||
private static string ColorToCIELAB(Color color)
|
||||
{
|
||||
var (lightness, chromaticityA, chromaticityB) = ColorFormatHelper.ConvertToCIELABColor(color);
|
||||
lightness = Math.Round(lightness, 2);
|
||||
chromaticityA = Math.Round(chromaticityA, 2);
|
||||
chromaticityB = Math.Round(chromaticityB, 2);
|
||||
|
||||
return $"CIELab({lightness.ToString(CultureInfo.InvariantCulture)}" +
|
||||
$", {chromaticityA.ToString(CultureInfo.InvariantCulture)}" +
|
||||
$", {chromaticityB.ToString(CultureInfo.InvariantCulture)})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="string"/> representation of a Oklab color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> for the Oklab color presentation</param>
|
||||
/// <returns>A <see cref="string"/> representation of a Oklab color</returns>
|
||||
private static string ColorToOklab(Color color)
|
||||
{
|
||||
var (lightness, chromaticityA, chromaticityB) = ColorFormatHelper.ConvertToOklabColor(color);
|
||||
lightness = Math.Round(lightness, 2);
|
||||
chromaticityA = Math.Round(chromaticityA, 2);
|
||||
chromaticityB = Math.Round(chromaticityB, 2);
|
||||
|
||||
return $"oklab({lightness.ToString(CultureInfo.InvariantCulture)}" +
|
||||
$", {chromaticityA.ToString(CultureInfo.InvariantCulture)}" +
|
||||
$", {chromaticityB.ToString(CultureInfo.InvariantCulture)})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="string"/> representation of a CIE LCh color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> for the CIE LCh color presentation</param>
|
||||
/// <returns>A <see cref="string"/> representation of a CIE LCh color</returns>
|
||||
private static string ColorToOklch(Color color)
|
||||
{
|
||||
var (lightness, chroma, hue) = ColorFormatHelper.ConvertToOklchColor(color);
|
||||
lightness = Math.Round(lightness, 2);
|
||||
chroma = Math.Round(chroma, 2);
|
||||
hue = Math.Round(hue, 2);
|
||||
|
||||
return $"oklch({lightness.ToString(CultureInfo.InvariantCulture)}" +
|
||||
$", {chroma.ToString(CultureInfo.InvariantCulture)}" +
|
||||
$", {hue.ToString(CultureInfo.InvariantCulture)})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="string"/> representation of a CIE XYZ color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> for the CIE XYZ color presentation</param>
|
||||
/// <returns>A <see cref="string"/> representation of a CIE XYZ color</returns>
|
||||
private static string ColorToCIEXYZ(Color color)
|
||||
{
|
||||
var (x, y, z) = ColorFormatHelper.ConvertToCIEXYZColor(color);
|
||||
|
||||
x = Math.Round(x * 100, 4);
|
||||
y = Math.Round(y * 100, 4);
|
||||
z = Math.Round(z * 100, 4);
|
||||
|
||||
return $"XYZ({x.ToString(CultureInfo.InvariantCulture)}" +
|
||||
$", {y.ToString(CultureInfo.InvariantCulture)}" +
|
||||
$", {z.ToString(CultureInfo.InvariantCulture)})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a hexadecimal integer <see cref="string"/> representation of a RGB color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> for the hexadecimal integer presentation</param>
|
||||
/// <returns>A hexadecimal integer <see cref="string"/> representation of a RGB color</returns>
|
||||
private static string ColorToHexInteger(Color color)
|
||||
{
|
||||
const string hexFormat = "X2";
|
||||
|
||||
return "0xFF"
|
||||
+ $"{color.R.ToString(hexFormat, CultureInfo.InvariantCulture)}"
|
||||
+ $"{color.G.ToString(hexFormat, CultureInfo.InvariantCulture)}"
|
||||
+ $"{color.B.ToString(hexFormat, CultureInfo.InvariantCulture)}";
|
||||
}
|
||||
|
||||
public static string GetColorNameFromColorIdentifier(string colorIdentifier)
|
||||
{
|
||||
switch (colorIdentifier)
|
||||
{
|
||||
case "TEXT_COLOR_WHITE": return Resources.TEXT_COLOR_WHITE;
|
||||
case "TEXT_COLOR_BLACK": return Resources.TEXT_COLOR_BLACK;
|
||||
case "TEXT_COLOR_LIGHTGRAY": return Resources.TEXT_COLOR_LIGHTGRAY;
|
||||
case "TEXT_COLOR_GRAY": return Resources.TEXT_COLOR_GRAY;
|
||||
case "TEXT_COLOR_DARKGRAY": return Resources.TEXT_COLOR_DARKGRAY;
|
||||
case "TEXT_COLOR_CORAL": return Resources.TEXT_COLOR_CORAL;
|
||||
case "TEXT_COLOR_ROSE": return Resources.TEXT_COLOR_ROSE;
|
||||
case "TEXT_COLOR_LIGHTORANGE": return Resources.TEXT_COLOR_LIGHTORANGE;
|
||||
case "TEXT_COLOR_TAN": return Resources.TEXT_COLOR_TAN;
|
||||
case "TEXT_COLOR_LIGHTYELLOW": return Resources.TEXT_COLOR_LIGHTYELLOW;
|
||||
case "TEXT_COLOR_LIGHTGREEN": return Resources.TEXT_COLOR_LIGHTGREEN;
|
||||
case "TEXT_COLOR_LIME": return Resources.TEXT_COLOR_LIME;
|
||||
case "TEXT_COLOR_AQUA": return Resources.TEXT_COLOR_AQUA;
|
||||
case "TEXT_COLOR_SKYBLUE": return Resources.TEXT_COLOR_SKYBLUE;
|
||||
case "TEXT_COLOR_LIGHTTURQUOISE": return Resources.TEXT_COLOR_LIGHTTURQUOISE;
|
||||
case "TEXT_COLOR_PALEBLUE": return Resources.TEXT_COLOR_PALEBLUE;
|
||||
case "TEXT_COLOR_LIGHTBLUE": return Resources.TEXT_COLOR_LIGHTBLUE;
|
||||
case "TEXT_COLOR_ICEBLUE": return Resources.TEXT_COLOR_ICEBLUE;
|
||||
case "TEXT_COLOR_PERIWINKLE": return Resources.TEXT_COLOR_PERIWINKLE;
|
||||
case "TEXT_COLOR_LAVENDER": return Resources.TEXT_COLOR_LAVENDER;
|
||||
case "TEXT_COLOR_PINK": return Resources.TEXT_COLOR_PINK;
|
||||
case "TEXT_COLOR_RED": return Resources.TEXT_COLOR_RED;
|
||||
case "TEXT_COLOR_ORANGE": return Resources.TEXT_COLOR_ORANGE;
|
||||
case "TEXT_COLOR_BROWN": return Resources.TEXT_COLOR_BROWN;
|
||||
case "TEXT_COLOR_GOLD": return Resources.TEXT_COLOR_GOLD;
|
||||
case "TEXT_COLOR_YELLOW": return Resources.TEXT_COLOR_YELLOW;
|
||||
case "TEXT_COLOR_OLIVEGREEN": return Resources.TEXT_COLOR_OLIVEGREEN;
|
||||
case "TEXT_COLOR_GREEN": return Resources.TEXT_COLOR_GREEN;
|
||||
case "TEXT_COLOR_BRIGHTGREEN": return Resources.TEXT_COLOR_BRIGHTGREEN;
|
||||
case "TEXT_COLOR_TEAL": return Resources.TEXT_COLOR_TEAL;
|
||||
case "TEXT_COLOR_TURQUOISE": return Resources.TEXT_COLOR_TURQUOISE;
|
||||
case "TEXT_COLOR_BLUE": return Resources.TEXT_COLOR_BLUE;
|
||||
case "TEXT_COLOR_BLUEGRAY": return Resources.TEXT_COLOR_BLUEGRAY;
|
||||
case "TEXT_COLOR_INDIGO": return Resources.TEXT_COLOR_INDIGO;
|
||||
case "TEXT_COLOR_PURPLE": return Resources.TEXT_COLOR_PURPLE;
|
||||
case "TEXT_COLOR_DARKRED": return Resources.TEXT_COLOR_DARKRED;
|
||||
case "TEXT_COLOR_DARKYELLOW": return Resources.TEXT_COLOR_DARKYELLOW;
|
||||
case "TEXT_COLOR_DARKGREEN": return Resources.TEXT_COLOR_DARKGREEN;
|
||||
case "TEXT_COLOR_DARKTEAL": return Resources.TEXT_COLOR_DARKTEAL;
|
||||
case "TEXT_COLOR_DARKBLUE": return Resources.TEXT_COLOR_DARKBLUE;
|
||||
case "TEXT_COLOR_DARKPURPLE": return Resources.TEXT_COLOR_DARKPURPLE;
|
||||
case "TEXT_COLOR_PLUM": return Resources.TEXT_COLOR_PLUM;
|
||||
default: return string.Empty;
|
||||
}
|
||||
// The color-name identifiers (e.g. "TEXT_COLOR_BLACK") are the .resw resource keys
|
||||
// verbatim, so resolve them directly through the resource loader. This replaces the
|
||||
// WPF-era switch over the generated Resources designer, which no longer exists after
|
||||
// the WinUI 3 migration moved the string table from Properties\Resources.resx to
|
||||
// Strings\en-us\Resources.resw.
|
||||
return ResourceLoaderInstance.GetString(colorIdentifier);
|
||||
}
|
||||
|
||||
public static string ReplaceName(string colorFormat, Color color)
|
||||
{
|
||||
return colorFormat.Replace(ColorFormatHelper.GetColorNameParameter(), GetColorNameFromColorIdentifier(ColorNameHelper.GetColorNameIdentifier(color)));
|
||||
var colorNameParameter = ColorFormatHelper.GetColorNameParameter();
|
||||
|
||||
// Only resolve the (localized, resource-backed) color name when the format string
|
||||
// actually contains the name placeholder. This skips a resource lookup for the common
|
||||
// numeric formats and keeps pure-conversion unit tests from requiring the MRT resource
|
||||
// loader (and the module .pri) to be initialized in the test host.
|
||||
if (!colorFormat.Contains(colorNameParameter, StringComparison.Ordinal))
|
||||
{
|
||||
return colorFormat;
|
||||
}
|
||||
|
||||
return colorFormat.Replace(colorNameParameter, GetColorNameFromColorIdentifier(ColorNameHelper.GetColorNameIdentifier(color)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,317 +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.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace ColorPicker.Helpers
|
||||
{
|
||||
public static class ControlHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the radius for the corners of the control's border.
|
||||
/// </summary>
|
||||
/// <param name="control">The element from which to read the property value.</param>
|
||||
/// <returns>
|
||||
/// The degree to which the corners are rounded, expressed as values of the CornerRadius
|
||||
/// structure.
|
||||
/// </returns>
|
||||
public static CornerRadius GetCornerRadius(Control control)
|
||||
{
|
||||
return (CornerRadius)control.GetValue(CornerRadiusProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the radius for the corners of the control's border.
|
||||
/// </summary>
|
||||
/// <param name="control">The element on which to set the attached property.</param>
|
||||
/// <param name="value">The property value to set.</param>
|
||||
public static void SetCornerRadius(Control control, CornerRadius value)
|
||||
{
|
||||
control.SetValue(CornerRadiusProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the CornerRadius dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty CornerRadiusProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"CornerRadius",
|
||||
typeof(CornerRadius),
|
||||
typeof(ControlHelper),
|
||||
null);
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the Header dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty HeaderProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"Header",
|
||||
typeof(object),
|
||||
typeof(ControlHelper),
|
||||
new FrameworkPropertyMetadata(OnHeaderChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content for the control's header.
|
||||
/// </summary>
|
||||
/// <param name="control">The element from which to read the property value.</param>
|
||||
/// <returns>The content of the control's header. The default is **null**.</returns>
|
||||
public static object GetHeader(Control control)
|
||||
{
|
||||
return control.GetValue(HeaderProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the content for the control's header.
|
||||
/// </summary>
|
||||
/// <param name="control">The element on which to set the attached property.</param>
|
||||
/// <param name="value">The property value to set.</param>
|
||||
public static void SetHeader(Control control, object value)
|
||||
{
|
||||
control.SetValue(HeaderProperty, value);
|
||||
}
|
||||
|
||||
private static void OnHeaderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
UpdateHeaderVisibility((Control)d);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the HeaderTemplate dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty HeaderTemplateProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"HeaderTemplate",
|
||||
typeof(DataTemplate),
|
||||
typeof(ControlHelper),
|
||||
new FrameworkPropertyMetadata(OnHeaderTemplateChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DataTemplate used to display the content of the control's header.
|
||||
/// </summary>
|
||||
/// <param name="control">The element from which to read the property value.</param>
|
||||
/// <returns>
|
||||
/// The template that specifies the visualization of the header object. The default
|
||||
/// is **null**.
|
||||
/// </returns>
|
||||
public static DataTemplate GetHeaderTemplate(Control control)
|
||||
{
|
||||
return (DataTemplate)control.GetValue(HeaderTemplateProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the DataTemplate used to display the content of the control's header.
|
||||
/// </summary>
|
||||
/// <param name="control">The element on which to set the attached property.</param>
|
||||
/// <param name="value">The property value to set.</param>
|
||||
public static void SetHeaderTemplate(Control control, DataTemplate value)
|
||||
{
|
||||
control.SetValue(HeaderTemplateProperty, value);
|
||||
}
|
||||
|
||||
private static void OnHeaderTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
UpdateHeaderVisibility((Control)d);
|
||||
}
|
||||
|
||||
private static readonly DependencyPropertyKey HeaderVisibilityPropertyKey =
|
||||
DependencyProperty.RegisterAttachedReadOnly(
|
||||
"HeaderVisibility",
|
||||
typeof(Visibility),
|
||||
typeof(ControlHelper),
|
||||
new FrameworkPropertyMetadata(Visibility.Collapsed));
|
||||
|
||||
public static readonly DependencyProperty HeaderVisibilityProperty =
|
||||
HeaderVisibilityPropertyKey.DependencyProperty;
|
||||
|
||||
public static Visibility GetHeaderVisibility(Control control)
|
||||
{
|
||||
return (Visibility)control.GetValue(HeaderVisibilityProperty);
|
||||
}
|
||||
|
||||
private static void SetHeaderVisibility(Control control, Visibility value)
|
||||
{
|
||||
control.SetValue(HeaderVisibilityPropertyKey, value);
|
||||
}
|
||||
|
||||
private static void UpdateHeaderVisibility(Control control)
|
||||
{
|
||||
Visibility visibility;
|
||||
|
||||
if (GetHeaderTemplate(control) != null)
|
||||
{
|
||||
visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
visibility = IsNullOrEmptyString(GetHeader(control)) ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
SetHeaderVisibility(control, visibility);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text that is displayed in the control until the value is changed
|
||||
/// by a user action or some other operation.
|
||||
/// </summary>
|
||||
/// <param name="control">The element from which to read the property value.</param>
|
||||
/// <returns>
|
||||
/// The text that is displayed in the control when no value is entered. The default
|
||||
/// is an empty string ("").
|
||||
/// </returns>
|
||||
public static string GetPlaceholderText(Control control)
|
||||
{
|
||||
return (string)control.GetValue(PlaceholderTextProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the text that is displayed in the control until the value is changed
|
||||
/// by a user action or some other operation.
|
||||
/// </summary>
|
||||
/// <param name="control">The element on which to set the attached property.</param>
|
||||
/// <param name="value">The property value to set.</param>
|
||||
public static void SetPlaceholderText(Control control, string value)
|
||||
{
|
||||
control.SetValue(PlaceholderTextProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the PlaceholderText dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PlaceholderTextProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"PlaceholderText",
|
||||
typeof(string),
|
||||
typeof(ControlHelper),
|
||||
new FrameworkPropertyMetadata(string.Empty, OnPlaceholderTextChanged));
|
||||
|
||||
private static void OnPlaceholderTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
UpdatePlaceholderTextVisibility((Control)d);
|
||||
}
|
||||
|
||||
public static Visibility GetPlaceholderTextVisibility(Control control)
|
||||
{
|
||||
return (Visibility)control.GetValue(PlaceholderTextVisibilityProperty);
|
||||
}
|
||||
|
||||
private static void SetPlaceholderTextVisibility(Control control, Visibility value)
|
||||
{
|
||||
control.SetValue(PlaceholderTextVisibilityPropertyKey, value);
|
||||
}
|
||||
|
||||
private static readonly DependencyPropertyKey PlaceholderTextVisibilityPropertyKey =
|
||||
DependencyProperty.RegisterAttachedReadOnly(
|
||||
"PlaceholderTextVisibility",
|
||||
typeof(Visibility),
|
||||
typeof(ControlHelper),
|
||||
new FrameworkPropertyMetadata(Visibility.Collapsed));
|
||||
|
||||
public static readonly DependencyProperty PlaceholderTextVisibilityProperty =
|
||||
PlaceholderTextVisibilityPropertyKey.DependencyProperty;
|
||||
|
||||
private static void UpdatePlaceholderTextVisibility(Control control)
|
||||
{
|
||||
SetPlaceholderTextVisibility(control, string.IsNullOrEmpty(GetPlaceholderText(control)) ? Visibility.Collapsed : Visibility.Visible);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a brush that describes the color of placeholder text.
|
||||
/// </summary>
|
||||
/// <param name="control">The element from which to read the property value.</param>
|
||||
/// <returns>The brush that describes the color of placeholder text.</returns>
|
||||
public static Brush GetPlaceholderForeground(Control control)
|
||||
{
|
||||
return (Brush)control.GetValue(PlaceholderForegroundProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a brush that describes the color of placeholder text.
|
||||
/// </summary>
|
||||
/// <param name="control">The element on which to set the attached property.</param>
|
||||
/// <param name="value">The property value to set.</param>
|
||||
public static void SetPlaceholderForeground(Control control, Brush value)
|
||||
{
|
||||
control.SetValue(PlaceholderForegroundProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the PlaceholderForeground dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PlaceholderForegroundProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"PlaceholderForeground",
|
||||
typeof(Brush),
|
||||
typeof(ControlHelper),
|
||||
null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets content that is shown below the control. The content should provide
|
||||
/// guidance about the input expected by the control.
|
||||
/// </summary>
|
||||
/// <param name="control">The element from which to read the property value.</param>
|
||||
/// <returns>The content to be displayed below the control. The default is **null**.</returns>
|
||||
public static object GetDescription(Control control)
|
||||
{
|
||||
return control.GetValue(DescriptionProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets content that is shown below the control. The content should provide
|
||||
/// guidance about the input expected by the control.
|
||||
/// </summary>
|
||||
/// <param name="control">The element on which to set the attached property.</param>
|
||||
/// <param name="value">The property value to set.</param>
|
||||
public static void SetDescription(Control control, object value)
|
||||
{
|
||||
control.SetValue(DescriptionProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the Description dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DescriptionProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"Description",
|
||||
typeof(object),
|
||||
typeof(ControlHelper),
|
||||
new FrameworkPropertyMetadata(OnDescriptionChanged));
|
||||
|
||||
private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
UpdateDescriptionVisibility((Control)d);
|
||||
}
|
||||
|
||||
private static readonly DependencyPropertyKey DescriptionVisibilityPropertyKey =
|
||||
DependencyProperty.RegisterAttachedReadOnly(
|
||||
"DescriptionVisibility",
|
||||
typeof(Visibility),
|
||||
typeof(ControlHelper),
|
||||
new FrameworkPropertyMetadata(Visibility.Collapsed));
|
||||
|
||||
public static readonly DependencyProperty DescriptionVisibilityProperty =
|
||||
DescriptionVisibilityPropertyKey.DependencyProperty;
|
||||
|
||||
public static Visibility GetDescriptionVisibility(Control control)
|
||||
{
|
||||
return (Visibility)control.GetValue(DescriptionVisibilityProperty);
|
||||
}
|
||||
|
||||
private static void SetDescriptionVisibility(Control control, Visibility value)
|
||||
{
|
||||
control.SetValue(DescriptionVisibilityPropertyKey, value);
|
||||
}
|
||||
|
||||
private static void UpdateDescriptionVisibility(Control control)
|
||||
{
|
||||
SetDescriptionVisibility(control, IsNullOrEmptyString(GetDescription(control)) ? Visibility.Collapsed : Visibility.Visible);
|
||||
}
|
||||
|
||||
internal static bool IsNullOrEmptyString(object obj)
|
||||
{
|
||||
return obj == null || (obj is string s && string.IsNullOrEmpty(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/modules/colorPicker/ColorPickerUI/Helpers/EditorState.cs
Normal file
21
src/modules/colorPicker/ColorPickerUI/Helpers/EditorState.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
// 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 ColorPicker.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Cross-cutting color-editor UI state shared between the color picker control (which sets
|
||||
/// the flag while the adjust-color flyout is open) and the global Esc handling in
|
||||
/// AppStateHandler (which reads it). Kept in its own static holder so the control does not
|
||||
/// depend on the window-coupled AppStateHandler.
|
||||
/// </summary>
|
||||
internal static class EditorState
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the Escape key should be blocked from
|
||||
/// closing the color picker editor (true while the adjust-color flyout is open).
|
||||
/// </summary>
|
||||
public static bool BlockEscapeKeyClosingColorPickerEditor { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,7 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Windows.Foundation;
|
||||
|
||||
using static ColorPicker.NativeMethods;
|
||||
|
||||
@@ -22,12 +21,12 @@ namespace ColorPicker.Helpers
|
||||
{
|
||||
var info = new MonitorInfoEx();
|
||||
GetMonitorInfo(new HandleRef(null, monitor), info);
|
||||
Bounds = new System.Windows.Rect(
|
||||
Bounds = new Windows.Foundation.Rect(
|
||||
info.rcMonitor.left,
|
||||
info.rcMonitor.top,
|
||||
info.rcMonitor.right - info.rcMonitor.left,
|
||||
info.rcMonitor.bottom - info.rcMonitor.top);
|
||||
WorkingArea = new System.Windows.Rect(
|
||||
WorkingArea = new Windows.Foundation.Rect(
|
||||
info.rcWork.left,
|
||||
info.rcWork.top,
|
||||
info.rcWork.right - info.rcWork.left,
|
||||
@@ -36,11 +35,6 @@ namespace ColorPicker.Helpers
|
||||
Name = new string(info.szDevice).TrimEnd((char)0);
|
||||
}
|
||||
|
||||
public static DpiScale GetCurrentMonitorDpi()
|
||||
{
|
||||
return VisualTreeHelper.GetDpi(Application.Current.MainWindow);
|
||||
}
|
||||
|
||||
public static IEnumerable<MonitorResolutionHelper> AllMonitors
|
||||
{
|
||||
get
|
||||
@@ -52,9 +46,9 @@ namespace ColorPicker.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public System.Windows.Rect Bounds { get; private set; }
|
||||
public Windows.Foundation.Rect Bounds { get; private set; }
|
||||
|
||||
public System.Windows.Rect WorkingArea { get; private set; }
|
||||
public Windows.Foundation.Rect WorkingArea { get; private set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// 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.Threading;
|
||||
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI.Dispatching;
|
||||
|
||||
namespace ColorPicker.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// WinUI 3 replacement for <c>Common.UI.NativeEventWaiter</c>. Waits on a Windows
|
||||
/// named <see cref="EventWaitHandle"/> on a background thread and marshals the
|
||||
/// callback onto the captured UI-thread <see cref="DispatcherQueue"/> via
|
||||
/// <see cref="DispatcherQueue.TryEnqueue(DispatcherQueueHandler)"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The Common.UI original uses <c>System.Windows.Threading.Dispatcher.BeginInvoke</c>,
|
||||
/// which has no WinUI 3 equivalent. This copy keeps the original
|
||||
/// <see cref="WaitHandle.WaitAny(WaitHandle[])"/> shape so the worker wakes the instant
|
||||
/// <paramref name="cancel"/> is signalled (ColorPicker's ExitToken is cancelled on runner
|
||||
/// exit / shutdown), rather than polling.
|
||||
/// </remarks>
|
||||
public static class NativeEventWaiter
|
||||
{
|
||||
/// <summary>
|
||||
/// Spawns a background worker that invokes <paramref name="callback"/> on the current
|
||||
/// thread's <see cref="DispatcherQueue"/> whenever the named event fires, and exits when
|
||||
/// <paramref name="cancel"/> is signalled. MUST be called from a thread that owns a
|
||||
/// <see cref="DispatcherQueue"/> (the WinUI UI thread); otherwise the registration is skipped.
|
||||
/// </summary>
|
||||
public static void WaitForEventLoop(string eventName, Action callback, CancellationToken cancel)
|
||||
{
|
||||
var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
if (dispatcherQueue == null)
|
||||
{
|
||||
Logger.LogError($"[NativeEventWaiter] No DispatcherQueue on the calling thread for event: {eventName}. Call from the UI thread.");
|
||||
return;
|
||||
}
|
||||
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
|
||||
var handles = new WaitHandle[] { cancel.WaitHandle, eventHandle };
|
||||
while (true)
|
||||
{
|
||||
// Index 0 == cancel (exit immediately); index 1 == event signalled.
|
||||
if (WaitHandle.WaitAny(handles) == 1)
|
||||
{
|
||||
dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
callback();
|
||||
}
|
||||
catch (Exception callbackEx)
|
||||
{
|
||||
Logger.LogError($"[NativeEventWaiter] Callback failed for event {eventName}: {callbackEx.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"[NativeEventWaiter] Event loop failed for {eventName}: {ex.Message}");
|
||||
}
|
||||
})
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = $"NativeEventWaiter_{eventName}",
|
||||
};
|
||||
|
||||
thread.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace ColorPicker.Helpers
|
||||
{
|
||||
|
||||
@@ -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 System;
|
||||
|
||||
using Microsoft.Windows.ApplicationModel.Resources;
|
||||
|
||||
namespace ColorPicker.Helpers
|
||||
{
|
||||
internal static class ResourceLoaderInstance
|
||||
{
|
||||
private static Func<string, string> _getString;
|
||||
|
||||
internal static Func<string, string> GetString
|
||||
{
|
||||
get => _getString ??= CreateDefault();
|
||||
set => _getString = value;
|
||||
}
|
||||
|
||||
private static Func<string, string> CreateDefault()
|
||||
{
|
||||
var loader = new ResourceLoader("PowerToys.ColorPickerUI.pri");
|
||||
return loader.GetString;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,9 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Windows.Media;
|
||||
|
||||
using ColorPicker.Models;
|
||||
using Windows.UI;
|
||||
|
||||
namespace ColorPicker.Helpers
|
||||
{
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.ComponentModel.Composition;
|
||||
using System.Threading;
|
||||
using System.Windows.Threading;
|
||||
|
||||
using Microsoft.UI.Dispatching;
|
||||
|
||||
namespace ColorPicker.Helpers
|
||||
{
|
||||
[Export(typeof(IThrottledActionInvoker))]
|
||||
// MEF [Export] removed; registered in AppServices.Register.
|
||||
public sealed class ThrottledActionInvoker : IThrottledActionInvoker
|
||||
{
|
||||
private readonly DispatcherQueueTimer _timer;
|
||||
|
||||
private Lock _invokerLock = new Lock();
|
||||
private Action _actionToRun;
|
||||
|
||||
private DispatcherTimer _timer;
|
||||
|
||||
public ThrottledActionInvoker()
|
||||
{
|
||||
_timer = new DispatcherTimer();
|
||||
// Must be constructed on the UI thread so it binds to the app's DispatcherQueue.
|
||||
var queue = DispatcherQueue.GetForCurrentThread()
|
||||
?? throw new InvalidOperationException("ThrottledActionInvoker must be created on a thread with a DispatcherQueue (the UI thread).");
|
||||
_timer = queue.CreateTimer();
|
||||
_timer.IsRepeating = false; // one-shot debounce: DispatcherQueueTimer repeats by default.
|
||||
_timer.Tick += Timer_Tick;
|
||||
}
|
||||
|
||||
@@ -27,19 +31,19 @@ namespace ColorPicker.Helpers
|
||||
{
|
||||
lock (_invokerLock)
|
||||
{
|
||||
if (_timer.IsEnabled)
|
||||
if (_timer.IsRunning)
|
||||
{
|
||||
_timer.Stop();
|
||||
}
|
||||
|
||||
_actionToRun = action;
|
||||
_timer.Interval = new TimeSpan(0, 0, 0, 0, milliseconds);
|
||||
_timer.Interval = TimeSpan.FromMilliseconds(milliseconds);
|
||||
|
||||
_timer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void Timer_Tick(object sender, EventArgs e)
|
||||
private void Timer_Tick(DispatcherQueueTimer sender, object e)
|
||||
{
|
||||
lock (_invokerLock)
|
||||
{
|
||||
|
||||
@@ -3,45 +3,59 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using ColorPicker.ViewModelContracts;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Windows.Graphics;
|
||||
using Windows.Graphics.DirectX;
|
||||
using WinUIEx;
|
||||
|
||||
using Point = Windows.Foundation.Point;
|
||||
|
||||
namespace ColorPicker.Helpers
|
||||
{
|
||||
[Export(typeof(ZoomWindowHelper))]
|
||||
/// <summary>
|
||||
/// Drives the zoom magnifier: captures a small screen region around the cursor with GDI,
|
||||
/// hands it to the Win2D-backed <see cref="Views.ZoomView"/> (via <see cref="ZoomWindow"/>),
|
||||
/// and centers the transparent magnifier window on the cursor once, when the zoom session first
|
||||
/// starts. The window then stays put while the wheel keeps zooming the region it opened over.
|
||||
/// </summary>
|
||||
public class ZoomWindowHelper
|
||||
{
|
||||
private const int ZoomFactor = 2;
|
||||
private const int BaseZoomImageSize = 50;
|
||||
private const int MaxZoomLevel = 4;
|
||||
private const int MinZoomLevel = 0;
|
||||
private const int WindowChrome = 30; // Border (12) + canvas (3) margins on each side.
|
||||
|
||||
// ALT-1: the magnifier window is a constant size for the whole session — the level-4
|
||||
// (factor 8) bounding box — so the centred card always fits and never clips. Only the inner
|
||||
// card animates its size (Composition scale in ZoomView), so there is no per-step window
|
||||
// resize and no shrink-trim bookkeeping.
|
||||
private const int MaxWindowSize = (BaseZoomImageSize * 8) + WindowChrome; // 50*8 + 30 = 430
|
||||
|
||||
private static readonly Bitmap _bmp = new Bitmap(BaseZoomImageSize, BaseZoomImageSize, PixelFormat.Format32bppArgb);
|
||||
private static readonly Graphics _graphics = Graphics.FromImage(_bmp);
|
||||
|
||||
private readonly IZoomViewModel _zoomViewModel;
|
||||
private readonly AppStateHandler _appStateHandler;
|
||||
|
||||
private int _currentZoomLevel;
|
||||
private int _previousZoomLevel;
|
||||
|
||||
private ZoomWindow _zoomWindow;
|
||||
private CanvasBitmap _capturedBitmap;
|
||||
private double _zoomFactorValue = 1;
|
||||
private bool _zoomWindowVisible;
|
||||
|
||||
[ImportingConstructor]
|
||||
public ZoomWindowHelper(IZoomViewModel zoomViewModel, AppStateHandler appStateHandler)
|
||||
public ZoomWindowHelper(AppStateHandler appStateHandler)
|
||||
{
|
||||
_zoomViewModel = zoomViewModel;
|
||||
_appStateHandler = appStateHandler;
|
||||
_appStateHandler.AppClosed += AppStateHandler_AppClosed;
|
||||
_appStateHandler.AppHidden += AppStateHandler_AppClosed;
|
||||
_appStateHandler.AppClosed += (s, e) => CloseZoomWindow();
|
||||
_appStateHandler.AppHidden += (s, e) => CloseZoomWindow();
|
||||
}
|
||||
|
||||
public void Zoom(System.Windows.Point position, bool zoomIn)
|
||||
public void Zoom(Point position, bool zoomIn)
|
||||
{
|
||||
if (zoomIn && _currentZoomLevel < MaxZoomLevel)
|
||||
{
|
||||
@@ -65,39 +79,53 @@ namespace ColorPicker.Helpers
|
||||
{
|
||||
_currentZoomLevel = 0;
|
||||
_previousZoomLevel = 0;
|
||||
HideZoomWindow(true);
|
||||
_zoomWindowVisible = false;
|
||||
_zoomWindow?.ZoomViewControl.ResetScale();
|
||||
_zoomWindow?.ZoomViewControl.ClearBitmap();
|
||||
_zoomWindow?.Hide();
|
||||
|
||||
// Release this session's captured GPU surface (the ZoomView reference is cleared above
|
||||
// so the canvas will not draw a disposed bitmap).
|
||||
_capturedBitmap?.Dispose();
|
||||
_capturedBitmap = null;
|
||||
}
|
||||
|
||||
private void SetZoomImage(System.Windows.Point point)
|
||||
private void SetZoomImage(Point point)
|
||||
{
|
||||
if (_currentZoomLevel == 0)
|
||||
{
|
||||
HideZoomWindow();
|
||||
_zoomWindowVisible = false;
|
||||
_zoomWindow?.ZoomViewControl.ResetScale();
|
||||
_zoomWindow?.Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// we just started zooming, copy screen area
|
||||
// Capture once when a zoom session starts (previous level was 0).
|
||||
if (_previousZoomLevel == 0)
|
||||
{
|
||||
// First, exclude the color picker window from the capture; otherwise its
|
||||
// corner will be included in the zoomed-in image.
|
||||
var mainWindowHandle = _appStateHandler.GetMainWindowHandle();
|
||||
bool exclusionSuccess =
|
||||
WindowCaptureExclusionHelper.Exclude(mainWindowHandle);
|
||||
// Release the previous session's GPU surface before capturing a new one. The window
|
||||
// is hidden between sessions, so clear the ZoomView reference first (it is not
|
||||
// drawing this bitmap) and then dispose.
|
||||
_zoomWindow?.ZoomViewControl.ClearBitmap();
|
||||
_capturedBitmap?.Dispose();
|
||||
_capturedBitmap = null;
|
||||
|
||||
var mainWindowHandle = _appStateHandler.GetMainWindowHandle();
|
||||
bool exclusionSuccess = WindowCaptureExclusionHelper.Exclude(mainWindowHandle);
|
||||
try
|
||||
{
|
||||
var x = (int)point.X - (BaseZoomImageSize / 2);
|
||||
var y = (int)point.Y - (BaseZoomImageSize / 2);
|
||||
|
||||
_graphics.CopyFromScreen(x, y, 0, 0, _bmp.Size, CopyPixelOperation.SourceCopy);
|
||||
|
||||
_zoomViewModel.ZoomArea = BitmapToImageSource(_bmp);
|
||||
_capturedBitmap = BitmapToCanvasBitmap(_bmp);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// CopyFromScreen can fail (invalid screen DC) on a non-interactive session.
|
||||
_capturedBitmap = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Restore the color picker window to normal display affinity so that
|
||||
// it can be captured again.
|
||||
if (exclusionSuccess)
|
||||
{
|
||||
WindowCaptureExclusionHelper.Include(mainWindowHandle);
|
||||
@@ -105,94 +133,69 @@ namespace ColorPicker.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
_zoomViewModel.ZoomFactor = Math.Pow(ZoomFactor, _currentZoomLevel - 1);
|
||||
_zoomFactorValue = Math.Pow(ZoomFactor, _currentZoomLevel - 1);
|
||||
|
||||
ShowZoomWindow(point);
|
||||
// The size the card is animating FROM: the previous level's factor (or the current one
|
||||
// on first appearance, which makes the scale a no-op snap).
|
||||
double previousFactor = _previousZoomLevel >= 1 ? Math.Pow(ZoomFactor, _previousZoomLevel - 1) : _zoomFactorValue;
|
||||
ShowZoomWindow(point, previousFactor);
|
||||
}
|
||||
|
||||
private static BitmapImage BitmapToImageSource(Bitmap bitmap)
|
||||
private static CanvasBitmap BitmapToCanvasBitmap(Bitmap bitmap)
|
||||
{
|
||||
using (MemoryStream memory = new MemoryStream())
|
||||
var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||||
try
|
||||
{
|
||||
bitmap.Save(memory, ImageFormat.Bmp);
|
||||
memory.Position = 0;
|
||||
var bytes = new byte[data.Stride * data.Height];
|
||||
Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
|
||||
|
||||
BitmapImage bitmapimage = new BitmapImage();
|
||||
bitmapimage.BeginInit();
|
||||
bitmapimage.StreamSource = memory;
|
||||
bitmapimage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapimage.EndInit();
|
||||
|
||||
return bitmapimage;
|
||||
// GDI 32bppArgb is BGRA in memory, matching B8G8R8A8.
|
||||
return CanvasBitmap.CreateFromBytes(CanvasDevice.GetSharedDevice(), bytes, bitmap.Width, bitmap.Height, DirectXPixelFormat.B8G8R8A8UIntNormalized);
|
||||
}
|
||||
finally
|
||||
{
|
||||
bitmap.UnlockBits(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void HideZoomWindow(bool fully = false)
|
||||
private void ShowZoomWindow(Point point, double previousFactor)
|
||||
{
|
||||
if (_zoomWindow != null)
|
||||
if (_capturedBitmap == null)
|
||||
{
|
||||
_zoomWindow.Opacity = 0;
|
||||
_zoomViewModel.DesiredWidth = 0;
|
||||
_zoomViewModel.DesiredHeight = 0;
|
||||
|
||||
if (fully)
|
||||
{
|
||||
_zoomWindow.Hide();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowZoomWindow(System.Windows.Point point)
|
||||
{
|
||||
_zoomWindow ??= new ZoomWindow
|
||||
{
|
||||
Content = _zoomViewModel,
|
||||
Opacity = 0,
|
||||
};
|
||||
_zoomWindow ??= new ZoomWindow();
|
||||
|
||||
if (!_zoomWindow.IsVisible)
|
||||
// Draw the capture at the FINAL zoom factor now; the compositor scales that crisp texture
|
||||
// during the resize tween (see ZoomView.AnimateResize).
|
||||
_zoomWindow.ZoomViewControl.SetZoom(_capturedBitmap, _zoomFactorValue);
|
||||
|
||||
if (!_zoomWindowVisible)
|
||||
{
|
||||
// First appearance this session. Give the window its constant max size (the level-4
|
||||
// bounding box, so the centred card never clips) and center it on the cursor ONCE,
|
||||
// then leave it there for the rest of the session — matching the WPF behavior, which
|
||||
// only repositions the window while it is still transparent (Opacity < 0.5). Moving
|
||||
// it on every scroll step would drag the magnifier to wherever the cursor currently
|
||||
// is instead of keeping it on — and zooming — the region it was first opened over.
|
||||
// (AppWindow.Size and the cursor are both physical pixels; the card is centred inside.)
|
||||
_zoomWindow.SetWindowSize(MaxWindowSize, MaxWindowSize);
|
||||
|
||||
var appWindow = _zoomWindow.AppWindow;
|
||||
appWindow.Move(new PointInt32((int)point.X - (appWindow.Size.Width / 2), (int)point.Y - (appWindow.Size.Height / 2)));
|
||||
|
||||
// The card shows directly at the current size (no tween), so there is no scale
|
||||
// animation to race against the async Show().
|
||||
_zoomWindow.ZoomViewControl.ResetScale();
|
||||
_zoomWindow.Show();
|
||||
_zoomWindowVisible = true;
|
||||
}
|
||||
|
||||
if (_zoomWindow.Opacity < 0.5)
|
||||
else
|
||||
{
|
||||
var halfWidth = _zoomWindow.Width / 2;
|
||||
var halfHeight = _zoomWindow.Height / 2;
|
||||
|
||||
// usually takes 1-3 iterations to converge
|
||||
// 5 is just an arbitrary limit to prevent infinite loops
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
// mouse position relative to top left of _zoomWindow
|
||||
var scaledPoint = _zoomWindow.PointFromScreen(point);
|
||||
|
||||
var diffX = scaledPoint.X - halfWidth;
|
||||
var diffY = scaledPoint.Y - halfHeight;
|
||||
|
||||
// minimum difference that is considered important
|
||||
const double minDiff = 0.05;
|
||||
if (Math.Abs(diffX) < minDiff && Math.Abs(diffY) < minDiff)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
_zoomWindow.Left += diffX;
|
||||
_zoomWindow.Top += diffY;
|
||||
}
|
||||
|
||||
// make sure color picker window is on top of just opened zoom window
|
||||
AppStateHandler.SetTopMost();
|
||||
_zoomWindow.Opacity = 1;
|
||||
// Same already-shown, laid-out window: animate the card between the old and new size.
|
||||
_zoomWindow.ZoomViewControl.AnimateResize(previousFactor, _zoomFactorValue);
|
||||
}
|
||||
|
||||
_zoomViewModel.DesiredHeight = BaseZoomImageSize * _zoomViewModel.ZoomFactor;
|
||||
_zoomViewModel.DesiredWidth = BaseZoomImageSize * _zoomViewModel.ZoomFactor;
|
||||
}
|
||||
|
||||
private void AppStateHandler_AppClosed(object sender, EventArgs e)
|
||||
{
|
||||
CloseZoomWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.ComponentModel.Composition;
|
||||
using System.Windows.Input;
|
||||
|
||||
using ColorPicker.Helpers;
|
||||
using ColorPicker.Settings;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
|
||||
using Windows.System;
|
||||
|
||||
using static ColorPicker.NativeMethods;
|
||||
|
||||
namespace ColorPicker.Keyboard
|
||||
{
|
||||
[Export(typeof(KeyboardMonitor))]
|
||||
public class KeyboardMonitor : IDisposable
|
||||
{
|
||||
private readonly AppStateHandler _appStateHandler;
|
||||
@@ -26,9 +24,8 @@ namespace ColorPicker.Keyboard
|
||||
private GlobalKeyboardHook _keyboardHook;
|
||||
private bool _activationShortcutPressed;
|
||||
private int keyboardMoveSpeed;
|
||||
private Key lastArrowKeyPressed = Key.None;
|
||||
private VirtualKey lastArrowKeyPressed = VirtualKey.None;
|
||||
|
||||
[ImportingConstructor]
|
||||
public KeyboardMonitor(AppStateHandler appStateHandler, IUserSettings userSettings)
|
||||
{
|
||||
_appStateHandler = appStateHandler;
|
||||
@@ -70,40 +67,40 @@ namespace ColorPicker.Keyboard
|
||||
var virtualCode = e.KeyboardData.VirtualCode;
|
||||
|
||||
// ESC pressed
|
||||
if (virtualCode == KeyInterop.VirtualKeyFromKey(Key.Escape) && e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown)
|
||||
if (virtualCode == (int)VirtualKey.Escape && e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown)
|
||||
{
|
||||
e.Handled = _appStateHandler.HandleEscPressed();
|
||||
return;
|
||||
}
|
||||
|
||||
if ((virtualCode == KeyInterop.VirtualKeyFromKey(Key.Space) || virtualCode == KeyInterop.VirtualKeyFromKey(Key.Enter)) && (e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown))
|
||||
if ((virtualCode == (int)VirtualKey.Space || virtualCode == (int)VirtualKey.Enter) && (e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown))
|
||||
{
|
||||
e.Handled = _appStateHandler.HandleEnterPressed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (virtualCode == KeyInterop.VirtualKeyFromKey(Key.Back) && e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown)
|
||||
if (virtualCode == (int)VirtualKey.Back && e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown)
|
||||
{
|
||||
e.Handled = _appStateHandler.HandleEscPressed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (CheckMoveNeeded(virtualCode, Key.Up, e, 0, -1))
|
||||
if (CheckMoveNeeded(virtualCode, VirtualKey.Up, e, 0, -1))
|
||||
{
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
else if (CheckMoveNeeded(virtualCode, Key.Down, e, 0, 1))
|
||||
else if (CheckMoveNeeded(virtualCode, VirtualKey.Down, e, 0, 1))
|
||||
{
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
else if (CheckMoveNeeded(virtualCode, Key.Left, e, -1, 0))
|
||||
else if (CheckMoveNeeded(virtualCode, VirtualKey.Left, e, -1, 0))
|
||||
{
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
else if (CheckMoveNeeded(virtualCode, Key.Right, e, 1, 0))
|
||||
else if (CheckMoveNeeded(virtualCode, VirtualKey.Right, e, 1, 0))
|
||||
{
|
||||
e.Handled = true;
|
||||
return;
|
||||
@@ -144,9 +141,9 @@ namespace ColorPicker.Keyboard
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckMoveNeeded(int virtualCode, Key key, GlobalKeyboardHookEventArgs e, int xMove, int yMove)
|
||||
private bool CheckMoveNeeded(int virtualCode, VirtualKey key, GlobalKeyboardHookEventArgs e, int xMove, int yMove)
|
||||
{
|
||||
if (virtualCode == KeyInterop.VirtualKeyFromKey(key))
|
||||
if (virtualCode == (int)key)
|
||||
{
|
||||
if (e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown && _appStateHandler.IsColorPickerVisible())
|
||||
{
|
||||
@@ -165,7 +162,7 @@ namespace ColorPicker.Keyboard
|
||||
}
|
||||
else if (e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyUp)
|
||||
{
|
||||
lastArrowKeyPressed = Key.None;
|
||||
lastArrowKeyPressed = VirtualKey.None;
|
||||
keyboardMoveSpeed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<Window
|
||||
x:Class="ColorPicker.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:behaviors="clr-namespace:ColorPicker.Behaviors"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:e="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Width="120"
|
||||
Height="64"
|
||||
MinWidth="0"
|
||||
MinHeight="0"
|
||||
AllowsTransparency="True"
|
||||
AutomationProperties.Name="Color Picker"
|
||||
Background="Transparent"
|
||||
Opacity="0.1"
|
||||
ResizeMode="NoResize"
|
||||
ShowInTaskbar="False"
|
||||
SizeToContent="WidthAndHeight"
|
||||
SourceInitialized="MainWindowSourceInitialized"
|
||||
Topmost="True"
|
||||
WindowStyle="None"
|
||||
mc:Ignorable="d">
|
||||
<e:Interaction.Behaviors>
|
||||
<behaviors:ChangeWindowPositionBehavior />
|
||||
<behaviors:AppearAnimationBehavior />
|
||||
</e:Interaction.Behaviors>
|
||||
<ContentControl x:Name="MainView" Content="{Binding MainViewModel}" />
|
||||
</Window>
|
||||
@@ -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.ComponentModel.Composition;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
|
||||
using ColorPicker.ViewModelContracts;
|
||||
|
||||
namespace ColorPicker
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
Closing += MainWindow_Closing;
|
||||
Bootstrapper.InitializeContainer(this);
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
Show(); // Call show just to make sure source is initialized at startup.
|
||||
Hide();
|
||||
}
|
||||
|
||||
[Import]
|
||||
public IMainViewModel MainViewModel { get; set; }
|
||||
|
||||
private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
Closing -= MainWindow_Closing;
|
||||
Bootstrapper.Dispose();
|
||||
}
|
||||
|
||||
private void MainWindowSourceInitialized(object sender, System.EventArgs e)
|
||||
{
|
||||
this.MainViewModel.RegisterWindowHandle(HwndSource.FromHwnd(new WindowInteropHelper(this).Handle));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,10 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
|
||||
using ColorPicker.Helpers;
|
||||
using ManagedCommon;
|
||||
using Windows.UI;
|
||||
|
||||
namespace ColorPicker.Models
|
||||
{
|
||||
|
||||
@@ -11,10 +11,10 @@ namespace ColorPicker.Mouse
|
||||
{
|
||||
event EventHandler<Color> MouseColorChanged;
|
||||
|
||||
event EventHandler<System.Windows.Point> MousePositionChanged;
|
||||
event EventHandler<Windows.Foundation.Point> MousePositionChanged;
|
||||
|
||||
// position and bool indicating zoom in or zoom out
|
||||
event EventHandler<Tuple<System.Windows.Point, bool>> OnMouseWheel;
|
||||
event EventHandler<Tuple<Windows.Foundation.Point, bool>> OnMouseWheel;
|
||||
|
||||
event PrimaryMouseDownEventHandler OnPrimaryMouseDown;
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace ColorPicker.Mouse
|
||||
|
||||
event MiddleMouseDownEventHandler OnMiddleMouseDown;
|
||||
|
||||
System.Windows.Point CurrentPosition { get; }
|
||||
Windows.Foundation.Point CurrentPosition { get; }
|
||||
|
||||
Color CurrentColor { get; }
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Input;
|
||||
|
||||
using ManagedCommon;
|
||||
|
||||
@@ -19,6 +18,11 @@ namespace ColorPicker.Mouse
|
||||
|
||||
public delegate void MiddleMouseDownEventHandler(object sender, IntPtr wParam);
|
||||
|
||||
// WinUI 3 has no System.Windows.Input.MouseWheelEventArgs/InputManager. Carry the raw
|
||||
// wheel delta (the signed high word of MSLLHOOKSTRUCT.mouseData, +/-120 per notch)
|
||||
// directly; consumers decide zoom direction from its sign.
|
||||
public delegate void MouseWheelEventHandler(object sender, int delta);
|
||||
|
||||
internal class MouseHook
|
||||
{
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Interop object")]
|
||||
@@ -184,8 +188,9 @@ namespace ColorPicker.Mouse
|
||||
{
|
||||
if (MouseWheel != null)
|
||||
{
|
||||
MouseDevice mouseDev = InputManager.Current.PrimaryMouseDevice;
|
||||
MouseWheel.Invoke(null, new MouseWheelEventArgs(mouseDev, Environment.TickCount, (int)mouseHookStruct.mouseData >> 16));
|
||||
// High word of mouseData is the signed wheel delta.
|
||||
int delta = (int)mouseHookStruct.mouseData >> 16;
|
||||
MouseWheel.Invoke(null, delta);
|
||||
return new IntPtr(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,37 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.ComponentModel.Composition;
|
||||
using System.Configuration;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
|
||||
using ColorPicker.Helpers;
|
||||
using ColorPicker.Settings;
|
||||
using Microsoft.UI.Dispatching;
|
||||
|
||||
using static ColorPicker.NativeMethods;
|
||||
|
||||
using Point = Windows.Foundation.Point;
|
||||
|
||||
namespace ColorPicker.Mouse
|
||||
{
|
||||
[Export(typeof(IMouseInfoProvider))]
|
||||
[PartCreationPolicy(CreationPolicy.Shared)]
|
||||
public class MouseInfoProvider : IMouseInfoProvider
|
||||
{
|
||||
private readonly double _mousePullInfoIntervalInMs;
|
||||
private readonly DispatcherTimer _timer = new DispatcherTimer();
|
||||
private readonly DispatcherQueueTimer _timer;
|
||||
private readonly MouseHook _mouseHook;
|
||||
private readonly IUserSettings _userSettings;
|
||||
private System.Windows.Point _previousMousePosition = new System.Windows.Point(-1, 1);
|
||||
private Point _previousMousePosition = new Point(-1, 1);
|
||||
private Color _previousColor = Color.Transparent;
|
||||
private bool _colorFormatChanged;
|
||||
|
||||
[ImportingConstructor]
|
||||
public MouseInfoProvider(AppStateHandler appStateMonitor, IUserSettings userSettings)
|
||||
{
|
||||
_mousePullInfoIntervalInMs = 1000.0 / GetMainDisplayRefreshRate();
|
||||
|
||||
// WPF DispatcherTimer -> the UI-thread DispatcherQueueTimer. Resolve on the UI thread.
|
||||
_timer = DispatcherQueue.GetForCurrentThread().CreateTimer();
|
||||
_timer.Interval = TimeSpan.FromMilliseconds(_mousePullInfoIntervalInMs);
|
||||
_timer.Tick += Timer_Tick;
|
||||
|
||||
@@ -52,9 +51,9 @@ namespace ColorPicker.Mouse
|
||||
|
||||
public event EventHandler<Color> MouseColorChanged;
|
||||
|
||||
public event EventHandler<System.Windows.Point> MousePositionChanged;
|
||||
public event EventHandler<Point> MousePositionChanged;
|
||||
|
||||
public event EventHandler<Tuple<System.Windows.Point, bool>> OnMouseWheel;
|
||||
public event EventHandler<Tuple<Point, bool>> OnMouseWheel;
|
||||
|
||||
public event PrimaryMouseDownEventHandler OnPrimaryMouseDown;
|
||||
|
||||
@@ -62,23 +61,11 @@ namespace ColorPicker.Mouse
|
||||
|
||||
public event MiddleMouseDownEventHandler OnMiddleMouseDown;
|
||||
|
||||
public System.Windows.Point CurrentPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return _previousMousePosition;
|
||||
}
|
||||
}
|
||||
public Point CurrentPosition => _previousMousePosition;
|
||||
|
||||
public Color CurrentColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return _previousColor;
|
||||
}
|
||||
}
|
||||
public Color CurrentColor => _previousColor;
|
||||
|
||||
private void Timer_Tick(object sender, EventArgs e)
|
||||
private void Timer_Tick(DispatcherQueueTimer sender, object args)
|
||||
{
|
||||
UpdateMouseInfo();
|
||||
}
|
||||
@@ -101,24 +88,35 @@ namespace ColorPicker.Mouse
|
||||
}
|
||||
}
|
||||
|
||||
private static Color GetPixelColor(System.Windows.Point mousePosition)
|
||||
private static Color GetPixelColor(Point mousePosition)
|
||||
{
|
||||
var rect = new Rectangle((int)mousePosition.X, (int)mousePosition.Y, 1, 1);
|
||||
using (var bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb))
|
||||
try
|
||||
{
|
||||
using (var g = Graphics.FromImage(bmp)) // Ensure Graphics object is disposed
|
||||
var rect = new Rectangle((int)mousePosition.X, (int)mousePosition.Y, 1, 1);
|
||||
using (var bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb))
|
||||
{
|
||||
g.CopyFromScreen(rect.Left, rect.Top, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
|
||||
}
|
||||
using (var g = Graphics.FromImage(bmp))
|
||||
{
|
||||
g.CopyFromScreen(rect.Left, rect.Top, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
|
||||
}
|
||||
|
||||
return bmp.GetPixel(0, 0);
|
||||
return bmp.GetPixel(0, 0);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// GDI CopyFromScreen can throw "the handle is invalid" when no screen device
|
||||
// context is available (e.g. a non-interactive / disconnected session, or before
|
||||
// the desktop is ready at startup). Degrade gracefully instead of crashing the
|
||||
// app; the next sample succeeds once a real desktop is present.
|
||||
return Color.Black;
|
||||
}
|
||||
}
|
||||
|
||||
private static System.Windows.Point GetCursorPosition()
|
||||
private static Point GetCursorPosition()
|
||||
{
|
||||
GetCursorPos(out PointInter lpPoint);
|
||||
return (System.Windows.Point)lpPoint;
|
||||
return (Point)lpPoint;
|
||||
}
|
||||
|
||||
private static double GetMainDisplayRefreshRate()
|
||||
@@ -145,7 +143,7 @@ namespace ColorPicker.Mouse
|
||||
private void AppStateMonitor_AppShown(object sender, EventArgs e)
|
||||
{
|
||||
UpdateMouseInfo();
|
||||
if (!_timer.IsEnabled)
|
||||
if (!_timer.IsRunning)
|
||||
{
|
||||
_timer.Start();
|
||||
}
|
||||
@@ -161,15 +159,15 @@ namespace ColorPicker.Mouse
|
||||
}
|
||||
}
|
||||
|
||||
private void MouseHook_OnMouseWheel(object sender, MouseWheelEventArgs e)
|
||||
private void MouseHook_OnMouseWheel(object sender, int delta)
|
||||
{
|
||||
if (e.Delta == 0)
|
||||
if (delta == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var zoomIn = e.Delta > 0;
|
||||
OnMouseWheel?.Invoke(this, new Tuple<System.Windows.Point, bool>(_previousMousePosition, zoomIn));
|
||||
var zoomIn = delta > 0;
|
||||
OnMouseWheel?.Invoke(this, new Tuple<Point, bool>(_previousMousePosition, zoomIn));
|
||||
}
|
||||
|
||||
private void MouseHook_OnPrimaryMouseDown(object sender, IntPtr wParam)
|
||||
@@ -197,12 +195,12 @@ namespace ColorPicker.Mouse
|
||||
|
||||
private void DisposeHook()
|
||||
{
|
||||
if (_timer.IsEnabled)
|
||||
if (_timer.IsRunning)
|
||||
{
|
||||
_timer.Stop();
|
||||
}
|
||||
|
||||
_previousMousePosition = new System.Windows.Point(-1, 1);
|
||||
_previousMousePosition = new Point(-1, 1);
|
||||
_mouseHook.OnPrimaryMouseDown -= MouseHook_OnPrimaryMouseDown;
|
||||
_mouseHook.OnMouseWheel -= MouseHook_OnMouseWheel;
|
||||
_mouseHook.OnSecondaryMouseUp -= MouseHook_OnSecondaryMouseUp;
|
||||
|
||||
@@ -6,17 +6,15 @@ using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
|
||||
// System.Windows / System.Windows.Interop removed: WinUI 3 has no WindowInteropHelper.
|
||||
// HandleRef lives in System.Runtime.InteropServices (already imported).
|
||||
namespace ColorPicker
|
||||
{
|
||||
// https://learn.microsoft.com/visualstudio/code-quality/ca1060?view=vs-2019
|
||||
// will have to rename
|
||||
public static class NativeMethods
|
||||
{
|
||||
private const int GWL_EX_STYLE = -20;
|
||||
private const int WS_EX_TOOLWINDOW = 0x00000080;
|
||||
public const int WH_KEYBOARD_LL = 13;
|
||||
public const int VkSnapshot = 0x2c;
|
||||
public const int KfAltdown = 0x2000;
|
||||
@@ -117,7 +115,7 @@ namespace ColorPicker
|
||||
public int X;
|
||||
public int Y;
|
||||
|
||||
public static explicit operator System.Windows.Point(PointInter point) => new System.Windows.Point(point.X, point.Y);
|
||||
public static explicit operator Windows.Foundation.Point(PointInter point) => new Windows.Foundation.Point(point.X, point.Y);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
@@ -220,18 +218,6 @@ namespace ColorPicker
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
internal static extern int GetWindowText(int hwnd, StringBuilder text, int count);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
internal static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
internal static void SetToolWindowStyle(Window win)
|
||||
{
|
||||
var hwnd = new WindowInteropHelper(win).Handle;
|
||||
_ = SetWindowLong(hwnd, GWL_EX_STYLE, GetWindowLong(hwnd, GWL_EX_STYLE) | WS_EX_TOOLWINDOW);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the display affinity of a window, which controls how the window is
|
||||
/// displayed on a monitor. Used to exclude the picker window from ZoomWindow's
|
||||
|
||||
@@ -1,62 +1,38 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.Threading;
|
||||
|
||||
using ColorPicker.Helpers;
|
||||
using ColorPicker.Mouse;
|
||||
using ColorPickerUI;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace ColorPicker
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
private static string[] _args;
|
||||
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Logger.InitializeLogger("\\ColorPicker\\Logs");
|
||||
|
||||
_args = args;
|
||||
Logger.LogInfo($"Color Picker started with pid={Environment.ProcessId}");
|
||||
|
||||
if (PowerToys.GPOWrapperProjection.GPOWrapper.GetConfiguredColorPickerEnabledValue() == PowerToys.GPOWrapperProjection.GpoRuleConfigured.Disabled)
|
||||
if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredColorPickerEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
|
||||
{
|
||||
Logger.LogWarning("Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.");
|
||||
return;
|
||||
}
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
try
|
||||
WinRT.ComWrappersSupport.InitializeComWrappers();
|
||||
Application.Start((p) =>
|
||||
{
|
||||
using (var application = new App())
|
||||
{
|
||||
application.InitializeComponent();
|
||||
application.Run();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Unhandled exception", ex);
|
||||
CursorManager.RestoreOriginalCursors();
|
||||
}
|
||||
}
|
||||
|
||||
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
if (e.ExceptionObject is Exception ex)
|
||||
{
|
||||
Logger.LogError("Unhandled exception", ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogError("Unhandled exception");
|
||||
}
|
||||
|
||||
CursorManager.RestoreOriginalCursors();
|
||||
var context = new DispatcherQueueSynchronizationContext(
|
||||
DispatcherQueue.GetForCurrentThread());
|
||||
SynchronizationContext.SetSynchronizationContext(context);
|
||||
_ = new App(args);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,738 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ColorPicker.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ColorPicker.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Blue value.
|
||||
/// </summary>
|
||||
public static string Blue_value {
|
||||
get {
|
||||
return ResourceManager.GetString("Blue_value", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Cancel.
|
||||
/// </summary>
|
||||
public static string Cancel {
|
||||
get {
|
||||
return ResourceManager.GetString("Cancel", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Color code.
|
||||
/// </summary>
|
||||
public static string Color_Code {
|
||||
get {
|
||||
return ResourceManager.GetString("Color_Code", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Color history.
|
||||
/// </summary>
|
||||
public static string Color_History {
|
||||
get {
|
||||
return ResourceManager.GetString("Color_History", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Color palette.
|
||||
/// </summary>
|
||||
public static string Color_Palette {
|
||||
get {
|
||||
return ResourceManager.GetString("Color_Palette", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Color picker control.
|
||||
/// </summary>
|
||||
public static string Color_Picker_Control {
|
||||
get {
|
||||
return ResourceManager.GetString("Color_Picker_Control", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Copied to clipboard.
|
||||
/// </summary>
|
||||
public static string Copied_to_clipboard {
|
||||
get {
|
||||
return ResourceManager.GetString("Copied_to_clipboard", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Copy to clipboard.
|
||||
/// </summary>
|
||||
public static string Copy_to_clipboard {
|
||||
get {
|
||||
return ResourceManager.GetString("Copy_to_clipboard", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Color Picker editor.
|
||||
/// </summary>
|
||||
public static string cp_editor {
|
||||
get {
|
||||
return ResourceManager.GetString("cp_editor", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Color Picker.
|
||||
/// </summary>
|
||||
public static string CP_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("CP_Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Color dark 1.
|
||||
/// </summary>
|
||||
public static string Darker_color {
|
||||
get {
|
||||
return ResourceManager.GetString("Darker_color", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Color dark 2.
|
||||
/// </summary>
|
||||
public static string Darkest_color {
|
||||
get {
|
||||
return ResourceManager.GetString("Darkest_color", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Export.
|
||||
/// </summary>
|
||||
public static string Export_by {
|
||||
get {
|
||||
return ResourceManager.GetString("Export_by", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Grouped by color.
|
||||
/// </summary>
|
||||
public static string Export_by_color {
|
||||
get {
|
||||
return ResourceManager.GetString("Export_by_color", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Grouped by format.
|
||||
/// </summary>
|
||||
public static string Export_by_format {
|
||||
get {
|
||||
return ResourceManager.GetString("Export_by_format", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Green value.
|
||||
/// </summary>
|
||||
public static string Green_value {
|
||||
get {
|
||||
return ResourceManager.GetString("Green_value", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Hex value.
|
||||
/// </summary>
|
||||
public static string Hex_value {
|
||||
get {
|
||||
return ResourceManager.GetString("Hex_value", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Hue slider.
|
||||
/// </summary>
|
||||
public static string Hue_slider {
|
||||
get {
|
||||
return ResourceManager.GetString("Hue_slider", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Color light 1.
|
||||
/// </summary>
|
||||
public static string Lighter_color {
|
||||
get {
|
||||
return ResourceManager.GetString("Lighter_color", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Color light 2.
|
||||
/// </summary>
|
||||
public static string Lightest_color {
|
||||
get {
|
||||
return ResourceManager.GetString("Lightest color", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Pick.
|
||||
/// </summary>
|
||||
public static string New_Label {
|
||||
get {
|
||||
return ResourceManager.GetString("New_Label", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Press the Color Picker icon to capture a color from your screen..
|
||||
/// </summary>
|
||||
public static string No_colors_yet {
|
||||
get {
|
||||
return ResourceManager.GetString("No_colors_yet", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open settings.
|
||||
/// </summary>
|
||||
public static string Open_settings {
|
||||
get {
|
||||
return ResourceManager.GetString("Open_settings", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Pick color from screen.
|
||||
/// </summary>
|
||||
public static string Pick_color {
|
||||
get {
|
||||
return ResourceManager.GetString("Pick_color", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Red value.
|
||||
/// </summary>
|
||||
public static string Red_value {
|
||||
get {
|
||||
return ResourceManager.GetString("Red_value", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Remove.
|
||||
/// </summary>
|
||||
public static string Remove {
|
||||
get {
|
||||
return ResourceManager.GetString("Remove", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Saturation slider.
|
||||
/// </summary>
|
||||
public static string Saturation_slider {
|
||||
get {
|
||||
return ResourceManager.GetString("Saturation_slider", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Select.
|
||||
/// </summary>
|
||||
public static string Select {
|
||||
get {
|
||||
return ResourceManager.GetString("Select", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Select color.
|
||||
/// </summary>
|
||||
public static string Select_color {
|
||||
get {
|
||||
return ResourceManager.GetString("Select_color", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Selected color.
|
||||
/// </summary>
|
||||
public static string Selected_color {
|
||||
get {
|
||||
return ResourceManager.GetString("Selected_color", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to This color can be adjusted.
|
||||
/// </summary>
|
||||
public static string Selected_color_helptext {
|
||||
get {
|
||||
return ResourceManager.GetString("Selected_color_helptext", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Adjust color.
|
||||
/// </summary>
|
||||
public static string Selected_color_tooltip {
|
||||
get {
|
||||
return ResourceManager.GetString("Selected_color_tooltip", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Aqua.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_AQUA {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_AQUA", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Black.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_BLACK {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_BLACK", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Blue.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_BLUE {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_BLUE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Blue gray.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_BLUEGRAY {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_BLUEGRAY", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Bright green.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_BRIGHTGREEN {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_BRIGHTGREEN", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Brown.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_BROWN {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_BROWN", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Coral.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_CORAL {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_CORAL", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Dark blue.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_DARKBLUE {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_DARKBLUE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Dark gray.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_DARKGRAY {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_DARKGRAY", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Dark green.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_DARKGREEN {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_DARKGREEN", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Dark purple.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_DARKPURPLE {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_DARKPURPLE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Dark red.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_DARKRED {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_DARKRED", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Dark teal.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_DARKTEAL {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_DARKTEAL", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Dark yellow.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_DARKYELLOW {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_DARKYELLOW", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Gold.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_GOLD {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_GOLD", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Gray.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_GRAY {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_GRAY", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Green.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_GREEN {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_GREEN", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Ice blue.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_ICEBLUE {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_ICEBLUE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Indigo.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_INDIGO {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_INDIGO", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Lavender.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_LAVENDER {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_LAVENDER", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Light blue.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_LIGHTBLUE {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_LIGHTBLUE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Light gray.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_LIGHTGRAY {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_LIGHTGRAY", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Light green.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_LIGHTGREEN {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_LIGHTGREEN", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Light orange.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_LIGHTORANGE {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_LIGHTORANGE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Light turquoise.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_LIGHTTURQUOISE {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_LIGHTTURQUOISE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Light yellow.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_LIGHTYELLOW {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_LIGHTYELLOW", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Lime.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_LIME {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_LIME", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Olive green.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_OLIVEGREEN {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_OLIVEGREEN", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Orange.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_ORANGE {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_ORANGE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Pale blue.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_PALEBLUE {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_PALEBLUE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Periwinkle.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_PERIWINKLE {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_PERIWINKLE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Pink.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_PINK {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_PINK", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Plum.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_PLUM {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_PLUM", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Purple.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_PURPLE {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_PURPLE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Red.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_RED {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_RED", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Rose.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_ROSE {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_ROSE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Sky blue.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_SKYBLUE {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_SKYBLUE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Tan.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_TAN {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_TAN", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Teal.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_TEAL {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_TEAL", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Turquoise.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_TURQUOISE {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_TURQUOISE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to White.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_WHITE {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_WHITE", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Yellow.
|
||||
/// </summary>
|
||||
public static string TEXT_COLOR_YELLOW {
|
||||
get {
|
||||
return ResourceManager.GetString("TEXT_COLOR_YELLOW", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Value slider.
|
||||
/// </summary>
|
||||
public static string Value_slider {
|
||||
get {
|
||||
return ResourceManager.GetString("Value_slider", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ColorPicker.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.6.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
||||
@@ -1,218 +1,88 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="clr-namespace:ColorPicker.Converters"
|
||||
xmlns:helpers="clr-namespace:ColorPicker.Helpers">
|
||||
xmlns:converters="using:ColorPicker.Converters">
|
||||
|
||||
<FontFamily x:Key="IconFontFamily">Segoe Fluent Icons, Segoe MDL2 Assets</FontFamily>
|
||||
|
||||
<!-- CONVERTERS -->
|
||||
<BooleanToVisibilityConverter x:Key="bool2VisibilityConverter" />
|
||||
<converters:BoolToVisibilityConverter x:Key="bool2VisibilityConverter" />
|
||||
<converters:BoolToInvertedVisibilityConverter x:Key="bool2InvertedVisibilityConverter" />
|
||||
<converters:ColorToBrushConverter x:Key="colorToBrushConverter" />
|
||||
<converters:ColorToStringConverter x:Key="colorToStringConverter" />
|
||||
<converters:NumberToVisibilityConverter x:Key="numberToVisibilityConverter" />
|
||||
<converters:NumberToInvertedVisibilityConverter x:Key="numberToInvertedVisibilityConverter" />
|
||||
|
||||
<!--
|
||||
Copied from https://github.com/dotnet/wpf/blob/v9.0.1/src/Microsoft.DotNet.Wpf/src/Themes/PresentationFramework.Fluent/Styles/Button.xaml
|
||||
This can be removed and use AccentButtonStyle when https://github.com/dotnet/wpf/issues/10413 gets fixed.
|
||||
Subtle (transparent, hover-fill) icon button. Built on the native WinUI button
|
||||
template so it gets proper Fluent visual states for free.
|
||||
-->
|
||||
<Style x:Key="CustomAccentButtonStyle" TargetType="{x:Type Button}">
|
||||
<Setter Property="FocusVisualStyle" Value="{DynamicResource DefaultControlFocusVisualStyle}" />
|
||||
<Setter Property="Background" Value="{DynamicResource AccentButtonBackground}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForeground}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource AccentControlElevationBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{StaticResource ButtonBorderThemeThickness}" />
|
||||
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="Border.CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
|
||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border
|
||||
x:Name="ContentBorder"
|
||||
Width="{TemplateBinding Width}"
|
||||
Height="{TemplateBinding Height}"
|
||||
MinWidth="{TemplateBinding MinWidth}"
|
||||
MinHeight="{TemplateBinding MinHeight}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalAlignment}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding Border.CornerRadius}">
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
TextElement.Foreground="{TemplateBinding Foreground}" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="ContentBorder" Property="Background" Value="{DynamicResource AccentButtonBackgroundPointerOver}" />
|
||||
<Setter TargetName="ContentBorder" Property="BorderBrush" Value="{DynamicResource ControlElevationBorderBrush}" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource AccentButtonForegroundPointerOver}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="ContentBorder" Property="Background" Value="{DynamicResource AccentButtonBackgroundPressed}" />
|
||||
<Setter TargetName="ContentBorder" Property="BorderBrush" Value="{DynamicResource AccentControlElevationBorderBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="ContentBorder" Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
|
||||
<Setter TargetName="ContentBorder" Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="TitleBarCloseButtonStyle" TargetType="{x:Type Button}">
|
||||
<Style
|
||||
x:Key="SubtleButtonStyle"
|
||||
BasedOn="{StaticResource DefaultButtonStyle}"
|
||||
TargetType="Button">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border
|
||||
x:Name="ContentBorder"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding Border.CornerRadius}">
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
TextElement.Foreground="{TemplateBinding Foreground}" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="ContentBorder" Property="Background" Value="Red" />
|
||||
<Setter TargetName="ContentPresenter" Property="TextElement.Foreground" Value="White" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SubtleButtonStyle" TargetType="{x:Type Button}">
|
||||
<Setter Property="FocusVisualStyle" Value="{DynamicResource DefaultControlFocusVisualStyle}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Focusable" Value="True" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="Padding" Value="1" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border
|
||||
x:Name="border"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4"
|
||||
SnapsToDevicePixels="true">
|
||||
<ContentPresenter
|
||||
x:Name="contentPresenter"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Focusable="False"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
TextElement.Foreground="{TemplateBinding Foreground}" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsDefaulted" Value="true">
|
||||
<Setter TargetName="border" Property="Background" Value="Transparent" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="true">
|
||||
<Setter TargetName="border" Property="Background" Value="{DynamicResource SubtleFillColorSecondaryBrush}" />
|
||||
<Setter TargetName="contentPresenter" Property="TextElement.Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="true">
|
||||
<Setter TargetName="border" Property="Background" Value="{DynamicResource SubtleFillColorTertiaryBrush}" />
|
||||
<Setter TargetName="contentPresenter" Property="TextElement.Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="false">
|
||||
<!-- <Setter TargetName="border" Property="Background" Value="{DynamicResource ControlFillColorDisabledBrush}" />
|
||||
<Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{DynamicResource TextFillColorDisabledBrush}"/>-->
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!--
|
||||
Color-shade swatch button: a thin colored bar whose content (a glyph/label) is
|
||||
revealed on hover. No native equivalent, so this 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).
|
||||
-->
|
||||
<Style x:Key="ColorShadeButtonStyle" TargetType="Button">
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
<Setter Property="Background" Value="{DynamicResource ButtonBackground}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{DynamicResource ButtonBorderThemeThickness}" />
|
||||
<Setter Property="Background" Value="{ThemeResource ButtonBackground}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource ButtonForeground}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource ButtonBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="Width" Value="12" />
|
||||
<Setter Property="MinWidth" Value="20" />
|
||||
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="FocusVisualStyle" Value="{DynamicResource {x:Static SystemParameters.FocusVisualStyleKey}}" />
|
||||
<!--<Setter Property="ui:FocusVisualHelper.UseSystemFocusVisuals" Value="{DynamicResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="ui:FocusVisualHelper.FocusVisualMargin" Value="-2" />-->
|
||||
<Setter Property="Stylus.IsPressAndHoldEnabled" Value="False" />
|
||||
<Setter Property="Width" Value="12" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
x:Name="Background"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="{TemplateBinding helpers:ControlHelper.CornerRadius}"
|
||||
SnapsToDevicePixels="True">
|
||||
<Grid x:Name="RootGrid">
|
||||
<Border
|
||||
x:Name="Border"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}">
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Focusable="False"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
Visibility="Collapsed" />
|
||||
x:Name="BackgroundBorder"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Border
|
||||
Padding="{TemplateBinding Padding}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}">
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Content="{TemplateBinding Content}"
|
||||
Opacity="0" />
|
||||
</Border>
|
||||
</Border>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Background" Property="Opacity" Value="0.8" />
|
||||
<Setter TargetName="ContentPresenter" Property="Visibility" Value="Visible" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="Background" Property="Opacity" Value="0.9" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal" />
|
||||
<VisualState x:Name="PointerOver">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="BackgroundBorder.Opacity" Value="0.8" />
|
||||
<Setter Target="ContentPresenter.Opacity" Value="1" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="BackgroundBorder.Opacity" Value="0.9" />
|
||||
<Setter Target="ContentPresenter.Opacity" Value="1" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Disabled" />
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:viewModels="clr-namespace:ColorPicker.ViewModels"
|
||||
xmlns:views="clr-namespace:ColorPicker.Views">
|
||||
<DataTemplate DataType="{x:Type viewModels:MainViewModel}">
|
||||
<views:MainView />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type viewModels:ZoomViewModel}">
|
||||
<views:ZoomView />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type viewModels:ColorEditorViewModel}">
|
||||
<views:ColorEditorView />
|
||||
</DataTemplate>
|
||||
</ResourceDictionary>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user