mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-15 03:07:56 +01:00
CmdPal: Light, dark, pink, and unicorns (#43505)
<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This PR introduces user settings for app mode themes (dark, light, or
system) and background customization options, including custom colors,
system accent colors, or custom images.
- Adds a new page to the Settings window with new appearance settings
and moves some existing settings there as well.
- Introduces a new core-level service abstraction, `IThemeService`, that
holds the state for the current theme.
- Uses the helper class `ResourceSwapper` to update application-level
XAML resources. The way WinUI / XAML handles these is painful, and XAML
Hot Reload is pain². Initialization must be lazy, as XAML resources can
only be accessed after the window is activated.
- `ThemeService` takes app and system settings and selects one of the
registered `IThemeProvider`s to calculate visuals and choose the
appropriate XAML resources.
- At the moment, there are two:
- `NormalThemeProvider`
- Provides the current uncolorized light and dark styles
- `ms-appx:///Styles/Theme.Normal.xaml`
- `ColorfulThemeProvider`
- Style that matches the Windows 11 visual style (based on the Start
menu) and colors
- `ms-appx:///Styles/Theme.Colorful.xaml`
- Applied when the background is colorized or a background image is
selected
- The app theme is applied only on the main window
(`WindowThemeSynchronizer` helper class can be used to synchronize other
windows if needed).
- Adds a new dependency on `Microsoft.Graphics.Win2D`.
- Adds a custom color picker popup; the one from the Community Toolkit
occasionally loses the selected color.
- Flyby: separates the keyword tag and localizable label for pages in
the Settings window navigation.
## Pictures? Pictures!
<img width="2027" height="1276" alt="image"
src="https://github.com/user-attachments/assets/e3485c71-7faa-495b-b455-b313ea6046ee"
/>
<img width="3776" height="2025" alt="image"
src="https://github.com/user-attachments/assets/820fa823-34d4-426d-b066-b1049dc3266f"
/>
Matching Windows accent color and tint:
<img width="3840" height="2160" alt="image"
src="https://github.com/user-attachments/assets/65f3b608-e282-4894-b7c8-e014a194f11f"
/>
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
- [x] Closes: #38444
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
---------
Co-authored-by: Niels Laute <niels.laute@live.nl>
This commit is contained in:
4
.github/actions/spell-check/expect.txt
vendored
4
.github/actions/spell-check/expect.txt
vendored
@@ -141,6 +141,7 @@ BITSPIXEL
|
||||
bla
|
||||
BLACKFRAME
|
||||
BLENDFUNCTION
|
||||
blittable
|
||||
Blockquotes
|
||||
blt
|
||||
BLURBEHIND
|
||||
@@ -250,6 +251,7 @@ colorformat
|
||||
colorhistory
|
||||
colorhistorylimit
|
||||
COLORKEY
|
||||
colorref
|
||||
comctl
|
||||
comdlg
|
||||
comexp
|
||||
@@ -1860,8 +1862,10 @@ Uniquifies
|
||||
unitconverter
|
||||
unittests
|
||||
UNLEN
|
||||
Uninitializes
|
||||
UNORM
|
||||
unremapped
|
||||
Unsubscribes
|
||||
unvirtualized
|
||||
unwide
|
||||
unzoom
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.10" />
|
||||
<!-- Including Microsoft.Bcl.AsyncInterfaces to force version, since it's used by Microsoft.SemanticKernel. -->
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
<PackageVersion Include="Microsoft.Windows.CppWinRT" Version="2.0.240111.5" />
|
||||
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.16" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI" Version="9.9.1" />
|
||||
|
||||
@@ -16,4 +16,5 @@ public static partial class CLSID
|
||||
public static readonly Guid CollatorDataSource = new Guid("9E175B8B-F52A-11D8-B9A5-505054503030");
|
||||
public static readonly Guid ApplicationActivationManager = new Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C");
|
||||
public static readonly Guid VirtualDesktopManager = new("aa509086-5ca9-4c25-8f95-589d3c07b48a");
|
||||
public static readonly Guid DesktopWallpaper = new("C2CF3110-460E-4FC1-B9D0-8A1C0C9CC4BD");
|
||||
}
|
||||
|
||||
@@ -16,6 +16,12 @@ public static partial class Ole32
|
||||
CLSCTX dwClsContext,
|
||||
ref Guid riid,
|
||||
out IntPtr rReturnedComObject);
|
||||
|
||||
[LibraryImport("ole32.dll")]
|
||||
internal static partial int CoInitializeEx(nint pvReserved, uint dwCoInit);
|
||||
|
||||
[LibraryImport("ole32.dll")]
|
||||
internal static partial void CoUninitialize();
|
||||
}
|
||||
|
||||
[Flags]
|
||||
|
||||
@@ -14,6 +14,7 @@ using Microsoft.CommandPalette.Extensions;
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public partial class ShellViewModel : ObservableObject,
|
||||
IDisposable,
|
||||
IRecipient<PerformCommandMessage>,
|
||||
IRecipient<HandleCommandResultMessage>
|
||||
{
|
||||
@@ -460,4 +461,12 @@ public partial class ShellViewModel : ObservableObject,
|
||||
{
|
||||
_navigationCts?.Cancel();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_handleInvokeTask?.Dispose();
|
||||
_navigationCts?.Dispose();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
// 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.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.UI;
|
||||
using Windows.UI.ViewManagement;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public sealed partial class AppearanceSettingsViewModel : ObservableObject, IDisposable
|
||||
{
|
||||
private static readonly ObservableCollection<Color> WindowsColorSwatches = [
|
||||
|
||||
// row 0
|
||||
Color.FromArgb(255, 255, 185, 0), // #ffb900
|
||||
Color.FromArgb(255, 255, 140, 0), // #ff8c00
|
||||
Color.FromArgb(255, 247, 99, 12), // #f7630c
|
||||
Color.FromArgb(255, 202, 80, 16), // #ca5010
|
||||
Color.FromArgb(255, 218, 59, 1), // #da3b01
|
||||
Color.FromArgb(255, 239, 105, 80), // #ef6950
|
||||
|
||||
// row 1
|
||||
Color.FromArgb(255, 209, 52, 56), // #d13438
|
||||
Color.FromArgb(255, 255, 67, 67), // #ff4343
|
||||
Color.FromArgb(255, 231, 72, 86), // #e74856
|
||||
Color.FromArgb(255, 232, 17, 35), // #e81123
|
||||
Color.FromArgb(255, 234, 0, 94), // #ea005e
|
||||
Color.FromArgb(255, 195, 0, 82), // #c30052
|
||||
|
||||
// row 2
|
||||
Color.FromArgb(255, 227, 0, 140), // #e3008c
|
||||
Color.FromArgb(255, 191, 0, 119), // #bf0077
|
||||
Color.FromArgb(255, 194, 57, 179), // #c239b3
|
||||
Color.FromArgb(255, 154, 0, 137), // #9a0089
|
||||
Color.FromArgb(255, 0, 120, 212), // #0078d4
|
||||
Color.FromArgb(255, 0, 99, 177), // #0063b1
|
||||
|
||||
// row 3
|
||||
Color.FromArgb(255, 142, 140, 216), // #8e8cd8
|
||||
Color.FromArgb(255, 107, 105, 214), // #6b69d6
|
||||
Color.FromArgb(255, 135, 100, 184), // #8764b8
|
||||
Color.FromArgb(255, 116, 77, 169), // #744da9
|
||||
Color.FromArgb(255, 177, 70, 194), // #b146c2
|
||||
Color.FromArgb(255, 136, 23, 152), // #881798
|
||||
|
||||
// row 4
|
||||
Color.FromArgb(255, 0, 153, 188), // #0099bc
|
||||
Color.FromArgb(255, 45, 125, 154), // #2d7d9a
|
||||
Color.FromArgb(255, 0, 183, 195), // #00b7c3
|
||||
Color.FromArgb(255, 3, 131, 135), // #038387
|
||||
Color.FromArgb(255, 0, 178, 148), // #00b294
|
||||
Color.FromArgb(255, 1, 133, 116), // #018574
|
||||
|
||||
// row 5
|
||||
Color.FromArgb(255, 0, 204, 106), // #00cc6a
|
||||
Color.FromArgb(255, 16, 137, 62), // #10893e
|
||||
Color.FromArgb(255, 122, 117, 116), // #7a7574
|
||||
Color.FromArgb(255, 93, 90, 88), // #5d5a58
|
||||
Color.FromArgb(255, 104, 118, 138), // #68768a
|
||||
Color.FromArgb(255, 81, 92, 107), // #515c6b
|
||||
|
||||
// row 6
|
||||
Color.FromArgb(255, 86, 124, 115), // #567c73
|
||||
Color.FromArgb(255, 72, 104, 96), // #486860
|
||||
Color.FromArgb(255, 73, 130, 5), // #498205
|
||||
Color.FromArgb(255, 16, 124, 16), // #107c10
|
||||
Color.FromArgb(255, 118, 118, 118), // #767676
|
||||
Color.FromArgb(255, 76, 74, 72), // #4c4a48
|
||||
|
||||
// row 7
|
||||
Color.FromArgb(255, 105, 121, 126), // #69797e
|
||||
Color.FromArgb(255, 74, 84, 89), // #4a5459
|
||||
Color.FromArgb(255, 100, 124, 100), // #647c64
|
||||
Color.FromArgb(255, 82, 94, 84), // #525e54
|
||||
Color.FromArgb(255, 132, 117, 69), // #847545
|
||||
Color.FromArgb(255, 126, 115, 95), // #7e735f
|
||||
];
|
||||
|
||||
private readonly SettingsModel _settings;
|
||||
private readonly UISettings _uiSettings;
|
||||
private readonly IThemeService _themeService;
|
||||
private readonly DispatcherQueueTimer _saveTimer = DispatcherQueue.GetForCurrentThread().CreateTimer();
|
||||
private readonly DispatcherQueue _uiDispatcher = DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
private ElementTheme? _elementThemeOverride;
|
||||
private Color _currentSystemAccentColor;
|
||||
|
||||
public ObservableCollection<Color> Swatches => WindowsColorSwatches;
|
||||
|
||||
public int ThemeIndex
|
||||
{
|
||||
get => (int)_settings.Theme;
|
||||
set => Theme = (UserTheme)value;
|
||||
}
|
||||
|
||||
public UserTheme Theme
|
||||
{
|
||||
get => _settings.Theme;
|
||||
set
|
||||
{
|
||||
if (_settings.Theme != value)
|
||||
{
|
||||
_settings.Theme = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(ThemeIndex));
|
||||
Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ColorizationMode ColorizationMode
|
||||
{
|
||||
get => _settings.ColorizationMode;
|
||||
set
|
||||
{
|
||||
if (_settings.ColorizationMode != value)
|
||||
{
|
||||
_settings.ColorizationMode = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(ColorizationModeIndex));
|
||||
OnPropertyChanged(nameof(IsCustomTintVisible));
|
||||
OnPropertyChanged(nameof(IsCustomTintIntensityVisible));
|
||||
OnPropertyChanged(nameof(IsBackgroundControlsVisible));
|
||||
OnPropertyChanged(nameof(IsNoBackgroundVisible));
|
||||
OnPropertyChanged(nameof(IsAccentColorControlsVisible));
|
||||
|
||||
if (value == ColorizationMode.WindowsAccentColor)
|
||||
{
|
||||
ThemeColor = _currentSystemAccentColor;
|
||||
}
|
||||
|
||||
IsColorizationDetailsExpanded = value != ColorizationMode.None;
|
||||
|
||||
Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int ColorizationModeIndex
|
||||
{
|
||||
get => (int)_settings.ColorizationMode;
|
||||
set => ColorizationMode = (ColorizationMode)value;
|
||||
}
|
||||
|
||||
public Color ThemeColor
|
||||
{
|
||||
get => _settings.CustomThemeColor;
|
||||
set
|
||||
{
|
||||
if (_settings.CustomThemeColor != value)
|
||||
{
|
||||
_settings.CustomThemeColor = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
|
||||
if (ColorIntensity == 0)
|
||||
{
|
||||
ColorIntensity = 100;
|
||||
}
|
||||
|
||||
Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int ColorIntensity
|
||||
{
|
||||
get => _settings.CustomThemeColorIntensity;
|
||||
set
|
||||
{
|
||||
_settings.CustomThemeColorIntensity = value;
|
||||
OnPropertyChanged();
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
public string BackgroundImagePath
|
||||
{
|
||||
get => _settings.BackgroundImagePath ?? string.Empty;
|
||||
set
|
||||
{
|
||||
if (_settings.BackgroundImagePath != value)
|
||||
{
|
||||
_settings.BackgroundImagePath = value;
|
||||
OnPropertyChanged();
|
||||
|
||||
if (BackgroundImageOpacity == 0)
|
||||
{
|
||||
BackgroundImageOpacity = 100;
|
||||
}
|
||||
|
||||
Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int BackgroundImageOpacity
|
||||
{
|
||||
get => _settings.BackgroundImageOpacity;
|
||||
set
|
||||
{
|
||||
if (_settings.BackgroundImageOpacity != value)
|
||||
{
|
||||
_settings.BackgroundImageOpacity = value;
|
||||
OnPropertyChanged();
|
||||
Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int BackgroundImageBrightness
|
||||
{
|
||||
get => _settings.BackgroundImageBrightness;
|
||||
set
|
||||
{
|
||||
if (_settings.BackgroundImageBrightness != value)
|
||||
{
|
||||
_settings.BackgroundImageBrightness = value;
|
||||
OnPropertyChanged();
|
||||
Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int BackgroundImageBlurAmount
|
||||
{
|
||||
get => _settings.BackgroundImageBlurAmount;
|
||||
set
|
||||
{
|
||||
if (_settings.BackgroundImageBlurAmount != value)
|
||||
{
|
||||
_settings.BackgroundImageBlurAmount = value;
|
||||
OnPropertyChanged();
|
||||
Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BackgroundImageFit BackgroundImageFit
|
||||
{
|
||||
get => _settings.BackgroundImageFit;
|
||||
set
|
||||
{
|
||||
if (_settings.BackgroundImageFit != value)
|
||||
{
|
||||
_settings.BackgroundImageFit = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(BackgroundImageFitIndex));
|
||||
Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int BackgroundImageFitIndex
|
||||
{
|
||||
// Naming between UI facing string and enum is a bit confusing, but the enum fields
|
||||
// are based on XAML Stretch enum values. So I'm choosing to keep the confusion here, close
|
||||
// to the UI.
|
||||
// - BackgroundImageFit.Fill corresponds to "Stretch"
|
||||
// - BackgroundImageFit.UniformToFill corresponds to "Fill"
|
||||
get => BackgroundImageFit switch
|
||||
{
|
||||
BackgroundImageFit.Fill => 1,
|
||||
_ => 0,
|
||||
};
|
||||
set => BackgroundImageFit = value switch
|
||||
{
|
||||
1 => BackgroundImageFit.Fill,
|
||||
_ => BackgroundImageFit.UniformToFill,
|
||||
};
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsColorizationDetailsExpanded { get; set; }
|
||||
|
||||
public bool IsCustomTintVisible => _settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.Image;
|
||||
|
||||
public bool IsCustomTintIntensityVisible => _settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.WindowsAccentColor or ColorizationMode.Image;
|
||||
|
||||
public bool IsBackgroundControlsVisible => _settings.ColorizationMode is ColorizationMode.Image;
|
||||
|
||||
public bool IsNoBackgroundVisible => _settings.ColorizationMode is ColorizationMode.None;
|
||||
|
||||
public bool IsAccentColorControlsVisible => _settings.ColorizationMode is ColorizationMode.WindowsAccentColor;
|
||||
|
||||
public AcrylicBackdropParameters EffectiveBackdrop { get; private set; } = new(Colors.Black, Colors.Black, 0.5f, 0.5f);
|
||||
|
||||
public ElementTheme EffectiveTheme => _elementThemeOverride ?? _themeService.Current.Theme;
|
||||
|
||||
public Color EffectiveThemeColor => ColorizationMode switch
|
||||
{
|
||||
ColorizationMode.WindowsAccentColor => _currentSystemAccentColor,
|
||||
ColorizationMode.CustomColor or ColorizationMode.Image => ThemeColor,
|
||||
_ => Colors.Transparent,
|
||||
};
|
||||
|
||||
// Since the blur amount is absolute, we need to scale it down for the preview (which is smaller than full screen).
|
||||
public int EffectiveBackgroundImageBlurAmount => (int)Math.Round(BackgroundImageBlurAmount / 4f);
|
||||
|
||||
public double EffectiveBackgroundImageBrightness => BackgroundImageBrightness / 100.0;
|
||||
|
||||
public ImageSource? EffectiveBackgroundImageSource =>
|
||||
ColorizationMode is ColorizationMode.Image
|
||||
&& !string.IsNullOrWhiteSpace(BackgroundImagePath)
|
||||
&& Uri.TryCreate(BackgroundImagePath, UriKind.RelativeOrAbsolute, out var uri)
|
||||
? new Microsoft.UI.Xaml.Media.Imaging.BitmapImage(uri)
|
||||
: null;
|
||||
|
||||
public AppearanceSettingsViewModel(IThemeService themeService, SettingsModel settings)
|
||||
{
|
||||
_themeService = themeService;
|
||||
_themeService.ThemeChanged += ThemeServiceOnThemeChanged;
|
||||
_settings = settings;
|
||||
|
||||
_uiSettings = new UISettings();
|
||||
_uiSettings.ColorValuesChanged += UiSettingsOnColorValuesChanged;
|
||||
UpdateAccentColor(_uiSettings);
|
||||
|
||||
Reapply();
|
||||
|
||||
IsColorizationDetailsExpanded = _settings.ColorizationMode != ColorizationMode.None;
|
||||
}
|
||||
|
||||
private void UiSettingsOnColorValuesChanged(UISettings sender, object args) => _uiDispatcher.TryEnqueue(() => UpdateAccentColor(sender));
|
||||
|
||||
private void UpdateAccentColor(UISettings sender)
|
||||
{
|
||||
_currentSystemAccentColor = sender.GetColorValue(UIColorType.Accent);
|
||||
if (ColorizationMode == ColorizationMode.WindowsAccentColor)
|
||||
{
|
||||
ThemeColor = _currentSystemAccentColor;
|
||||
}
|
||||
}
|
||||
|
||||
private void ThemeServiceOnThemeChanged(object? sender, ThemeChangedEventArgs e)
|
||||
{
|
||||
_saveTimer.Debounce(Reapply, TimeSpan.FromMilliseconds(200));
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
SettingsModel.SaveSettings(_settings);
|
||||
_saveTimer.Debounce(Reapply, TimeSpan.FromMilliseconds(200));
|
||||
}
|
||||
|
||||
private void Reapply()
|
||||
{
|
||||
// Theme services recalculates effective color and opacity based on current settings.
|
||||
EffectiveBackdrop = _themeService.Current.BackdropParameters;
|
||||
OnPropertyChanged(nameof(EffectiveBackdrop));
|
||||
OnPropertyChanged(nameof(EffectiveBackgroundImageBrightness));
|
||||
OnPropertyChanged(nameof(EffectiveBackgroundImageSource));
|
||||
OnPropertyChanged(nameof(EffectiveThemeColor));
|
||||
OnPropertyChanged(nameof(EffectiveBackgroundImageBlurAmount));
|
||||
|
||||
// LOAD BEARING:
|
||||
// We need to cycle through the EffectiveTheme property to force reload of resources.
|
||||
_elementThemeOverride = ElementTheme.Light;
|
||||
OnPropertyChanged(nameof(EffectiveTheme));
|
||||
_elementThemeOverride = ElementTheme.Dark;
|
||||
OnPropertyChanged(nameof(EffectiveTheme));
|
||||
_elementThemeOverride = null;
|
||||
OnPropertyChanged(nameof(EffectiveTheme));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ResetBackgroundImageProperties()
|
||||
{
|
||||
BackgroundImageBrightness = 0;
|
||||
BackgroundImageBlurAmount = 0;
|
||||
BackgroundImageFit = BackgroundImageFit.UniformToFill;
|
||||
BackgroundImageOpacity = 100;
|
||||
ColorIntensity = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_uiSettings.ColorValuesChanged -= UiSettingsOnColorValuesChanged;
|
||||
_themeService.ThemeChanged -= ThemeServiceOnThemeChanged;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public enum BackgroundImageFit
|
||||
{
|
||||
Fill,
|
||||
UniformToFill,
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public enum ColorizationMode
|
||||
{
|
||||
None,
|
||||
WindowsAccentColor,
|
||||
CustomColor,
|
||||
Image,
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// 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 CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public partial class MainWindowViewModel : ObservableObject, IDisposable
|
||||
{
|
||||
private readonly IThemeService _themeService;
|
||||
private readonly DispatcherQueue _uiDispatcherQueue = DispatcherQueue.GetForCurrentThread()!;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ImageSource? BackgroundImageSource { get; private set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Stretch BackgroundImageStretch { get; private set; } = Stretch.Fill;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial double BackgroundImageOpacity { get; private set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Color BackgroundImageTint { get; private set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial double BackgroundImageTintIntensity { get; private set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int BackgroundImageBlurAmount { get; private set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial double BackgroundImageBrightness { get; private set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool ShowBackgroundImage { get; private set; }
|
||||
|
||||
public MainWindowViewModel(IThemeService themeService)
|
||||
{
|
||||
_themeService = themeService;
|
||||
_themeService.ThemeChanged += ThemeService_ThemeChanged;
|
||||
}
|
||||
|
||||
private void ThemeService_ThemeChanged(object? sender, ThemeChangedEventArgs e)
|
||||
{
|
||||
_uiDispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
BackgroundImageSource = _themeService.Current.BackgroundImageSource;
|
||||
BackgroundImageStretch = _themeService.Current.BackgroundImageStretch;
|
||||
BackgroundImageOpacity = _themeService.Current.BackgroundImageOpacity;
|
||||
|
||||
BackgroundImageBrightness = _themeService.Current.BackgroundBrightness;
|
||||
BackgroundImageTint = _themeService.Current.Tint;
|
||||
BackgroundImageTintIntensity = _themeService.Current.TintIntensity;
|
||||
BackgroundImageBlurAmount = _themeService.Current.BlurAmount;
|
||||
|
||||
ShowBackgroundImage = BackgroundImageSource != null;
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_themeService.ThemeChanged -= ThemeService_ThemeChanged;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,12 @@
|
||||
<PackageReference Include="CommunityToolkit.Common" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||
<PackageReference Include="AdaptiveCards.Templating" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
|
||||
<PackageReference Include="Microsoft.Bot.AdaptiveExpressions.Core" />
|
||||
<PackageReference Include="AdaptiveCards.ObjectModel.WinUI3" GeneratePathProperty="true">
|
||||
<ExcludeAssets>compile</ExcludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="AdaptiveCards.Rendering.WinUI3" GeneratePathProperty="True" >
|
||||
<PackageReference Include="AdaptiveCards.Rendering.WinUI3" GeneratePathProperty="True">
|
||||
<ExcludeAssets>compile</ExcludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Microsoft.CmdPal.UI.ViewModels.Properties {
|
||||
// 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.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
@@ -411,6 +411,15 @@ namespace Microsoft.CmdPal.UI.ViewModels.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Pick background image.
|
||||
/// </summary>
|
||||
public static string builtin_settings_appearance_pick_background_image_title {
|
||||
get {
|
||||
return ResourceManager.GetString("builtin_settings_appearance_pick_background_image_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} extensions found.
|
||||
/// </summary>
|
||||
|
||||
@@ -239,4 +239,7 @@
|
||||
<data name="builtin_settings_extension_n_extensions_installed" xml:space="preserve">
|
||||
<value>{0} extensions installed</value>
|
||||
</data>
|
||||
<data name="builtin_settings_appearance_pick_background_image_title" xml:space="preserve">
|
||||
<value>Pick background image</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Windows.UI;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
|
||||
public sealed record AcrylicBackdropParameters(Color TintColor, Color FallbackColor, float TintOpacity, float LuminosityOpacity);
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Provides theme-related values for the Command Palette and notifies listeners about
|
||||
/// changes that affect visual appearance (theme, tint, background image, and backdrop).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implementations are expected to monitor system/app theme changes and raise
|
||||
/// <see cref="ThemeChanged"/> accordingly. Consumers should call <see cref="Initialize"/>
|
||||
/// once to hook required sources and then query properties/methods for the current visuals.
|
||||
/// </remarks>
|
||||
public interface IThemeService
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the effective theme or any visual-affecting setting changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Triggered for changes such as app theme (light/dark/default), background image,
|
||||
/// tint/accent, or backdrop parameters that would require UI to refresh styling.
|
||||
/// </remarks>
|
||||
event EventHandler<ThemeChangedEventArgs>? ThemeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the theme service and starts listening for theme-related changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Safe to call once during application startup before consuming the service.
|
||||
/// </remarks>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current theme settings.
|
||||
/// </summary>
|
||||
ThemeSnapshot Current { get; }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for theme-related changes. </summary>
|
||||
public class ThemeChangedEventArgs : EventArgs;
|
||||
@@ -0,0 +1,62 @@
|
||||
// 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.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a snapshot of theme-related visual settings, including accent color, theme preference, and background
|
||||
/// image configuration, for use in rendering the Command Palette UI.
|
||||
/// </summary>
|
||||
public sealed class ThemeSnapshot
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the accent tint color used by the Command Palette visuals.
|
||||
/// </summary>
|
||||
public required Color Tint { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the accent tint color used by the Command Palette visuals.
|
||||
/// </summary>
|
||||
public required float TintIntensity { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configured application theme preference.
|
||||
/// </summary>
|
||||
public required ElementTheme Theme { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the image source to render as the background, if any.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns <see langword="null"/> when no background image is configured.
|
||||
/// </remarks>
|
||||
public required ImageSource? BackgroundImageSource { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stretch mode used to lay out the background image.
|
||||
/// </summary>
|
||||
public required Stretch BackgroundImageStretch { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the opacity applied to the background image.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A value in the range [0, 1], where 0 is fully transparent and 1 is fully opaque.
|
||||
/// </value>
|
||||
public required double BackgroundImageOpacity { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the effective acrylic backdrop parameters based on current settings and theme.
|
||||
/// </summary>
|
||||
/// <returns>The resolved <c>AcrylicBackdropParameters</c> to apply.</returns>
|
||||
public required AcrylicBackdropParameters BackdropParameters { get; init; }
|
||||
|
||||
public required int BlurAmount { get; init; }
|
||||
|
||||
public required float BackgroundBrightness { get; init; }
|
||||
}
|
||||
@@ -11,7 +11,9 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Settings;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.UI;
|
||||
using Windows.Foundation;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
@@ -62,6 +64,24 @@ public partial class SettingsModel : ObservableObject
|
||||
|
||||
public EscapeKeyBehavior EscapeKeyBehaviorSetting { get; set; } = EscapeKeyBehavior.ClearSearchFirstThenGoBack;
|
||||
|
||||
public UserTheme Theme { get; set; } = UserTheme.Default;
|
||||
|
||||
public ColorizationMode ColorizationMode { get; set; }
|
||||
|
||||
public Color CustomThemeColor { get; set; } = Colors.Transparent;
|
||||
|
||||
public int CustomThemeColorIntensity { get; set; } = 100;
|
||||
|
||||
public int BackgroundImageOpacity { get; set; } = 20;
|
||||
|
||||
public int BackgroundImageBlurAmount { get; set; }
|
||||
|
||||
public int BackgroundImageBrightness { get; set; }
|
||||
|
||||
public BackgroundImageFit BackgroundImageFit { get; set; }
|
||||
|
||||
public string? BackgroundImagePath { get; set; }
|
||||
|
||||
// END SETTINGS
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using Microsoft.CmdPal.Core.Common.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Settings;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@@ -29,6 +31,8 @@ public partial class SettingsViewModel : INotifyPropertyChanged
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public AppearanceSettingsViewModel Appearance { get; }
|
||||
|
||||
public HotkeySettings? Hotkey
|
||||
{
|
||||
get => _settings.Hotkey;
|
||||
@@ -179,6 +183,9 @@ public partial class SettingsViewModel : INotifyPropertyChanged
|
||||
_settings = settings;
|
||||
_serviceProvider = serviceProvider;
|
||||
|
||||
var themeService = serviceProvider.GetRequiredService<IThemeService>();
|
||||
Appearance = new AppearanceSettingsViewModel(themeService, _settings);
|
||||
|
||||
var activeProviders = GetCommandProviders();
|
||||
var allProviderSettings = _settings.ProviderSettings;
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public enum UserTheme
|
||||
{
|
||||
Default,
|
||||
Light,
|
||||
Dark,
|
||||
}
|
||||
@@ -4,19 +4,23 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Microsoft.CmdPal.UI.Controls"
|
||||
xmlns:local="using:Microsoft.CmdPal.UI">
|
||||
xmlns:local="using:Microsoft.CmdPal.UI"
|
||||
xmlns:services="using:Microsoft.CmdPal.UI.Services">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<!-- Other merged dictionaries here -->
|
||||
<ResourceDictionary Source="Styles/Colors.xaml" />
|
||||
<ResourceDictionary Source="Styles/TextBlock.xaml" />
|
||||
<ResourceDictionary Source="Styles/TextBox.xaml" />
|
||||
<ResourceDictionary Source="Styles/Settings.xaml" />
|
||||
<ResourceDictionary Source="Controls/Tag.xaml" />
|
||||
<ResourceDictionary Source="Controls/KeyVisual/KeyVisual.xaml" />
|
||||
<ResourceDictionary Source="Controls/IsEnabledTextBlock.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Styles/Colors.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Styles/TextBlock.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Styles/TextBox.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Styles/Settings.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Controls/Tag.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Controls/KeyVisual/KeyVisual.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///Controls/IsEnabledTextBlock.xaml" />
|
||||
<!-- Default theme dictionary -->
|
||||
<ResourceDictionary Source="ms-appx:///Styles/Theme.Normal.xaml" />
|
||||
<services:MutableOverridesDictionary />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!-- Other app resources here -->
|
||||
|
||||
|
||||
@@ -24,9 +24,11 @@ using Microsoft.CmdPal.Ext.WindowsTerminal;
|
||||
using Microsoft.CmdPal.Ext.WindowWalker;
|
||||
using Microsoft.CmdPal.Ext.WinGet;
|
||||
using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.CmdPal.UI.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.BuiltinCommands;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
@@ -112,6 +114,17 @@ public partial class App : Application
|
||||
// Root services
|
||||
services.AddSingleton(TaskScheduler.FromCurrentSynchronizationContext());
|
||||
|
||||
AddBuiltInCommands(services);
|
||||
|
||||
AddCoreServices(services);
|
||||
|
||||
AddUIServices(services);
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
private static void AddBuiltInCommands(ServiceCollection services)
|
||||
{
|
||||
// Built-in Commands. Order matters - this is the order they'll be presented by default.
|
||||
var allApps = new AllAppsCommandProvider();
|
||||
var files = new IndexerCommandsProvider();
|
||||
@@ -154,17 +167,32 @@ public partial class App : Application
|
||||
services.AddSingleton<ICommandProvider, TimeDateCommandsProvider>();
|
||||
services.AddSingleton<ICommandProvider, SystemCommandExtensionProvider>();
|
||||
services.AddSingleton<ICommandProvider, RemoteDesktopCommandProvider>();
|
||||
}
|
||||
|
||||
private static void AddUIServices(ServiceCollection services)
|
||||
{
|
||||
// Models
|
||||
services.AddSingleton<TopLevelCommandManager>();
|
||||
services.AddSingleton<AliasManager>();
|
||||
services.AddSingleton<HotkeyManager>();
|
||||
var sm = SettingsModel.LoadSettings();
|
||||
services.AddSingleton(sm);
|
||||
var state = AppStateModel.LoadState();
|
||||
services.AddSingleton(state);
|
||||
services.AddSingleton<IExtensionService, ExtensionService>();
|
||||
|
||||
// Services
|
||||
services.AddSingleton<TopLevelCommandManager>();
|
||||
services.AddSingleton<AliasManager>();
|
||||
services.AddSingleton<HotkeyManager>();
|
||||
|
||||
services.AddSingleton<MainWindowViewModel>();
|
||||
services.AddSingleton<TrayIconService>();
|
||||
|
||||
services.AddSingleton<IThemeService, ThemeService>();
|
||||
services.AddSingleton<ResourceSwapper>();
|
||||
}
|
||||
|
||||
private static void AddCoreServices(ServiceCollection services)
|
||||
{
|
||||
// Core services
|
||||
services.AddSingleton<IExtensionService, ExtensionService>();
|
||||
services.AddSingleton<IRunHistoryService, RunHistoryService>();
|
||||
|
||||
services.AddSingleton<IRootPageService, PowerToysRootPageService>();
|
||||
@@ -174,7 +202,5 @@ public partial class App : Application
|
||||
// ViewModels
|
||||
services.AddSingleton<ShellViewModel>();
|
||||
services.AddSingleton<IPageViewModelFactoryService, CommandPalettePageViewModelFactory>();
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
// 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.Numerics;
|
||||
using ManagedCommon;
|
||||
using Microsoft.Graphics.Canvas.Effects;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Composition;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Hosting;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Controls;
|
||||
|
||||
internal sealed partial class BlurImageControl : Control
|
||||
{
|
||||
private const string ImageSourceParameterName = "ImageSource";
|
||||
|
||||
private const string BrightnessEffectName = "Brightness";
|
||||
private const string BrightnessOverlayEffectName = "BrightnessOverlay";
|
||||
private const string BlurEffectName = "Blur";
|
||||
private const string TintBlendEffectName = "TintBlend";
|
||||
private const string TintEffectName = "Tint";
|
||||
|
||||
#pragma warning disable CA1507 // Use nameof to express symbol names ... some of these refer to effect properties that are separate from the class properties
|
||||
private static readonly string BrightnessSource1AmountEffectProperty = GetPropertyName(BrightnessEffectName, "Source1Amount");
|
||||
private static readonly string BrightnessSource2AmountEffectProperty = GetPropertyName(BrightnessEffectName, "Source2Amount");
|
||||
private static readonly string BrightnessOverlayColorEffectProperty = GetPropertyName(BrightnessOverlayEffectName, "Color");
|
||||
private static readonly string BlurBlurAmountEffectProperty = GetPropertyName(BlurEffectName, "BlurAmount");
|
||||
private static readonly string TintColorEffectProperty = GetPropertyName(TintEffectName, "Color");
|
||||
#pragma warning restore CA1507
|
||||
|
||||
private static readonly string[] AnimatableProperties = [
|
||||
BrightnessSource1AmountEffectProperty,
|
||||
BrightnessSource2AmountEffectProperty,
|
||||
BrightnessOverlayColorEffectProperty,
|
||||
BlurBlurAmountEffectProperty,
|
||||
TintColorEffectProperty
|
||||
];
|
||||
|
||||
public static readonly DependencyProperty ImageSourceProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(ImageSource),
|
||||
typeof(ImageSource),
|
||||
typeof(BlurImageControl),
|
||||
new PropertyMetadata(null, OnImageChanged));
|
||||
|
||||
public static readonly DependencyProperty ImageStretchProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(ImageStretch),
|
||||
typeof(Stretch),
|
||||
typeof(BlurImageControl),
|
||||
new PropertyMetadata(Stretch.UniformToFill, OnImageStretchChanged));
|
||||
|
||||
public static readonly DependencyProperty ImageOpacityProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(ImageOpacity),
|
||||
typeof(double),
|
||||
typeof(BlurImageControl),
|
||||
new PropertyMetadata(1.0, OnOpacityChanged));
|
||||
|
||||
public static readonly DependencyProperty ImageBrightnessProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(ImageBrightness),
|
||||
typeof(double),
|
||||
typeof(BlurImageControl),
|
||||
new PropertyMetadata(1.0, OnBrightnessChanged));
|
||||
|
||||
public static readonly DependencyProperty BlurAmountProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(BlurAmount),
|
||||
typeof(double),
|
||||
typeof(BlurImageControl),
|
||||
new PropertyMetadata(0.0, OnBlurAmountChanged));
|
||||
|
||||
public static readonly DependencyProperty TintColorProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(TintColor),
|
||||
typeof(Color),
|
||||
typeof(BlurImageControl),
|
||||
new PropertyMetadata(Colors.Transparent, OnVisualPropertyChanged));
|
||||
|
||||
public static readonly DependencyProperty TintIntensityProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(TintIntensity),
|
||||
typeof(double),
|
||||
typeof(BlurImageControl),
|
||||
new PropertyMetadata(0.0, OnVisualPropertyChanged));
|
||||
|
||||
private Compositor? _compositor;
|
||||
private SpriteVisual? _effectVisual;
|
||||
private CompositionEffectBrush? _effectBrush;
|
||||
private CompositionSurfaceBrush? _imageBrush;
|
||||
|
||||
public BlurImageControl()
|
||||
{
|
||||
this.DefaultStyleKey = typeof(BlurImageControl);
|
||||
this.Loaded += OnLoaded;
|
||||
this.SizeChanged += OnSizeChanged;
|
||||
}
|
||||
|
||||
public ImageSource ImageSource
|
||||
{
|
||||
get => (ImageSource)GetValue(ImageSourceProperty);
|
||||
set => SetValue(ImageSourceProperty, value);
|
||||
}
|
||||
|
||||
public Stretch ImageStretch
|
||||
{
|
||||
get => (Stretch)GetValue(ImageStretchProperty);
|
||||
set => SetValue(ImageStretchProperty, value);
|
||||
}
|
||||
|
||||
public double ImageOpacity
|
||||
{
|
||||
get => (double)GetValue(ImageOpacityProperty);
|
||||
set => SetValue(ImageOpacityProperty, value);
|
||||
}
|
||||
|
||||
public double ImageBrightness
|
||||
{
|
||||
get => (double)GetValue(ImageBrightnessProperty);
|
||||
set => SetValue(ImageBrightnessProperty, Math.Clamp(value, -1, 1));
|
||||
}
|
||||
|
||||
public double BlurAmount
|
||||
{
|
||||
get => (double)GetValue(BlurAmountProperty);
|
||||
set => SetValue(BlurAmountProperty, value);
|
||||
}
|
||||
|
||||
public Color TintColor
|
||||
{
|
||||
get => (Color)GetValue(TintColorProperty);
|
||||
set => SetValue(TintColorProperty, value);
|
||||
}
|
||||
|
||||
public double TintIntensity
|
||||
{
|
||||
get => (double)GetValue(TintIntensityProperty);
|
||||
set => SetValue(TintIntensityProperty, value);
|
||||
}
|
||||
|
||||
private static void OnImageStretchChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is BlurImageControl control && control._imageBrush != null)
|
||||
{
|
||||
control._imageBrush.Stretch = ConvertStretch((Stretch)e.NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnVisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is BlurImageControl control && control._compositor != null)
|
||||
{
|
||||
control.UpdateEffect();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnOpacityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is BlurImageControl control && control._effectVisual != null)
|
||||
{
|
||||
control._effectVisual.Opacity = (float)(double)e.NewValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnBlurAmountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is BlurImageControl control && control._effectBrush != null)
|
||||
{
|
||||
control.UpdateEffect();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnBrightnessChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is BlurImageControl control && control._effectBrush != null)
|
||||
{
|
||||
control.UpdateEffect();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
InitializeComposition();
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
if (_effectVisual != null)
|
||||
{
|
||||
_effectVisual.Size = new Vector2(
|
||||
(float)Math.Max(1, e.NewSize.Width),
|
||||
(float)Math.Max(1, e.NewSize.Height));
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnImageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is not BlurImageControl control)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
control.EnsureEffect(force: true);
|
||||
control.UpdateEffect();
|
||||
}
|
||||
|
||||
private void InitializeComposition()
|
||||
{
|
||||
var visual = ElementCompositionPreview.GetElementVisual(this);
|
||||
_compositor = visual.Compositor;
|
||||
|
||||
_effectVisual = _compositor.CreateSpriteVisual();
|
||||
_effectVisual.Size = new Vector2(
|
||||
(float)Math.Max(1, ActualWidth),
|
||||
(float)Math.Max(1, ActualHeight));
|
||||
_effectVisual.Opacity = (float)ImageOpacity;
|
||||
|
||||
ElementCompositionPreview.SetElementChildVisual(this, _effectVisual);
|
||||
|
||||
UpdateEffect();
|
||||
}
|
||||
|
||||
private void EnsureEffect(bool force = false)
|
||||
{
|
||||
if (_compositor is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_effectBrush is not null && !force)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var imageSource = new CompositionEffectSourceParameter(ImageSourceParameterName);
|
||||
|
||||
// 1) Brightness via ArithmeticCompositeEffect
|
||||
// We blend between the original image and either black or white,
|
||||
// depending on whether we want to darken or brighten. BrightnessEffect isn't supported
|
||||
// in the composition graph.
|
||||
var brightnessEffect = new ArithmeticCompositeEffect
|
||||
{
|
||||
Name = BrightnessEffectName,
|
||||
Source1 = imageSource, // original image
|
||||
Source2 = new ColorSourceEffect
|
||||
{
|
||||
Name = BrightnessOverlayEffectName,
|
||||
Color = Colors.Black, // we'll swap black/white via properties
|
||||
},
|
||||
|
||||
MultiplyAmount = 0.0f,
|
||||
Source1Amount = 1.0f, // original
|
||||
Source2Amount = 0.0f, // overlay
|
||||
Offset = 0.0f,
|
||||
};
|
||||
|
||||
// 2) Blur
|
||||
var blurEffect = new GaussianBlurEffect
|
||||
{
|
||||
Name = BlurEffectName,
|
||||
BlurAmount = 0.0f,
|
||||
BorderMode = EffectBorderMode.Hard,
|
||||
Optimization = EffectOptimization.Balanced,
|
||||
Source = brightnessEffect,
|
||||
};
|
||||
|
||||
// 3) Tint (always in the chain; intensity via alpha)
|
||||
var tintEffect = new BlendEffect
|
||||
{
|
||||
Name = TintBlendEffectName,
|
||||
Background = blurEffect,
|
||||
Foreground = new ColorSourceEffect
|
||||
{
|
||||
Name = TintEffectName,
|
||||
Color = Colors.Transparent,
|
||||
},
|
||||
Mode = BlendEffectMode.Multiply,
|
||||
};
|
||||
|
||||
var effectFactory = _compositor.CreateEffectFactory(tintEffect, AnimatableProperties);
|
||||
|
||||
_effectBrush?.Dispose();
|
||||
_effectBrush = effectFactory.CreateBrush();
|
||||
|
||||
// Set initial source
|
||||
if (ImageSource is not null)
|
||||
{
|
||||
_imageBrush ??= _compositor.CreateSurfaceBrush();
|
||||
LoadImageAsync(ImageSource);
|
||||
_effectBrush.SetSourceParameter(ImageSourceParameterName, _imageBrush);
|
||||
}
|
||||
else
|
||||
{
|
||||
_effectBrush.SetSourceParameter(ImageSourceParameterName, _compositor.CreateBackdropBrush());
|
||||
}
|
||||
|
||||
if (_effectVisual is not null)
|
||||
{
|
||||
_effectVisual.Brush = _effectBrush;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateEffect()
|
||||
{
|
||||
if (_compositor is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureEffect();
|
||||
if (_effectBrush is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var props = _effectBrush.Properties;
|
||||
|
||||
// Brightness
|
||||
var b = (float)Math.Clamp(ImageBrightness, -1.0, 1.0);
|
||||
|
||||
float source1Amount;
|
||||
float source2Amount;
|
||||
Color overlayColor;
|
||||
|
||||
if (b >= 0)
|
||||
{
|
||||
// Brighten: blend towards white
|
||||
overlayColor = Colors.White;
|
||||
source1Amount = 1.0f - b; // original image contribution
|
||||
source2Amount = b; // white overlay contribution
|
||||
}
|
||||
else
|
||||
{
|
||||
// Darken: blend towards black
|
||||
overlayColor = Colors.Black;
|
||||
var t = -b; // 0..1
|
||||
source1Amount = 1.0f - t; // original image
|
||||
source2Amount = t; // black overlay
|
||||
}
|
||||
|
||||
props.InsertScalar(BrightnessSource1AmountEffectProperty, source1Amount);
|
||||
props.InsertScalar(BrightnessSource2AmountEffectProperty, source2Amount);
|
||||
props.InsertColor(BrightnessOverlayColorEffectProperty, overlayColor);
|
||||
|
||||
// Blur
|
||||
props.InsertScalar(BlurBlurAmountEffectProperty, (float)BlurAmount);
|
||||
|
||||
// Tint
|
||||
var tintColor = TintColor;
|
||||
var clampedIntensity = (float)Math.Clamp(TintIntensity, 0.0, 1.0);
|
||||
|
||||
var adjustedColor = Color.FromArgb(
|
||||
(byte)(clampedIntensity * 255),
|
||||
tintColor.R,
|
||||
tintColor.G,
|
||||
tintColor.B);
|
||||
|
||||
props.InsertColor(TintColorEffectProperty, adjustedColor);
|
||||
}
|
||||
|
||||
private void LoadImageAsync(ImageSource imageSource)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (imageSource is Microsoft.UI.Xaml.Media.Imaging.BitmapImage bitmapImage)
|
||||
{
|
||||
_imageBrush ??= _compositor?.CreateSurfaceBrush();
|
||||
if (_imageBrush is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var loadedSurface = LoadedImageSurface.StartLoadFromUri(bitmapImage.UriSource);
|
||||
loadedSurface.LoadCompleted += (_, _) =>
|
||||
{
|
||||
if (_imageBrush is not null)
|
||||
{
|
||||
_imageBrush.Surface = loadedSurface;
|
||||
_imageBrush.Stretch = ConvertStretch(ImageStretch);
|
||||
_imageBrush.BitmapInterpolationMode = CompositionBitmapInterpolationMode.Linear;
|
||||
}
|
||||
};
|
||||
|
||||
_effectBrush?.SetSourceParameter(ImageSourceParameterName, _imageBrush);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to load image for BlurImageControl: {0}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static CompositionStretch ConvertStretch(Stretch stretch)
|
||||
{
|
||||
return stretch switch
|
||||
{
|
||||
Stretch.None => CompositionStretch.None,
|
||||
Stretch.Fill => CompositionStretch.Fill,
|
||||
Stretch.Uniform => CompositionStretch.Uniform,
|
||||
Stretch.UniformToFill => CompositionStretch.UniformToFill,
|
||||
_ => CompositionStretch.UniformToFill,
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetPropertyName(string effectName, string propertyName) => $"{effectName}.{propertyName}";
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="Microsoft.CmdPal.UI.Controls.ColorPalette"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Microsoft.CmdPal.UI.Controls"
|
||||
xmlns:localConverters="using:Microsoft.CmdPal.UI.Converters"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:toolkitConverters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
|
||||
<toolkitConverters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
|
||||
<localConverters:ContrastBrushConverter x:Key="ContrastBrushConverter" />
|
||||
|
||||
<Style x:Key="PaletteGridViewItemStyle" TargetType="GridViewItem">
|
||||
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseHighBrush}" />
|
||||
<Setter Property="TabNavigation" Value="Local" />
|
||||
<Setter Property="IsHoldingEnabled" Value="True" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="MinWidth" Value="32" />
|
||||
<Setter Property="MinHeight" Value="32" />
|
||||
<Setter Property="AllowDrop" Value="False" />
|
||||
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="FocusVisualMargin" Value="-2" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="GridViewItem">
|
||||
<Grid
|
||||
x:Name="ContentBorder"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Control.IsTemplateFocusTarget="True"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
FocusVisualMargin="{TemplateBinding FocusVisualMargin}"
|
||||
RenderTransformOrigin="0.5,0.5">
|
||||
<Grid.RenderTransform>
|
||||
<ScaleTransform x:Name="ContentBorderScale" />
|
||||
</Grid.RenderTransform>
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}" />
|
||||
<!--
|
||||
The 'Xg' text simulates the amount of space one line of text will occupy.
|
||||
In the DataPlaceholder state, the Content is not loaded yet so we
|
||||
approximate the size of the item using placeholder text.
|
||||
-->
|
||||
<TextBlock
|
||||
x:Name="PlaceholderTextBlock"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Foreground="{x:Null}"
|
||||
IsHitTestVisible="False"
|
||||
Text="Xg"
|
||||
Visibility="Collapsed" />
|
||||
<Rectangle
|
||||
x:Name="PlaceholderRect"
|
||||
Fill="{ThemeResource ListViewItemPlaceholderBackground}"
|
||||
Visibility="Collapsed" />
|
||||
<Rectangle
|
||||
x:Name="BorderRectangle"
|
||||
IsHitTestVisible="False"
|
||||
Opacity="0"
|
||||
RadiusX="6"
|
||||
RadiusY="6"
|
||||
Stroke="{ThemeResource SystemControlHighlightListAccentLowBrush}"
|
||||
StrokeThickness="2" />
|
||||
<Border
|
||||
x:Name="MultiSelectSquare"
|
||||
Width="20"
|
||||
Height="20"
|
||||
Margin="0,2,2,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Background="{ThemeResource SystemControlBackgroundChromeMediumBrush}"
|
||||
CornerRadius="6"
|
||||
Visibility="Collapsed">
|
||||
<FontIcon
|
||||
x:Name="MultiSelectCheck"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumHighBrush}"
|
||||
Glyph=""
|
||||
Opacity="0" />
|
||||
</Border>
|
||||
<Border
|
||||
x:Name="MultiArrangeOverlayTextBorder"
|
||||
Height="20"
|
||||
MinWidth="20"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Background="{ThemeResource SystemControlBackgroundAccentBrush}"
|
||||
BorderBrush="{ThemeResource SystemControlBackgroundChromeWhiteBrush}"
|
||||
BorderThickness="2"
|
||||
CornerRadius="6"
|
||||
IsHitTestVisible="False"
|
||||
Opacity="0">
|
||||
<TextBlock
|
||||
x:Name="MultiArrangeOverlayText"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
IsHitTestVisible="False"
|
||||
Opacity="0"
|
||||
Style="{ThemeResource CaptionTextBlockStyle}"
|
||||
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.DragItemsCount}" />
|
||||
</Border>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="FocusStates">
|
||||
<VisualState x:Name="Focused">
|
||||
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BorderRectangle" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Unfocused" />
|
||||
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal">
|
||||
|
||||
<Storyboard>
|
||||
<PointerUpThemeAnimation Storyboard.TargetName="ContentPresenter" />
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
<VisualState x:Name="PointerOver">
|
||||
|
||||
<Storyboard>
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="BorderRectangle"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
To="1"
|
||||
Duration="0" />
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BorderRectangle" Storyboard.TargetProperty="Stroke">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding Converter={StaticResource ContrastBrushConverter}, ConverterParameter={ThemeResource TextControlForeground}}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding Converter={StaticResource ContrastBrushConverter}, ConverterParameter={ThemeResource TextControlForeground}}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentBorder" Storyboard.TargetProperty="FocusVisualSecondaryBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding Converter={StaticResource ContrastBrushConverter}, ConverterParameter={ThemeResource TextControlForeground}}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentBorder" Storyboard.TargetProperty="FocusVisualSecondaryThickness">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="2" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<PointerUpThemeAnimation Storyboard.TargetName="ContentPresenter" />
|
||||
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetName="MultiSelectCheck"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
To="1"
|
||||
Duration="0" />
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MultiSelectSquare" Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding Converter={StaticResource ContrastBrushConverter}, ConverterParameter={ThemeResource TextControlForeground}}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid HorizontalAlignment="Stretch">
|
||||
<GridView
|
||||
Margin="0"
|
||||
Padding="0"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="ListViewBase_OnItemClick"
|
||||
ItemContainerStyle="{StaticResource PaletteGridViewItemStyle}"
|
||||
ItemsSource="{x:Bind PaletteColors}"
|
||||
SelectionMode="None">
|
||||
<GridView.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<controls:UniformGrid ui:FrameworkElementExtensions.AncestorType="local:ColorPalette" Columns="{Binding (ui:FrameworkElementExtensions.Ancestor).CustomPaletteColumnCount, RelativeSource={RelativeSource Self}}" />
|
||||
</ItemsPanelTemplate>
|
||||
</GridView.ItemsPanel>
|
||||
<GridView.ItemTemplate>
|
||||
<DataTemplate x:DataType="Color">
|
||||
<Border
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
AutomationProperties.Name="{Binding Converter={StaticResource ColorToDisplayNameConverter}}"
|
||||
CornerRadius="4"
|
||||
ToolTipService.ToolTip="{Binding Converter={StaticResource ColorToDisplayNameConverter}}">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{Binding}" />
|
||||
</Border.Background>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</GridView.ItemTemplate>
|
||||
</GridView>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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.ObjectModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Controls;
|
||||
|
||||
public sealed partial class ColorPalette : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty PaletteColorsProperty = DependencyProperty.Register(nameof(PaletteColors), typeof(ObservableCollection<Color>), typeof(ColorPalette), null!)!;
|
||||
|
||||
public static readonly DependencyProperty CustomPaletteColumnCountProperty = DependencyProperty.Register(nameof(CustomPaletteColumnCount), typeof(int), typeof(ColorPalette), new PropertyMetadata(10))!;
|
||||
|
||||
public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register(nameof(SelectedColor), typeof(Color), typeof(ColorPalette), new PropertyMetadata(null!))!;
|
||||
|
||||
public event EventHandler<Color?>? SelectedColorChanged;
|
||||
|
||||
private Color? _selectedColor;
|
||||
|
||||
public Color? SelectedColor
|
||||
{
|
||||
get => _selectedColor;
|
||||
|
||||
set
|
||||
{
|
||||
if (_selectedColor != value)
|
||||
{
|
||||
_selectedColor = value;
|
||||
if (value is not null)
|
||||
{
|
||||
SetValue(SelectedColorProperty, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearValue(SelectedColorProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<Color> PaletteColors
|
||||
{
|
||||
get => (ObservableCollection<Color>)GetValue(PaletteColorsProperty)!;
|
||||
set => SetValue(PaletteColorsProperty, value);
|
||||
}
|
||||
|
||||
public int CustomPaletteColumnCount
|
||||
{
|
||||
get => (int)GetValue(CustomPaletteColumnCountProperty);
|
||||
set => SetValue(CustomPaletteColumnCountProperty, value);
|
||||
}
|
||||
|
||||
public ColorPalette()
|
||||
{
|
||||
PaletteColors = [];
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void ListViewBase_OnItemClick(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
if (e.ClickedItem is Color color)
|
||||
{
|
||||
SelectedColor = color;
|
||||
SelectedColorChanged?.Invoke(this, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<UserControl
|
||||
x:Class="Microsoft.CmdPal.UI.Controls.ColorPickerButton"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Microsoft.CmdPal.UI.Controls"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
d:DesignHeight="300"
|
||||
d:DesignWidth="400"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:BoolToVisibilityConverter
|
||||
x:Key="BoolToVisibilityInvertedConverter"
|
||||
FalseValue="Visible"
|
||||
TrueValue="Collapsed" />
|
||||
</UserControl.Resources>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<DropDownButton Padding="{x:Bind ToDropDownPadding(HasSelectedColor), Mode=OneWay}">
|
||||
<Grid>
|
||||
<TextBlock x:Uid="OptionalColorPickerButton_UnsetTextBlock" Visibility="{x:Bind HasSelectedColor, Mode=OneWay, Converter={StaticResource BoolToVisibilityInvertedConverter}}" />
|
||||
<Border
|
||||
x:Name="ColorPreviewBorder"
|
||||
Width="48"
|
||||
Height="24"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}"
|
||||
Visibility="{x:Bind HasSelectedColor, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{x:Bind SelectedColor, Mode=OneWay}" />
|
||||
</Border.Background>
|
||||
</Border>
|
||||
</Grid>
|
||||
<DropDownButton.Flyout>
|
||||
<Flyout
|
||||
x:Name="ColorPickerFlyout"
|
||||
Opened="FlyoutBase_OnOpened"
|
||||
Placement="Bottom"
|
||||
ShouldConstrainToRootBounds="False">
|
||||
<Flyout.FlyoutPresenterStyle>
|
||||
<Style BasedOn="{StaticResource DefaultFlyoutPresenterStyle}" TargetType="FlyoutPresenter">
|
||||
<Setter Property="MinWidth" Value="660" />
|
||||
</Style>
|
||||
</Flyout.FlyoutPresenterStyle>
|
||||
<StackPanel
|
||||
x:Name="FlyoutRoot"
|
||||
Orientation="Horizontal"
|
||||
SizeChanged="FlyoutRoot_OnSizeChanged"
|
||||
Spacing="20">
|
||||
<!-- Left column: Preset colors and reset button -->
|
||||
<StackPanel Margin="2">
|
||||
<TextBlock
|
||||
x:Uid="OptionalColorPickerButton_WindowsColorsSectionHeading"
|
||||
Margin="0,0,0,12"
|
||||
Style="{StaticResource BodyTextBlockStyle}" />
|
||||
|
||||
<controls:ColorPalette
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
CustomPaletteColumnCount="9"
|
||||
PaletteColors="{x:Bind PaletteColors}"
|
||||
SelectedColorChanged="ColorPalette_OnSelectedColorChanged" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Right column: Spectrum -->
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
x:Uid="OptionalColorPickerButton_CustomColorsSectionHeading"
|
||||
Margin="0,0,0,12"
|
||||
Style="{StaticResource BodyTextBlockStyle}" />
|
||||
|
||||
<ColorPicker
|
||||
IsAlphaEnabled="{x:Bind IsAlphaEnabled, Mode=OneWay}"
|
||||
IsAlphaSliderVisible="{x:Bind IsAlphaEnabled, Mode=OneWay}"
|
||||
IsAlphaTextInputVisible="{x:Bind IsAlphaEnabled, Mode=OneWay}"
|
||||
IsColorChannelTextInputVisible="True"
|
||||
IsColorSliderVisible="True"
|
||||
IsHexInputVisible="True"
|
||||
IsMoreButtonVisible="True"
|
||||
Color="{x:Bind SelectedColor, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</DropDownButton.Flyout>
|
||||
</DropDownButton>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,146 @@
|
||||
// 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.ObjectModel;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Controls;
|
||||
|
||||
public sealed partial class ColorPickerButton : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty PaletteColorsProperty = DependencyProperty.Register(nameof(PaletteColors), typeof(ObservableCollection<Color>), typeof(ColorPickerButton), new PropertyMetadata(new ObservableCollection<Color>()))!;
|
||||
|
||||
public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register(nameof(SelectedColor), typeof(Color), typeof(ColorPickerButton), new PropertyMetadata(Colors.Black))!;
|
||||
|
||||
public static readonly DependencyProperty IsAlphaEnabledProperty = DependencyProperty.Register(nameof(IsAlphaEnabled), typeof(bool), typeof(ColorPickerButton), new PropertyMetadata(defaultValue: false))!;
|
||||
|
||||
public static readonly DependencyProperty IsValueEditorEnabledProperty = DependencyProperty.Register(nameof(IsValueEditorEnabled), typeof(bool), typeof(ColorPickerButton), new PropertyMetadata(false))!;
|
||||
|
||||
public static readonly DependencyProperty HasSelectedColorProperty = DependencyProperty.Register(nameof(HasSelectedColor), typeof(bool), typeof(ColorPickerButton), new PropertyMetadata(false))!;
|
||||
|
||||
private Color _selectedColor;
|
||||
|
||||
public Color SelectedColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return _selectedColor;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_selectedColor != value)
|
||||
{
|
||||
_selectedColor = value;
|
||||
SetValue(SelectedColorProperty, value);
|
||||
HasSelectedColor = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasSelectedColor
|
||||
{
|
||||
get { return (bool)GetValue(HasSelectedColorProperty); }
|
||||
set { SetValue(HasSelectedColorProperty, value); }
|
||||
}
|
||||
|
||||
public bool IsAlphaEnabled
|
||||
{
|
||||
get => (bool)GetValue(IsAlphaEnabledProperty);
|
||||
set => SetValue(IsAlphaEnabledProperty, value);
|
||||
}
|
||||
|
||||
public bool IsValueEditorEnabled
|
||||
{
|
||||
get { return (bool)GetValue(IsValueEditorEnabledProperty); }
|
||||
set { SetValue(IsValueEditorEnabledProperty, value); }
|
||||
}
|
||||
|
||||
public ObservableCollection<Color> PaletteColors
|
||||
{
|
||||
get { return (ObservableCollection<Color>)GetValue(PaletteColorsProperty); }
|
||||
set { SetValue(PaletteColorsProperty, value); }
|
||||
}
|
||||
|
||||
public ColorPickerButton()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
IsEnabledChanged -= ColorPickerButton_IsEnabledChanged;
|
||||
SetEnabledState();
|
||||
IsEnabledChanged += ColorPickerButton_IsEnabledChanged;
|
||||
}
|
||||
|
||||
private void ColorPickerButton_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
SetEnabledState();
|
||||
}
|
||||
|
||||
private void SetEnabledState()
|
||||
{
|
||||
if (this.IsEnabled)
|
||||
{
|
||||
ColorPreviewBorder.Opacity = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
ColorPreviewBorder.Opacity = 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
private void ColorPalette_OnSelectedColorChanged(object? sender, Color? e)
|
||||
{
|
||||
if (e.HasValue)
|
||||
{
|
||||
HasSelectedColor = true;
|
||||
SelectedColor = e.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private void FlyoutBase_OnOpened(object? sender, object e)
|
||||
{
|
||||
if (sender is not Flyout flyout || (flyout.Content as FrameworkElement)?.Parent is not FlyoutPresenter flyoutPresenter)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FlyoutRoot!.UpdateLayout();
|
||||
flyoutPresenter.UpdateLayout();
|
||||
|
||||
// Logger.LogInfo($"FlyoutBase_OnOpened: {flyoutPresenter}, {FlyoutRoot!.ActualWidth}");
|
||||
flyoutPresenter.MaxWidth = FlyoutRoot!.ActualWidth;
|
||||
flyoutPresenter.MinWidth = 660;
|
||||
flyoutPresenter.Width = FlyoutRoot!.ActualWidth;
|
||||
}
|
||||
|
||||
private void FlyoutRoot_OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
if ((ColorPickerFlyout!.Content as FrameworkElement)?.Parent is not FlyoutPresenter flyoutPresenter)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FlyoutRoot!.UpdateLayout();
|
||||
flyoutPresenter.UpdateLayout();
|
||||
|
||||
flyoutPresenter.MaxWidth = FlyoutRoot!.ActualWidth;
|
||||
flyoutPresenter.MinWidth = 660;
|
||||
flyoutPresenter.Width = FlyoutRoot!.ActualWidth;
|
||||
}
|
||||
|
||||
private Thickness ToDropDownPadding(bool hasColor)
|
||||
{
|
||||
return hasColor ? new Thickness(3, 3, 8, 3) : new Thickness(8, 4, 8, 4);
|
||||
}
|
||||
|
||||
private void ResetButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
HasSelectedColor = false;
|
||||
ColorPickerFlyout?.Hide();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="Microsoft.CmdPal.UI.Controls.CommandPalettePreview"
|
||||
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:h="using:Microsoft.CmdPal.UI.Helpers"
|
||||
xmlns:local="using:Microsoft.CmdPal.UI.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
<Border
|
||||
Width="200"
|
||||
Height="120"
|
||||
BorderBrush="{ThemeResource SurfaceStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Translation="0,0,8">
|
||||
<Grid>
|
||||
<Border
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="{ThemeResource SurfaceStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
Visibility="{x:Bind h:BindTransformers.NegateVisibility(ShowBackgroundImage), Mode=OneWay}">
|
||||
<Border.Background>
|
||||
<AcrylicBrush
|
||||
FallbackColor="{x:Bind PreviewBackgroundColor, Mode=OneWay}"
|
||||
TintColor="{x:Bind PreviewBackgroundColor, Mode=OneWay}"
|
||||
TintOpacity="{x:Bind PreviewBackgroundOpacity, Mode=OneWay}" />
|
||||
</Border.Background>
|
||||
</Border>
|
||||
<local:BlurImageControl
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BlurAmount="{x:Bind PreviewBackgroundImageBlurAmount, Mode=OneWay}"
|
||||
ImageBrightness="{x:Bind PreviewBackgroundImageBrightness, Mode=OneWay}"
|
||||
ImageSource="{x:Bind PreviewBackgroundImageSource, Mode=OneWay}"
|
||||
ImageStretch="{x:Bind ToStretch(PreviewBackgroundImageFit), Mode=OneWay}"
|
||||
IsHitTestVisible="False"
|
||||
TintColor="{x:Bind PreviewBackgroundImageTint, Mode=OneWay}"
|
||||
TintIntensity="{x:Bind ToTintIntensity(PreviewBackgroundImageTintIntensity), Mode=OneWay}"
|
||||
Visibility="{x:Bind ShowBackgroundImage, Mode=OneWay}" />
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="100" />
|
||||
<RowDefinition Height="20" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border
|
||||
x:Name="ContentPreview"
|
||||
Grid.Row="0"
|
||||
Background="{ThemeResource LayerOnAcrylicPrimaryBackgroundBrush}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="20" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border BorderBrush="{ThemeResource CmdPal.TopBarBorderBrush}" BorderThickness="0,0,0,1" />
|
||||
</Grid>
|
||||
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
x:Name="CommandBarPreview"
|
||||
Grid.Row="1"
|
||||
Background="{ThemeResource LayerOnAcrylicSecondaryBackgroundBrush}"
|
||||
BorderBrush="{ThemeResource CmdPal.CommandBarBorderBrush}"
|
||||
BorderThickness="0,1,0,0" />
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Controls;
|
||||
|
||||
public sealed partial class CommandPalettePreview : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty PreviewBackgroundOpacityProperty = DependencyProperty.Register(nameof(PreviewBackgroundOpacity), typeof(double), typeof(CommandPalettePreview), new PropertyMetadata(0d));
|
||||
|
||||
public static readonly DependencyProperty PreviewBackgroundColorProperty = DependencyProperty.Register(nameof(PreviewBackgroundColor), typeof(Color), typeof(CommandPalettePreview), new PropertyMetadata(default(Color)));
|
||||
|
||||
public static readonly DependencyProperty PreviewBackgroundImageSourceProperty = DependencyProperty.Register(nameof(PreviewBackgroundImageSource), typeof(ImageSource), typeof(CommandPalettePreview), new PropertyMetadata(null, PropertyChangedCallback));
|
||||
|
||||
public static readonly DependencyProperty PreviewBackgroundImageOpacityProperty = DependencyProperty.Register(nameof(PreviewBackgroundImageOpacity), typeof(int), typeof(CommandPalettePreview), new PropertyMetadata(0));
|
||||
|
||||
public static readonly DependencyProperty PreviewBackgroundImageFitProperty = DependencyProperty.Register(nameof(PreviewBackgroundImageFit), typeof(BackgroundImageFit), typeof(CommandPalettePreview), new PropertyMetadata(default(BackgroundImageFit)));
|
||||
|
||||
public static readonly DependencyProperty PreviewBackgroundImageBrightnessProperty = DependencyProperty.Register(nameof(PreviewBackgroundImageBrightness), typeof(double), typeof(CommandPalettePreview), new PropertyMetadata(0d));
|
||||
|
||||
public static readonly DependencyProperty PreviewBackgroundImageBlurAmountProperty = DependencyProperty.Register(nameof(PreviewBackgroundImageBlurAmount), typeof(double), typeof(CommandPalettePreview), new PropertyMetadata(0d));
|
||||
|
||||
public static readonly DependencyProperty PreviewBackgroundImageTintProperty = DependencyProperty.Register(nameof(PreviewBackgroundImageTint), typeof(Color), typeof(CommandPalettePreview), new PropertyMetadata(default(Color)));
|
||||
|
||||
public static readonly DependencyProperty PreviewBackgroundImageTintIntensityProperty = DependencyProperty.Register(nameof(PreviewBackgroundImageTintIntensity), typeof(int), typeof(CommandPalettePreview), new PropertyMetadata(0));
|
||||
|
||||
public static readonly DependencyProperty ShowBackgroundImageProperty = DependencyProperty.Register(nameof(ShowBackgroundImage), typeof(Visibility), typeof(CommandPalettePreview), new PropertyMetadata(Visibility.Collapsed));
|
||||
|
||||
public BackgroundImageFit PreviewBackgroundImageFit
|
||||
{
|
||||
get { return (BackgroundImageFit)GetValue(PreviewBackgroundImageFitProperty); }
|
||||
set { SetValue(PreviewBackgroundImageFitProperty, value); }
|
||||
}
|
||||
|
||||
public double PreviewBackgroundOpacity
|
||||
{
|
||||
get { return (double)GetValue(PreviewBackgroundOpacityProperty); }
|
||||
set { SetValue(PreviewBackgroundOpacityProperty, value); }
|
||||
}
|
||||
|
||||
public Color PreviewBackgroundColor
|
||||
{
|
||||
get { return (Color)GetValue(PreviewBackgroundColorProperty); }
|
||||
set { SetValue(PreviewBackgroundColorProperty, value); }
|
||||
}
|
||||
|
||||
public ImageSource PreviewBackgroundImageSource
|
||||
{
|
||||
get { return (ImageSource)GetValue(PreviewBackgroundImageSourceProperty); }
|
||||
set { SetValue(PreviewBackgroundImageSourceProperty, value); }
|
||||
}
|
||||
|
||||
public int PreviewBackgroundImageOpacity
|
||||
{
|
||||
get { return (int)GetValue(PreviewBackgroundImageOpacityProperty); }
|
||||
set { SetValue(PreviewBackgroundImageOpacityProperty, value); }
|
||||
}
|
||||
|
||||
public double PreviewBackgroundImageBrightness
|
||||
{
|
||||
get => (double)GetValue(PreviewBackgroundImageBrightnessProperty);
|
||||
set => SetValue(PreviewBackgroundImageBrightnessProperty, value);
|
||||
}
|
||||
|
||||
public double PreviewBackgroundImageBlurAmount
|
||||
{
|
||||
get => (double)GetValue(PreviewBackgroundImageBlurAmountProperty);
|
||||
set => SetValue(PreviewBackgroundImageBlurAmountProperty, value);
|
||||
}
|
||||
|
||||
public Color PreviewBackgroundImageTint
|
||||
{
|
||||
get => (Color)GetValue(PreviewBackgroundImageTintProperty);
|
||||
set => SetValue(PreviewBackgroundImageTintProperty, value);
|
||||
}
|
||||
|
||||
public int PreviewBackgroundImageTintIntensity
|
||||
{
|
||||
get => (int)GetValue(PreviewBackgroundImageTintIntensityProperty);
|
||||
set => SetValue(PreviewBackgroundImageTintIntensityProperty, value);
|
||||
}
|
||||
|
||||
public Visibility ShowBackgroundImage
|
||||
{
|
||||
get => (Visibility)GetValue(ShowBackgroundImageProperty);
|
||||
set => SetValue(ShowBackgroundImageProperty, value);
|
||||
}
|
||||
|
||||
public CommandPalettePreview()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is not CommandPalettePreview preview)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
preview.ShowBackgroundImage = e.NewValue is ImageSource ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private double ToOpacity(int value) => value / 100.0;
|
||||
|
||||
private double ToTintIntensity(int value) => value / 100.0;
|
||||
|
||||
private Stretch ToStretch(BackgroundImageFit fit)
|
||||
{
|
||||
return fit switch
|
||||
{
|
||||
BackgroundImageFit.Fill => Stretch.Fill,
|
||||
BackgroundImageFit.UniformToFill => Stretch.UniformToFill,
|
||||
_ => Stretch.None,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="Microsoft.CmdPal.UI.Controls.ScreenPreview"
|
||||
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">
|
||||
|
||||
<Border
|
||||
x:Name="ScreenBorder"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
BorderBrush="Black"
|
||||
BorderThickness="8"
|
||||
CornerRadius="8"
|
||||
UseLayoutRounding="True">
|
||||
<Viewbox Height="120" UseLayoutRounding="True">
|
||||
<Grid>
|
||||
<Image
|
||||
x:Name="WallpaperImage"
|
||||
MaxHeight="200"
|
||||
Stretch="Uniform"
|
||||
UseLayoutRounding="True" />
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
Margin="40"
|
||||
Content="{x:Bind PreviewContent}"
|
||||
UseLayoutRounding="True" />
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
</Border>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Markup;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Controls;
|
||||
|
||||
[ContentProperty(Name = nameof(PreviewContent))]
|
||||
public sealed partial class ScreenPreview : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty PreviewContentProperty =
|
||||
DependencyProperty.Register(nameof(PreviewContent), typeof(object), typeof(ScreenPreview), new PropertyMetadata(null!))!;
|
||||
|
||||
public object PreviewContent
|
||||
{
|
||||
get => GetValue(PreviewContentProperty)!;
|
||||
set => SetValue(PreviewContentProperty, value);
|
||||
}
|
||||
|
||||
public ScreenPreview()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
var wallpaperHelper = new WallpaperHelper();
|
||||
WallpaperImage!.Source = wallpaperHelper.GetWallpaperImage()!;
|
||||
ScreenBorder!.Background = new SolidColorBrush(wallpaperHelper.GetWallpaperColor());
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,8 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cmdpalUi="using:Microsoft.CmdPal.UI"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:cpcontrols="using:Microsoft.CmdPal.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:h="using:Microsoft.CmdPal.UI.Helpers"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
@@ -22,6 +21,7 @@
|
||||
MinHeight="32"
|
||||
VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
h:TextBoxCaretColor.SyncWithForeground="True"
|
||||
AutomationProperties.AutomationId="MainSearchBox"
|
||||
KeyDown="FilterBox_KeyDown"
|
||||
PlaceholderText="{x:Bind CurrentPageViewModel.PlaceholderText, Converter={StaticResource PlaceholderTextConverter}, Mode=OneWay}"
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// 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.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a color, either black or white, depending on the brightness of the supplied color.
|
||||
/// </summary>
|
||||
public sealed partial class ContrastBrushConverter : IValueConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the alpha channel threshold below which a default color is used instead of black/white.
|
||||
/// </summary>
|
||||
public byte AlphaThreshold { get; set; } = 128;
|
||||
|
||||
/// <inheritdoc />
|
||||
public object Convert(
|
||||
object value,
|
||||
Type targetType,
|
||||
object parameter,
|
||||
string language)
|
||||
{
|
||||
Color comparisonColor;
|
||||
Color? defaultColor = null;
|
||||
|
||||
// Get the changing color to compare against
|
||||
if (value is Color valueColor)
|
||||
{
|
||||
comparisonColor = valueColor;
|
||||
}
|
||||
else if (value is SolidColorBrush valueBrush)
|
||||
{
|
||||
comparisonColor = valueBrush.Color;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Invalid color value provided
|
||||
return DependencyProperty.UnsetValue;
|
||||
}
|
||||
|
||||
// Get the default color when transparency is high
|
||||
if (parameter is Color parameterColor)
|
||||
{
|
||||
defaultColor = parameterColor;
|
||||
}
|
||||
else if (parameter is SolidColorBrush parameterBrush)
|
||||
{
|
||||
defaultColor = parameterBrush.Color;
|
||||
}
|
||||
|
||||
if (comparisonColor.A < AlphaThreshold &&
|
||||
defaultColor.HasValue)
|
||||
{
|
||||
// If the transparency is less than 50 %, just use the default brush
|
||||
// This can commonly be something like the TextControlForeground brush
|
||||
return new SolidColorBrush(defaultColor.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Chose a white/black brush based on contrast to the base color
|
||||
return UseLightContrastColor(comparisonColor)
|
||||
? new SolidColorBrush(Colors.White)
|
||||
: new SolidColorBrush(Colors.Black);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(
|
||||
object value,
|
||||
Type targetType,
|
||||
object parameter,
|
||||
string language)
|
||||
{
|
||||
return DependencyProperty.UnsetValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a light or dark contrast color should be used with the given displayed color.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This code is using the WinUI algorithm.
|
||||
/// </remarks>
|
||||
private bool UseLightContrastColor(Color displayedColor)
|
||||
{
|
||||
// The selection ellipse should be light if and only if the chosen color
|
||||
// contrasts more with black than it does with white.
|
||||
// To find how much something contrasts with white, we use the equation
|
||||
// for relative luminance, which is given by
|
||||
//
|
||||
// L = 0.2126 * Rg + 0.7152 * Gg + 0.0722 * Bg
|
||||
//
|
||||
// where Xg = { X/3294 if X <= 10, (R/269 + 0.0513)^2.4 otherwise }
|
||||
//
|
||||
// If L is closer to 1, then the color is closer to white; if it is closer to 0,
|
||||
// then the color is closer to black. This is based on the fact that the human
|
||||
// eye perceives green to be much brighter than red, which in turn is perceived to be
|
||||
// brighter than blue.
|
||||
//
|
||||
// If the third dimension is value, then we won't be updating the spectrum's displayed colors,
|
||||
// so in that case we should use a value of 1 when considering the backdrop
|
||||
// for the selection ellipse.
|
||||
var rg = displayedColor.R <= 10
|
||||
? displayedColor.R / 3294.0
|
||||
: Math.Pow((displayedColor.R / 269.0) + 0.0513, 2.4);
|
||||
var gg = displayedColor.G <= 10
|
||||
? displayedColor.G / 3294.0
|
||||
: Math.Pow((displayedColor.G / 269.0) + 0.0513, 2.4);
|
||||
var bg = displayedColor.B <= 10
|
||||
? displayedColor.B / 3294.0
|
||||
: Math.Pow((displayedColor.B / 269.0) + 0.0513, 2.4);
|
||||
|
||||
return (0.2126 * rg) + (0.7152 * gg) + (0.0722 * bg) <= 0.5;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ internal static class BindTransformers
|
||||
{
|
||||
public static bool Negate(bool value) => !value;
|
||||
|
||||
public static Visibility NegateVisibility(Visibility value) => value == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
public static Visibility EmptyToCollapsed(string? input)
|
||||
=> string.IsNullOrEmpty(input) ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
// 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 CommunityToolkit.WinUI.Helpers;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="Color"/>.
|
||||
/// </summary>
|
||||
internal static class ColorExtensions
|
||||
{
|
||||
/// <param name="color">Input color.</param>
|
||||
public static double CalculateBrightness(this Color color)
|
||||
{
|
||||
return color.ToHsv().V;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows to change the brightness by a factor based on the HSV color space.
|
||||
/// </summary>
|
||||
/// <param name="color">Input color.</param>
|
||||
/// <param name="brightnessFactor">The brightness adjustment factor, ranging from -1 to 1.</param>
|
||||
/// <returns>Updated color.</returns>
|
||||
public static Color UpdateBrightness(this Color color, double brightnessFactor)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(brightnessFactor, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(brightnessFactor, -1);
|
||||
|
||||
var hsvColor = color.ToHsv();
|
||||
return ColorHelper.FromHsv(hsvColor.H, hsvColor.S, Math.Clamp(hsvColor.V + brightnessFactor, 0, 1), hsvColor.A);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the color by adjusting brightness, saturation, and luminance factors.
|
||||
/// </summary>
|
||||
/// <param name="color">Input color.</param>
|
||||
/// <param name="brightnessFactor">The brightness adjustment factor, ranging from -1 to 1.</param>
|
||||
/// <param name="saturationFactor">The saturation adjustment factor, ranging from -1 to 1. Defaults to 0.</param>
|
||||
/// <param name="luminanceFactor">The luminance adjustment factor, ranging from -1 to 1. Defaults to 0.</param>
|
||||
/// <returns>Updated color.</returns>
|
||||
public static Color Update(this Color color, double brightnessFactor, double saturationFactor = 0, double luminanceFactor = 0)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(brightnessFactor, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(brightnessFactor, -1);
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(saturationFactor, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(saturationFactor, -1);
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(luminanceFactor, 1);
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(luminanceFactor, -1);
|
||||
|
||||
var hsv = color.ToHsv();
|
||||
|
||||
var rgb = ColorHelper.FromHsv(
|
||||
hsv.H,
|
||||
Clamp01(hsv.S + saturationFactor),
|
||||
Clamp01(hsv.V + brightnessFactor));
|
||||
|
||||
if (luminanceFactor == 0)
|
||||
{
|
||||
return rgb;
|
||||
}
|
||||
|
||||
var hsl = rgb.ToHsl();
|
||||
var lightness = Clamp01(hsl.L + luminanceFactor);
|
||||
return ColorHelper.FromHsl(hsl.H, hsl.S, lightness);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolates between two colors in HSV space.
|
||||
/// Hue is blended along the shortest arc on the color wheel (wrap-aware).
|
||||
/// Saturation, Value, and Alpha are blended linearly.
|
||||
/// </summary>
|
||||
/// <param name="a">Start color.</param>
|
||||
/// <param name="b">End color.</param>
|
||||
/// <param name="t">Interpolation factor in [0,1].</param>
|
||||
/// <returns>Interpolated color.</returns>
|
||||
public static Color LerpHsv(this Color a, Color b, double t)
|
||||
{
|
||||
t = Clamp01(t);
|
||||
|
||||
// Convert to HSV
|
||||
var hslA = a.ToHsv();
|
||||
var hslB = b.ToHsv();
|
||||
|
||||
var h1 = hslA.H;
|
||||
var h2 = hslB.H;
|
||||
|
||||
// Handle near-gray hues (undefined hue) by inheriting the other's hue
|
||||
const double satEps = 1e-4f;
|
||||
if (hslA.S < satEps && hslB.S >= satEps)
|
||||
{
|
||||
h1 = h2;
|
||||
}
|
||||
else if (hslB.S < satEps && hslA.S >= satEps)
|
||||
{
|
||||
h2 = h1;
|
||||
}
|
||||
|
||||
return ColorHelper.FromHsv(
|
||||
hue: LerpHueDegrees(h1, h2, t),
|
||||
saturation: Lerp(hslA.S, hslB.S, t),
|
||||
value: Lerp(hslA.V, hslB.V, t),
|
||||
alpha: (byte)Math.Round(Lerp(hslA.A, hslB.A, t)));
|
||||
}
|
||||
|
||||
private static double LerpHueDegrees(double a, double b, double t)
|
||||
{
|
||||
a = Mod360(a);
|
||||
b = Mod360(b);
|
||||
var delta = ((b - a + 540f) % 360f) - 180f;
|
||||
return Mod360(a + (delta * t));
|
||||
}
|
||||
|
||||
private static double Mod360(double angle)
|
||||
{
|
||||
angle %= 360f;
|
||||
if (angle < 0f)
|
||||
{
|
||||
angle += 360f;
|
||||
}
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
private static double Lerp(double a, double b, double t) => a + ((b - a) * t);
|
||||
|
||||
private static double Clamp01(double x) => Math.Clamp(x, 0, 1);
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
// 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.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Rectangle = Microsoft.UI.Xaml.Shapes.Rectangle;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Attached property to color internal caret/overlay rectangles inside a TextBox
|
||||
/// so they follow the TextBox's actual Foreground brush.
|
||||
/// </summary>
|
||||
public static class TextBoxCaretColor
|
||||
{
|
||||
public static readonly DependencyProperty SyncWithForegroundProperty =
|
||||
DependencyProperty.RegisterAttached("SyncWithForeground", typeof(bool), typeof(TextBoxCaretColor), new PropertyMetadata(false, OnSyncCaretRectanglesChanged))!;
|
||||
|
||||
private static readonly ConditionalWeakTable<TextBox, State> States = [];
|
||||
|
||||
public static void SetSyncWithForeground(DependencyObject obj, bool value)
|
||||
{
|
||||
obj.SetValue(SyncWithForegroundProperty, value);
|
||||
}
|
||||
|
||||
public static bool GetSyncWithForeground(DependencyObject obj)
|
||||
{
|
||||
return (bool)obj.GetValue(SyncWithForegroundProperty);
|
||||
}
|
||||
|
||||
private static void OnSyncCaretRectanglesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is not TextBox tb)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((bool)e.NewValue)
|
||||
{
|
||||
Attach(tb);
|
||||
}
|
||||
else
|
||||
{
|
||||
Detach(tb);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Attach(TextBox tb)
|
||||
{
|
||||
if (States.TryGetValue(tb, out var st) && st.IsHooked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
st ??= new State();
|
||||
st.IsHooked = true;
|
||||
States.Remove(tb);
|
||||
States.Add(tb, st);
|
||||
|
||||
tb.Loaded += TbOnLoaded;
|
||||
tb.Unloaded += TbOnUnloaded;
|
||||
tb.GotFocus += TbOnGotFocus;
|
||||
|
||||
st.ForegroundToken = tb.RegisterPropertyChangedCallback(Control.ForegroundProperty!, (_, _) => Apply(tb));
|
||||
|
||||
if (tb.IsLoaded)
|
||||
{
|
||||
Apply(tb);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Detach(TextBox tb)
|
||||
{
|
||||
if (!States.TryGetValue(tb, out var st))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tb.Loaded -= TbOnLoaded;
|
||||
tb.Unloaded -= TbOnUnloaded;
|
||||
tb.GotFocus -= TbOnGotFocus;
|
||||
|
||||
if (st.ForegroundToken != 0)
|
||||
{
|
||||
tb.UnregisterPropertyChangedCallback(Control.ForegroundProperty!, st.ForegroundToken);
|
||||
st.ForegroundToken = 0;
|
||||
}
|
||||
|
||||
st.IsHooked = false;
|
||||
}
|
||||
|
||||
private static void TbOnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is TextBox tb)
|
||||
{
|
||||
Apply(tb);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TbOnUnloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is TextBox tb)
|
||||
{
|
||||
Detach(tb);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TbOnGotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is TextBox tb)
|
||||
{
|
||||
Apply(tb);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Apply(TextBox tb)
|
||||
{
|
||||
try
|
||||
{
|
||||
ApplyCore(tb);
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyCore(TextBox tb)
|
||||
{
|
||||
// Ensure template is realized
|
||||
tb.ApplyTemplate();
|
||||
|
||||
// Find the internal ScrollContentPresenter within the TextBox template
|
||||
var scp = tb.FindDescendant<ScrollContentPresenter>(s => s.Name == "ScrollContentPresenter");
|
||||
if (scp is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var brush = tb.Foreground; // use the actual current foreground brush
|
||||
if (brush == null)
|
||||
{
|
||||
brush = new SolidColorBrush(Colors.Black);
|
||||
}
|
||||
|
||||
foreach (var rect in scp.FindDescendants().OfType<Rectangle>())
|
||||
{
|
||||
try
|
||||
{
|
||||
rect.Fill = brush;
|
||||
rect.CompositeMode = ElementCompositeMode.SourceOver;
|
||||
rect.Opacity = 0.9;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// best-effort; some rectangles might be template-owned
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class State
|
||||
{
|
||||
public long ForegroundToken { get; set; }
|
||||
|
||||
public bool IsHooked { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
// 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.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.Marshalling;
|
||||
using ManagedCommon;
|
||||
using ManagedCsWin32;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Lightweight helper to access wallpaper information.
|
||||
/// </summary>
|
||||
internal sealed partial class WallpaperHelper
|
||||
{
|
||||
private readonly IDesktopWallpaper? _desktopWallpaper;
|
||||
|
||||
public WallpaperHelper()
|
||||
{
|
||||
try
|
||||
{
|
||||
var desktopWallpaper = ComHelper.CreateComInstance<IDesktopWallpaper>(
|
||||
ref Unsafe.AsRef(in CLSID.DesktopWallpaper),
|
||||
CLSCTX.ALL);
|
||||
|
||||
_desktopWallpaper = desktopWallpaper;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// If COM initialization fails, keep helper usable with safe fallbacks
|
||||
Logger.LogError("Failed to initialize DesktopWallpaper COM interface", ex);
|
||||
_desktopWallpaper = null;
|
||||
}
|
||||
}
|
||||
|
||||
private string? GetWallpaperPathForFirstMonitor()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_desktopWallpaper is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
_desktopWallpaper.GetMonitorDevicePathCount(out var monitorCount);
|
||||
|
||||
for (uint i = 0; monitorCount != 0 && i < monitorCount; i++)
|
||||
{
|
||||
_desktopWallpaper.GetMonitorDevicePathAt(i, out var monitorId);
|
||||
if (string.IsNullOrEmpty(monitorId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_desktopWallpaper.GetWallpaper(monitorId, out var wallpaperPath);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(wallpaperPath) && File.Exists(wallpaperPath))
|
||||
{
|
||||
return wallpaperPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to query wallpaper path", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the wallpaper background color.
|
||||
/// </summary>
|
||||
/// <returns>The wallpaper background color, or black if it cannot be determined.</returns>
|
||||
public Color GetWallpaperColor()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_desktopWallpaper is null)
|
||||
{
|
||||
return Colors.Black;
|
||||
}
|
||||
|
||||
_desktopWallpaper.GetBackgroundColor(out var colorref);
|
||||
var r = (byte)(colorref.Value & 0x000000FF);
|
||||
var g = (byte)((colorref.Value & 0x0000FF00) >> 8);
|
||||
var b = (byte)((colorref.Value & 0x00FF0000) >> 16);
|
||||
return Color.FromArgb(255, r, g, b);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to load wallpaper color", ex);
|
||||
return Colors.Black;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the wallpaper image for the primary monitor.
|
||||
/// </summary>
|
||||
/// <returns>The wallpaper image, or null if it cannot be determined.</returns>
|
||||
public BitmapImage? GetWallpaperImage()
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = GetWallpaperPathForFirstMonitor();
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var image = new BitmapImage();
|
||||
using var stream = File.OpenRead(path);
|
||||
var randomAccessStream = stream.AsRandomAccessStream();
|
||||
if (randomAccessStream == null)
|
||||
{
|
||||
Logger.LogError("Failed to convert file stream to RandomAccessStream for wallpaper image.");
|
||||
return null;
|
||||
}
|
||||
|
||||
image.SetSource(randomAccessStream);
|
||||
return image;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to load wallpaper image", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// blittable type for COM interop
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal readonly partial struct COLORREF
|
||||
{
|
||||
internal readonly uint Value;
|
||||
}
|
||||
|
||||
// blittable type for COM interop
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal readonly partial struct RECT
|
||||
{
|
||||
internal readonly int Left;
|
||||
internal readonly int Top;
|
||||
internal readonly int Right;
|
||||
internal readonly int Bottom;
|
||||
}
|
||||
|
||||
// COM interface for IDesktopWallpaper, GeneratedComInterface to be AOT compatible
|
||||
[GeneratedComInterface]
|
||||
[Guid("B92B56A9-8B55-4E14-9A89-0199BBB6F93B")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
internal partial interface IDesktopWallpaper
|
||||
{
|
||||
void SetWallpaper(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string? monitorId,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string wallpaper);
|
||||
|
||||
void GetWallpaper(
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string? monitorId,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] out string wallpaper);
|
||||
|
||||
void GetMonitorDevicePathAt(uint monitorIndex, [MarshalAs(UnmanagedType.LPWStr)] out string monitorId);
|
||||
|
||||
void GetMonitorDevicePathCount(out uint count);
|
||||
|
||||
void GetMonitorRECT([MarshalAs(UnmanagedType.LPWStr)] string? monitorId, out RECT rect);
|
||||
|
||||
void SetBackgroundColor(COLORREF color);
|
||||
|
||||
void GetBackgroundColor(out COLORREF color);
|
||||
|
||||
// Other methods omitted for brevity
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
x:Class="Microsoft.CmdPal.UI.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Microsoft.CmdPal.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:pages="using:Microsoft.CmdPal.UI.Pages"
|
||||
@@ -15,6 +16,21 @@
|
||||
Closed="MainWindow_Closed"
|
||||
mc:Ignorable="d">
|
||||
<Grid x:Name="RootElement">
|
||||
|
||||
<controls:BlurImageControl
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BlurAmount="{x:Bind ViewModel.BackgroundImageBlurAmount, Mode=OneWay}"
|
||||
ImageBrightness="{x:Bind ViewModel.BackgroundImageBrightness, Mode=OneWay}"
|
||||
ImageOpacity="{x:Bind ViewModel.BackgroundImageOpacity, Mode=OneWay}"
|
||||
ImageSource="{x:Bind ViewModel.BackgroundImageSource, Mode=OneWay}"
|
||||
ImageStretch="{x:Bind ViewModel.BackgroundImageStretch, Mode=OneWay}"
|
||||
IsHitTestVisible="False"
|
||||
IsHoldingEnabled="False"
|
||||
TintColor="{x:Bind ViewModel.BackgroundImageTint, Mode=OneWay}"
|
||||
TintIntensity="{x:Bind ViewModel.BackgroundImageTintIntensity, Mode=OneWay}"
|
||||
Visibility="{x:Bind ViewModel.ShowBackgroundImage, Mode=OneWay}" />
|
||||
|
||||
<pages:ShellPage />
|
||||
</Grid>
|
||||
</winuiex:WindowEx>
|
||||
|
||||
@@ -15,8 +15,10 @@ using Microsoft.CmdPal.UI.Controls;
|
||||
using Microsoft.CmdPal.UI.Events;
|
||||
using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.CmdPal.UI.Messages;
|
||||
using Microsoft.CmdPal.UI.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.UI;
|
||||
@@ -66,7 +68,10 @@ public sealed partial class MainWindow : WindowEx,
|
||||
private readonly KeyboardListener _keyboardListener;
|
||||
private readonly LocalKeyboardListener _localKeyboardListener;
|
||||
private readonly HiddenOwnerWindowBehavior _hiddenOwnerBehavior = new();
|
||||
private readonly IThemeService _themeService;
|
||||
private readonly WindowThemeSynchronizer _windowThemeSynchronizer;
|
||||
private bool _ignoreHotKeyWhenFullScreen = true;
|
||||
private bool _themeServiceInitialized;
|
||||
|
||||
private DesktopAcrylicController? _acrylicController;
|
||||
private SystemBackdropConfiguration? _configurationSource;
|
||||
@@ -74,13 +79,21 @@ public sealed partial class MainWindow : WindowEx,
|
||||
|
||||
private WindowPosition _currentWindowPosition = new();
|
||||
|
||||
private MainWindowViewModel ViewModel { get; }
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
ViewModel = App.Current.Services.GetService<MainWindowViewModel>()!;
|
||||
|
||||
_autoGoHomeTimer = new DispatcherTimer();
|
||||
_autoGoHomeTimer.Tick += OnAutoGoHomeTimerOnTick;
|
||||
|
||||
_themeService = App.Current.Services.GetRequiredService<IThemeService>();
|
||||
_themeService.ThemeChanged += ThemeServiceOnThemeChanged;
|
||||
_windowThemeSynchronizer = new WindowThemeSynchronizer(_themeService, this);
|
||||
|
||||
_hwnd = new HWND(WinRT.Interop.WindowNative.GetWindowHandle(this).ToInt32());
|
||||
|
||||
unsafe
|
||||
@@ -88,6 +101,8 @@ public sealed partial class MainWindow : WindowEx,
|
||||
CommandPaletteHost.SetHostHwnd((ulong)_hwnd.Value);
|
||||
}
|
||||
|
||||
SetAcrylic();
|
||||
|
||||
_hiddenOwnerBehavior.ShowInTaskbar(this, Debugger.IsAttached);
|
||||
|
||||
_keyboardListener = new KeyboardListener();
|
||||
@@ -100,8 +115,6 @@ public sealed partial class MainWindow : WindowEx,
|
||||
RestoreWindowPosition();
|
||||
UpdateWindowPositionInMemory();
|
||||
|
||||
SetAcrylic();
|
||||
|
||||
WeakReferenceMessenger.Default.Register<DismissMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<QuitMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<ShowWindowMessage>(this);
|
||||
@@ -156,6 +169,11 @@ public sealed partial class MainWindow : WindowEx,
|
||||
WeakReferenceMessenger.Default.Send(new GoHomeMessage(WithAnimation: false, FocusSearch: false));
|
||||
}
|
||||
|
||||
private void ThemeServiceOnThemeChanged(object? sender, ThemeChangedEventArgs e)
|
||||
{
|
||||
UpdateAcrylic();
|
||||
}
|
||||
|
||||
private static void LocalKeyboardListener_OnKeyPressed(object? sender, LocalKeyboardListenerKeyPressedEventArgs e)
|
||||
{
|
||||
if (e.Key == VirtualKey.GoBack)
|
||||
@@ -247,8 +265,6 @@ public sealed partial class MainWindow : WindowEx,
|
||||
_autoGoHomeTimer.Interval = _autoGoHomeInterval;
|
||||
}
|
||||
|
||||
// We want to use DesktopAcrylicKind.Thin and custom colors as this is the default material
|
||||
// other Shell surfaces are using, this cannot be set in XAML however.
|
||||
private void SetAcrylic()
|
||||
{
|
||||
if (DesktopAcrylicController.IsSupported())
|
||||
@@ -265,41 +281,32 @@ public sealed partial class MainWindow : WindowEx,
|
||||
|
||||
private void UpdateAcrylic()
|
||||
{
|
||||
if (_acrylicController != null)
|
||||
try
|
||||
{
|
||||
_acrylicController.RemoveAllSystemBackdropTargets();
|
||||
_acrylicController.Dispose();
|
||||
}
|
||||
|
||||
_acrylicController = GetAcrylicConfig(Content);
|
||||
|
||||
// Enable the system backdrop.
|
||||
// Note: Be sure to have "using WinRT;" to support the Window.As<...>() call.
|
||||
_acrylicController.AddSystemBackdropTarget(this.As<ICompositionSupportsSystemBackdrop>());
|
||||
_acrylicController.SetSystemBackdropConfiguration(_configurationSource);
|
||||
}
|
||||
|
||||
private static DesktopAcrylicController GetAcrylicConfig(UIElement content)
|
||||
{
|
||||
var feContent = content as FrameworkElement;
|
||||
|
||||
return feContent?.ActualTheme == ElementTheme.Light
|
||||
? new DesktopAcrylicController()
|
||||
if (_acrylicController != null)
|
||||
{
|
||||
Kind = DesktopAcrylicKind.Thin,
|
||||
TintColor = Color.FromArgb(255, 243, 243, 243),
|
||||
LuminosityOpacity = 0.90f,
|
||||
TintOpacity = 0.0f,
|
||||
FallbackColor = Color.FromArgb(255, 238, 238, 238),
|
||||
_acrylicController.RemoveAllSystemBackdropTargets();
|
||||
_acrylicController.Dispose();
|
||||
}
|
||||
: new DesktopAcrylicController()
|
||||
|
||||
var backdrop = _themeService.Current.BackdropParameters;
|
||||
_acrylicController = new DesktopAcrylicController
|
||||
{
|
||||
Kind = DesktopAcrylicKind.Thin,
|
||||
TintColor = Color.FromArgb(255, 32, 32, 32),
|
||||
LuminosityOpacity = 0.96f,
|
||||
TintOpacity = 0.5f,
|
||||
FallbackColor = Color.FromArgb(255, 28, 28, 28),
|
||||
TintColor = backdrop.TintColor,
|
||||
TintOpacity = backdrop.TintOpacity,
|
||||
FallbackColor = backdrop.FallbackColor,
|
||||
LuminosityOpacity = backdrop.LuminosityOpacity,
|
||||
};
|
||||
|
||||
// Enable the system backdrop.
|
||||
// Note: Be sure to have "using WinRT;" to support the Window.As<...>() call.
|
||||
_acrylicController.AddSystemBackdropTarget(this.As<ICompositionSupportsSystemBackdrop>());
|
||||
_acrylicController.SetSystemBackdropConfiguration(_configurationSource);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to update backdrop", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowHwnd(IntPtr hwndValue, MonitorBehavior target)
|
||||
@@ -711,6 +718,19 @@ public sealed partial class MainWindow : WindowEx,
|
||||
|
||||
internal void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
|
||||
{
|
||||
if (!_themeServiceInitialized && args.WindowActivationState != WindowActivationState.Deactivated)
|
||||
{
|
||||
try
|
||||
{
|
||||
_themeService.Initialize();
|
||||
_themeServiceInitialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to initialize ThemeService", ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (args.WindowActivationState == WindowActivationState.Deactivated)
|
||||
{
|
||||
// Save the current window position before hiding the window
|
||||
@@ -1004,6 +1024,7 @@ public sealed partial class MainWindow : WindowEx,
|
||||
public void Dispose()
|
||||
{
|
||||
_localKeyboardListener.Dispose();
|
||||
_windowThemeSynchronizer.Dispose();
|
||||
DisposeAcrylic();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,8 +68,11 @@
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Controls\ActionBar.xaml" />
|
||||
<None Remove="Controls\ColorPalette.xaml" />
|
||||
<None Remove="Controls\CommandPalettePreview.xaml" />
|
||||
<None Remove="Controls\DevRibbon.xaml" />
|
||||
<None Remove="Controls\KeyVisual\KeyCharPresenter.xaml" />
|
||||
<None Remove="Controls\ScreenPreview.xaml" />
|
||||
<None Remove="Controls\SearchBar.xaml" />
|
||||
<None Remove="IsEnabledTextBlock.xaml" />
|
||||
<None Remove="ListDetailPage.xaml" />
|
||||
@@ -78,10 +81,12 @@
|
||||
<None Remove="Pages\Settings\ExtensionsPage.xaml" />
|
||||
<None Remove="Pages\Settings\GeneralPage.xaml" />
|
||||
<None Remove="SettingsWindow.xaml" />
|
||||
<None Remove="Settings\AppearancePage.xaml" />
|
||||
<None Remove="ShellPage.xaml" />
|
||||
<None Remove="Styles\Colors.xaml" />
|
||||
<None Remove="Styles\Settings.xaml" />
|
||||
<None Remove="Styles\TextBox.xaml" />
|
||||
<None Remove="Styles\Theme.Normal.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -93,6 +98,7 @@
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" />
|
||||
@@ -207,6 +213,39 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\CommandPalettePreview.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\ScreenPreview.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\ColorPalette.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Styles\Theme.Colorful.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Update="Styles\Theme.Normal.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Settings\AppearancePage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="IsEnabledTextBlock.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:h="using:Microsoft.CmdPal.UI.Helpers"
|
||||
xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
|
||||
xmlns:labToolkit="using:CommunityToolkit.Labs.WinUI.MarkdownTextBlock"
|
||||
xmlns:markdownImageProviders="using:Microsoft.CmdPal.UI.Helpers.MarkdownImageProviders"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
|
||||
@@ -177,7 +176,7 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Background="{ThemeResource LayerOnAcrylicPrimaryBackgroundBrush}">
|
||||
<Grid Grid.Row="0" Background="{ThemeResource LayerOnAcrylicPrimaryBackgroundBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
@@ -190,7 +189,7 @@
|
||||
Padding="0,12,0,12"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CmdPal.TopBarBorderBrush}"
|
||||
BorderThickness="0,0,0,1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@@ -390,7 +389,7 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
ui:VisualExtensions.NormalizedCenterPoint="0.5,0.5"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CmdPal.DividerStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{StaticResource ControlCornerRadius}"
|
||||
Visibility="Collapsed">
|
||||
@@ -518,7 +517,7 @@
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Background="{ThemeResource LayerOnAcrylicSecondaryBackgroundBrush}"
|
||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CmdPal.DividerStrokeColorDefaultBrush}"
|
||||
BorderThickness="0,1,0,0">
|
||||
<cpcontrols:CommandBar CurrentPageViewModel="{x:Bind ViewModel.CurrentPage, Mode=OneWay}" />
|
||||
</Grid>
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
// 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 CommunityToolkit.WinUI.Helpers;
|
||||
using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.UI;
|
||||
using Windows.UI.ViewManagement;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Provides theme appropriate for colorful (accented) appearance.
|
||||
/// </summary>
|
||||
internal sealed class ColorfulThemeProvider : IThemeProvider
|
||||
{
|
||||
// Fluent dark: #202020
|
||||
private static readonly Color DarkBaseColor = Color.FromArgb(255, 32, 32, 32);
|
||||
|
||||
// Fluent light: #F3F3F3
|
||||
private static readonly Color LightBaseColor = Color.FromArgb(255, 243, 243, 243);
|
||||
|
||||
private readonly UISettings _uiSettings;
|
||||
|
||||
public string ThemeKey => "colorful";
|
||||
|
||||
public string ResourcePath => "ms-appx:///Styles/Theme.Colorful.xaml";
|
||||
|
||||
public ColorfulThemeProvider(UISettings uiSettings)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(uiSettings);
|
||||
_uiSettings = uiSettings;
|
||||
}
|
||||
|
||||
public AcrylicBackdropParameters GetAcrylicBackdrop(ThemeContext context)
|
||||
{
|
||||
var isLight = context.Theme == ElementTheme.Light ||
|
||||
(context.Theme == ElementTheme.Default &&
|
||||
_uiSettings.GetColorValue(UIColorType.Background).R > 128);
|
||||
|
||||
var baseColor = isLight ? LightBaseColor : DarkBaseColor;
|
||||
|
||||
// Windows is warping the hue of accent colors and running it through some curves to produce their accent shades.
|
||||
// This will attempt to mimic that behavior.
|
||||
var accentShades = AccentShades.Compute(context.Tint.LerpHsv(WindowsAccentHueWarpTransform.Transform(context.Tint), 0.5f));
|
||||
var blended = isLight ? accentShades.Light3 : accentShades.Dark2;
|
||||
var colorIntensityUser = (context.ColorIntensity ?? 100) / 100f;
|
||||
|
||||
// For light theme, we want to reduce intensity a bit, and also we need to keep the color fairly light,
|
||||
// to avoid issues with text box caret.
|
||||
var colorIntensity = isLight ? 0.6f * colorIntensityUser : colorIntensityUser;
|
||||
var effectiveBgColor = ColorBlender.Blend(baseColor, blended, colorIntensity);
|
||||
|
||||
return new AcrylicBackdropParameters(effectiveBgColor, effectiveBgColor, 0.8f, 0.8f);
|
||||
}
|
||||
|
||||
private static class ColorBlender
|
||||
{
|
||||
/// <summary>
|
||||
/// Blends a semitransparent tint color over an opaque base color using alpha compositing.
|
||||
/// </summary>
|
||||
/// <param name="baseColor">The opaque base color (background)</param>
|
||||
/// <param name="tintColor">The semitransparent tint color (foreground)</param>
|
||||
/// <param name="intensity">The intensity of the tint (0.0 - 1.0)</param>
|
||||
/// <returns>The resulting blended color</returns>
|
||||
public static Color Blend(Color baseColor, Color tintColor, float intensity)
|
||||
{
|
||||
// Normalize alpha to 0.0 - 1.0 range
|
||||
intensity = Math.Clamp(intensity, 0f, 1f);
|
||||
|
||||
// Alpha compositing formula: result = tint * alpha + base * (1 - alpha)
|
||||
var r = (byte)((tintColor.R * intensity) + (baseColor.R * (1 - intensity)));
|
||||
var g = (byte)((tintColor.G * intensity) + (baseColor.G * (1 - intensity)));
|
||||
var b = (byte)((tintColor.B * intensity) + (baseColor.B * (1 - intensity)));
|
||||
|
||||
// Result is fully opaque since base is opaque
|
||||
return Color.FromArgb(255, r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
private static class WindowsAccentHueWarpTransform
|
||||
{
|
||||
private static readonly (double HIn, double HOut)[] HueMap =
|
||||
[
|
||||
(0, 0),
|
||||
(10, 1),
|
||||
(20, 6),
|
||||
(30, 10),
|
||||
(40, 14),
|
||||
(50, 19),
|
||||
(60, 36),
|
||||
(70, 94),
|
||||
(80, 112),
|
||||
(90, 120),
|
||||
(100, 120),
|
||||
(110, 120),
|
||||
(120, 120),
|
||||
(130, 120),
|
||||
(140, 120),
|
||||
(150, 125),
|
||||
(160, 135),
|
||||
(170, 142),
|
||||
(180, 178),
|
||||
(190, 205),
|
||||
(200, 220),
|
||||
(210, 229),
|
||||
(220, 237),
|
||||
(230, 241),
|
||||
(240, 243),
|
||||
(250, 244),
|
||||
(260, 245),
|
||||
(270, 248),
|
||||
(280, 252),
|
||||
(290, 276),
|
||||
(300, 293),
|
||||
(310, 313),
|
||||
(320, 330),
|
||||
(330, 349),
|
||||
(340, 353),
|
||||
(350, 357)
|
||||
];
|
||||
|
||||
public static Color Transform(Color input, Options? opt = null)
|
||||
{
|
||||
opt ??= new Options();
|
||||
var hsv = input.ToHsv();
|
||||
return ColorHelper.FromHsv(
|
||||
RemapHueLut(hsv.H),
|
||||
Clamp01(Math.Pow(hsv.S, opt.SaturationGamma) * opt.SaturationGain),
|
||||
Clamp01((opt.ValueScaleA * hsv.V) + opt.ValueBiasB),
|
||||
input.A);
|
||||
}
|
||||
|
||||
// Hue LUT remap (piecewise-linear with cyclic wrap)
|
||||
private static double RemapHueLut(double hDeg)
|
||||
{
|
||||
// Normalize to [0,360)
|
||||
hDeg = Mod(hDeg, 360.0);
|
||||
|
||||
// Handle wrap-around case: hDeg is between last entry (350°) and 360°
|
||||
var last = HueMap[^1];
|
||||
var first = HueMap[0];
|
||||
if (hDeg >= last.HIn)
|
||||
{
|
||||
// Interpolate between last entry and first entry (wrapped by 360°)
|
||||
var t = (hDeg - last.HIn) / (first.HIn + 360.0 - last.HIn + 1e-12);
|
||||
var ho = Lerp(last.HOut, first.HOut + 360.0, t);
|
||||
return Mod(ho, 360.0);
|
||||
}
|
||||
|
||||
// Find segment [i, i+1] where HueMap[i].HIn <= hDeg < HueMap[i+1].HIn
|
||||
for (var i = 0; i < HueMap.Length - 1; i++)
|
||||
{
|
||||
var a = HueMap[i];
|
||||
var b = HueMap[i + 1];
|
||||
|
||||
if (hDeg >= a.HIn && hDeg < b.HIn)
|
||||
{
|
||||
var t = (hDeg - a.HIn) / (b.HIn - a.HIn + 1e-12);
|
||||
return Lerp(a.HOut, b.HOut, t);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback (shouldn't happen)
|
||||
return hDeg;
|
||||
}
|
||||
|
||||
private static double Lerp(double a, double b, double t) => a + ((b - a) * t);
|
||||
|
||||
private static double Mod(double x, double m) => ((x % m) + m) % m;
|
||||
|
||||
private static double Clamp01(double x) => x < 0 ? 0 : (x > 1 ? 1 : x);
|
||||
|
||||
public sealed class Options
|
||||
{
|
||||
// Saturation boost (1.0 = no change). Typical: 1.3–1.8
|
||||
public double SaturationGain { get; init; } = 1.0;
|
||||
|
||||
// Optional saturation gamma (1.0 = linear). <1.0 raises low S a bit; >1.0 preserves low S.
|
||||
public double SaturationGamma { get; init; } = 1.0;
|
||||
|
||||
// Value (V) remap: V' = a*V + b (tone curve; clamp applied)
|
||||
// Example that lifts blacks & compresses whites slightly: a=0.50, b=0.08
|
||||
public double ValueScaleA { get; init; } = 0.6;
|
||||
|
||||
public double ValueBiasB { get; init; } = 0.01;
|
||||
}
|
||||
}
|
||||
|
||||
private static class AccentShades
|
||||
{
|
||||
public static (Color Light3, Color Light2, Color Light1, Color Dark1, Color Dark2, Color Dark3) Compute(Color accent)
|
||||
{
|
||||
var light1 = accent.Update(brightnessFactor: 0.15, saturationFactor: -0.12);
|
||||
var light2 = accent.Update(brightnessFactor: 0.30, saturationFactor: -0.24);
|
||||
var light3 = accent.Update(brightnessFactor: 0.45, saturationFactor: -0.36);
|
||||
|
||||
var dark1 = accent.UpdateBrightness(brightnessFactor: -0.05f);
|
||||
var dark2 = accent.UpdateBrightness(brightnessFactor: -0.01f);
|
||||
var dark3 = accent.UpdateBrightness(brightnessFactor: -0.015f);
|
||||
|
||||
return (light3, light2, light1, dark1, dark2, dark3);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Core.Common.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Provides theme identification, resource path resolution, and creation of acrylic
|
||||
/// backdrop parameters based on the current <see cref="ThemeContext"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implementations should expose a stable <see cref="ThemeKey"/> and a valid XAML resource
|
||||
/// dictionary path via <see cref="ResourcePath"/>. The
|
||||
/// <see cref="GetAcrylicBackdrop(ThemeContext)"/> method computes
|
||||
/// <see cref="AcrylicBackdropParameters"/> using the supplied theme context.
|
||||
/// </remarks>
|
||||
internal interface IThemeProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the unique key identifying this theme provider.
|
||||
/// </summary>
|
||||
string ThemeKey { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the resource dictionary path for this theme.
|
||||
/// </summary>
|
||||
string ResourcePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates acrylic backdrop parameters based on the provided theme context.
|
||||
/// </summary>
|
||||
/// <param name="context">The current theme context, including theme, tint, and optional background details.</param>
|
||||
/// <returns>The computed <see cref="AcrylicBackdropParameters"/> for the backdrop.</returns>
|
||||
AcrylicBackdropParameters GetAcrylicBackdrop(ThemeContext context);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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.UI.Xaml;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Dedicated ResourceDictionary for dynamic overrides that win over base theme resources. Since
|
||||
/// we can't use a key or name to identify the dictionary in Application resources, we use a dedicated type.
|
||||
/// </summary>
|
||||
internal sealed partial class MutableOverridesDictionary : ResourceDictionary;
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.UI;
|
||||
using Windows.UI.ViewManagement;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Provides theme resources and acrylic backdrop parameters matching the default Command Palette theme.
|
||||
/// </summary>
|
||||
internal sealed class NormalThemeProvider : IThemeProvider
|
||||
{
|
||||
private static readonly Color DarkBaseColor = Color.FromArgb(255, 32, 32, 32);
|
||||
private static readonly Color LightBaseColor = Color.FromArgb(255, 243, 243, 243);
|
||||
private readonly UISettings _uiSettings;
|
||||
|
||||
public NormalThemeProvider(UISettings uiSettings)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(uiSettings);
|
||||
_uiSettings = uiSettings;
|
||||
}
|
||||
|
||||
public string ThemeKey => "normal";
|
||||
|
||||
public string ResourcePath => "ms-appx:///Styles/Theme.Normal.xaml";
|
||||
|
||||
public AcrylicBackdropParameters GetAcrylicBackdrop(ThemeContext context)
|
||||
{
|
||||
var isLight = context.Theme == ElementTheme.Light ||
|
||||
(context.Theme == ElementTheme.Default &&
|
||||
_uiSettings.GetColorValue(UIColorType.Background).R > 128);
|
||||
|
||||
return new AcrylicBackdropParameters(
|
||||
TintColor: isLight ? LightBaseColor : DarkBaseColor,
|
||||
FallbackColor: isLight ? LightBaseColor : DarkBaseColor,
|
||||
TintOpacity: 0.5f,
|
||||
LuminosityOpacity: isLight ? 0.9f : 0.96f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
// 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.UI.Xaml;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Simple theme switcher that swaps application ResourceDictionaries at runtime.
|
||||
/// Can also operate in event-only mode for consumers to apply resources themselves.
|
||||
/// Exposes a dedicated override dictionary that stays merged and is cleared on theme changes.
|
||||
/// </summary>
|
||||
internal sealed partial class ResourceSwapper
|
||||
{
|
||||
private readonly Lock _resourceSwapGate = new();
|
||||
private readonly Dictionary<string, Uri> _themeUris = new(StringComparer.OrdinalIgnoreCase);
|
||||
private ResourceDictionary? _activeDictionary;
|
||||
private string? _currentThemeName;
|
||||
private Uri? _currentThemeUri;
|
||||
|
||||
private ResourceDictionary? _overrideDictionary;
|
||||
|
||||
/// <summary>
|
||||
/// Raised after a theme has been activated.
|
||||
/// </summary>
|
||||
public event EventHandler<ResourcesSwappedEventArgs>? ResourcesSwapped;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether when true (default) ResourceSwapper updates Application.Current.Resources. When false, it only raises ResourcesSwapped.
|
||||
/// </summary>
|
||||
public bool ApplyToAppResources { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets name of the currently selected theme (if any).
|
||||
/// </summary>
|
||||
public string? CurrentThemeName
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_resourceSwapGate)
|
||||
{
|
||||
return _currentThemeName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes ResourceSwapper by checking Application resources for an already merged theme dictionary.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
// Find merged dictionary in Application resources that matches a registered theme by URI
|
||||
// This allows ResourceSwapper to pick up an initial theme set in XAML
|
||||
var app = Application.Current;
|
||||
var resourcesMergedDictionaries = app?.Resources?.MergedDictionaries;
|
||||
if (resourcesMergedDictionaries == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var dict in resourcesMergedDictionaries)
|
||||
{
|
||||
var uri = dict.Source;
|
||||
if (uri is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var name = GetNameForUri(uri);
|
||||
if (name is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
lock (_resourceSwapGate)
|
||||
{
|
||||
_currentThemeName = name;
|
||||
_currentThemeUri = uri;
|
||||
_activeDictionary = dict;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets uri of the currently selected theme dictionary (if any).
|
||||
/// </summary>
|
||||
public Uri? CurrentThemeUri
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_resourceSwapGate)
|
||||
{
|
||||
return _currentThemeUri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ResourceDictionary GetOverrideDictionary(bool clear = false)
|
||||
{
|
||||
var app = Application.Current ?? throw new InvalidOperationException("App is null");
|
||||
|
||||
if (app.Resources == null)
|
||||
{
|
||||
throw new InvalidOperationException("Application.Resources is null");
|
||||
}
|
||||
|
||||
// (Re)locate the slot – Hot Reload may rebuild Application.Resources.
|
||||
var slot = app.Resources!.MergedDictionaries!
|
||||
.OfType<MutableOverridesDictionary>()
|
||||
.FirstOrDefault();
|
||||
|
||||
if (slot is null)
|
||||
{
|
||||
// If the slot vanished (Hot Reload), create it again at the end so it wins precedence.
|
||||
slot = new MutableOverridesDictionary();
|
||||
app.Resources.MergedDictionaries!.Add(slot);
|
||||
}
|
||||
|
||||
// Ensure the slot has exactly one child RD we can swap safely.
|
||||
if (slot.MergedDictionaries!.Count == 0)
|
||||
{
|
||||
slot.MergedDictionaries.Add(new ResourceDictionary());
|
||||
}
|
||||
else if (slot.MergedDictionaries.Count > 1)
|
||||
{
|
||||
// Normalize to a single child to keep semantics predictable.
|
||||
var keep = slot.MergedDictionaries[^1];
|
||||
slot.MergedDictionaries.Clear();
|
||||
slot.MergedDictionaries.Add(keep);
|
||||
}
|
||||
|
||||
if (clear)
|
||||
{
|
||||
// Swap the child dictionary instead of Clear() to avoid reentrancy issues.
|
||||
var fresh = new ResourceDictionary();
|
||||
slot.MergedDictionaries[0] = fresh;
|
||||
return fresh;
|
||||
}
|
||||
|
||||
return slot.MergedDictionaries[0]!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a theme name mapped to a XAML ResourceDictionary URI (e.g. ms-appx:///Themes/Red.xaml)
|
||||
/// </summary>
|
||||
public void RegisterTheme(string name, Uri dictionaryUri)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new ArgumentException("Theme name is required", nameof(name));
|
||||
}
|
||||
|
||||
lock (_resourceSwapGate)
|
||||
{
|
||||
_themeUris[name] = dictionaryUri ?? throw new ArgumentNullException(nameof(dictionaryUri));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a theme with a string URI.
|
||||
/// </summary>
|
||||
public void RegisterTheme(string name, string dictionaryUri)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dictionaryUri);
|
||||
RegisterTheme(name, new Uri(dictionaryUri));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a previously registered theme.
|
||||
/// </summary>
|
||||
public bool UnregisterTheme(string name)
|
||||
{
|
||||
lock (_resourceSwapGate)
|
||||
{
|
||||
return _themeUris.Remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the names of all registered themes.
|
||||
/// </summary>
|
||||
public IEnumerable<string> GetRegisteredThemes()
|
||||
{
|
||||
lock (_resourceSwapGate)
|
||||
{
|
||||
// return a copy to avoid external mutation
|
||||
return new List<string>(_themeUris.Keys);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates a theme by name. The dictionary for the given name must be registered first.
|
||||
/// </summary>
|
||||
public void ActivateTheme(string theme)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(theme))
|
||||
{
|
||||
throw new ArgumentException("Theme name is required", nameof(theme));
|
||||
}
|
||||
|
||||
Uri uri;
|
||||
lock (_resourceSwapGate)
|
||||
{
|
||||
if (!_themeUris.TryGetValue(theme, out uri!))
|
||||
{
|
||||
throw new KeyNotFoundException($"Theme '{theme}' is not registered.");
|
||||
}
|
||||
}
|
||||
|
||||
ActivateThemeInternal(theme, uri);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to activate a theme by name without throwing.
|
||||
/// </summary>
|
||||
public bool TryActivateTheme(string theme)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(theme))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Uri uri;
|
||||
lock (_resourceSwapGate)
|
||||
{
|
||||
if (!_themeUris.TryGetValue(theme, out uri!))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ActivateThemeInternal(theme, uri);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates a theme by URI to a ResourceDictionary.
|
||||
/// </summary>
|
||||
public void ActivateTheme(Uri dictionaryUri)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dictionaryUri);
|
||||
|
||||
ActivateThemeInternal(GetNameForUri(dictionaryUri), dictionaryUri);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the currently active theme ResourceDictionary. Also clears the override dictionary.
|
||||
/// </summary>
|
||||
public void ClearActiveTheme()
|
||||
{
|
||||
lock (_resourceSwapGate)
|
||||
{
|
||||
var app = Application.Current;
|
||||
if (app is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_activeDictionary is not null && ApplyToAppResources)
|
||||
{
|
||||
_ = app.Resources.MergedDictionaries.Remove(_activeDictionary);
|
||||
_activeDictionary = null;
|
||||
}
|
||||
|
||||
// Clear overrides but keep the override dictionary merged for future updates
|
||||
_overrideDictionary?.Clear();
|
||||
|
||||
_currentThemeName = null;
|
||||
_currentThemeUri = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ActivateThemeInternal(string? name, Uri dictionaryUri)
|
||||
{
|
||||
lock (_resourceSwapGate)
|
||||
{
|
||||
_currentThemeName = name;
|
||||
_currentThemeUri = dictionaryUri;
|
||||
}
|
||||
|
||||
if (ApplyToAppResources)
|
||||
{
|
||||
ActivateThemeCore(dictionaryUri);
|
||||
}
|
||||
|
||||
OnResourcesSwapped(new(name, dictionaryUri));
|
||||
}
|
||||
|
||||
private void ActivateThemeCore(Uri dictionaryUri)
|
||||
{
|
||||
var app = Application.Current ?? throw new InvalidOperationException("Application.Current is null");
|
||||
|
||||
// Remove previously applied base theme dictionary
|
||||
if (_activeDictionary is not null)
|
||||
{
|
||||
_ = app.Resources.MergedDictionaries.Remove(_activeDictionary);
|
||||
_activeDictionary = null;
|
||||
}
|
||||
|
||||
// Load and merge the new base theme dictionary
|
||||
var newDict = new ResourceDictionary { Source = dictionaryUri };
|
||||
app.Resources.MergedDictionaries.Add(newDict);
|
||||
_activeDictionary = newDict;
|
||||
|
||||
// Ensure override dictionary exists and is merged last, then clear it to avoid leaking stale overrides
|
||||
_overrideDictionary = GetOverrideDictionary(clear: true);
|
||||
}
|
||||
|
||||
private string? GetNameForUri(Uri dictionaryUri)
|
||||
{
|
||||
lock (_resourceSwapGate)
|
||||
{
|
||||
foreach (var (key, value) in _themeUris)
|
||||
{
|
||||
if (Uri.Compare(value, dictionaryUri, UriComponents.AbsoluteUri, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnResourcesSwapped(ResourcesSwappedEventArgs e)
|
||||
{
|
||||
ResourcesSwapped?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Services;
|
||||
|
||||
public sealed class ResourcesSwappedEventArgs(string? name, Uri dictionaryUri) : EventArgs
|
||||
{
|
||||
public string? Name { get; } = name;
|
||||
|
||||
public Uri DictionaryUri { get; } = dictionaryUri ?? throw new ArgumentNullException(nameof(dictionaryUri));
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// 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.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Services;
|
||||
|
||||
internal sealed record ThemeContext
|
||||
{
|
||||
public ElementTheme Theme { get; init; }
|
||||
|
||||
public Color Tint { get; init; }
|
||||
|
||||
public ImageSource? BackgroundImageSource { get; init; }
|
||||
|
||||
public Stretch BackgroundImageStretch { get; init; }
|
||||
|
||||
public double BackgroundImageOpacity { get; init; }
|
||||
|
||||
public int? ColorIntensity { get; init; }
|
||||
}
|
||||
261
src/modules/cmdpal/Microsoft.CmdPal.UI/Services/ThemeService.cs
Normal file
261
src/modules/cmdpal/Microsoft.CmdPal.UI/Services/ThemeService.cs
Normal file
@@ -0,0 +1,261 @@
|
||||
// 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 CommunityToolkit.WinUI;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.Helpers;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Windows.UI.ViewManagement;
|
||||
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Services;
|
||||
|
||||
/// <summary>
|
||||
/// ThemeService is a hub that translates user settings and system preferences into concrete
|
||||
/// theme resources and notifies listeners of changes.
|
||||
/// </summary>
|
||||
internal sealed partial class ThemeService : IThemeService, IDisposable
|
||||
{
|
||||
private static readonly TimeSpan ReloadDebounceInterval = TimeSpan.FromMilliseconds(500);
|
||||
|
||||
private readonly UISettings _uiSettings;
|
||||
private readonly SettingsModel _settings;
|
||||
private readonly ResourceSwapper _resourceSwapper;
|
||||
private readonly NormalThemeProvider _normalThemeProvider;
|
||||
private readonly ColorfulThemeProvider _colorfulThemeProvider;
|
||||
|
||||
private DispatcherQueue? _dispatcherQueue;
|
||||
private DispatcherQueueTimer? _dispatcherQueueTimer;
|
||||
private bool _isInitialized;
|
||||
private bool _disposed;
|
||||
private InternalThemeState _currentState;
|
||||
|
||||
public event EventHandler<ThemeChangedEventArgs>? ThemeChanged;
|
||||
|
||||
public ThemeSnapshot Current => Volatile.Read(ref _currentState).Snapshot;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the theme service. Must be called after the application window is activated and on UI thread.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
if (_dispatcherQueue is null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to get DispatcherQueue for the current thread. Ensure Initialize is called on the UI thread after window activation.");
|
||||
}
|
||||
|
||||
_dispatcherQueueTimer = _dispatcherQueue.CreateTimer();
|
||||
|
||||
_resourceSwapper.Initialize();
|
||||
_isInitialized = true;
|
||||
Reload();
|
||||
}
|
||||
|
||||
private void Reload()
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// provider selection
|
||||
var intensity = Math.Clamp(_settings.CustomThemeColorIntensity, 0, 100);
|
||||
IThemeProvider provider = intensity > 0 && _settings.ColorizationMode is ColorizationMode.CustomColor or ColorizationMode.WindowsAccentColor or ColorizationMode.Image
|
||||
? _colorfulThemeProvider
|
||||
: _normalThemeProvider;
|
||||
|
||||
// Calculate values
|
||||
var tint = _settings.ColorizationMode switch
|
||||
{
|
||||
ColorizationMode.CustomColor => _settings.CustomThemeColor,
|
||||
ColorizationMode.WindowsAccentColor => _uiSettings.GetColorValue(UIColorType.Accent),
|
||||
ColorizationMode.Image => _settings.CustomThemeColor,
|
||||
_ => Colors.Transparent,
|
||||
};
|
||||
var effectiveTheme = GetElementTheme((ElementTheme)_settings.Theme);
|
||||
var imageSource = _settings.ColorizationMode == ColorizationMode.Image
|
||||
? LoadImageSafe(_settings.BackgroundImagePath)
|
||||
: null;
|
||||
var stretch = _settings.BackgroundImageFit switch
|
||||
{
|
||||
BackgroundImageFit.Fill => Stretch.Fill,
|
||||
_ => Stretch.UniformToFill,
|
||||
};
|
||||
var opacity = Math.Clamp(_settings.BackgroundImageOpacity, 0, 100) / 100.0;
|
||||
|
||||
// create context and offload to actual theme provider
|
||||
var context = new ThemeContext
|
||||
{
|
||||
Tint = tint,
|
||||
ColorIntensity = intensity,
|
||||
Theme = effectiveTheme,
|
||||
BackgroundImageSource = imageSource,
|
||||
BackgroundImageStretch = stretch,
|
||||
BackgroundImageOpacity = opacity,
|
||||
};
|
||||
var backdrop = provider.GetAcrylicBackdrop(context);
|
||||
var blur = _settings.BackgroundImageBlurAmount;
|
||||
var brightness = _settings.BackgroundImageBrightness;
|
||||
|
||||
// Create public snapshot (no provider!)
|
||||
var snapshot = new ThemeSnapshot
|
||||
{
|
||||
Tint = tint,
|
||||
TintIntensity = intensity / 100f,
|
||||
Theme = effectiveTheme,
|
||||
BackgroundImageSource = imageSource,
|
||||
BackgroundImageStretch = stretch,
|
||||
BackgroundImageOpacity = opacity,
|
||||
BackdropParameters = backdrop,
|
||||
BlurAmount = blur,
|
||||
BackgroundBrightness = brightness / 100f,
|
||||
};
|
||||
|
||||
// Bundle with provider for internal use
|
||||
var newState = new InternalThemeState
|
||||
{
|
||||
Snapshot = snapshot,
|
||||
Provider = provider,
|
||||
};
|
||||
|
||||
// Atomic swap
|
||||
Interlocked.Exchange(ref _currentState, newState);
|
||||
|
||||
_resourceSwapper.TryActivateTheme(provider.ThemeKey);
|
||||
ThemeChanged?.Invoke(this, new ThemeChangedEventArgs());
|
||||
}
|
||||
|
||||
private static BitmapImage? LoadImageSafe(string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// If it looks like a file path and exists, prefer absolute file URI
|
||||
if (!Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out var uri))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!uri.IsAbsoluteUri && File.Exists(path))
|
||||
{
|
||||
uri = new Uri(Path.GetFullPath(path));
|
||||
}
|
||||
|
||||
return new BitmapImage(uri);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"Failed to load background image '{path}'. {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public ThemeService(SettingsModel settings, ResourceSwapper resourceSwapper)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(settings);
|
||||
ArgumentNullException.ThrowIfNull(resourceSwapper);
|
||||
|
||||
_settings = settings;
|
||||
_settings.SettingsChanged += SettingsOnSettingsChanged;
|
||||
|
||||
_resourceSwapper = resourceSwapper;
|
||||
|
||||
_uiSettings = new UISettings();
|
||||
_uiSettings.ColorValuesChanged += UiSettings_ColorValuesChanged;
|
||||
|
||||
_normalThemeProvider = new NormalThemeProvider(_uiSettings);
|
||||
_colorfulThemeProvider = new ColorfulThemeProvider(_uiSettings);
|
||||
List<IThemeProvider> providers = [_normalThemeProvider, _colorfulThemeProvider];
|
||||
|
||||
foreach (var provider in providers)
|
||||
{
|
||||
_resourceSwapper.RegisterTheme(provider.ThemeKey, provider.ResourcePath);
|
||||
}
|
||||
|
||||
_currentState = new InternalThemeState
|
||||
{
|
||||
Snapshot = new ThemeSnapshot
|
||||
{
|
||||
Tint = Colors.Transparent,
|
||||
Theme = ElementTheme.Light,
|
||||
BackdropParameters = new AcrylicBackdropParameters(Colors.Black, Colors.Black, 0.5f, 0.5f),
|
||||
BackgroundImageOpacity = 1,
|
||||
BackgroundImageSource = null,
|
||||
BackgroundImageStretch = Stretch.Fill,
|
||||
BlurAmount = 0,
|
||||
TintIntensity = 1.0f,
|
||||
BackgroundBrightness = 0,
|
||||
},
|
||||
Provider = _normalThemeProvider,
|
||||
};
|
||||
}
|
||||
|
||||
private void RequestReload()
|
||||
{
|
||||
if (!_isInitialized || _dispatcherQueueTimer is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_dispatcherQueueTimer.Debounce(Reload, ReloadDebounceInterval);
|
||||
}
|
||||
|
||||
private ElementTheme GetElementTheme(ElementTheme theme)
|
||||
{
|
||||
return theme switch
|
||||
{
|
||||
ElementTheme.Light => ElementTheme.Light,
|
||||
ElementTheme.Dark => ElementTheme.Dark,
|
||||
_ => _uiSettings.GetColorValue(UIColorType.Background).CalculateBrightness() < 0.5
|
||||
? ElementTheme.Dark
|
||||
: ElementTheme.Light,
|
||||
};
|
||||
}
|
||||
|
||||
private void SettingsOnSettingsChanged(SettingsModel sender, object? args)
|
||||
{
|
||||
RequestReload();
|
||||
}
|
||||
|
||||
private void UiSettings_ColorValuesChanged(UISettings sender, object args)
|
||||
{
|
||||
RequestReload();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
_dispatcherQueueTimer?.Stop();
|
||||
_uiSettings.ColorValuesChanged -= UiSettings_ColorValuesChanged;
|
||||
_settings.SettingsChanged -= SettingsOnSettingsChanged;
|
||||
}
|
||||
|
||||
private sealed class InternalThemeState
|
||||
{
|
||||
public required ThemeSnapshot Snapshot { get; init; }
|
||||
|
||||
public required IThemeProvider Provider { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Core.Common.Services;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Services;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes a window's theme with <see cref="IThemeService"/>.
|
||||
/// </summary>
|
||||
internal sealed partial class WindowThemeSynchronizer : IDisposable
|
||||
{
|
||||
private readonly IThemeService _themeService;
|
||||
private readonly Window _window;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WindowThemeSynchronizer"/> class and subscribes to theme changes.
|
||||
/// </summary>
|
||||
/// <param name="themeService">The theme service to monitor for changes.</param>
|
||||
/// <param name="window">The window to synchronize.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="themeService"/> or <paramref name="window"/> is null.</exception>
|
||||
public WindowThemeSynchronizer(IThemeService themeService, Window window)
|
||||
{
|
||||
_themeService = themeService ?? throw new ArgumentNullException(nameof(themeService));
|
||||
_window = window ?? throw new ArgumentNullException(nameof(window));
|
||||
_themeService.ThemeChanged += ThemeServiceOnThemeChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes from theme change events.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_themeService.ThemeChanged -= ThemeServiceOnThemeChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the current theme to the window when theme changes occur.
|
||||
/// </summary>
|
||||
private void ThemeServiceOnThemeChanged(object? sender, ThemeChangedEventArgs e)
|
||||
{
|
||||
if (_window.Content is not FrameworkElement fe)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dispatcherQueue = fe.DispatcherQueue;
|
||||
|
||||
if (dispatcherQueue is not null && dispatcherQueue.HasThreadAccess)
|
||||
{
|
||||
ApplyRequestedTheme(fe);
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatcherQueue?.TryEnqueue(() => ApplyRequestedTheme(fe));
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyRequestedTheme(FrameworkElement fe)
|
||||
{
|
||||
// LOAD BEARING: Changing the RequestedTheme to Dark then Light then target forces
|
||||
// a refresh of the theme.
|
||||
fe.RequestedTheme = ElementTheme.Dark;
|
||||
fe.RequestedTheme = ElementTheme.Light;
|
||||
fe.RequestedTheme = _themeService.Current.Theme;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="Microsoft.CmdPal.UI.Settings.AppearancePage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:helpers="using:Microsoft.CmdPal.UI.Helpers"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ptControls="using:Microsoft.CmdPal.UI.Controls"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ScrollViewer Grid.Row="1">
|
||||
<Grid Padding="16">
|
||||
<StackPanel
|
||||
MaxWidth="1000"
|
||||
HorizontalAlignment="Stretch"
|
||||
Spacing="{StaticResource SettingsCardSpacing}">
|
||||
|
||||
<ptControls:ScreenPreview Margin="0,0,0,16" HorizontalAlignment="Left">
|
||||
<ptControls:CommandPalettePreview
|
||||
PreviewBackgroundColor="{x:Bind ViewModel.Appearance.EffectiveBackdrop.TintColor, Mode=OneWay}"
|
||||
PreviewBackgroundImageBlurAmount="{x:Bind ViewModel.Appearance.EffectiveBackgroundImageBlurAmount, Mode=OneWay}"
|
||||
PreviewBackgroundImageBrightness="{x:Bind ViewModel.Appearance.EffectiveBackgroundImageBrightness, Mode=OneWay}"
|
||||
PreviewBackgroundImageFit="{x:Bind ViewModel.Appearance.BackgroundImageFit, Mode=OneWay}"
|
||||
PreviewBackgroundImageSource="{x:Bind ViewModel.Appearance.EffectiveBackgroundImageSource, Mode=OneWay}"
|
||||
PreviewBackgroundImageTint="{x:Bind ViewModel.Appearance.EffectiveThemeColor, Mode=OneWay}"
|
||||
PreviewBackgroundImageTintIntensity="{x:Bind ViewModel.Appearance.ColorIntensity, Mode=OneWay}"
|
||||
PreviewBackgroundOpacity="{x:Bind ViewModel.Appearance.EffectiveBackdrop.TintOpacity, Mode=OneWay}"
|
||||
RequestedTheme="{x:Bind ViewModel.Appearance.EffectiveTheme, Mode=OneWay}" />
|
||||
</ptControls:ScreenPreview>
|
||||
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_AppTheme_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind ViewModel.Appearance.ThemeIndex, Mode=TwoWay}">
|
||||
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_AppTheme_Mode_System_Automation" Tag="Default">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="16" Glyph="" />
|
||||
<TextBlock x:Uid="Settings_GeneralPage_AppTheme_Mode_System" />
|
||||
</StackPanel>
|
||||
</ComboBoxItem>
|
||||
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_AppTheme_Mode_Light_Automation" Tag="Light">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="16" Glyph="" />
|
||||
<TextBlock x:Uid="Settings_GeneralPage_AppTheme_Mode_Light" />
|
||||
</StackPanel>
|
||||
</ComboBoxItem>
|
||||
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_AppTheme_Mode_Dark_Automation" Tag="Dark">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<FontIcon FontSize="16" Glyph="" />
|
||||
<TextBlock x:Uid="Settings_GeneralPage_AppTheme_Mode_Dark" />
|
||||
</StackPanel>
|
||||
</ComboBoxItem>
|
||||
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsExpander
|
||||
x:Uid="Settings_GeneralPage_Background_SettingsExpander"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="{x:Bind ViewModel.Appearance.IsColorizationDetailsExpanded, Mode=TwoWay}">
|
||||
<ComboBox
|
||||
x:Uid="Settings_GeneralPage_ColorizationMode"
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
SelectedIndex="{x:Bind ViewModel.Appearance.ColorizationModeIndex, Mode=TwoWay}">
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_ColorizationMode_None" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_ColorizationMode_WindowsAccent" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_ColorizationMode_CustomColor" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_ColorizationMode_Image" />
|
||||
</ComboBox>
|
||||
<controls:SettingsExpander.Items>
|
||||
<!-- none -->
|
||||
<controls:SettingsCard
|
||||
x:Uid="Settings_GeneralPage_NoBackground_SettingsCard"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
ContentAlignment="Vertical"
|
||||
Visibility="{x:Bind ViewModel.Appearance.IsNoBackgroundVisible, Mode=OneWay}">
|
||||
<TextBlock
|
||||
x:Uid="Settings_GeneralPage_NoBackground_DescriptionTextBlock"
|
||||
Margin="24"
|
||||
HorizontalAlignment="Stretch"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
HorizontalTextAlignment="Center"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- system accent color -->
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_WindowsAccentColor_SettingsCard" Visibility="{x:Bind ViewModel.Appearance.IsAccentColorControlsVisible, Mode=OneWay}">
|
||||
<controls:SettingsCard.Description>
|
||||
<TextBlock>
|
||||
<Run x:Uid="Settings_GeneralPage_WindowsAccentColor_SettingsCard_Description1" />
|
||||
<Hyperlink
|
||||
Click="OpenWindowsColorsSettings_Click"
|
||||
TextDecorations="None"
|
||||
UnderlineStyle="None">
|
||||
<Run x:Uid="Settings_GeneralPage_WindowsAccentColor_OpenWindowsColorsLinkText" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
</controls:SettingsCard.Description>
|
||||
<controls:SettingsCard.Content>
|
||||
<Border
|
||||
MinWidth="32"
|
||||
MinHeight="32"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{x:Bind ViewModel.Appearance.EffectiveThemeColor, Mode=OneWay}" />
|
||||
</Border.Background>
|
||||
</Border>
|
||||
</controls:SettingsCard.Content>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- background -->
|
||||
<controls:SettingsCard
|
||||
x:Uid="Settings_GeneralPage_BackgroundImage_SettingsCard"
|
||||
Description="{x:Bind ViewModel.Appearance.BackgroundImagePath, Mode=OneWay}"
|
||||
Visibility="{x:Bind ViewModel.Appearance.IsBackgroundControlsVisible, Mode=OneWay}">
|
||||
<Button x:Uid="Settings_GeneralPage_BackgroundImage_ChooseImageButton" Click="PickBackgroundImage_Click" />
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_BackgroundImageBrightness_SettingsCard" Visibility="{x:Bind ViewModel.Appearance.IsBackgroundControlsVisible, Mode=OneWay}">
|
||||
<Slider
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
Maximum="100"
|
||||
Minimum="-100"
|
||||
StepFrequency="1"
|
||||
Value="{x:Bind ViewModel.Appearance.BackgroundImageBrightness, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_BackgroundImageBlur_SettingsCard" Visibility="{x:Bind ViewModel.Appearance.IsBackgroundControlsVisible, Mode=OneWay}">
|
||||
<Slider
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
Maximum="50"
|
||||
Minimum="0"
|
||||
StepFrequency="1"
|
||||
Value="{x:Bind ViewModel.Appearance.BackgroundImageBlurAmount, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_BackgroundImageFit_SettingsCard" Visibility="{x:Bind ViewModel.Appearance.IsBackgroundControlsVisible, Mode=OneWay}">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.Appearance.BackgroundImageFitIndex, Mode=TwoWay}">
|
||||
<ComboBoxItem x:Uid="BackgroundImageFit_ComboBoxItem_Fill" />
|
||||
<ComboBoxItem x:Uid="BackgroundImageFit_ComboBoxItem_Stretch" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- Background tint color and intensity -->
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_BackgroundTint_SettingsCard" Visibility="{x:Bind ViewModel.Appearance.IsCustomTintVisible, Mode=OneWay}">
|
||||
<ptControls:ColorPickerButton
|
||||
HasSelectedColor="True"
|
||||
IsAlphaEnabled="False"
|
||||
PaletteColors="{x:Bind ViewModel.Appearance.Swatches}"
|
||||
SelectedColor="{x:Bind ViewModel.Appearance.ThemeColor, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_BackgroundTintIntensity_SettingsCard" Visibility="{x:Bind ViewModel.Appearance.IsCustomTintIntensityVisible, Mode=OneWay}">
|
||||
<Slider
|
||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||
Maximum="100"
|
||||
Minimum="1"
|
||||
StepFrequency="1"
|
||||
Value="{x:Bind ViewModel.Appearance.ColorIntensity, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- Reset background image properties -->
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_BackgroundImage_ResetProperties_SettingsCard" Visibility="{x:Bind ViewModel.Appearance.IsBackgroundControlsVisible, Mode=OneWay}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Button x:Uid="Settings_GeneralPage_Background_ResetImagePropertiesButton" Command="{x:Bind ViewModel.Appearance.ResetBackgroundImagePropertiesCommand}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<!-- 'Behavior' section -->
|
||||
|
||||
<TextBlock x:Uid="BehaviorSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_ShowAppDetails_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.ShowAppDetails, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_BackspaceGoesBack_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.BackspaceGoesBack, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_EscapeKeyBehavior_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind ViewModel.EscapeKeyBehaviorIndex, Mode=TwoWay}">
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_EscapeKeyBehavior_Option_DismissEmptySearchOrGoBack" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_EscapeKeyBehavior_Option_AlwaysGoBack" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_EscapeKeyBehavior_Option_AlwaysDismiss" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_EscapeKeyBehavior_Option_AlwaysHide" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_SingleClickActivation_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.SingleClickActivates, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_DisableAnimations_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.DisableAnimations, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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.Diagnostics;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Documents;
|
||||
using Microsoft.Windows.Storage.Pickers;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class AppearancePage : Page
|
||||
{
|
||||
private readonly TaskScheduler _mainTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
|
||||
|
||||
internal SettingsViewModel ViewModel { get; }
|
||||
|
||||
public AppearancePage()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
var settings = App.Current.Services.GetService<SettingsModel>()!;
|
||||
ViewModel = new SettingsViewModel(settings, App.Current.Services, _mainTaskScheduler);
|
||||
}
|
||||
|
||||
private async void PickBackgroundImage_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (XamlRoot?.ContentIslandEnvironment is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var windowId = XamlRoot?.ContentIslandEnvironment?.AppWindowId ?? new WindowId(0);
|
||||
|
||||
var picker = new FileOpenPicker(windowId)
|
||||
{
|
||||
CommitButtonText = ViewModels.Properties.Resources.builtin_settings_appearance_pick_background_image_title!,
|
||||
SuggestedStartLocation = PickerLocationId.PicturesLibrary,
|
||||
ViewMode = PickerViewMode.Thumbnail,
|
||||
};
|
||||
|
||||
string[] extensions = [".png", ".bmp", ".jpg", ".jpeg", ".jfif", ".gif", ".tiff", ".tif", ".webp", ".jxr"];
|
||||
foreach (var ext in extensions)
|
||||
{
|
||||
picker.FileTypeFilter!.Add(ext);
|
||||
}
|
||||
|
||||
var file = await picker.PickSingleFileAsync()!;
|
||||
if (file != null)
|
||||
{
|
||||
ViewModel.Appearance.BackgroundImagePath = file.Path ?? string.Empty;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to pick background image file", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenWindowsColorsSettings_Click(Hyperlink sender, HyperlinkClickEventArgs args)
|
||||
{
|
||||
// LOAD BEARING (or BEAR LOADING?): Process.Start with UseShellExecute inside a XAML input event can trigger WinUI reentrancy
|
||||
// and cause FailFast crashes. Task.Run moves the call off the UI thread to prevent hard process termination.
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = Process.Start(new ProcessStartInfo("ms-settings:colors") { UseShellExecute = true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to open Windows Settings", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -81,35 +81,10 @@
|
||||
|
||||
<TextBlock x:Uid="BehaviorSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_ShowAppDetails_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind viewModel.ShowAppDetails, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_BackspaceGoesBack_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind viewModel.BackspaceGoesBack, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_EscapeKeyBehavior_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind viewModel.EscapeKeyBehaviorIndex, Mode=TwoWay}">
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_EscapeKeyBehavior_Option_DismissEmptySearchOrGoBack" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_EscapeKeyBehavior_Option_AlwaysGoBack" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_EscapeKeyBehavior_Option_AlwaysDismiss" />
|
||||
<ComboBoxItem x:Uid="Settings_GeneralPage_EscapeKeyBehavior_Option_AlwaysHide" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_SingleClickActivation_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind viewModel.SingleClickActivates, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_ShowSystemTrayIcon_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind viewModel.ShowSystemTrayIcon, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="Settings_GeneralPage_DisableAnimations_SettingsCard" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind viewModel.DisableAnimations, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- 'For Developers' section -->
|
||||
|
||||
<TextBlock x:Uid="ForDevelopersSettingsHeader" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
@@ -62,6 +62,11 @@
|
||||
x:Uid="Settings_GeneralPage_NavigationViewItem_General"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="General" />
|
||||
<NavigationViewItem
|
||||
x:Name="AppearancePageNavItem"
|
||||
x:Uid="Settings_GeneralPage_NavigationViewItem_Appearance"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="Appearance" />
|
||||
<NavigationViewItem
|
||||
x:Name="ExtensionPageNavItem"
|
||||
x:Uid="Settings_GeneralPage_NavigationViewItem_Extensions"
|
||||
|
||||
@@ -101,10 +101,10 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
var pageType = page switch
|
||||
{
|
||||
"General" => typeof(GeneralPage),
|
||||
"Appearance" => typeof(AppearancePage),
|
||||
"Extensions" => typeof(ExtensionsPage),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
if (pageType is not null)
|
||||
{
|
||||
NavFrame.Navigate(pageType);
|
||||
@@ -248,6 +248,12 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
var pageType = RS_.GetString("Settings_PageTitles_GeneralPage");
|
||||
BreadCrumbs.Add(new(pageType, pageType));
|
||||
}
|
||||
else if (e.SourcePageType == typeof(AppearancePage))
|
||||
{
|
||||
NavView.SelectedItem = AppearancePageNavItem;
|
||||
var pageType = RS_.GetString("Settings_PageTitles_AppearancePage");
|
||||
BreadCrumbs.Add(new(pageType, pageType));
|
||||
}
|
||||
else if (e.SourcePageType == typeof(ExtensionsPage))
|
||||
{
|
||||
NavView.SelectedItem = ExtensionPageNavItem;
|
||||
|
||||
@@ -550,9 +550,69 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<data name="Settings_GeneralPage_AutoGoHome_SettingsCard.Description" xml:space="preserve">
|
||||
<value>Automatically returns to home page after a period of inactivity when Command Palette is closed</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_NavigationViewItem_Appearance.Content" xml:space="preserve">
|
||||
<value>Personalization</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AppTheme_SettingsCard.Header" xml:space="preserve">
|
||||
<value>App theme mode</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AppTheme_SettingsCard.Description" xml:space="preserve">
|
||||
<value>Select which app theme to display</value>
|
||||
</data>
|
||||
<data name="AppearanceSettingsHeader.Text" xml:space="preserve">
|
||||
<value>Appearance</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AppTheme_Mode_System.Text" xml:space="preserve">
|
||||
<value>Use system settings</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AppTheme_Mode_Light.Text" xml:space="preserve">
|
||||
<value>Light</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AppTheme_Mode_Dark.Text" xml:space="preserve">
|
||||
<value>Dark</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_BackgroundTint_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Color tint</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_BackgroundTintIntensity_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Color intensity</value>
|
||||
</data>
|
||||
<data name="OptionalColorPickerButton_UnsetTextBlock.Text" xml:space="preserve">
|
||||
<value>Choose color</value>
|
||||
</data>
|
||||
<data name="OptionalColorPickerButton_ResetButton.Content" xml:space="preserve">
|
||||
<value>Use default</value>
|
||||
</data>
|
||||
<data name="OptionalColorPickerButton_TransparentColorButton.Content" xml:space="preserve">
|
||||
<value>Use default color</value>
|
||||
</data>
|
||||
<data name="OptionalColorPickerButton_WindowsColorsSectionHeading.Text" xml:space="preserve">
|
||||
<value>Windows colors</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_BackgroundImage_SettingsExpander.Header" xml:space="preserve">
|
||||
<value>Background image</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_BackgroundImageOpacity_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Background image opacity</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_BackgroundImageFit_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Background image fit</value>
|
||||
</data>
|
||||
<data name="BackgroundImageFit_ComboBoxItem_Fill.Content" xml:space="preserve">
|
||||
<value>Fill</value>
|
||||
</data>
|
||||
<data name="BackgroundImageFit_ComboBoxItem_Fit.Content" xml:space="preserve">
|
||||
<value>Fit</value>
|
||||
</data>
|
||||
<data name="BackgroundImageFit_ComboBoxItem_Stretch.Content" xml:space="preserve">
|
||||
<value>Stretch</value>
|
||||
</data>
|
||||
<data name="Settings_PageTitles_GeneralPage" xml:space="preserve">
|
||||
<value>General</value>
|
||||
</data>
|
||||
<data name="Settings_PageTitles_AppearancePage" xml:space="preserve">
|
||||
<value>Personalization</value>
|
||||
</data>
|
||||
<data name="Settings_PageTitles_ExtensionsPage" xml:space="preserve">
|
||||
<value>Extensions</value>
|
||||
</data>
|
||||
@@ -577,4 +637,73 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<data name="SettingsButtonTextBlock.Text" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="OptionalColorPickerButton_CustomColorsSectionHeading.Text" xml:space="preserve">
|
||||
<value>Custom colors</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_ColorizationMode_None.Content" xml:space="preserve">
|
||||
<value>None</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_ColorizationMode_CustomColor.Content" xml:space="preserve">
|
||||
<value>Custom color</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_ColorizationMode_WindowsAccent.Content" xml:space="preserve">
|
||||
<value>Accent color</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_ColorizationMode_Image.Content" xml:space="preserve">
|
||||
<value>Image</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_BackgroundImage_ChooseImageButton.Content" xml:space="preserve">
|
||||
<value>Browse...</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_BackgroundImage_ResetImageButton.Content" xml:space="preserve">
|
||||
<value>Remove image</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_BackgroundColor_SettingsExpander.Header" xml:space="preserve">
|
||||
<value>Background color</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_BackgroundColor_SettingsExpander.Description" xml:space="preserve">
|
||||
<value>Choose a custom background color or use the current accent color</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_BackgroundImage_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Background image</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_NoBackground_DescriptionTextBlock.Text" xml:space="preserve">
|
||||
<value>No settings</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_Background_SettingsExpander.Header" xml:space="preserve">
|
||||
<value>Background</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_Background_SettingsExpander.Description" xml:space="preserve">
|
||||
<value>Choose a custom background color or image</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_WindowsAccentColor_SettingsCard.Header" xml:space="preserve">
|
||||
<value>System accent color</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_WindowsAccentColor_OpenWindowsColorsLinkText.Text" xml:space="preserve">
|
||||
<value>Personalization › Colors</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_BackgroundImageBlur_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Background image blur</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_BackgroundImageBrightness_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Background image brightness</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_BackgroundImage_ResetProperties_SettingsCard.Header" xml:space="preserve">
|
||||
<value>Restore defaults</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_Background_ResetImagePropertiesButton.Content" xml:space="preserve">
|
||||
<value>Reset</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_WindowsAccentColor_SettingsCard_Description1.Text" xml:space="preserve">
|
||||
<value>Change the system accent in Windows Settings:</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AppTheme_Mode_Light_Automation.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Light</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AppTheme_Mode_Dark_Automation.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Dark</value>
|
||||
</data>
|
||||
<data name="Settings_GeneralPage_AppTheme_Mode_System_Automation.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Use system settings</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -4,37 +4,5 @@
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<!-- Other merged dictionaries here -->
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<!-- For slightly adjust the LayerOnAcrylicFillColorDefault color so that the cursor of the searchbox shows -->
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<!-- This is a local copy of LayerOnAcrylicFillColorDefaultBrush -->
|
||||
<SolidColorBrush
|
||||
x:Key="LayerOnAcrylicPrimaryBackgroundBrush"
|
||||
Opacity="0.3"
|
||||
Color="#222222" />
|
||||
<SolidColorBrush
|
||||
x:Key="LayerOnAcrylicSecondaryBackgroundBrush"
|
||||
Opacity="0.0"
|
||||
Color="#222222" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<SolidColorBrush
|
||||
x:Key="LayerOnAcrylicPrimaryBackgroundBrush"
|
||||
Opacity="0.65"
|
||||
Color="#FFFFFF" />
|
||||
<!-- Because we are tweaking the LayerOnAcrylicPrimaryBackgroundBrush, we need to tweak the command bar background too. If not, it's too bright. -->
|
||||
<SolidColorBrush
|
||||
x:Key="LayerOnAcrylicSecondaryBackgroundBrush"
|
||||
Opacity="0.4"
|
||||
Color="#FFFFFF" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<!-- This is a local copy of LayerOnAcrylicFillColorDefaultBrush -->
|
||||
<SolidColorBrush x:Key="LayerOnAcrylicPrimaryBackgroundBrush" Color="{ThemeResource LayerOnAcrylicFillColorDefault}" />
|
||||
<SolidColorBrush x:Key="LayerOnAcrylicSecondaryBackgroundBrush" Color="Transparent" />
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<StaticResource x:Key="LayerOnAcrylicPrimaryBackgroundBrush" ResourceKey="LayerOnAccentAcrylicFillColorDefaultBrush" />
|
||||
<SolidColorBrush x:Key="LayerOnAcrylicSecondaryBackgroundBrush" Color="Transparent" />
|
||||
<StaticResource x:Key="CmdPal.CommandBarBorderBrush" ResourceKey="CardStrokeColorDefaultBrush" />
|
||||
<StaticResource x:Key="CmdPal.TopBarBorderBrush" ResourceKey="DividerStrokeColorDefaultBrush" />
|
||||
<StaticResource x:Key="CmdPal.DividerStrokeColorDefaultBrush" ResourceKey="CardStrokeColorDefaultBrush" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<SolidColorBrush x:Key="LayerOnAcrylicPrimaryBackgroundBrush" Color="#A0FFFFFF" />
|
||||
<SolidColorBrush x:Key="LayerOnAcrylicSecondaryBackgroundBrush" Color="Transparent" />
|
||||
<StaticResource x:Key="CmdPal.CommandBarBorderBrush" ResourceKey="CardStrokeColorDefaultBrush" />
|
||||
<StaticResource x:Key="CmdPal.TopBarBorderBrush" ResourceKey="DividerStrokeColorDefaultBrush" />
|
||||
<StaticResource x:Key="CmdPal.DividerStrokeColorDefaultBrush" ResourceKey="CardStrokeColorDefaultBrush" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<SolidColorBrush x:Key="LayerOnAcrylicPrimaryBackgroundBrush" Color="{ThemeResource SystemColorWindowColor}" />
|
||||
<SolidColorBrush x:Key="LayerOnAcrylicSecondaryBackgroundBrush" Color="{ThemeResource SystemColorWindowColor}" />
|
||||
<SolidColorBrush x:Key="CmdPal.CommandBarBorderBrush" Color="{ThemeResource SystemColorWindowTextColor}" />
|
||||
<SolidColorBrush x:Key="CmdPal.TopBarBorderBrush" Color="{ThemeResource SystemColorWindowTextColor}" />
|
||||
<SolidColorBrush x:Key="CmdPal.DividerStrokeColorDefaultBrush" Color="{ThemeResource SystemColorWindowTextColor}" />
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<SolidColorBrush x:Key="LayerOnAcrylicPrimaryBackgroundBrush" Color="#50202020" />
|
||||
<SolidColorBrush x:Key="LayerOnAcrylicSecondaryBackgroundBrush" Color="Transparent" />
|
||||
<StaticResource x:Key="CmdPal.CommandBarBorderBrush" ResourceKey="DividerStrokeColorDefaultBrush" />
|
||||
<StaticResource x:Key="CmdPal.TopBarBorderBrush" ResourceKey="DividerStrokeColorDefaultBrush" />
|
||||
<StaticResource x:Key="CmdPal.DividerStrokeColorDefaultBrush" ResourceKey="DividerStrokeColorDefaultBrush" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<!--
|
||||
TextBox caret is rendered as inverted and needs clearly-defined background
|
||||
https://github.com/zadjii-msft/PowerToys/issues/348
|
||||
Because we are tweaking the LayerOnAcrylicPrimaryBackgroundBrush, we need to tweak the command bar background too. If not, it's too bright.
|
||||
-->
|
||||
<SolidColorBrush x:Key="LayerOnAcrylicPrimaryBackgroundBrush" Color="#A0FFFFFF" />
|
||||
<SolidColorBrush x:Key="LayerOnAcrylicSecondaryBackgroundBrush" Color="#66FFFFFF" />
|
||||
<StaticResource x:Key="CmdPal.CommandBarBorderBrush" ResourceKey="DividerStrokeColorDefaultBrush" />
|
||||
<StaticResource x:Key="CmdPal.TopBarBorderBrush" ResourceKey="DividerStrokeColorDefaultBrush" />
|
||||
<StaticResource x:Key="CmdPal.DividerStrokeColorDefaultBrush" ResourceKey="DividerStrokeColorDefaultBrush" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<SolidColorBrush x:Key="LayerOnAcrylicPrimaryBackgroundBrush" Color="{ThemeResource SystemColorWindowColor}" />
|
||||
<SolidColorBrush x:Key="LayerOnAcrylicSecondaryBackgroundBrush" Color="{ThemeResource SystemColorWindowColor}" />
|
||||
<SolidColorBrush x:Key="CmdPal.CommandBarBorderBrush" Color="{ThemeResource SystemColorWindowTextColor}" />
|
||||
<SolidColorBrush x:Key="CmdPal.TopBarBorderBrush" Color="{ThemeResource SystemColorWindowTextColor}" />
|
||||
<SolidColorBrush x:Key="CmdPal.DividerStrokeColorDefaultBrush" Color="{ThemeResource SystemColorWindowTextColor}" />
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
</ResourceDictionary>
|
||||
Reference in New Issue
Block a user