Allow both light and dark icons (#286)

This adds one _last_ change to the API to allow apps to specify different icons for light and dark mode. 

If it's important to an app to specify different icons for light vs dark mode, then `IconInfo` is exactly what you want to use. It contains _two_ `IconDataType`s, for two different icons. Simple as that. 

And to keep things even easier, I slapped on a `IconInfo(string)` constructor, so that you can easily build a `IconInfo` with both icons set to the same string. Especially useful for font icons, which we're using everywhere already. That allows almost all the extensions to have _no code change_ here, so that's super!

Some of the places where we were evaluating if an icon existed or not - that needs to move into the view. `ShellPage.xaml.cs` does one variant of that already for `IDetails.HeroImage`. The view is the only part of the app that knows what the theme is. 

Closes #78
This commit is contained in:
Mike Griese
2025-01-09 16:30:25 -06:00
committed by GitHub
parent f679c88a16
commit 2efdbb6cbd
36 changed files with 244 additions and 174 deletions

View File

@@ -64,7 +64,7 @@ public static class ServiceHelper
];
}
IconDataType icon = new("\U0001f7e2"); // unicode LARGE GREEN CIRCLE
IconInfo icon = new("\U0001f7e2"); // unicode LARGE GREEN CIRCLE
switch (s.Status)
{
case ServiceControllerStatus.Stopped:
@@ -191,15 +191,13 @@ public static class ServiceHelper
{
return Resources.wox_plugin_service_running;
}
else if (status == ServiceControllerStatus.ContinuePending)
{
return Resources.wox_plugin_service_continue_pending;
}
else
{
return status == ServiceControllerStatus.PausePending
? Resources.wox_plugin_service_pause_pending
: status == ServiceControllerStatus.Paused ? Resources.wox_plugin_service_paused : status.ToString();
return status == ServiceControllerStatus.ContinuePending
? Resources.wox_plugin_service_continue_pending
: status == ServiceControllerStatus.PausePending
? Resources.wox_plugin_service_pause_pending
: status == ServiceControllerStatus.Paused ? Resources.wox_plugin_service_paused : status.ToString();
}
}
@@ -213,44 +211,32 @@ public static class ServiceHelper
{
return Resources.wox_plugin_service_start_mode_system;
}
else if (startMode == ServiceStartMode.Automatic)
{
return !IsDelayedStart(serviceName) ? Resources.wox_plugin_service_start_mode_automatic : Resources.wox_plugin_service_start_mode_automaticDelayed;
}
else
{
return startMode == ServiceStartMode.Manual
? Resources.wox_plugin_service_start_mode_manual
: startMode == ServiceStartMode.Disabled ? Resources.wox_plugin_service_start_mode_disabled : startMode.ToString();
return startMode == ServiceStartMode.Automatic
? !IsDelayedStart(serviceName) ? Resources.wox_plugin_service_start_mode_automatic : Resources.wox_plugin_service_start_mode_automaticDelayed
: startMode == ServiceStartMode.Manual
? Resources.wox_plugin_service_start_mode_manual
: startMode == ServiceStartMode.Disabled ? Resources.wox_plugin_service_start_mode_disabled : startMode.ToString();
}
}
private static string GetLocalizedMessage(Action action)
{
if (action == Action.Start)
{
return Resources.wox_plugin_service_started_notification;
}
else
{
return action == Action.Stop
return action == Action.Start
? Resources.wox_plugin_service_started_notification
: action == Action.Stop
? Resources.wox_plugin_service_stopped_notification
: action == Action.Restart ? Resources.wox_plugin_service_restarted_notification : string.Empty;
}
}
private static string GetLocalizedErrorMessage(Action action)
{
if (action == Action.Start)
{
return Resources.wox_plugin_service_start_error_notification;
}
else
{
return action == Action.Stop
return action == Action.Start
? Resources.wox_plugin_service_start_error_notification
: action == Action.Stop
? Resources.wox_plugin_service_stop_error_notification
: action == Action.Restart ? Resources.wox_plugin_service_restart_error_notification : string.Empty;
}
}
private static bool IsDelayedStart(string serviceName)

View File

@@ -25,7 +25,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel
public string Subtitle { get; private set; } = string.Empty;
public IconDataType Icon { get; private set; } = new(string.Empty);
public IconInfo Icon { get; private set; } = new(string.Empty);
public ExtensionObject<ICommand> Command { get; private set; } = new(null);
@@ -81,9 +81,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel
Subtitle = model.Subtitle;
var listIcon = model.Icon;
Icon = !string.IsNullOrEmpty(listIcon.Icon) ?
listIcon :
Command.Unsafe!.Icon;
Icon = listIcon ?? Command.Unsafe!.Icon;
var more = model.MoreCommands;
if (more != null)
@@ -171,7 +169,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel
break;
case nameof(Icon):
var listIcon = model.Icon;
Icon = !string.IsNullOrEmpty(listIcon.Icon) ? listIcon : Command.Unsafe!.Icon;
Icon = listIcon != null ? listIcon : Command.Unsafe!.Icon;
break;
// TODO! MoreCommands array, which needs to also raise HasMoreCommands

View File

@@ -28,7 +28,7 @@ public sealed class CommandProviderWrapper
public string DisplayName { get; private set; } = string.Empty;
public IconDataType Icon { get; private set; } = new(string.Empty);
public IconInfo Icon { get; private set; } = new(string.Empty);
public string ProviderId => $"{extensionWrapper?.PackageFamilyName ?? string.Empty}/{Id}";

View File

@@ -13,9 +13,7 @@ public partial class DetailsViewModel(IDetails _details, IPageContext context) :
// Remember - "observable" properties from the model (via PropChanged)
// cannot be marked [ObservableProperty]
public IconDataType HeroImage { get; private set; } = new(string.Empty);
public bool HasHeroImage => !string.IsNullOrEmpty(HeroImage.Icon) || HeroImage.Data != null;
public IconInfo HeroImage { get; private set; } = new(string.Empty);
// TODO: Metadata is an array of IDetailsElement,
// where IDetailsElement = {IDetailsTags, IDetailsLink, IDetailsSeparator}
@@ -38,6 +36,5 @@ public partial class DetailsViewModel(IDetails _details, IPageContext context) :
UpdateProperty(nameof(Title));
UpdateProperty(nameof(Body));
UpdateProperty(nameof(HeroImage));
UpdateProperty(nameof(HasHeroImage));
}
}

View File

@@ -45,7 +45,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
// `IsLoading` property as a combo of this value and `IsInitialized`
public bool ModelIsLoading { get; protected set; } = true;
public IconDataType Icon { get; protected set; } = new(string.Empty);
public IconInfo Icon { get; protected set; } = new(string.Empty);
public PageViewModel(IPage? model, TaskScheduler scheduler)
: base(null)

View File

@@ -13,7 +13,7 @@ public partial class ProviderSettingsViewModel(CommandProviderWrapper _provider,
public string ExtensionName => _provider.Extension?.ExtensionDisplayName ?? "Built-in";
public IconDataType Icon => _provider.Icon;
public IconInfo Icon => _provider.Icon;
public bool IsEnabled
{

View File

@@ -21,9 +21,10 @@ public partial class TagViewModel(ITag _tag, IPageContext context) : ExtensionOb
public OptionalColor Background { get; private set; }
public IconDataType Icon { get; private set; } = new(string.Empty);
public IconInfo Icon { get; private set; } = new(string.Empty);
public bool HasIcon => !string.IsNullOrEmpty(Icon.Icon);
// TODO Terrible. When we redo the icons in tags, make this something the view exposes
public bool HasIcon => !string.IsNullOrEmpty(Icon.Light.Icon);
public ExtensionObject<ICommand> Command { get; private set; } = new(null);

View File

@@ -46,7 +46,7 @@ public partial class TopLevelCommandWrapper : ListItem
Title = model.Title;
Subtitle = model.Subtitle;
Icon = new(model.Icon.Icon);
Icon = model.Icon;
MoreCommands = model.MoreCommands;
model.PropChanged += Model_PropChanged;

View File

@@ -92,7 +92,8 @@ public partial class IconBox : ContentControl
// _ = @this._queue.EnqueueAsync(() =>
@this._queue.TryEnqueue(new(() =>
{
var eventArgs = new SourceRequestedEventArgs(e.NewValue);
var requestedTheme = @this.ActualTheme;
var eventArgs = new SourceRequestedEventArgs(e.NewValue, requestedTheme);
if (@this.SourceRequested != null)
{
@@ -110,7 +111,7 @@ public partial class IconBox : ContentControl
// Segoe icons, then let's give the icon some extra space
@this.Padding = new Thickness(0);
if (eventArgs.Key is IconDataType iconData &&
if (eventArgs.Key is IconData iconData &&
@this.Source is FontIconSource)
{
if (!string.IsNullOrEmpty(iconData.Icon) && iconData.Icon.Length <= 2)

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Common.Deferred;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.CmdPal.UI.Controls;
@@ -10,9 +11,11 @@ namespace Microsoft.CmdPal.UI.Controls;
/// <summary>
/// See <see cref="IconBox.SourceRequested"/> event.
/// </summary>
public class SourceRequestedEventArgs(object? key) : DeferredEventArgs
public class SourceRequestedEventArgs(object? key, ElementTheme requestedTheme) : DeferredEventArgs
{
public object? Key { get; private set; } = key;
public IconSource? Value { get; set; }
public ElementTheme Theme => requestedTheme;
}

View File

@@ -13,14 +13,14 @@ namespace Microsoft.CmdPal.UI.ExtViews;
public sealed class IconCacheService(DispatcherQueue dispatcherQueue)
{
public Task<IconSource?> GetIconSource(IconDataType icon) =>
public Task<IconSource?> GetIconSource(IconData icon) =>
// todo: actually implement a cache of some sort
IconToSource(icon);
private async Task<IconSource?> IconToSource(IconDataType icon)
private async Task<IconSource?> IconToSource(IconData icon)
{
// bodgy: apparently IconDataType, despite being a struct, doesn't get
// bodgy: apparently IconData, despite being a struct, doesn't get
// MarshalByValue'd into our process. What's even the point then?
try
{

View File

@@ -24,12 +24,21 @@ public static partial class IconCacheProvider
return;
}
if (args.Key is IconDataType iconData)
if (args.Key is IconData iconData)
{
var deferral = args.GetDeferral();
args.Value = await IconService.GetIconSource(iconData);
deferral.Complete();
}
else if (args.Key is IconInfo iconInfo)
{
var deferral = args.GetDeferral();
var data = args.Theme == Microsoft.UI.Xaml.ElementTheme.Dark ? iconInfo.Dark : iconInfo.Light;
args.Value = await IconService.GetIconSource(data);
deferral.Complete();
}
}

View File

@@ -168,10 +168,9 @@
x:Name="HeroImageBorder"
Width="64"
Height="64"
x:Load="{x:Bind IsLoaded, Mode=OneWay}"
SourceKey="{x:Bind ViewModel.Details.HeroImage, Mode=OneWay}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}"
Visibility="{x:Bind ViewModel.Details.HasHeroImage, Mode=OneWay}" />
Visibility="{x:Bind HasHeroImage, Mode=OneWay}" />
<TextBlock
Grid.Row="1"

View File

@@ -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.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Extensions;
using Microsoft.CmdPal.UI.ViewModels;
@@ -27,7 +28,8 @@ public sealed partial class ShellPage :
IRecipient<HideDetailsMessage>,
IRecipient<ClearSearchMessage>,
IRecipient<HandleCommandResultMessage>,
IRecipient<LaunchUriMessage>
IRecipient<LaunchUriMessage>,
INotifyPropertyChanged
{
private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread();
@@ -37,7 +39,8 @@ public sealed partial class ShellPage :
public ShellViewModel ViewModel { get; private set; } = App.Current.Services.GetService<ShellViewModel>()!;
// private readonly SettingsViewModel _settingsViewModel;
public event PropertyChangedEventHandler? PropertyChanged;
public ShellPage()
{
this.InitializeComponent();
@@ -229,6 +232,10 @@ public sealed partial class ShellPage :
{
ViewModel.Details = message.Details;
ViewModel.IsDetailsVisible = true;
// Trigger a re-evaluation of whether we have a hero image based on
// the current theme
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(HasHeroImage)));
}
public void Receive(HideDetailsMessage message) => HideDetails();
@@ -285,4 +292,29 @@ public sealed partial class ShellPage :
ViewModel.CurrentPage = page;
}
}
/// <summary>
/// Gets a value indicating whether determines if the current Details have a HeroImage, given the theme
/// we're currently in. This needs to be evaluated in the view, because the
/// viewModel doesn't actually know what the current theme is.
/// </summary>
public bool HasHeroImage
{
get
{
var requestedTheme = ActualTheme;
var iconInfo = ViewModel.Details?.HeroImage;
var data = requestedTheme == Microsoft.UI.Xaml.ElementTheme.Dark ?
iconInfo?.Dark :
iconInfo?.Light;
// We have an icon, AND EITHER:
// We have a string icon, OR
// we have a data blob
var hasIcon = (data != null) &&
(!string.IsNullOrEmpty(data.Icon) ||
data.Data != null);
return hasIcon;
}
}
}

View File

@@ -13,5 +13,5 @@ public sealed class ActionViewModel(ICommand cmd)
internal bool CanInvoke => cmd is IInvokableCommand;
internal IconElement IcoElement => Microsoft.Terminal.UI.IconPathConverter.IconMUX(Command.Icon.Icon);
internal IconElement IcoElement => Microsoft.Terminal.UI.IconPathConverter.IconMUX(Command.Icon.Dark.Icon);
}

View File

@@ -14,7 +14,7 @@ public sealed class ContextItemViewModel : INotifyPropertyChanged
internal string Name => Command.Name;
internal IconDataType Icon => Command.Icon;
internal IconData Icon => Command.Icon.Dark;
public event PropertyChangedEventHandler? PropertyChanged;

View File

@@ -107,7 +107,7 @@ public sealed class ListItemViewModel : INotifyPropertyChanged, IDisposable, IEq
this.ListItem = new(model);
this.Title = model.Title;
this.Subtitle = model.Subtitle;
this.Icon = model.Icon.Icon;
this.Icon = model.Icon.Dark.Icon;
this.TextToSuggest = model.TextToSuggest;
if (model.Tags != null)
@@ -151,7 +151,7 @@ public sealed class ListItemViewModel : INotifyPropertyChanged, IDisposable, IEq
BubbleXamlPropertyChanged(nameof(ContextActions));
break;
case nameof(Icon):
this.Icon = item.Command.Icon.Icon;
this.Icon = item.Command.Icon.Dark.Icon;
BubbleXamlPropertyChanged(nameof(IcoElement));
break;
case nameof(TextToSuggest):

View File

@@ -90,7 +90,6 @@
<ProjectReference Include="..\Exts\Microsoft.CmdPal.Ext.Apps\Microsoft.CmdPal.Ext.Apps.csproj" />
<ProjectReference Include="..\Exts\Microsoft.CmdPal.Ext.Bookmark\Microsoft.CmdPal.Ext.Bookmarks.csproj" />
<ProjectReference Include="..\Exts\Microsoft.CmdPal.Ext.Calc\Microsoft.CmdPal.Ext.Calc.csproj" />
<ProjectReference Include="..\Exts\Microsoft.CmdPal.Ext.CmdPalSettings\Microsoft.CmdPal.Ext.Settings.csproj" />
<ProjectReference Include="..\Exts\Microsoft.CmdPal.Ext.Indexer\Microsoft.CmdPal.Ext.Indexer.csproj" />
<ProjectReference Include="..\Exts\Microsoft.CmdPal.Ext.Registry\Microsoft.CmdPal.Ext.Registry.csproj" />
<ProjectReference Include="..\Exts\Microsoft.CmdPal.Ext.Shell\Microsoft.CmdPal.Ext.Shell.csproj" />

View File

@@ -12,13 +12,13 @@ public sealed class TagViewModel
{
private readonly ITag _tag;
internal IconDataType Icon => _tag.Icon;
internal IconInfo Icon => _tag.Icon;
internal string Text => _tag.Text;
public bool HasIcon => !string.IsNullOrEmpty(Icon?.Icon);
public bool HasIcon => !string.IsNullOrEmpty(Icon?.Dark?.Icon);
internal IconElement IcoElement => Microsoft.Terminal.UI.IconPathConverter.IconMUX(Icon?.Icon ?? string.Empty, 10);
internal IconElement IcoElement => Microsoft.Terminal.UI.IconPathConverter.IconMUX(Icon?.Dark?.Icon ?? string.Empty, 10);
public Windows.UI.Color Foreground
{

View File

@@ -2,21 +2,6 @@
// 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.CmdPal.Ext.Apps.Programs;
using Microsoft.CmdPal.Ext.Bookmarks;
using Microsoft.CmdPal.Ext.Calc;
using Microsoft.CmdPal.Ext.Registry;
using Microsoft.CmdPal.Ext.Settings;
using Microsoft.CmdPal.Ext.WindowsServices;
using Microsoft.CmdPal.Ext.WindowsSettings;
using Microsoft.CmdPal.Ext.WindowsTerminal;
using Microsoft.CmdPal.Extensions;
using Microsoft.CmdPal.Extensions.Helpers;
using Windows.Foundation;
using WindowsCommandPalette.BuiltinCommands;
using WindowsCommandPalette.Models;
namespace WindowsCommandPalette.Views;
public class CommandAlias(string shortcut, string commandId, bool direct = false)

View File

@@ -13,7 +13,7 @@ public sealed class DetailsViewModel
internal string Body { get; init; } = string.Empty;
internal IconDataType HeroImage { get; init; } = new(string.Empty);
internal IconData HeroImage { get; init; } = new(string.Empty);
internal IconElement IcoElement => Microsoft.Terminal.UI.IconPathConverter.IconMUX(HeroImage.Icon);
@@ -21,6 +21,6 @@ public sealed class DetailsViewModel
{
this.Title = details.Title;
this.Body = details.Body;
this.HeroImage = details.HeroImage ?? new(string.Empty);
this.HeroImage = details.HeroImage?.Dark ?? new(string.Empty);
}
}

View File

@@ -8,7 +8,6 @@ using Microsoft.CmdPal.Ext.Bookmarks;
using Microsoft.CmdPal.Ext.Calc;
using Microsoft.CmdPal.Ext.Indexer;
using Microsoft.CmdPal.Ext.Registry;
using Microsoft.CmdPal.Ext.Settings;
using Microsoft.CmdPal.Ext.Shell;
using Microsoft.CmdPal.Ext.WebSearch;
using Microsoft.CmdPal.Ext.WindowsServices;
@@ -59,7 +58,6 @@ public sealed class MainViewModel : IDisposable
BuiltInCommands.Add(new IndexerCommandsProvider());
BuiltInCommands.Add(new BookmarksCommandProvider());
BuiltInCommands.Add(new CalculatorCommandProvider());
BuiltInCommands.Add(new SettingsCommandProvider());
BuiltInCommands.Add(_quitCommandProvider);
BuiltInCommands.Add(_reloadCommandProvider);
BuiltInCommands.Add(new WindowsTerminalCommandsProvider());

View File

@@ -65,14 +65,23 @@ namespace Microsoft.CmdPal.Extensions
};
[contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)]
runtimeclass IconDataType {
IconDataType(String iconString);
static IconDataType FromStream(Windows.Storage.Streams.IRandomAccessStreamReference stream);
runtimeclass IconData {
IconData(String iconString);
static IconData FromStream(Windows.Storage.Streams.IRandomAccessStreamReference stream);
String Icon { get; };
Windows.Storage.Streams.IRandomAccessStreamReference Data { get; };
};
[contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)]
runtimeclass IconInfo {
IconInfo(String iconString);
IconInfo(IconData lightIcon, IconData darkIcon);
IconData Light { get; };
IconData Dark { get; };
};
[contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)]
runtimeclass KeyChord
{

View File

@@ -1,13 +1,13 @@
---
author: Mike Griese
created on: 2024-07-19
last updated: 2024-12-31
last updated: 2025-01-08
issue id: n/a
---
# Run v2 Extensions SDK
_aka "DevPal", "PT Run v2", "DevSearch", "Windows Command Palette", this thing has many names. I'll use DevPal throughout the doc_
_aka "DevPal", "PT Run v2", "DevSearch", "Windows Command Palette", this thing has many names. I'll use "DevPal" throughout the doc_
> [NOTE!]
> Are you here to just see what the SDK looks like? Skip to the [Actions
@@ -59,7 +59,7 @@ functionality.
- [Form Pages](#form-pages)
- [Other types](#other-types)
- [`ContextItem`s](#contextitems)
- [`IconDataType`](#icondatatype)
- [Icons - `IconInfo` and `IconData`](#icons---iconinfo-and-icondatatype)
- [`OptionalColor`](#optionalcolor)
- [`Details`](#details)
- [`INotifyPropChanged`](#inotifypropchanged)
@@ -533,7 +533,7 @@ Use `cs` for samples.
interface ICommand requires INotifyPropChanged{
String Name{ get; };
String Id{ get; };
IconDataType Icon{ get; };
IconInfo Icon{ get; };
}
enum CommandResultKind {
@@ -716,7 +716,7 @@ interface IContextItem {}
interface ICommandItem requires INotifyPropChanged {
ICommand Command{ get; };
IContextItem[] MoreCommands{ get; };
IconDataType Icon{ get; };
IconInfo Icon{ get; };
String Title{ get; };
String Subtitle{ get; };
}
@@ -1045,7 +1045,7 @@ interface ISeparatorFilterItem requires IFilterItem {}
interface IFilter requires IFilterItem {
String Id { get; };
String Name { get; };
IconDataType Icon { get; };
IconInfo Icon { get; };
}
interface IFilters {
@@ -1223,10 +1223,10 @@ flyout. Mostly, these are just commands and seperators.
If an `ICommandContextItem` has `MoreCommands`, then when it's invoked, we'll
create a sub-menu with those items in it.
#### `IconDataType`
#### Icons - `IconInfo` and `IconData`
This is a wrapper type for passing information about an icon to DevPal. This
allows extensions to specify apps in a variety of ways, including:
`IconData` is a wrapper type for passing information about an icon to
DevPal. This allows extensions to specify apps in a variety of ways, including:
* A URL to an image on the web or filesystem
* A string for an emoji or Segoe Fluent icon
@@ -1235,15 +1235,25 @@ allows extensions to specify apps in a variety of ways, including:
extensions that want to pass us raw image data, which isn't necessarily a file
which DevPal can load itself.
When specifying icons, elements can specify both the light theme and dark theme
versions of an icon with `IconInfo`.
<!-- In .CS because it's manually added to the idl -->
```cs
struct IconDataType {
IconDataType(String iconString);
static IconDataType FromStream(Windows.Storage.Streams.IRandomAccessStreamReference stream);
struct IconData {
IconData(String iconString);
static IconData FromStream(Windows.Storage.Streams.IRandomAccessStreamReference stream);
String Icon { get; };
Windows.Storage.Streams.IRandomAccessStreamReference Data { get; };
}
struct IconInfo {
IconInfo(String iconString);
IconInfo(IconData lightIcon, IconData darkIcon);
IconData Light { get; };
IconData Dark { get; };
}
```
Terminal already has a robust arbitrary string -> icon loader that we can easily
@@ -1302,7 +1312,7 @@ block, and the generator will pull this into the file first. -->
```c#
interface ITag {
IconDataType Icon { get; };
IconInfo Icon { get; };
String Text { get; };
OptionalColor Foreground { get; };
OptionalColor Background { get; };
@@ -1317,7 +1327,7 @@ interface IDetailsElement {
IDetailsData Data { get; };
}
interface IDetails {
IconDataType HeroImage { get; };
IconInfo HeroImage { get; };
String Title { get; };
String Body { get; };
IDetailsElement[] Metadata { get; };
@@ -1383,7 +1393,7 @@ interface ICommandProvider requires Windows.Foundation.IClosable, INotifyItemsCh
{
String Id { get; };
String DisplayName { get; };
IconDataType Icon { get; };
IconInfo Icon { get; };
ICommandSettings Settings { get; };
Boolean Frozen { get; };
@@ -1585,9 +1595,12 @@ For example, we should have something like:
```cs
class OpenUrlAction(string targetUrl, ActionResult result) : Microsoft.Windows.Run.Extensions.InvokableCommand {
public string Name => "Open";
public IconDataType Icon => "\uE8A7"; // OpenInNewWindow
public ActionResult Invoke() {
public OpenUrlAction()
{
Name = "Open";
Icon = new("\uE8A7"); // OpenInNewWindow
}
public CommandResult Invoke() {
Process.Start(new ProcessStartInfo(targetUrl) { UseShellExecute = true });
return result;
}
@@ -1599,12 +1612,17 @@ that no longer do we need to add additional classes for the actions. We just use
the helper:
```cs
class NewsListItem(NewsPost post) : Microsoft.Windows.Run.Extensions.ListItem {
public string Title => post.Title;
public string Subtitle => post.Url;
class NewsListItem : Microsoft.Windows.Run.Extensions.ListItem {
private NewsPost _post;
public NewsListItem(NewsPost post)
{
_post = post;
Title = post.Title;
Subtitle = post.Url;
}
public IContextItem[] Commands => [
new CommandContextItem(new Microsoft.Windows.Run.Extensions.OpenUrlAction(post.Url, ActionResult.KeepOpen)),
new CommandContextItem(new Microsoft.Windows.Run.Extensions.OpenUrlAction(post.CommentsUrl, ActionResult.KeepOpen){
new CommandContextItem(new OpenUrlAction(post.Url, CommandResult.KeepOpen)),
new CommandContextItem(new OpenUrlAction(post.CommentsUrl, CommandResult.KeepOpen){
Name = "Open comments",
Icon = "\uE8F2" // ChatBubbles
})
@@ -1612,7 +1630,10 @@ class NewsListItem(NewsPost post) : Microsoft.Windows.Run.Extensions.ListItem {
public ITag[] Tags => [ new Tag(){ Text=post.Poster, new Tag(){ Text=post.Points } } ];
}
class HackerNewsPage: Microsoft.Windows.Run.Extensions.ListPage {
public bool Loading => true;
public HackerNewsPage()
{
Loading = true;
}
IListItem[] GetItems(String query) {
List<NewsItem> items = /* do some RSS feed stuff */;
this.IsLoading = false;
@@ -1832,7 +1853,7 @@ When displaying a page:
## Class diagram
This is a diagram attempting to show the relationships between the various types we've defined for the SDK. Some elements are omitted for clarity. (Notably, `IconDataType` and `IPropChanged`, which are used in many places.)
This is a diagram attempting to show the relationships between the various types we've defined for the SDK. Some elements are omitted for clarity. (Notably, `IconData` and `IPropChanged`, which are used in many places.)
The notes on the arrows help indicate the multiplicity of the relationship.
* "*" means 0 or more (for arrays)
@@ -1844,7 +1865,7 @@ classDiagram
class ICommand {
String Name
String Id
IconDataType Icon
IconInfo Icon
}
IPage --|> ICommand
class IPage {
@@ -1889,7 +1910,7 @@ classDiagram
class IFilter {
String Id
String Name
IconDataType Icon
IconInfo Icon
}
class IFilters {
@@ -1905,7 +1926,7 @@ classDiagram
%% IListItem --|> INotifyPropChanged
class IListItem {
IconDataType Icon
IconInfo Icon
String Title
String Subtitle
ICommand Command
@@ -1948,14 +1969,14 @@ classDiagram
}
class IDetails {
IconDataType HeroImage
IconInfo HeroImage
String Title
String Body
IDetailsElement[] Metadata
}
class ITag {
IconDataType Icon
IconInfo Icon
String Text
Color Color
String ToolTip
@@ -1980,7 +2001,7 @@ classDiagram
class ICommandProvider {
String DisplayName
IconDataType Icon
IconInfo Icon
Boolean Frozen
ICommandItem[] TopLevelCommands()

View File

@@ -8,7 +8,7 @@ public class Command : BaseObservable, ICommand
{
private string _name = string.Empty;
private string _id = string.Empty;
private IconDataType _icon = new(string.Empty);
private IconInfo _icon = new(string.Empty);
public string Name
{
@@ -22,7 +22,7 @@ public class Command : BaseObservable, ICommand
public string Id { get => _id; protected set => _id = value; }
public IconDataType Icon
public IconInfo Icon
{
get => _icon;
set

View File

@@ -6,13 +6,13 @@ namespace Microsoft.CmdPal.Extensions.Helpers;
public partial class CommandItem : BaseObservable, ICommandItem
{
private IconDataType? _icon;
private IconInfo? _icon;
private string _title = string.Empty;
private string _subtitle = string.Empty;
private ICommand? _command;
private IContextItem[] _moreCommands = [];
public IconDataType? Icon
public IconInfo? Icon
{
get => _icon ?? _command?.Icon;
set

View File

@@ -12,7 +12,7 @@ public abstract partial class CommandProvider : ICommandProvider
private string _displayName = string.Empty;
private IconDataType _icon = new(string.Empty);
private IconInfo _icon = new(string.Empty);
private ICommandSettings? _settings;
@@ -20,7 +20,7 @@ public abstract partial class CommandProvider : ICommandProvider
public string DisplayName { get => _displayName; protected set => _displayName = value; }
public IconDataType Icon { get => _icon; protected set => _icon = value; }
public IconInfo Icon { get => _icon; protected set => _icon = value; }
public event TypedEventHandler<object, ItemsChangedEventArgs>? ItemsChanged;

View File

@@ -6,12 +6,12 @@ namespace Microsoft.CmdPal.Extensions.Helpers;
public class Details : BaseObservable, IDetails
{
private IconDataType _heroImage = new(string.Empty);
private IconInfo _heroImage = new(string.Empty);
private string _title = string.Empty;
private string _body = string.Empty;
private IDetailsElement[] _metadata = [];
public IconDataType HeroImage
public IconInfo HeroImage
{
get => _heroImage;
set

View File

@@ -6,7 +6,7 @@ namespace Microsoft.CmdPal.Extensions.Helpers;
public class Filter : IFilter
{
public IconDataType Icon => throw new NotImplementedException();
public IconInfo Icon => throw new NotImplementedException();
public string Id => throw new NotImplementedException();

View File

@@ -8,7 +8,7 @@ public class Tag : BaseObservable, ITag
{
private OptionalColor _foreground;
private OptionalColor _background;
private IconDataType _icon = new(string.Empty);
private IconInfo _icon = new(string.Empty);
private string _text = string.Empty;
private string _toolTip = string.Empty;
private ICommand? _command;
@@ -33,7 +33,7 @@ public class Tag : BaseObservable, ITag
}
}
public IconDataType Icon
public IconInfo Icon
{
get => _icon;
set

View File

@@ -0,0 +1,5 @@
#include "pch.h"
#include "IconData.h"
#include "IconData.g.cpp"
#include "IconInfo.g.cpp"

View File

@@ -0,0 +1,52 @@
#pragma once
#include "IconData.g.h"
#include "IconInfo.g.h"
namespace winrt::Microsoft::CmdPal::Extensions::implementation
{
struct IconData : IconDataT<IconData>
{
IconData(hstring iconPath) :
Icon(iconPath){};
IconData(Windows::Storage::Streams::IRandomAccessStreamReference iconData) :
Data(iconData){};
static Microsoft::CmdPal::Extensions::IconData FromStream(Windows::Storage::Streams::IRandomAccessStreamReference stream)
{
return *winrt::make_self<IconData>(stream);
}
til::property<hstring> Icon;
til::property<Windows::Storage::Streams::IRandomAccessStreamReference> Data;
};
}
namespace winrt::Microsoft::CmdPal::Extensions::factory_implementation
{
struct IconData : IconDataT<IconData, implementation::IconData>
{
};
}
namespace winrt::Microsoft::CmdPal::Extensions::implementation
{
struct IconInfo : IconInfoT<IconInfo>
{
IconInfo(hstring iconPath) :
Light(iconPath),
Dark(iconPath){};
IconInfo(Extensions::IconData light, Extensions::IconData dark) :
Light(light),
Dark(dark) {};
til::property<Extensions::IconData> Light;
til::property<Extensions::IconData> Dark;
};
}
namespace winrt::Microsoft::CmdPal::Extensions::factory_implementation
{
struct IconInfo : IconInfoT<IconInfo, implementation::IconInfo>
{
};
}

View File

@@ -1,4 +0,0 @@
#include "pch.h"
#include "IconDataType.h"
#include "IconDataType.g.cpp"

View File

@@ -1,30 +0,0 @@
#pragma once
#include "IconDataType.g.h"
namespace winrt::Microsoft::CmdPal::Extensions::implementation
{
struct IconDataType : IconDataTypeT<IconDataType>
{
IconDataType(hstring iconPath) :
Icon(iconPath){};
IconDataType(Windows::Storage::Streams::IRandomAccessStreamReference iconData) :
Data(iconData){};
static Microsoft::CmdPal::Extensions::IconDataType FromStream(Windows::Storage::Streams::IRandomAccessStreamReference stream)
{
return *winrt::make_self<IconDataType>(stream);
}
til::property<hstring> Icon;
til::property<Windows::Storage::Streams::IRandomAccessStreamReference> Data;
};
}
namespace winrt::Microsoft::CmdPal::Extensions::factory_implementation
{
struct IconDataType : IconDataTypeT<IconDataType, implementation::IconDataType>
{
};
}

View File

@@ -15,14 +15,23 @@ namespace Microsoft.CmdPal.Extensions
};
[contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)]
runtimeclass IconDataType {
IconDataType(String iconString);
static IconDataType FromStream(Windows.Storage.Streams.IRandomAccessStreamReference stream);
runtimeclass IconData {
IconData(String iconString);
static IconData FromStream(Windows.Storage.Streams.IRandomAccessStreamReference stream);
String Icon { get; };
Windows.Storage.Streams.IRandomAccessStreamReference Data { get; };
};
[contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)]
runtimeclass IconInfo {
IconInfo(String iconString);
IconInfo(IconData lightIcon, IconData darkIcon);
IconData Light { get; };
IconData Dark { get; };
};
[contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)]
runtimeclass KeyChord
{
@@ -62,7 +71,7 @@ namespace Microsoft.CmdPal.Extensions
interface ICommand requires INotifyPropChanged{
String Name{ get; };
String Id{ get; };
IconDataType Icon{ get; };
IconInfo Icon{ get; };
}
enum CommandResultKind {
@@ -114,7 +123,7 @@ namespace Microsoft.CmdPal.Extensions
interface IFilter requires IFilterItem {
String Id { get; };
String Name { get; };
IconDataType Icon { get; };
IconInfo Icon { get; };
}
[contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)]
@@ -139,7 +148,7 @@ namespace Microsoft.CmdPal.Extensions
[contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)]
interface ITag {
IconDataType Icon { get; };
IconInfo Icon { get; };
String Text { get; };
OptionalColor Foreground { get; };
OptionalColor Background { get; };
@@ -157,7 +166,7 @@ namespace Microsoft.CmdPal.Extensions
}
[contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)]
interface IDetails {
IconDataType HeroImage { get; };
IconInfo HeroImage { get; };
String Title { get; };
String Body { get; };
IDetailsElement[] Metadata { get; };
@@ -234,7 +243,7 @@ namespace Microsoft.CmdPal.Extensions
interface ICommandItem requires INotifyPropChanged {
ICommand Command{ get; };
IContextItem[] MoreCommands{ get; };
IconDataType Icon{ get; };
IconInfo Icon{ get; };
String Title{ get; };
String Subtitle{ get; };
}
@@ -323,7 +332,7 @@ namespace Microsoft.CmdPal.Extensions
{
String Id { get; };
String DisplayName { get; };
IconDataType Icon { get; };
IconInfo Icon { get; };
ICommandSettings Settings { get; };
Boolean Frozen { get; };

View File

@@ -141,7 +141,7 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="IconDataType.h" />
<ClInclude Include="IconData.h" />
<ClInclude Include="PropChangedEventArgs.h" />
<ClInclude Include="ItemsChangedEventArgs.h" />
<ClInclude Include="KeyChord.h" />
@@ -151,7 +151,7 @@
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="IconDataType.cpp" />
<ClCompile Include="IconData.cpp" />
<ClCompile Include="PropChangedEventArgs.cpp" />
<ClCompile Include="ItemsChangedEventArgs.cpp" />
<ClCompile Include="KeyChord.cpp" />