mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-06 12:27:01 +01:00
Compare commits
10 Commits
Jaylyn-Bar
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b8fe03e48 | ||
|
|
b23d2ea796 | ||
|
|
9b5987a30f | ||
|
|
3d6abb07b6 | ||
|
|
44fb9e18dd | ||
|
|
e124388ae2 | ||
|
|
910ac6b5de | ||
|
|
b7cabea064 | ||
|
|
4a924571de | ||
|
|
b8faacc679 |
@@ -3,17 +3,35 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Microsoft.CmdPal.Core.ViewModels;
|
||||
using Microsoft.Terminal.UI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.Graphics.DirectWrite;
|
||||
using Windows.Win32.Graphics.Gdi;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Helpers;
|
||||
|
||||
public sealed class IconCacheService(DispatcherQueue dispatcherQueue)
|
||||
public sealed class IconCacheService
|
||||
{
|
||||
private readonly DispatcherQueue dispatcherQueue;
|
||||
private IDWriteFontFace? fontFace;
|
||||
private IDWriteRenderingParams? renderingParams;
|
||||
private IDWriteGdiInterop? interop;
|
||||
|
||||
public IconCacheService(DispatcherQueue dispatcherQueue)
|
||||
{
|
||||
this.dispatcherQueue = dispatcherQueue;
|
||||
this.InitDwrite();
|
||||
}
|
||||
|
||||
public Task<IconSource?> GetIconSource(IconDataViewModel icon) =>
|
||||
|
||||
// todo: actually implement a cache of some sort
|
||||
@@ -25,6 +43,15 @@ public sealed class IconCacheService(DispatcherQueue dispatcherQueue)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(icon.Icon))
|
||||
{
|
||||
if (FontIconGlyphClassifier.Classify(icon.Icon) == FontIconGlyphKind.Emoji)
|
||||
{
|
||||
// use leonard's magic
|
||||
if (ImageSourceToIcon(MagicEmoji(icon.Icon)) is IconSource ico)
|
||||
{
|
||||
return ico;
|
||||
}
|
||||
}
|
||||
|
||||
var source = IconPathConverter.IconSourceMUX(icon.Icon, false, icon.FontFamily);
|
||||
return source;
|
||||
}
|
||||
@@ -96,4 +123,137 @@ public sealed class IconCacheService(DispatcherQueue dispatcherQueue)
|
||||
|
||||
return completionSource.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes DirectWrite and related objects needed for rendering emoji.
|
||||
/// </summary>
|
||||
private void InitDwrite()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var factory = Native.CreateDWriteCoreFactory();
|
||||
factory.CreateRenderingParams(out renderingParams);
|
||||
factory.GetGdiInterop(out interop);
|
||||
|
||||
interop.CreateBitmapRenderTarget(HDC.Null, 100, 100, out var renderTarget);
|
||||
var renderTarget3 = (IDWriteBitmapRenderTarget3)renderTarget;
|
||||
|
||||
// Get the font face
|
||||
{
|
||||
factory.GetSystemFontCollection(out var fontCollection, false);
|
||||
fontCollection.FindFamilyName("Segoe UI Emoji", out var index, out var exists);
|
||||
fontCollection.GetFontFamily(index, out var fontFamily);
|
||||
fontFamily.GetFirstMatchingFont(
|
||||
DWRITE_FONT_WEIGHT.DWRITE_FONT_WEIGHT_NORMAL,
|
||||
DWRITE_FONT_STRETCH.DWRITE_FONT_STRETCH_NORMAL,
|
||||
DWRITE_FONT_STYLE.DWRITE_FONT_STYLE_NORMAL,
|
||||
out var font);
|
||||
font.CreateFontFace(out fontFace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders an emoji glyph to an ImageSource. Unbelievably, it is faster to
|
||||
/// MANUALLY render the emoji using DirectWrite than it is to ask the normal
|
||||
/// WinUI font icon renderer to render it.
|
||||
///
|
||||
/// Big shoutout to @lhecker for writing this for us
|
||||
/// </summary>
|
||||
/// <param name="glyph">The emoji glyph to render.</param>
|
||||
/// <returns>An ImageSource containing the rendered emoji, or null if
|
||||
/// rendering failed.</returns>
|
||||
private ImageSource? MagicEmoji(string glyph)
|
||||
{
|
||||
if (string.IsNullOrEmpty(glyph))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (fontFace is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (interop is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var size = 54;
|
||||
unsafe
|
||||
{
|
||||
interop.CreateBitmapRenderTarget(HDC.Null, (uint)size, (uint)size, out var renderTarget);
|
||||
var renderTarget3 = (IDWriteBitmapRenderTarget3)renderTarget;
|
||||
|
||||
var glyphIndices = new ushort[1];
|
||||
List<uint> codepoints = [];
|
||||
for (var i = 0; i < glyph.Length; i += char.IsSurrogatePair(glyph, i) ? 2 : 1)
|
||||
{
|
||||
var x = char.ConvertToUtf32(glyph, i);
|
||||
codepoints.Add((uint)x);
|
||||
}
|
||||
|
||||
fontFace.GetGlyphIndices(codepoints.ToArray(), 1, glyphIndices);
|
||||
|
||||
var glyphIndex = glyphIndices[0];
|
||||
var advance = (float)0.0f;
|
||||
var offset = new DWRITE_GLYPH_OFFSET { };
|
||||
var run = new DWRITE_GLYPH_RUN
|
||||
{
|
||||
fontFace = fontFace,
|
||||
fontEmSize = 48,
|
||||
glyphCount = 1,
|
||||
glyphIndices = &glyphIndex,
|
||||
glyphAdvances = &advance,
|
||||
glyphOffsets = &offset,
|
||||
isSideways = false,
|
||||
bidiLevel = 0,
|
||||
};
|
||||
var rect = new RECT { };
|
||||
renderTarget3.DrawGlyphRunWithColorSupport(
|
||||
-6,
|
||||
(float)45.0f,
|
||||
DWRITE_MEASURING_MODE.DWRITE_MEASURING_MODE_NATURAL,
|
||||
in run,
|
||||
renderingParams,
|
||||
new COLORREF(0xffffffff),
|
||||
0,
|
||||
&rect);
|
||||
|
||||
renderTarget3.GetBitmapData(out var bitmapData);
|
||||
|
||||
var bitmap = new WriteableBitmap(size, size);
|
||||
using (var stream = bitmap.PixelBuffer.AsStream())
|
||||
{
|
||||
var pixels = new Span<uint>(bitmapData.pixels, size * size);
|
||||
var bytes = MemoryMarshal.AsBytes(pixels);
|
||||
stream.Write(bytes.ToArray(), 0, bytes.Length);
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
private static IconSource? ImageSourceToIcon(ImageSource? img)
|
||||
{
|
||||
return img is null ? null : new ImageIconSource() { ImageSource = img };
|
||||
}
|
||||
|
||||
internal sealed partial class Native
|
||||
{
|
||||
[DllImport("DWriteCore.dll", ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
|
||||
public static extern HRESULT DWriteCoreCreateFactory(DWRITE_FACTORY_TYPE factoryType, in Guid iid, out IntPtr factory);
|
||||
|
||||
public static unsafe IDWriteFactory8 CreateDWriteCoreFactory()
|
||||
{
|
||||
var iid = typeof(IDWriteFactory8).GUID;
|
||||
var hr = DWriteCoreCreateFactory(DWRITE_FACTORY_TYPE.DWRITE_FACTORY_TYPE_SHARED, in iid, out var factory);
|
||||
#pragma warning disable CA2201 // Do not raise reserved exception types
|
||||
return hr.Failed
|
||||
? throw new COMException("DWriteCoreCreateFactory failed", hr)
|
||||
: (IDWriteFactory8)Marshal.GetObjectForIUnknown(factory);
|
||||
#pragma warning restore CA2201 // Do not raise reserved exception types
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,4 +53,9 @@ GetCurrentThreadId
|
||||
SetWindowsHookEx
|
||||
UnhookWindowsHookEx
|
||||
CallNextHookEx
|
||||
GetModuleHandle
|
||||
GetModuleHandle
|
||||
|
||||
|
||||
DWRITE_FACTORY_TYPE
|
||||
IDWriteFactory8
|
||||
IDWriteBitmapRenderTarget3
|
||||
@@ -225,6 +225,41 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
return iconSource;
|
||||
}
|
||||
|
||||
// static Microsoft::UI::Xaml::Controls::IconSource _leonardsMagicEmojiRenderer(const winrt::hstring& glyph)
|
||||
// {
|
||||
// // magic static init dwrite factory
|
||||
// const static auto dwriteFactory = []() {
|
||||
// wil::com_ptr<IDWriteFactory> factory;
|
||||
// THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown**>(factory.put())));
|
||||
// return factory;
|
||||
// }();
|
||||
|
||||
// // magic static font face
|
||||
// const static auto emojiFontFace = []() {
|
||||
// wil::com_ptr<IDWriteFontCollection> fontCollection;
|
||||
// THROW_IF_FAILED(dwriteFactory->GetSystemFontCollection(fontCollection.put()));
|
||||
|
||||
// UINT32 index;
|
||||
// BOOL exists;
|
||||
// THROW_IF_FAILED(fontCollection->FindFamilyName(L"Segoe UI Emoji", &index, &exists));
|
||||
// if (!exists)
|
||||
// {
|
||||
// throw winrt::hresult_error(E_FAIL, L"Segoe UI Emoji font not found");
|
||||
// }
|
||||
|
||||
// wil::com_ptr<IDWriteFontFamily> fontFamily;
|
||||
// THROW_IF_FAILED(fontCollection->GetFontFamily(index, fontFamily.put()));
|
||||
|
||||
// wil::com_ptr<IDWriteFont> font;
|
||||
// THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, font.put()));
|
||||
|
||||
// wil::com_ptr<IDWriteFontFace> fontFace;
|
||||
// THROW_IF_FAILED(font->CreateFontFace(fontFace.put()));
|
||||
|
||||
// return fontFace;
|
||||
// }();
|
||||
// }
|
||||
|
||||
// Windows::UI::Xaml::Controls::IconSource IconPathConverter::IconSourceWUX(const hstring& path)
|
||||
// {
|
||||
// // * If the icon is a path to an image, we'll use that.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,8 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory;
|
||||
public partial class ClipboardHistoryCommandsProvider : CommandProvider
|
||||
{
|
||||
private readonly ListItem _clipboardHistoryListItem;
|
||||
private readonly ListItem _emojiListItem;
|
||||
private readonly ListItem _segoeListItem;
|
||||
private readonly SettingsManager _settingsManager = new();
|
||||
|
||||
public ClipboardHistoryCommandsProvider()
|
||||
@@ -25,6 +27,16 @@ public partial class ClipboardHistoryCommandsProvider : CommandProvider
|
||||
new CommandContextItem(_settingsManager.Settings.SettingsPage),
|
||||
],
|
||||
};
|
||||
_emojiListItem = new ListItem(new EmojiPage())
|
||||
{
|
||||
Title = "Emoji Picker",
|
||||
Subtitle = "Browse and copy emojis",
|
||||
};
|
||||
_segoeListItem = new ListItem(new SegoeIconsExtensionPage())
|
||||
{
|
||||
Title = "Segoe Icons",
|
||||
Subtitle = "Browse and copy Segoe Fluent Icons",
|
||||
};
|
||||
|
||||
DisplayName = Properties.Resources.provider_display_name;
|
||||
Icon = Icons.ClipboardListIcon;
|
||||
@@ -35,6 +47,6 @@ public partial class ClipboardHistoryCommandsProvider : CommandProvider
|
||||
|
||||
public override IListItem[] TopLevelCommands()
|
||||
{
|
||||
return [_clipboardHistoryListItem];
|
||||
return [_clipboardHistoryListItem, _emojiListItem, _segoeListItem];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ internal sealed partial class PasteCommand : InvokableCommand
|
||||
{
|
||||
private readonly ClipboardItem _clipboardItem;
|
||||
private readonly ClipboardFormat _clipboardFormat;
|
||||
private readonly ISettingOptions _settings;
|
||||
private readonly ISettingOptions? _settings;
|
||||
|
||||
internal PasteCommand(ClipboardItem clipboardItem, ClipboardFormat clipboardFormat, ISettingOptions settings)
|
||||
internal PasteCommand(ClipboardItem clipboardItem, ClipboardFormat clipboardFormat, ISettingOptions? settings)
|
||||
{
|
||||
_clipboardItem = clipboardItem;
|
||||
_clipboardFormat = clipboardFormat;
|
||||
@@ -42,7 +42,9 @@ internal sealed partial class PasteCommand : InvokableCommand
|
||||
|
||||
ClipboardHelper.SendPasteKeyCombination();
|
||||
|
||||
if (!_settings.KeepAfterPaste)
|
||||
// If there were settings passed in, AND the setting was set to not keep
|
||||
// after paste, remove the item from history.
|
||||
if (_settings is not null && !_settings.KeepAfterPaste)
|
||||
{
|
||||
Clipboard.DeleteItemFromHistory(_clipboardItem.Item);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("Style", "IDE0161:Convert to file-scoped namespace", Justification = "OSS", Scope = "namespace", Target = "~N:J3QQ4")]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.Win32;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.ClipboardHistory.Pages;
|
||||
|
||||
internal sealed partial class EmojiListItem : ListItem
|
||||
{
|
||||
private readonly string _emoji;
|
||||
private readonly IconInfo _icon;
|
||||
public override IconInfo Icon => _icon;
|
||||
public EmojiListItem(string emoji)
|
||||
: base()
|
||||
{
|
||||
_emoji = emoji;
|
||||
_icon = new IconInfo(emoji);
|
||||
Title = emoji;
|
||||
|
||||
DataPackage textDataPackage = new()
|
||||
{
|
||||
RequestedOperation = DataPackageOperation.Copy,
|
||||
};
|
||||
textDataPackage.SetText(emoji);
|
||||
|
||||
ClipboardItem content = new()
|
||||
{
|
||||
Item = textDataPackage,
|
||||
};
|
||||
|
||||
var copyCommand = new CopyTextCommand(emoji) { Icon = _icon };
|
||||
|
||||
var pasteCommand = new PasteCommand(content, ClipboardFormat.Text, null)
|
||||
{
|
||||
Icon = _icon,
|
||||
Name = Properties.Resources.paste_command_name,
|
||||
};
|
||||
|
||||
Command = pasteCommand;
|
||||
MoreCommands = [ new CommandContextItem(copyCommand) ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// 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.Linq;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.ClipboardHistory.Pages;
|
||||
|
||||
#pragma warning disable SA1402 // File may only contain a single type
|
||||
|
||||
public sealed partial class EmojiPage : ListPage
|
||||
{
|
||||
private readonly Dictionary<string, List<ListItem>> _emojiListItems;
|
||||
|
||||
public EmojiPage()
|
||||
{
|
||||
Icon = new IconInfo("\uE899");
|
||||
Name = "Emoji"; // Properties.Resources.emoji_page_name;
|
||||
Id = "com.microsoft.cmdpal.emoji";
|
||||
ShowDetails = false;
|
||||
GridProperties = new SmallGridLayout();
|
||||
|
||||
_emojiListItems = new Dictionary<string, List<ListItem>>();
|
||||
foreach (var group in EmojiDict.Data)
|
||||
{
|
||||
var listItems = group.Value.Select(s => new EmojiListItem(s.Emoji) { Title = s.Name }).Cast<ListItem>().ToList();
|
||||
_emojiListItems.Add(group.Key, listItems);
|
||||
}
|
||||
|
||||
var filters = new EmojiFilters();
|
||||
filters.PropChanged += Filters_PropChanged;
|
||||
Filters = filters;
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
if (Filters is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
if (Filters.CurrentFilterId == "all")
|
||||
{
|
||||
return _emojiListItems.Values.SelectMany(x => x).ToArray();
|
||||
}
|
||||
|
||||
if (_emojiListItems.TryGetValue(Filters.CurrentFilterId, out var items))
|
||||
{
|
||||
return items.ToArray();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private void Filters_PropChanged(object sender, IPropChangedEventArgs args) => RaiseItemsChanged();
|
||||
}
|
||||
|
||||
public partial class EmojiFilters : Filters
|
||||
{
|
||||
private List<IFilterItem> _allFilters = new()
|
||||
{
|
||||
new Filter() { Id = "all", Name = "All Emoji" },
|
||||
new Separator(),
|
||||
};
|
||||
|
||||
public EmojiFilters()
|
||||
{
|
||||
CurrentFilterId = EmojiDict.Data.Keys.First();
|
||||
|
||||
foreach (var group in EmojiDict.Data)
|
||||
{
|
||||
_allFilters.Add(new Filter() { Id = group.Key, Name = group.Key });
|
||||
}
|
||||
}
|
||||
|
||||
public override IFilterItem[] GetFilters()
|
||||
{
|
||||
return _allFilters.ToArray();
|
||||
}
|
||||
}
|
||||
#pragma warning restore SA1402 // File may only contain a single type
|
||||
@@ -0,0 +1,175 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.ClipboardHistory.Pages;
|
||||
|
||||
#pragma warning disable SA1402 // File may only contain a single type
|
||||
|
||||
internal sealed partial class SegoeIconsExtensionPage : ListPage
|
||||
{
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
private IListItem[]? _items;
|
||||
|
||||
public SegoeIconsExtensionPage()
|
||||
{
|
||||
Icon = new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory.ToString(), "Assets/WinUI3Gallery.png"));
|
||||
Name = "Segoe Icons";
|
||||
IsLoading = true;
|
||||
GridProperties = new SmallGridLayout();
|
||||
PreloadIcons();
|
||||
}
|
||||
|
||||
public void PreloadIcons()
|
||||
{
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var t = GenerateIconItems();
|
||||
t.ConfigureAwait(false);
|
||||
_items = t.Result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
IsLoading = false;
|
||||
return _items ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IListItem[]> GenerateIconItems()
|
||||
{
|
||||
var timer = new Stopwatch();
|
||||
timer.Start();
|
||||
var rawIcons = await IconsDataSource.Instance.LoadIcons()!;
|
||||
var items = rawIcons.Select(ToItem).ToArray();
|
||||
IsLoading = false;
|
||||
timer.Stop();
|
||||
ExtensionHost.LogMessage($"Generating icon items took {timer.ElapsedMilliseconds}ms");
|
||||
return items;
|
||||
}
|
||||
|
||||
private IconListItem ToItem(IconData d) => new(d);
|
||||
}
|
||||
|
||||
internal sealed partial class IconListItem : ListItem
|
||||
{
|
||||
private readonly IconData _data;
|
||||
|
||||
public IconListItem(IconData data)
|
||||
: base(new CopyTextCommand(data.CodeGlyph) { Name = $"Copy {data.CodeGlyph}" })
|
||||
{
|
||||
_data = data;
|
||||
this.Title = _data.Name;
|
||||
this.Icon = new IconInfo(data.Character);
|
||||
this.Subtitle = _data.CodeGlyph;
|
||||
if (data.Tags != null && data.Tags.Length > 0)
|
||||
{
|
||||
this.Tags = data.Tags.Select(t => new Tag() { Text = t }).ToArray();
|
||||
}
|
||||
|
||||
this.MoreCommands =
|
||||
[
|
||||
new CommandContextItem(new CopyTextCommand(data.Character)) { Title = $"Copy {data.Character}", Icon = new IconInfo(data.Character) },
|
||||
new CommandContextItem(new CopyTextCommand(data.TextGlyph)) { Title = $"Copy {data.TextGlyph}" },
|
||||
new CommandContextItem(new CopyTextCommand(data.Name)) { Title = $"Copy {data.Name}" },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// very shamelessly from
|
||||
// https://github.com/microsoft/WinUI-Gallery/blob/main/WinUIGallery/DataModel/IconsDataSource.cs
|
||||
public class IconData
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
|
||||
public required string Code { get; set; }
|
||||
|
||||
public string[] Tags { get; set; } = [];
|
||||
|
||||
public string Character => char.ConvertFromUtf32(Convert.ToInt32(Code, 16));
|
||||
|
||||
public string CodeGlyph => "\\u" + Code;
|
||||
|
||||
public string TextGlyph => "&#x" + Code + ";";
|
||||
}
|
||||
|
||||
[JsonSourceGenerationOptions(PropertyNameCaseInsensitive = true)]
|
||||
[JsonSerializable(typeof(List<IconData>), TypeInfoPropertyName = "IconList")]
|
||||
internal sealed partial class IconDataListContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
|
||||
internal sealed class IconsDataSource
|
||||
{
|
||||
public static IconsDataSource Instance { get; } = new();
|
||||
|
||||
public static List<IconData> Icons => Instance.icons;
|
||||
|
||||
private List<IconData> icons = [];
|
||||
|
||||
private IconsDataSource()
|
||||
{
|
||||
}
|
||||
|
||||
private readonly object _lock = new();
|
||||
|
||||
public async Task<List<IconData>> LoadIcons()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (icons.Count != 0)
|
||||
{
|
||||
return icons;
|
||||
}
|
||||
}
|
||||
|
||||
Stopwatch stopwatch = new();
|
||||
stopwatch.Start();
|
||||
var jsonText = await LoadText("Microsoft.CmdPal.Ext.ClipboardHistory/Assets/icons.json");
|
||||
lock (_lock)
|
||||
{
|
||||
if (icons.Count == 0 &&
|
||||
!string.IsNullOrEmpty(jsonText))
|
||||
{
|
||||
icons = JsonSerializer.Deserialize<List<IconData>>(jsonText, IconDataListContext.Default.IconList) is List<IconData> i ? i
|
||||
|
||||
// icons = JsonSerializer.Deserialize<List<IconData>>(jsonText) is List<IconData> i ? i
|
||||
: throw new InvalidDataException($"Cannot load icon data: {jsonText}");
|
||||
}
|
||||
|
||||
stopwatch.Stop();
|
||||
ExtensionHost.LogMessage($"Reading file and parsing JSON took {stopwatch.ElapsedMilliseconds}ms");
|
||||
|
||||
return icons;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<string> LoadText(string relativeFilePath)
|
||||
{
|
||||
// if the file exists, load it and append the new item
|
||||
var sourcePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory.ToString(), relativeFilePath);
|
||||
|
||||
return File.Exists(sourcePath) ? await File.ReadAllTextAsync(sourcePath) : string.Empty;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user