mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 17:56:44 +02:00
CmdPal: Make it easier to add APIs in the future (#41056)
We learned a lot about adding interfaces in WinRT this week. I figured I'd send a PR to write it all down.
This commit is contained in:
@@ -29,6 +29,15 @@ public partial class CommandViewModel : ExtensionObjectViewModel
|
||||
|
||||
public IconInfoViewModel Icon { get; private set; }
|
||||
|
||||
// UNDER NO CIRCUMSTANCES MAY SOMEONE WRITE TO THIS DICTIONARY.
|
||||
// This is our copy of the data from the extension.
|
||||
// Adding values to it does not add to the extension.
|
||||
// Modifying it will not modify the extension
|
||||
// (except it might, if the dictionary was passed by ref)
|
||||
private Dictionary<string, ExtensionObject<object>>? _properties;
|
||||
|
||||
public IReadOnlyDictionary<string, ExtensionObject<object>>? Properties => _properties?.AsReadOnly();
|
||||
|
||||
public CommandViewModel(ICommand? command, WeakReference<IPageContext> pageContext)
|
||||
: base(pageContext)
|
||||
{
|
||||
@@ -80,6 +89,11 @@ public partial class CommandViewModel : ExtensionObjectViewModel
|
||||
UpdateProperty(nameof(Icon));
|
||||
}
|
||||
|
||||
if (model is IExtendedAttributesProvider command2)
|
||||
{
|
||||
UpdatePropertiesFromExtension(command2);
|
||||
}
|
||||
|
||||
model.PropChanged += Model_PropChanged;
|
||||
}
|
||||
|
||||
@@ -130,4 +144,26 @@ public partial class CommandViewModel : ExtensionObjectViewModel
|
||||
model.PropChanged -= Model_PropChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePropertiesFromExtension(IExtendedAttributesProvider? model)
|
||||
{
|
||||
var propertiesFromExtension = model?.GetProperties();
|
||||
if (propertiesFromExtension == null)
|
||||
{
|
||||
_properties = null;
|
||||
return;
|
||||
}
|
||||
|
||||
_properties = [];
|
||||
|
||||
// COPY the properties into us.
|
||||
// The IDictionary that was passed to us may be marshalled by-ref or by-value, we _don't know_.
|
||||
//
|
||||
// If it's by-ref, the values are arbitrary objects that are out-of-proc.
|
||||
// If it's bu-value, then everything is in-proc, and we can't mutate the data.
|
||||
foreach (var property in propertiesFromExtension)
|
||||
{
|
||||
_properties.Add(property.Key, new(property.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ public partial class IconDataViewModel : ObservableObject, IIconData
|
||||
|
||||
IRandomAccessStreamReference? IIconData.Data => Data.Unsafe;
|
||||
|
||||
public string? FontFamily { get; private set; }
|
||||
|
||||
public IconDataViewModel(IIconData? icon)
|
||||
{
|
||||
_model = new(icon);
|
||||
@@ -43,5 +45,22 @@ public partial class IconDataViewModel : ObservableObject, IIconData
|
||||
|
||||
Icon = model.Icon;
|
||||
Data = new(model.Data);
|
||||
|
||||
if (model is IExtendedAttributesProvider icon2)
|
||||
{
|
||||
var props = icon2.GetProperties();
|
||||
|
||||
// From Raymond Chen:
|
||||
// Make sure you don't try do do something like
|
||||
// icon2.GetProperties().TryGetValue("awesomeKey", out var awesomeValue);
|
||||
// icon2.GetProperties().TryGetValue("slackerKey", out var slackerValue);
|
||||
// because each call to GetProperties() is a cross process hop, and if you
|
||||
// marshal-by-value the property set, then you don't want to throw it away and
|
||||
// re-marshal it for every property. MAKE SURE YOU CACHE IT.
|
||||
if (props?.TryGetValue("FontFamily", out var family) ?? false)
|
||||
{
|
||||
FontFamily = family as string;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +153,11 @@ public sealed class CommandProviderWrapper
|
||||
// On a BG thread here
|
||||
fallbacks = model.FallbackCommands();
|
||||
|
||||
if (model is ICommandProvider2 two)
|
||||
{
|
||||
UnsafePreCacheApiAdditions(two);
|
||||
}
|
||||
|
||||
Id = model.Id;
|
||||
DisplayName = model.DisplayName;
|
||||
Icon = new(model.Icon);
|
||||
@@ -203,6 +208,19 @@ public sealed class CommandProviderWrapper
|
||||
}
|
||||
}
|
||||
|
||||
private void UnsafePreCacheApiAdditions(ICommandProvider2 provider)
|
||||
{
|
||||
var apiExtensions = provider.GetApiExtensionStubs();
|
||||
Logger.LogDebug($"Provider supports {apiExtensions.Length} extensions");
|
||||
foreach (var a in apiExtensions)
|
||||
{
|
||||
if (a is IExtendedAttributesProvider command2)
|
||||
{
|
||||
Logger.LogDebug($"{ProviderId}: Found an IExtendedAttributesProvider");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj) => obj is CommandProviderWrapper wrapper && isValid == wrapper.isValid;
|
||||
|
||||
public override int GetHashCode() => _commandProvider.GetHashCode();
|
||||
|
||||
@@ -25,7 +25,7 @@ public sealed class IconCacheService(DispatcherQueue dispatcherQueue)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(icon.Icon))
|
||||
{
|
||||
var source = IconPathConverter.IconSourceMUX(icon.Icon, false);
|
||||
var source = IconPathConverter.IconSourceMUX(icon.Icon, false, icon.FontFamily);
|
||||
return source;
|
||||
}
|
||||
else if (icon.Data is not null)
|
||||
|
||||
@@ -158,7 +158,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
// Return Value:
|
||||
// - An IconElement with its IconSource set, if possible.
|
||||
template<typename TIconSource>
|
||||
TIconSource _getIconSource(const winrt::hstring& iconPath, bool monochrome, const int targetSize)
|
||||
TIconSource _getIconSource(const winrt::hstring& iconPath, bool monochrome, const winrt::hstring& fontFamily, const int targetSize)
|
||||
{
|
||||
TIconSource iconSource{ nullptr };
|
||||
|
||||
@@ -187,6 +187,11 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
{
|
||||
icon.FontFamily(winrt::Microsoft::UI::Xaml::Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" });
|
||||
}
|
||||
else if (!fontFamily.empty())
|
||||
{
|
||||
icon.FontFamily(winrt::Microsoft::UI::Xaml::Media::FontFamily{ fontFamily });
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note: you _do_ need to manually set the font here.
|
||||
@@ -225,9 +230,9 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
// return _getIconSource<Windows::UI::Xaml::Controls::IconSource>(path, false);
|
||||
// }
|
||||
|
||||
static Microsoft::UI::Xaml::Controls::IconSource _IconSourceMUX(const hstring& path, bool monochrome, const int targetSize)
|
||||
static Microsoft::UI::Xaml::Controls::IconSource _IconSourceMUX(const hstring& path, bool monochrome, const winrt::hstring& fontFamily, const int targetSize)
|
||||
{
|
||||
return _getIconSource<Microsoft::UI::Xaml::Controls::IconSource>(path, monochrome, targetSize);
|
||||
return _getIconSource<Microsoft::UI::Xaml::Controls::IconSource>(path, monochrome, fontFamily, targetSize);
|
||||
}
|
||||
|
||||
static SoftwareBitmap _convertToSoftwareBitmap(HICON hicon,
|
||||
@@ -343,13 +348,14 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
|
||||
MUX::Controls::IconSource IconPathConverter::IconSourceMUX(const winrt::hstring& iconPath,
|
||||
const bool monochrome,
|
||||
const winrt::hstring& fontFamily,
|
||||
const int targetSize)
|
||||
{
|
||||
std::wstring_view iconPathWithoutIndex;
|
||||
const auto indexOpt = _getIconIndex(iconPath, iconPathWithoutIndex);
|
||||
if (!indexOpt.has_value())
|
||||
{
|
||||
return _IconSourceMUX(iconPath, monochrome, targetSize);
|
||||
return _IconSourceMUX(iconPath, monochrome, fontFamily, targetSize);
|
||||
}
|
||||
|
||||
const auto bitmapSource = _getImageIconSourceForBinary(iconPathWithoutIndex, indexOpt.value(), targetSize);
|
||||
@@ -369,7 +375,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
const auto indexOpt = _getIconIndex(iconPath, iconPathWithoutIndex);
|
||||
if (!indexOpt.has_value())
|
||||
{
|
||||
auto source = IconSourceMUX(iconPath, false, targetSize);
|
||||
auto source = IconSourceMUX(iconPath, false, L"", targetSize);
|
||||
Microsoft::UI::Xaml::Controls::IconSourceElement icon;
|
||||
icon.IconSource(source);
|
||||
return icon;
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
|
||||
//static Windows::UI::Xaml::Controls::IconElement IconWUX(const winrt::hstring& iconPath);
|
||||
//static Windows::UI::Xaml::Controls::IconSource IconSourceWUX(const winrt::hstring& iconPath);
|
||||
static Microsoft::UI::Xaml::Controls::IconSource IconSourceMUX(const winrt::hstring& iconPath, bool convertToGrayscale, const int targetSize=24);
|
||||
static Microsoft::UI::Xaml::Controls::IconSource IconSourceMUX(const winrt::hstring& iconPath, bool convertToGrayscale, const winrt::hstring& fontFamily, const int targetSize=24);
|
||||
static Microsoft::UI::Xaml::Controls::IconElement IconMUX(const winrt::hstring& iconPath);
|
||||
static Microsoft::UI::Xaml::Controls::IconElement IconMUX(const winrt::hstring& iconPath, const int targetSize);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Microsoft.Terminal.UI
|
||||
{
|
||||
// static Windows.UI.Xaml.Controls.IconElement IconWUX(String path);
|
||||
// static Windows.UI.Xaml.Controls.IconSource IconSourceWUX(String path);
|
||||
static Microsoft.UI.Xaml.Controls.IconSource IconSourceMUX(String path, Boolean convertToGrayscale);
|
||||
static Microsoft.UI.Xaml.Controls.IconSource IconSourceMUX(String path, Boolean convertToGrayscale, String fontFamily);
|
||||
static Microsoft.UI.Xaml.Controls.IconElement IconMUX(String path);
|
||||
static Microsoft.UI.Xaml.Controls.IconElement IconMUX(String path, Int32 targetSize);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
author: Mike Griese
|
||||
created on: 2024-07-19
|
||||
last updated: 2025-03-10
|
||||
last updated: 2025-08-08
|
||||
issue id: n/a
|
||||
---
|
||||
|
||||
@@ -1410,8 +1410,8 @@ interface IDetailsLink requires IDetailsData {
|
||||
Windows.Foundation.Uri Link { get; };
|
||||
String Text { get; };
|
||||
}
|
||||
interface IDetailsCommand requires IDetailsData {
|
||||
ICommand Command { get; };
|
||||
interface IDetailsCommands requires IDetailsData {
|
||||
ICommand[] Commands { get; };
|
||||
}
|
||||
[uuid("58070392-02bb-4e89-9beb-47ceb8c3d741")]
|
||||
interface IDetailsSeparator requires IDetailsData {}
|
||||
@@ -1936,6 +1936,115 @@ When displaying a page:
|
||||
* The title will be `IPage.Title ?? ICommand.Name`
|
||||
* The icon will be `ICommand.Icon`
|
||||
|
||||
## Addenda I: API additions (ICommandProvider2)
|
||||
|
||||
In experiments with extending our API, we've found some quirks with the way
|
||||
that we use WinRT's metadata-based marshalling (MBM). Typically, you'd add
|
||||
another contract version, add the new runtimeclass under the new contract
|
||||
version, and then have the client app just check if that contract is available.
|
||||
|
||||
However, we're not using `runtimeclass`es that are exposed from the extensions.
|
||||
Everything is being transferred over MBM, based on the
|
||||
`Microsoft.CommandPalette.Extensions.winmd`. And out-of-proc MBM has some
|
||||
limitations. You can essentially only have a linear chain of requires for
|
||||
extension interfaces.
|
||||
|
||||
> E.g. if it implements `IWidget2` and `IWidget2 requires IWidget`, and the object's `GetRuntimeClassName` gives `IWidget2`, we know to look at `IWidget2` directly and `IWidget` due to requires.
|
||||
>
|
||||
> The unfortunate thing for the developer experience when authoring an extension with cppwinrt/CsWinRT implementations of interfaces, is they implement each interface separately. So the `IInspectable::GetRuntimeClassName` method inherited by `Interface1` gives `"Interface1"` and the method inherited by `Interface2` gives `"Interface2"`.
|
||||
>
|
||||
> Only one of these interfaces can be what the object responds to with a QI for `IInspectable`, and that's the implementation that MBM calls.
|
||||
|
||||
That means we can't just add another interface easily. But what we can do:
|
||||
|
||||
> It might be possible to prefill the cache with the interfaces in question by
|
||||
> marshaling objects that implement each of the interfaces in a way that
|
||||
> registration-free MBM can work with.
|
||||
>
|
||||
> E.g. to keep it simple, marshal an
|
||||
> instance of a separate implementation class per interface that "implements"
|
||||
> each interface
|
||||
|
||||
So that's exactly what we're going to do, because it works. As an example,
|
||||
we're going to add the following interface to our API:
|
||||
|
||||
```csharp
|
||||
interface IExtendedAttributesProvider
|
||||
{
|
||||
Windows.Foundation.Collections.IMap<String, Object> GetProperties();
|
||||
};
|
||||
|
||||
interface ICommandProvider2 requires ICommandProvider
|
||||
{
|
||||
Object[] GetApiExtensionStubs();
|
||||
};
|
||||
```
|
||||
|
||||
`IExtendedAttributesProvider` is just a simple interface, indicating that there's some
|
||||
property bag of additional values that the host could read. We're starting with
|
||||
this, because it's a helpful tool for us to add arbitrary properties to object
|
||||
in an experimental fashion. We can continue to add more things we read from
|
||||
this property set, without breaking the ABI.
|
||||
|
||||
As an example, `ICommand` proves uniquely challenging to extend, because it has
|
||||
both the `IInvokableCommand` and `IPage` family trees of interfaces which
|
||||
extend from it. Typically, it would be impossible for a class to be defined as
|
||||
|
||||
```cs
|
||||
class MyCommandWithProperties : IInvokableCommand, IExtendedAttributesProvider { ... }
|
||||
```
|
||||
|
||||
because Command Palette would only ever see the _first_ interface
|
||||
(`IInvokableCommand`) via MBM, and would never be able to check if an extension
|
||||
object was an `IExtendedAttributesProvider`. But a class defined like
|
||||
|
||||
```cs
|
||||
class CommandWithOnlyProperties : IExtendedAttributesProvider { ... }
|
||||
```
|
||||
|
||||
will populate the WinRT type cache in Command Palette with the type information
|
||||
for `ICommandWithProperties`. In fact, if Command Palette has the
|
||||
`IExtendedAttributesProvider` type info in it's cache, and then later receives a new
|
||||
`MyCommandWithProperties` object, it'll actually be able to know that
|
||||
`MyCommandWithProperties` is an `IExtendedAttributesProvider`. WinRT is just weird
|
||||
like that some times.
|
||||
|
||||
`ICommandProvider2` is where the magic happens. This is a _linear_ addition to
|
||||
`ICommandProvider`, which merely adds a method to return a set of objects.
|
||||
Extensions can implement that method, by returning out stub implementations of
|
||||
all the future additions to the API that we may add. In so doing, CmdPal will
|
||||
be able to ask each extension for these stubs, pre-load the type cache for each
|
||||
extension, and then never have to worry in the future.
|
||||
|
||||
As an example:
|
||||
|
||||
```cs
|
||||
public partial class SamplePagesCommandsProvider : CommandProvider, ICommandProvider2 {
|
||||
public SamplePagesCommandsProvider() {
|
||||
DisplayName = "Sample Pages Commands";
|
||||
Icon = new IconInfo("\uE82D");
|
||||
}
|
||||
public override ICommandItem[] TopLevelCommands() {
|
||||
return [
|
||||
new CommandItem(new SamplesListPage()) { Title = "Sample Pages", Subtitle = "View example commands" },
|
||||
];
|
||||
}
|
||||
|
||||
// Here is where we enable support for future additions to the API
|
||||
public object[] GetApiExtensionStubs() {
|
||||
return [new SupportCommandsWithProperties()];
|
||||
}
|
||||
private sealed partial class SupportCommandsWithProperties : IExtendedAttributesProvider {
|
||||
public IDictionary<string, object>? GetProperties() => null;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Fortunately, we can put all of that (`GetApiExtensionStubs`,
|
||||
`SupportCommandsWithProperties`) directly in `Toolkit.CommandProvider`, so
|
||||
developers won't have to do anything. The toolkit will just do the right thing
|
||||
for them.
|
||||
|
||||
## Class diagram
|
||||
|
||||
@@ -2210,6 +2319,8 @@ this prevents us from being able to use `[contract]` attributes to add to the
|
||||
interfaces. We'll instead need to rely on the tried-and-true method of adding a
|
||||
`IFoo2` when we want to add methods to `IFoo`.
|
||||
|
||||
[Addenda I](#addenda-i-api-additions-icommandprovider2) talks a little more on some of the challenges with adding more APIs.
|
||||
|
||||
[^1]: In this example, as in other places, I've referenced a
|
||||
`Microsoft.DevPal.Extensions.InvokableCommand` class, as the base for that action.
|
||||
Our SDK will include partial class implementations for interfaces like
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
// 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.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Foundation;
|
||||
using Windows.System;
|
||||
using Windows.Win32;
|
||||
|
||||
@@ -169,6 +172,61 @@ internal sealed partial class SampleListPage : ListPage
|
||||
{
|
||||
Title = "Get the name of the Foreground window",
|
||||
},
|
||||
|
||||
new ListItem(new CommandWithProperties())
|
||||
{
|
||||
Title = "I have properties",
|
||||
},
|
||||
new ListItem(new OtherCommandWithProperties())
|
||||
{
|
||||
Title = "I also have properties",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
internal sealed partial class CommandWithProperties : InvokableCommand, IExtendedAttributesProvider
|
||||
{
|
||||
private FontIconData _icon = new("\u0026", "Wingdings");
|
||||
|
||||
public override IconInfo Icon => new(_icon, _icon);
|
||||
|
||||
public override string Name => "Whatever";
|
||||
|
||||
// LOAD-BEARING: Use a Windows.Foundation.Collections.ValueSet as the
|
||||
// backing store for Properties. A regular `Dictionary<string, object>`
|
||||
// will not work across the ABI
|
||||
public IDictionary<string, object> GetProperties() => new Windows.Foundation.Collections.ValueSet()
|
||||
{
|
||||
{ "Foo", "bar" },
|
||||
{ "Secret", 42 },
|
||||
{ "hmm?", null },
|
||||
};
|
||||
}
|
||||
|
||||
internal sealed partial class OtherCommandWithProperties : IExtendedAttributesProvider, IInvokableCommand
|
||||
{
|
||||
public string Name => "Whatever 2";
|
||||
|
||||
public IIconInfo Icon => new IconInfo("\uF146");
|
||||
|
||||
public string Id => string.Empty;
|
||||
|
||||
public event TypedEventHandler<object, IPropChangedEventArgs> PropChanged;
|
||||
|
||||
public ICommandResult Invoke(object sender)
|
||||
{
|
||||
PropChanged?.Invoke(this, new PropChangedEventArgs(nameof(Name)));
|
||||
return CommandResult.ShowToast("whoop");
|
||||
}
|
||||
|
||||
// LOAD-BEARING: Use a Windows.Foundation.Collections.ValueSet as the
|
||||
// backing store for Properties. A regular `Dictionary<string, object>`
|
||||
// will not work across the ABI
|
||||
public IDictionary<string, object> GetProperties() => new Windows.Foundation.Collections.ValueSet()
|
||||
{
|
||||
{ "yo", "dog" },
|
||||
{ "Secret", 12345 },
|
||||
{ "hmm?", null },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"SamplePagesExtension (Package)": {
|
||||
"commandName": "MsixPackage",
|
||||
"doNotLaunchApp": true,
|
||||
"nativeDebugging": true
|
||||
"nativeDebugging": false
|
||||
},
|
||||
"SamplePagesExtension (Unpackaged)": {
|
||||
"commandName": "Project"
|
||||
|
||||
@@ -6,7 +6,7 @@ using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
public abstract partial class CommandProvider : ICommandProvider
|
||||
public abstract partial class CommandProvider : ICommandProvider, ICommandProvider2
|
||||
{
|
||||
public virtual string Id { get; protected set; } = string.Empty;
|
||||
|
||||
@@ -47,4 +47,26 @@ public abstract partial class CommandProvider : ICommandProvider
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used to manually populate the WinRT type cache in CmdPal with
|
||||
/// any interfaces that might not follow a straight linear path of requires.
|
||||
///
|
||||
/// You don't need to call this as an extension author.
|
||||
/// </summary>
|
||||
/// <returns>an array of objects that implement all the leaf interfaces we support</returns>
|
||||
public object[] GetApiExtensionStubs()
|
||||
{
|
||||
return [new SupportCommandsWithProperties()];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A stub class which implements IExtendedAttributesProvider. Just marshalling this
|
||||
/// across the ABI will be enough for CmdPal to store IExtendedAttributesProvider in
|
||||
/// its type cache.
|
||||
/// </summary>
|
||||
private sealed partial class SupportCommandsWithProperties : IExtendedAttributesProvider
|
||||
{
|
||||
public IDictionary<string, object>? GetProperties() => null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an icon that is a font glyph.
|
||||
/// This is used for icons that are defined by a specific font face,
|
||||
/// such as Wingdings.
|
||||
///
|
||||
/// Note that Command Palette will default to using the Segoe Fluent Icons,
|
||||
/// Segoe MDL2 Assets font for glyphs in the Segoe UI Symbol range, or Segoe
|
||||
/// UI for any other glyphs. This class is only needed if you want a non-Segoe
|
||||
/// font icon.
|
||||
/// </summary>
|
||||
public partial class FontIconData : IconData, IExtendedAttributesProvider
|
||||
{
|
||||
public string FontFamily { get; set; }
|
||||
|
||||
public FontIconData(string glyph, string fontFamily)
|
||||
: base(glyph)
|
||||
{
|
||||
FontFamily = fontFamily;
|
||||
}
|
||||
|
||||
public IDictionary<string, object>? GetProperties() => new ValueSet()
|
||||
{
|
||||
{ "FontFamily", FontFamily },
|
||||
};
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// 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;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
@@ -27,6 +27,12 @@ public partial class IconInfo : IIconInfo
|
||||
Dark = dark;
|
||||
}
|
||||
|
||||
public IconInfo(IconData icon)
|
||||
{
|
||||
Light = icon;
|
||||
Dark = icon;
|
||||
}
|
||||
|
||||
internal IconInfo()
|
||||
: this(string.Empty)
|
||||
{
|
||||
|
||||
@@ -364,5 +364,17 @@ namespace Microsoft.CommandPalette.Extensions
|
||||
void InitializeWithHost(IExtensionHost host);
|
||||
};
|
||||
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
interface IExtendedAttributesProvider
|
||||
{
|
||||
Windows.Foundation.Collections.IMap<String, Object> GetProperties();
|
||||
};
|
||||
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
interface ICommandProvider2 requires ICommandProvider
|
||||
{
|
||||
Object[] GetApiExtensionStubs();
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user