diff --git a/PowerToys.sln b/PowerToys.sln index ca8964e86f..0447aadd42 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -831,6 +831,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerToys.ModuleContracts", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Awake.ModuleServices", "src\modules\awake\Awake.ModuleServices\Awake.ModuleServices.csproj", "{2141FF78-5F51-ED6B-E11B-C7079CCA1456}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorPicker.ModuleServices", "src\modules\colorPicker\ColorPicker.ModuleServices\ColorPicker.ModuleServices.csproj", "{C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -5273,6 +5275,22 @@ Global {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Release|x64.Build.0 = Release|x64 {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Release|x86.ActiveCfg = Release|x64 {2141FF78-5F51-ED6B-E11B-C7079CCA1456}.Release|x86.Build.0 = Release|x64 + {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Debug|Any CPU.ActiveCfg = Debug|x64 + {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Debug|Any CPU.Build.0 = Debug|x64 + {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Debug|ARM64.Build.0 = Debug|ARM64 + {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Debug|x64.ActiveCfg = Debug|x64 + {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Debug|x64.Build.0 = Debug|x64 + {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Debug|x86.ActiveCfg = Debug|x64 + {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Debug|x86.Build.0 = Debug|x64 + {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Release|Any CPU.ActiveCfg = Release|x64 + {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Release|Any CPU.Build.0 = Release|x64 + {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Release|ARM64.ActiveCfg = Release|ARM64 + {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Release|ARM64.Build.0 = Release|ARM64 + {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Release|x64.ActiveCfg = Release|x64 + {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Release|x64.Build.0 = Release|x64 + {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Release|x86.ActiveCfg = Release|x64 + {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52}.Release|x86.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -5607,6 +5625,7 @@ Global {D52AAF95-DE88-49EA-B28A-10E382BCD4AB} = {A2221D7E-55E7-4BEA-90D1-4F162D670BBF} {094E65D5-585E-4898-B465-97A47CD44380} = {1AFB6476-670D-4E80-A464-657E01DFF482} {2141FF78-5F51-ED6B-E11B-C7079CCA1456} = {127F38E0-40AA-4594-B955-5616BF206882} + {C9F3F4D1-A457-2A6E-DE4E-ED0DDB8DDA52} = {1D78B84B-CA39-406C-98F4-71F7EC266CC0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/ColorPicker/CopySavedColorCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/ColorPicker/CopySavedColorCommand.cs new file mode 100644 index 0000000000..f7849bb4e5 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/ColorPicker/CopySavedColorCommand.cs @@ -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. + +using System; +using System.Windows.Forms; +using ColorPicker.ModuleServices; +using Microsoft.CommandPalette.Extensions.Toolkit; + +namespace PowerToysExtension.Commands; + +/// +/// Copies a saved color in a chosen format. +/// +internal sealed partial class CopySavedColorCommand : InvokableCommand +{ + private readonly SavedColor _color; + private readonly string _copyValue; + + public CopySavedColorCommand(SavedColor color, string copyValue) + { + _color = color; + _copyValue = copyValue; + Name = $"Copy {_color.Hex}"; + } + + public override CommandResult Invoke() + { + try + { + Clipboard.SetText(_copyValue); + return CommandResult.ShowToast($"Copied {_copyValue}"); + } + catch (Exception ex) + { + return CommandResult.ShowToast($"Failed to copy color: {ex.Message}"); + } + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/ColorPicker/OpenColorPickerCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/ColorPicker/OpenColorPickerCommand.cs new file mode 100644 index 0000000000..6982c5dffe --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/ColorPicker/OpenColorPickerCommand.cs @@ -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 System; +using ColorPicker.ModuleServices; +using Microsoft.CommandPalette.Extensions.Toolkit; + +namespace PowerToysExtension.Commands; + +/// +/// Opens the Color Picker picker session via shared event. +/// +internal sealed partial class OpenColorPickerCommand : InvokableCommand +{ + public OpenColorPickerCommand() + { + Name = "Open Color Picker"; + } + + public override CommandResult Invoke() + { + try + { + var result = ColorPickerService.Instance.OpenPickerAsync().GetAwaiter().GetResult(); + if (!result.Success) + { + return CommandResult.ShowToast(result.Error ?? "Failed to open Color Picker."); + } + + return CommandResult.Dismiss(); + } + catch (Exception ex) + { + return CommandResult.ShowToast($"Failed to open Color Picker: {ex.Message}"); + } + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/ColorSwatchIconFactory.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/ColorSwatchIconFactory.cs new file mode 100644 index 0000000000..3370268611 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/ColorSwatchIconFactory.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; +using Microsoft.CommandPalette.Extensions.Toolkit; + +namespace PowerToysExtension.Helpers; + +internal static class ColorSwatchIconFactory +{ + public static IconInfo Create(byte r, byte g, byte b, byte a) + { + try + { + using var bmp = new Bitmap(32, 32, PixelFormat.Format32bppArgb); + using var gfx = Graphics.FromImage(bmp); + gfx.SmoothingMode = SmoothingMode.AntiAlias; + gfx.Clear(Color.Transparent); + + using var brush = new SolidBrush(Color.FromArgb(a, r, g, b)); + const int padding = 4; + gfx.FillEllipse(brush, padding, padding, bmp.Width - (padding * 2), bmp.Height - (padding * 2)); + + using var ms = new MemoryStream(); + bmp.Save(ms, ImageFormat.Png); + var iconData = IconInfo.FromStream(ms.AsRandomAccessStream()).Dark as IconData; + return iconData != null ? new IconInfo(iconData, iconData) : new IconInfo("\u25CF"); + } + catch + { + // Fallback to a simple colored glyph when drawing fails. + return new IconInfo("\u25CF"); // Black circle glyph + } + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj index c5f0f021bf..89214fd45e 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj @@ -57,6 +57,7 @@ + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs index 7eb11172de..821b3b1c92 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs @@ -7,6 +7,7 @@ using Common.UI; using Microsoft.CommandPalette.Extensions.Toolkit; using PowerToysExtension.Commands; using PowerToysExtension.Helpers; +using PowerToysExtension.Pages; namespace PowerToysExtension.Modules; @@ -17,19 +18,39 @@ internal sealed class ColorPickerModuleCommandProvider : ModuleCommandProvider var title = SettingsDeepLink.SettingsWindow.ColorPicker.ModuleDisplayName(); var icon = SettingsDeepLink.SettingsWindow.ColorPicker.ModuleIcon(); + var commands = new List(); + + // Quick actions under MoreCommands. var more = new List { + new CommandContextItem(new OpenColorPickerCommand()), new CommandContextItem(new CopyColorCommand()), + new CommandContextItem(new ColorPickerSavedColorsPage()), }; - var item = new ListItem(new OpenInSettingsCommand(SettingsDeepLink.SettingsWindow.ColorPicker, title)) + commands.Add(new ListItem(new OpenInSettingsCommand(SettingsDeepLink.SettingsWindow.ColorPicker, title)) { Title = title, Subtitle = "Open Color Picker settings", Icon = icon, MoreCommands = more.ToArray(), - }; + }); - return [item]; + // Direct entries in the module list. + commands.Add(new ListItem(new OpenColorPickerCommand()) + { + Title = "Open Color Picker", + Subtitle = "Start a color pick session", + Icon = icon, + }); + + commands.Add(new ListItem(new CommandItem(new ColorPickerSavedColorsPage())) + { + Title = "Saved colors", + Subtitle = "Browse and copy saved colors", + Icon = icon, + }); + + return commands; } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/ColorPickerSavedColorsPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/ColorPickerSavedColorsPage.cs new file mode 100644 index 0000000000..6193ee2a3d --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/ColorPickerSavedColorsPage.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Text; +using ColorPicker.ModuleServices; +using Microsoft.CommandPalette.Extensions; +using Microsoft.CommandPalette.Extensions.Toolkit; +using PowerToysExtension.Commands; +using PowerToysExtension.Helpers; + +namespace PowerToysExtension.Pages; + +internal sealed partial class ColorPickerSavedColorsPage : DynamicListPage +{ + private readonly CommandItem _emptyContent; + + public ColorPickerSavedColorsPage() + { + Icon = IconHelpers.FromRelativePath("Assets\\ColorPicker.png"); + Title = "Saved colors"; + Name = "ColorPickerSavedColors"; + Id = "com.microsoft.powertoys.colorpicker.savedcolors"; + + _emptyContent = new CommandItem() + { + Title = "No saved colors", + Subtitle = "Pick a color first, then try again.", + Icon = IconHelpers.FromRelativePath("Assets\\ColorPicker.png"), + }; + + EmptyContent = _emptyContent; + } + + public override IListItem[] GetItems() + { + var result = ColorPickerService.Instance.GetSavedColorsAsync().GetAwaiter().GetResult(); + if (!result.Success || result.Value is null || result.Value.Count == 0) + { + return Array.Empty(); + } + + var search = SearchText; + var filtered = string.IsNullOrWhiteSpace(search) + ? result.Value + : result.Value.Where(saved => + saved.Hex.Contains(search, StringComparison.OrdinalIgnoreCase) || + saved.Formats.Any(f => f.Value.Contains(search, StringComparison.OrdinalIgnoreCase) || + f.Format.Contains(search, StringComparison.OrdinalIgnoreCase))); + + var items = filtered.Select(saved => + { + var copyValue = SelectPreferredFormat(saved); + var subtitle = BuildSubtitle(saved); + + return (IListItem)new CommandItem(new CopySavedColorCommand(saved, copyValue)) + { + Title = saved.Hex, + Subtitle = subtitle, + Icon = ColorSwatchIconFactory.Create(saved.R, saved.G, saved.B, saved.A), + }; + }).ToArray(); + + return items; + } + + public override void UpdateSearchText(string oldSearch, string newSearch) + { + _emptyContent.Subtitle = string.IsNullOrWhiteSpace(newSearch) + ? "Pick a color first, then try again." + : $"No saved colors matching '{newSearch}'"; + + RaiseItemsChanged(0); + } + + private static string SelectPreferredFormat(SavedColor saved) + { + // Prefer RGBA, then RGB, otherwise fallback to hex. + var rgba = saved.Formats.FirstOrDefault(f => f.Format.Equals("RGBA", StringComparison.OrdinalIgnoreCase)); + if (rgba is not null) + { + return rgba.Value; + } + + var rgb = saved.Formats.FirstOrDefault(f => f.Format.Equals("RGB", StringComparison.OrdinalIgnoreCase)); + if (rgb is not null) + { + return rgb.Value; + } + + return saved.Hex; + } + + private static string BuildSubtitle(SavedColor saved) + { + var sb = new StringBuilder(); + foreach (var format in saved.Formats.Take(3)) + { + if (sb.Length > 0) + { + sb.Append(" ยท "); + } + + sb.Append(format.Value); + } + + return sb.Length > 0 ? sb.ToString() : saved.Hex; + } +} diff --git a/src/modules/colorPicker/ColorPicker.ModuleServices/ColorFormatValue.cs b/src/modules/colorPicker/ColorPicker.ModuleServices/ColorFormatValue.cs new file mode 100644 index 0000000000..90e71e6f18 --- /dev/null +++ b/src/modules/colorPicker/ColorPicker.ModuleServices/ColorFormatValue.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace ColorPicker.ModuleServices; + +public sealed record ColorFormatValue(string Format, string Value); diff --git a/src/modules/colorPicker/ColorPicker.ModuleServices/ColorPicker.ModuleServices.csproj b/src/modules/colorPicker/ColorPicker.ModuleServices/ColorPicker.ModuleServices.csproj new file mode 100644 index 0000000000..1efe5cdc8d --- /dev/null +++ b/src/modules/colorPicker/ColorPicker.ModuleServices/ColorPicker.ModuleServices.csproj @@ -0,0 +1,29 @@ + + + + + + + enable + enable + false + false + false + + + + + + + + + + + + + + + + + + diff --git a/src/modules/colorPicker/ColorPicker.ModuleServices/ColorPickerService.cs b/src/modules/colorPicker/ColorPicker.ModuleServices/ColorPickerService.cs new file mode 100644 index 0000000000..c68ef4ad36 --- /dev/null +++ b/src/modules/colorPicker/ColorPicker.ModuleServices/ColorPickerService.cs @@ -0,0 +1,157 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text.Json; +using Common.UI; +using ManagedCommon; +using Microsoft.PowerToys.Settings.UI.Library; +using PowerToys.Interop; +using PowerToys.ModuleContracts; + +namespace ColorPicker.ModuleServices; + +/// +/// Provides programmatic control for Color Picker actions. +/// +public sealed class ColorPickerService : ModuleServiceBase, IColorPickerService +{ + public static ColorPickerService Instance { get; } = new(); + + public override string Key => SettingsDeepLink.SettingsWindow.ColorPicker.ToString(); + + protected override SettingsDeepLink.SettingsWindow SettingsWindow => SettingsDeepLink.SettingsWindow.ColorPicker; + + public override Task LaunchAsync(CancellationToken cancellationToken = default) + { + // Default launch -> open picker. + return OpenPickerAsync(cancellationToken); + } + + public Task OpenPickerAsync(CancellationToken cancellationToken = default) + { + return SignalEventAsync(Constants.ShowColorPickerSharedEvent(), "Color Picker"); + } + + public Task>> GetSavedColorsAsync(CancellationToken cancellationToken = default) + { + try + { + cancellationToken.ThrowIfCancellationRequested(); + + var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + var historyPath = Path.Combine(localAppData, "Microsoft", "PowerToys", "ColorPicker", "colorHistory.json"); + if (!File.Exists(historyPath)) + { + return Task.FromResult(OperationResults.Ok>(Array.Empty())); + } + + using var stream = File.OpenRead(historyPath); + var colors = JsonSerializer.Deserialize(stream, ColorPickerServiceJsonContext.Default.ListString) ?? new List(); + + var settingsUtils = new SettingsUtils(); + var settings = settingsUtils.GetSettingsOrDefault(ColorPickerSettings.ModuleName); + + var results = new List(colors.Count); + foreach (var entry in colors) + { + if (!TryParseArgb(entry, out var color)) + { + continue; + } + + var formats = BuildFormats(color, settings); + var hex = $"#{color.R:X2}{color.G:X2}{color.B:X2}"; + + results.Add(new SavedColor( + hex, + color.A, + color.R, + color.G, + color.B, + formats)); + } + + return Task.FromResult(OperationResults.Ok>(results)); + } + catch (OperationCanceledException) + { + return Task.FromResult(OperationResults.Fail>("Reading saved colors was cancelled.")); + } + catch (Exception ex) + { + return Task.FromResult(OperationResults.Fail>($"Failed to read saved colors: {ex.Message}")); + } + } + + private static Task SignalEventAsync(string eventName, string actionDescription) + { + try + { + using var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); + if (!eventHandle.Set()) + { + return Task.FromResult(OperationResult.Fail($"Failed to signal {actionDescription}.")); + } + + return Task.FromResult(OperationResult.Ok()); + } + catch (Exception ex) + { + return Task.FromResult(OperationResult.Fail($"Failed to signal {actionDescription}: {ex.Message}")); + } + } + + private static bool TryParseArgb(string value, out Color color) + { + color = Color.Empty; + if (string.IsNullOrWhiteSpace(value)) + { + return false; + } + + var parts = value.Split('|'); + if (parts.Length != 4) + { + return false; + } + + if (byte.TryParse(parts[0], out var a) && + byte.TryParse(parts[1], out var r) && + byte.TryParse(parts[2], out var g) && + byte.TryParse(parts[3], out var b)) + { + color = Color.FromArgb(a, r, g, b); + return true; + } + + return false; + } + + private static IReadOnlyList BuildFormats(Color color, ColorPickerSettings settings) + { + var formats = new List(); + foreach (var kvp in settings.Properties.VisibleColorFormats) + { + var formatName = kvp.Key; + var (isVisible, formatString) = kvp.Value; + if (!isVisible) + { + continue; + } + + var formatted = ColorFormatHelper.GetStringRepresentation(color, formatString); + if (formatName.Equals("HEX", StringComparison.OrdinalIgnoreCase) && !formatted.StartsWith('#')) + { + formatted = "#" + formatted; + } + + formats.Add(new ColorFormatValue(formatName, formatted)); + } + + return formats; + } +} diff --git a/src/modules/colorPicker/ColorPicker.ModuleServices/ColorPickerServiceJsonContext.cs b/src/modules/colorPicker/ColorPicker.ModuleServices/ColorPickerServiceJsonContext.cs new file mode 100644 index 0000000000..f26e9009d3 --- /dev/null +++ b/src/modules/colorPicker/ColorPicker.ModuleServices/ColorPickerServiceJsonContext.cs @@ -0,0 +1,19 @@ +// 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.Generic; +using System.Text.Json.Serialization; +using Microsoft.PowerToys.Settings.UI.Library; + +namespace ColorPicker.ModuleServices; + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(SavedColor))] +[JsonSerializable(typeof(ColorFormatValue))] +[JsonSerializable(typeof(ColorPickerSettings))] +internal sealed partial class ColorPickerServiceJsonContext : JsonSerializerContext +{ +} diff --git a/src/modules/colorPicker/ColorPicker.ModuleServices/IColorPickerService.cs b/src/modules/colorPicker/ColorPicker.ModuleServices/IColorPickerService.cs new file mode 100644 index 0000000000..4ad2ca3da3 --- /dev/null +++ b/src/modules/colorPicker/ColorPicker.ModuleServices/IColorPickerService.cs @@ -0,0 +1,14 @@ +// 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 PowerToys.ModuleContracts; + +namespace ColorPicker.ModuleServices; + +public interface IColorPickerService : IModuleService +{ + Task OpenPickerAsync(CancellationToken cancellationToken = default); + + Task>> GetSavedColorsAsync(CancellationToken cancellationToken = default); +} diff --git a/src/modules/colorPicker/ColorPicker.ModuleServices/SavedColor.cs b/src/modules/colorPicker/ColorPicker.ModuleServices/SavedColor.cs new file mode 100644 index 0000000000..3697129aa0 --- /dev/null +++ b/src/modules/colorPicker/ColorPicker.ModuleServices/SavedColor.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace ColorPicker.ModuleServices; + +public sealed record SavedColor(string Hex, byte A, byte R, byte G, byte B, IReadOnlyList Formats);