Files
PowerToys/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandViewModel.cs
Michael Jolley 910bf7f616 Worst merge ever.
2026-02-20 19:43:31 -06:00

172 lines
4.7 KiB
C#

// 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.Models;
using Microsoft.CommandPalette.Extensions;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class CommandViewModel : ExtensionObjectViewModel
{
public ExtensionObject<ICommand> Model { get; private set; } = new(null);
public bool IsSet => Model.Unsafe is not null;
protected bool IsInitialized { get; private set; }
protected bool IsFastInitialized { get; private set; }
public bool HasIcon => Icon.IsSet;
// These are properties that are "observable" from the extension object
// itself, in the sense that they get raised by PropChanged events from the
// extension. However, we don't want to actually make them
// [ObservableProperty]s, because PropChanged comes in off the UI thread,
// and ObservableProperty is not smart enough to raise the PropertyChanged
// on the UI thread.
public string Id { get; private set; } = string.Empty;
public string Name { get; private set; } = string.Empty;
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)
{
Model = new(command);
Icon = new(null);
}
public void FastInitializeProperties()
{
if (IsFastInitialized)
{
return;
}
var model = Model.Unsafe;
if (model is null)
{
return;
}
Id = model.Id ?? string.Empty;
Name = model.Name ?? string.Empty;
IsFastInitialized = true;
}
public override void InitializeProperties()
{
if (IsInitialized)
{
return;
}
if (!IsFastInitialized)
{
FastInitializeProperties();
}
var model = Model.Unsafe;
if (model is null)
{
return;
}
var ico = model.Icon;
if (ico is not null)
{
Icon = new(ico);
Icon.InitializeProperties();
UpdateProperty(nameof(Icon));
}
if (model is IExtendedAttributesProvider command2)
{
UpdatePropertiesFromExtension(command2);
}
model.PropChanged += Model_PropChanged;
}
private void Model_PropChanged(object sender, IPropChangedEventArgs args)
{
try
{
FetchProperty(args.PropertyName);
}
catch (Exception ex)
{
ShowException(ex, Name);
}
}
protected void FetchProperty(string propertyName)
{
var model = Model.Unsafe;
if (model is null)
{
return; // throw?
}
switch (propertyName)
{
case nameof(Name):
Name = model.Name;
break;
case nameof(Icon):
var iconInfo = model.Icon;
Icon = new(iconInfo);
Icon.InitializeProperties();
break;
}
UpdateProperty(propertyName);
}
protected override void UnsafeCleanup()
{
base.UnsafeCleanup();
Icon = new(null); // necessary?
var model = Model.Unsafe;
if (model is not null)
{
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));
}
}
}