mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 09:46:54 +02:00
CmdPal: Use a factory for building the context menu VMs (#45572)
_targets #45566_ doesn't actually do anything, just moves around the instantiation of command context item VMs. This will let use add pin/unpin commands later related to https://github.com/microsoft/PowerToys/issues/45191 related to https://github.com/microsoft/PowerToys/issues/45201
This commit is contained in:
@@ -9,11 +9,11 @@ using Microsoft.CommandPalette.Extensions;
|
|||||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||||
|
|
||||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
|
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
|
||||||
public partial class CommandContextItemViewModel(ICommandContextItem contextItem, WeakReference<IPageContext> context) : CommandItemViewModel(new(contextItem), context), IContextItemViewModel
|
public partial class CommandContextItemViewModel : CommandItemViewModel, IContextItemViewModel
|
||||||
{
|
{
|
||||||
private readonly KeyChord nullKeyChord = new(0, 0, 0);
|
private readonly KeyChord nullKeyChord = new(0, 0, 0);
|
||||||
|
|
||||||
public new ExtensionObject<ICommandContextItem> Model { get; } = new(contextItem);
|
public new ExtensionObject<ICommandContextItem> Model { get; }
|
||||||
|
|
||||||
public bool IsCritical { get; private set; }
|
public bool IsCritical { get; private set; }
|
||||||
|
|
||||||
@@ -21,6 +21,13 @@ public partial class CommandContextItemViewModel(ICommandContextItem contextItem
|
|||||||
|
|
||||||
public bool HasRequestedShortcut => RequestedShortcut is not null && (RequestedShortcut.Value != nullKeyChord);
|
public bool HasRequestedShortcut => RequestedShortcut is not null && (RequestedShortcut.Value != nullKeyChord);
|
||||||
|
|
||||||
|
public CommandContextItemViewModel(ICommandContextItem contextItem, WeakReference<IPageContext> context)
|
||||||
|
: base(new(contextItem), context)
|
||||||
|
{
|
||||||
|
Model = new(contextItem);
|
||||||
|
IsContextMenuItem = true;
|
||||||
|
}
|
||||||
|
|
||||||
public override void InitializeProperties()
|
public override void InitializeProperties()
|
||||||
{
|
{
|
||||||
if (IsInitialized)
|
if (IsInitialized)
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
{
|
{
|
||||||
public ExtensionObject<ICommandItem> Model => _commandItemModel;
|
public ExtensionObject<ICommandItem> Model => _commandItemModel;
|
||||||
|
|
||||||
|
private readonly IContextMenuFactory? _contextMenuFactory;
|
||||||
|
|
||||||
private ExtensionObject<IExtendedAttributesProvider>? ExtendedAttributesProvider { get; set; }
|
private ExtensionObject<IExtendedAttributesProvider>? ExtendedAttributesProvider { get; set; }
|
||||||
|
|
||||||
private readonly ExtensionObject<ICommandItem> _commandItemModel = new(null);
|
private readonly ExtensionObject<ICommandItem> _commandItemModel = new(null);
|
||||||
@@ -35,6 +37,8 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
|
|
||||||
protected bool IsSelectedInitialized => IsInErrorState || Initialized.HasFlag(InitializedState.SelectionInitialized);
|
protected bool IsSelectedInitialized => IsInErrorState || Initialized.HasFlag(InitializedState.SelectionInitialized);
|
||||||
|
|
||||||
|
public bool IsContextMenuItem { get; protected init; }
|
||||||
|
|
||||||
public bool IsInErrorState => Initialized.HasFlag(InitializedState.Error);
|
public bool IsInErrorState => Initialized.HasFlag(InitializedState.Error);
|
||||||
|
|
||||||
// These are properties that are "observable" from the extension object
|
// These are properties that are "observable" from the extension object
|
||||||
@@ -96,10 +100,14 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
_errorIcon.InitializeProperties();
|
_errorIcon.InitializeProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandItemViewModel(ExtensionObject<ICommandItem> item, WeakReference<IPageContext> errorContext)
|
public CommandItemViewModel(
|
||||||
|
ExtensionObject<ICommandItem> item,
|
||||||
|
WeakReference<IPageContext> errorContext,
|
||||||
|
IContextMenuFactory? contextMenuFactory = null)
|
||||||
: base(errorContext)
|
: base(errorContext)
|
||||||
{
|
{
|
||||||
_commandItemModel = item;
|
_commandItemModel = item;
|
||||||
|
_contextMenuFactory = contextMenuFactory;
|
||||||
Command = new(null, errorContext);
|
Command = new(null, errorContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,26 +205,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var more = model.MoreCommands;
|
BuildAndInitMoreCommands();
|
||||||
if (more is not null)
|
|
||||||
{
|
|
||||||
MoreCommands = more
|
|
||||||
.Select<IContextItem, IContextItemViewModel>(item =>
|
|
||||||
{
|
|
||||||
return item is ICommandContextItem contextItem ? new CommandContextItemViewModel(contextItem, PageContext) : new SeparatorViewModel();
|
|
||||||
})
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Here, we're already theoretically in the async context, so we can
|
|
||||||
// use Initialize straight up
|
|
||||||
MoreCommands
|
|
||||||
.OfType<CommandContextItemViewModel>()
|
|
||||||
.ToList()
|
|
||||||
.ForEach(contextItem =>
|
|
||||||
{
|
|
||||||
contextItem.SlowInitializeProperties();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(model.Command?.Name))
|
if (!string.IsNullOrEmpty(model.Command?.Name))
|
||||||
{
|
{
|
||||||
@@ -370,36 +359,7 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case nameof(model.MoreCommands):
|
case nameof(model.MoreCommands):
|
||||||
var more = model.MoreCommands;
|
BuildAndInitMoreCommands();
|
||||||
if (more is not null)
|
|
||||||
{
|
|
||||||
var newContextMenu = more
|
|
||||||
.Select<IContextItem, IContextItemViewModel>(item =>
|
|
||||||
{
|
|
||||||
return item is ICommandContextItem contextItem ? new CommandContextItemViewModel(contextItem, PageContext) : new SeparatorViewModel();
|
|
||||||
})
|
|
||||||
.ToList();
|
|
||||||
lock (MoreCommands)
|
|
||||||
{
|
|
||||||
ListHelpers.InPlaceUpdateList(MoreCommands, newContextMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
newContextMenu
|
|
||||||
.OfType<CommandContextItemViewModel>()
|
|
||||||
.ToList()
|
|
||||||
.ForEach(contextItem =>
|
|
||||||
{
|
|
||||||
contextItem.InitializeProperties();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
lock (MoreCommands)
|
|
||||||
{
|
|
||||||
MoreCommands.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateProperty(nameof(SecondaryCommand));
|
UpdateProperty(nameof(SecondaryCommand));
|
||||||
UpdateProperty(nameof(SecondaryCommandName));
|
UpdateProperty(nameof(SecondaryCommandName));
|
||||||
UpdateProperty(nameof(HasMoreCommands));
|
UpdateProperty(nameof(HasMoreCommands));
|
||||||
@@ -477,6 +437,33 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
|||||||
public FuzzyTarget GetSubtitleTarget(IPrecomputedFuzzyMatcher matcher)
|
public FuzzyTarget GetSubtitleTarget(IPrecomputedFuzzyMatcher matcher)
|
||||||
=> _subtitleCache.GetOrUpdate(matcher, Subtitle);
|
=> _subtitleCache.GetOrUpdate(matcher, Subtitle);
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// * Does call SlowInitializeProperties on the created items.
|
||||||
|
/// * does NOT call UpdateProperty ; caller must do that.
|
||||||
|
/// </remarks>
|
||||||
|
private void BuildAndInitMoreCommands()
|
||||||
|
{
|
||||||
|
var model = _commandItemModel.Unsafe;
|
||||||
|
if (model is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var more = model.MoreCommands;
|
||||||
|
var factory = _contextMenuFactory ?? DefaultContextMenuFactory.Instance;
|
||||||
|
var results = factory.UnsafeBuildAndInitMoreCommands(more, this);
|
||||||
|
|
||||||
|
List<IContextItemViewModel>? freedItems;
|
||||||
|
lock (MoreCommands)
|
||||||
|
{
|
||||||
|
ListHelpers.InPlaceUpdateList(MoreCommands, results, out freedItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
freedItems.OfType<CommandContextItemViewModel>()
|
||||||
|
.ToList()
|
||||||
|
.ForEach(c => c.SafeCleanup());
|
||||||
|
}
|
||||||
|
|
||||||
protected override void UnsafeCleanup()
|
protected override void UnsafeCleanup()
|
||||||
{
|
{
|
||||||
base.UnsafeCleanup();
|
base.UnsafeCleanup();
|
||||||
|
|||||||
@@ -10,17 +10,19 @@ public class CommandPalettePageViewModelFactory
|
|||||||
: IPageViewModelFactoryService
|
: IPageViewModelFactoryService
|
||||||
{
|
{
|
||||||
private readonly TaskScheduler _scheduler;
|
private readonly TaskScheduler _scheduler;
|
||||||
|
private readonly IContextMenuFactory? _contextMenuFactory;
|
||||||
|
|
||||||
public CommandPalettePageViewModelFactory(TaskScheduler scheduler)
|
public CommandPalettePageViewModelFactory(TaskScheduler scheduler, IContextMenuFactory? contextMenuFactory)
|
||||||
{
|
{
|
||||||
_scheduler = scheduler;
|
_scheduler = scheduler;
|
||||||
|
_contextMenuFactory = contextMenuFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host, CommandProviderContext providerContext)
|
public PageViewModel? TryCreatePageViewModel(IPage page, bool nested, AppExtensionHost host, CommandProviderContext providerContext)
|
||||||
{
|
{
|
||||||
return page switch
|
return page switch
|
||||||
{
|
{
|
||||||
IListPage listPage => new ListViewModel(listPage, _scheduler, host, providerContext) { IsNested = nested },
|
IListPage listPage => new ListViewModel(listPage, _scheduler, host, providerContext, _contextMenuFactory) { IsNested = nested },
|
||||||
IContentPage contentPage => new CommandPaletteContentPageViewModel(contentPage, _scheduler, host, providerContext),
|
IContentPage contentPage => new CommandPaletteContentPageViewModel(contentPage, _scheduler, host, providerContext),
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using Microsoft.CommandPalette.Extensions;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||||
|
|
||||||
|
public partial class DefaultContextMenuFactory : IContextMenuFactory
|
||||||
|
{
|
||||||
|
public static readonly DefaultContextMenuFactory Instance = new();
|
||||||
|
|
||||||
|
private DefaultContextMenuFactory()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IContextItemViewModel> UnsafeBuildAndInitMoreCommands(
|
||||||
|
IContextItem[] items,
|
||||||
|
CommandItemViewModel commandItem)
|
||||||
|
{
|
||||||
|
List<IContextItemViewModel> results = [];
|
||||||
|
if (items is null)
|
||||||
|
{
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (item is ICommandContextItem contextItem)
|
||||||
|
{
|
||||||
|
var contextItemViewModel = new CommandContextItemViewModel(contextItem, commandItem.PageContext);
|
||||||
|
contextItemViewModel.SlowInitializeProperties();
|
||||||
|
results.Add(contextItemViewModel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
results.Add(new SeparatorViewModel());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using Microsoft.CommandPalette.Extensions;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||||
|
|
||||||
|
public interface IContextMenuFactory
|
||||||
|
{
|
||||||
|
List<IContextItemViewModel> UnsafeBuildAndInitMoreCommands(IContextItem[] items, CommandItemViewModel commandItem);
|
||||||
|
}
|
||||||
@@ -63,8 +63,8 @@ public partial class ListItemViewModel : CommandItemViewModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListItemViewModel(IListItem model, WeakReference<IPageContext> context)
|
public ListItemViewModel(IListItem model, WeakReference<IPageContext> context, IContextMenuFactory? contextMenuFactory = null)
|
||||||
: base(new(model), context)
|
: base(new(model), context, contextMenuFactory)
|
||||||
{
|
{
|
||||||
Model = new ExtensionObject<IListItem>(model);
|
Model = new ExtensionObject<IListItem>(model);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
|||||||
private readonly ExtensionObject<IListPage> _model;
|
private readonly ExtensionObject<IListPage> _model;
|
||||||
|
|
||||||
private readonly Lock _listLock = new();
|
private readonly Lock _listLock = new();
|
||||||
|
private readonly IContextMenuFactory? _contextMenuFactory;
|
||||||
|
|
||||||
private InterlockedBoolean _isLoading;
|
private InterlockedBoolean _isLoading;
|
||||||
private bool _isFetching;
|
private bool _isFetching;
|
||||||
@@ -88,11 +89,12 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListViewModel(IListPage model, TaskScheduler scheduler, AppExtensionHost host, CommandProviderContext providerContext)
|
public ListViewModel(IListPage model, TaskScheduler scheduler, AppExtensionHost host, CommandProviderContext providerContext, IContextMenuFactory? contextMenuFactory)
|
||||||
: base(model, scheduler, host, providerContext)
|
: base(model, scheduler, host, providerContext)
|
||||||
{
|
{
|
||||||
_model = new(model);
|
_model = new(model);
|
||||||
EmptyContent = new(new(null), PageContext);
|
_contextMenuFactory = contextMenuFactory;
|
||||||
|
EmptyContent = new(new(null), PageContext, _contextMenuFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FiltersPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
private void FiltersPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ public partial class App : Application, IDisposable
|
|||||||
|
|
||||||
// ViewModels
|
// ViewModels
|
||||||
services.AddSingleton<ShellViewModel>();
|
services.AddSingleton<ShellViewModel>();
|
||||||
|
services.AddSingleton<IContextMenuFactory, CommandPaletteContextMenuFactory>();
|
||||||
services.AddSingleton<IPageViewModelFactoryService, CommandPalettePageViewModelFactory>();
|
services.AddSingleton<IPageViewModelFactoryService, CommandPalettePageViewModelFactory>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using Microsoft.CmdPal.UI.ViewModels;
|
||||||
|
using Microsoft.CommandPalette.Extensions;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.UI;
|
||||||
|
|
||||||
|
internal sealed partial class CommandPaletteContextMenuFactory : IContextMenuFactory
|
||||||
|
{
|
||||||
|
private readonly SettingsModel _settingsModel;
|
||||||
|
private readonly TopLevelCommandManager _topLevelCommandManager;
|
||||||
|
|
||||||
|
public CommandPaletteContextMenuFactory(SettingsModel settingsModel, TopLevelCommandManager topLevelCommandManager)
|
||||||
|
{
|
||||||
|
_settingsModel = settingsModel;
|
||||||
|
_topLevelCommandManager = topLevelCommandManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<IContextItemViewModel> UnsafeBuildAndInitMoreCommands(
|
||||||
|
IContextItem[] items,
|
||||||
|
CommandItemViewModel commandItem)
|
||||||
|
{
|
||||||
|
var results = DefaultContextMenuFactory.Instance.UnsafeBuildAndInitMoreCommands(items, commandItem);
|
||||||
|
|
||||||
|
// TODO: #45201 Here, we'll want to add pin/unpin commands for pinning
|
||||||
|
// items to the top-level or to the dock.
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user