From a76ecec97d4b4628f27651a270dfeecdcd084617 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 13 May 2023 11:18:59 +0200 Subject: [PATCH 1/6] Profile editor - Fix render quality of devices --- .../ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml index 1056b0f8f..ef1ba1f2b 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml @@ -53,7 +53,7 @@ - + From c86dba30fcaa4771ec0003a24b223afc9cb284d2 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 18 May 2023 11:18:55 +0200 Subject: [PATCH 2/6] Settings - Brought the with and margins of different tabs in line with eachother Settings - Tweaked card title font size and spacing Plugins - Show extra information on plugins in their config dialog Plugins - Added the ability to provide a license in plugin.json --- src/Artemis.Core/Plugins/PluginInfo.cs | 22 +++++++ .../Controls/ArtemisIcon.axaml.cs | 48 +++------------ src/Artemis.UI.Shared/Styles/Artemis.axaml | 1 + src/Artemis.UI.Shared/Styles/Plugins.axaml | 5 ++ src/Artemis.UI.Shared/Styles/TextBlock.axaml | 5 ++ .../Screens/Device/DeviceSettingsView.axaml | 8 +-- .../Plugins/PluginSettingsWindowView.axaml | 61 ++++++++++++++++++- .../Plugins/PluginSettingsWindowViewModel.cs | 1 + .../Screens/Plugins/PluginView.axaml | 3 +- .../Panels/Playback/PlaybackView.axaml | 12 ++-- .../Screens/Settings/Tabs/AboutTabView.axaml | 9 ++- .../Settings/Tabs/DevicesTabView.axaml | 20 +++--- .../Settings/Tabs/GeneralTabView.axaml | 16 +++-- .../Settings/Tabs/PluginsTabView.axaml | 4 +- .../StartupWizard/Steps/SettingsStep.axaml | 4 +- 15 files changed, 146 insertions(+), 73 deletions(-) create mode 100644 src/Artemis.UI.Shared/Styles/Plugins.axaml diff --git a/src/Artemis.Core/Plugins/PluginInfo.cs b/src/Artemis.Core/Plugins/PluginInfo.cs index 1eebd03c6..91e49eeab 100644 --- a/src/Artemis.Core/Plugins/PluginInfo.cs +++ b/src/Artemis.Core/Plugins/PluginInfo.cs @@ -29,6 +29,8 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject private Uri? _website; private Uri? _helpPage; private bool _hotReloadSupported; + private Uri? _license; + private string? _licenseName; internal PluginInfo() { @@ -103,6 +105,26 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject get => _helpPage; set => SetAndNotify(ref _helpPage, value); } + + /// + /// Gets or sets the help page of this plugin + /// + [JsonProperty] + public Uri? License + { + get => _license; + set => SetAndNotify(ref _license, value); + } + + /// + /// Gets or sets the author of this plugin + /// + [JsonProperty] + public string? LicenseName + { + get => _licenseName; + set => SetAndNotify(ref _licenseName, value); + } /// /// The plugins display icon that's shown in the settings see for diff --git a/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml.cs b/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml.cs index 6e174235d..7aadc91bf 100644 --- a/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml.cs +++ b/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml.cs @@ -43,30 +43,18 @@ public partial class ArtemisIcon : UserControl // If it's a string there are several options else if (Icon is string iconString) { + // An URI pointing to an image + if (ImageRegex.IsMatch(iconString)) + { + Image image = new() {Source = new Bitmap(iconString), VerticalAlignment = VerticalAlignment.Stretch, HorizontalAlignment = HorizontalAlignment.Stretch}; + RenderOptions.SetBitmapInterpolationMode(image, BitmapInterpolationMode.HighQuality); + Content = image; + } // An enum defined as a string - if (Enum.TryParse(iconString, true, out MaterialIconKind parsedIcon)) + else if (Enum.TryParse(iconString, true, out MaterialIconKind parsedIcon)) { Content = new MaterialIcon {Kind = parsedIcon, Width = Bounds.Width, Height = Bounds.Height}; } - // An URI pointing to an image - else if (ImageRegex.IsMatch(iconString)) - { - if (!Fill) - Content = new Image - { - Source = new Bitmap(iconString), - VerticalAlignment = VerticalAlignment.Stretch, - HorizontalAlignment = HorizontalAlignment.Stretch - }; - else - Content = new Border - { - Background = TextElement.GetForeground(this), - VerticalAlignment = VerticalAlignment.Stretch, - HorizontalAlignment = HorizontalAlignment.Stretch, - OpacityMask = new ImageBrush(new Bitmap(iconString)) - }; - } else { Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark, Width = Bounds.Width, Height = Bounds.Height}; @@ -87,10 +75,10 @@ public partial class ArtemisIcon : UserControl contentControl.Height = Bounds.Height; } } - + private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) { - if (e.Property == IconProperty || e.Property == FillProperty) + if (e.Property == IconProperty) Update(); } @@ -119,21 +107,5 @@ public partial class ArtemisIcon : UserControl set => SetValue(IconProperty, value); } - /// - /// Gets or sets a boolean indicating whether or not the icon should be filled in with the primary text color of the - /// theme - /// - public static readonly StyledProperty FillProperty = AvaloniaProperty.Register(nameof(Icon)); - - /// - /// Gets or sets a boolean indicating whether or not the icon should be filled in with the primary text color of the - /// theme - /// - public bool Fill - { - get => GetValue(FillProperty); - set => SetValue(FillProperty, value); - } - #endregion } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Styles/Artemis.axaml b/src/Artemis.UI.Shared/Styles/Artemis.axaml index 988213ad4..e41674945 100644 --- a/src/Artemis.UI.Shared/Styles/Artemis.axaml +++ b/src/Artemis.UI.Shared/Styles/Artemis.axaml @@ -33,4 +33,5 @@ + \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Styles/Plugins.axaml b/src/Artemis.UI.Shared/Styles/Plugins.axaml new file mode 100644 index 000000000..05a5db2d1 --- /dev/null +++ b/src/Artemis.UI.Shared/Styles/Plugins.axaml @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Styles/TextBlock.axaml b/src/Artemis.UI.Shared/Styles/TextBlock.axaml index 4a2a605e2..9d3cf6ab4 100644 --- a/src/Artemis.UI.Shared/Styles/TextBlock.axaml +++ b/src/Artemis.UI.Shared/Styles/TextBlock.axaml @@ -45,4 +45,9 @@ + diff --git a/src/Artemis.UI/Screens/Device/DeviceSettingsView.axaml b/src/Artemis.UI/Screens/Device/DeviceSettingsView.axaml index 080b36def..064ea7154 100644 --- a/src/Artemis.UI/Screens/Device/DeviceSettingsView.axaml +++ b/src/Artemis.UI/Screens/Device/DeviceSettingsView.axaml @@ -9,13 +9,13 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:DataType="local:DeviceSettingsViewModel" x:Class="Artemis.UI.Screens.Device.DeviceSettingsView"> - - + + @@ -28,7 +28,7 @@ Command="{CompiledBinding IdentifyDevice}"> - + diff --git a/src/Artemis.UI/Screens/Plugins/PluginSettingsWindowView.axaml b/src/Artemis.UI/Screens/Plugins/PluginSettingsWindowView.axaml index 7943954b6..66dcbb1c0 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginSettingsWindowView.axaml +++ b/src/Artemis.UI/Screens/Plugins/PluginSettingsWindowView.axaml @@ -4,6 +4,9 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia" xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins" + xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" + xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.Plugins.PluginSettingsWindowView" x:DataType="plugins:PluginSettingsWindowViewModel" @@ -11,9 +14,63 @@ Title="{CompiledBinding DisplayName}" Width="800" Height="800" + MaxWidth="800" WindowStartupLocation="CenterOwner"> - - + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/PluginSettingsWindowViewModel.cs b/src/Artemis.UI/Screens/Plugins/PluginSettingsWindowViewModel.cs index 401deb4c0..2ef07a933 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginSettingsWindowViewModel.cs +++ b/src/Artemis.UI/Screens/Plugins/PluginSettingsWindowViewModel.cs @@ -16,4 +16,5 @@ public class PluginSettingsWindowViewModel : ActivatableViewModelBase public PluginConfigurationViewModel ConfigurationViewModel { get; } public Plugin Plugin { get; } + public string LicenseButtonText => Plugin.Info.LicenseName ?? "View license"; } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/PluginView.axaml b/src/Artemis.UI/Screens/Plugins/PluginView.axaml index f35e54630..b9dc83c55 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginView.axaml +++ b/src/Artemis.UI/Screens/Plugins/PluginView.axaml @@ -12,7 +12,6 @@ - + - - + + + + + + + + From ed3a77088103bceaed7484c14e5ea68e5ca4d448 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 18 May 2023 19:39:15 +0200 Subject: [PATCH 4/6] UI - Added new icon picker to profile configuration dialog --- .../Flyouts/MaterialIconPickerFlyout.cs | 15 +++- .../MaterialIconPicker/MaterialIconPicker.cs | 69 ++++++++++++++----- .../MaterialIconPickerButton.cs | 23 ++++--- .../Controls/GradientPickerButton.axaml | 1 - .../Controls/MaterialIconPickerButton.axaml | 27 +++++--- .../ProfileConfigurationEditView.axaml | 28 ++------ .../ProfileConfigurationEditViewModel.cs | 10 +-- .../Sidebar/Dialogs/ProfileIconViewModel.cs | 13 +++- .../Screens/Workshop/WorkshopView.axaml | 3 +- 9 files changed, 119 insertions(+), 70 deletions(-) diff --git a/src/Artemis.UI.Shared/Controls/Flyouts/MaterialIconPickerFlyout.cs b/src/Artemis.UI.Shared/Controls/Flyouts/MaterialIconPickerFlyout.cs index cf220336d..99321b5e0 100644 --- a/src/Artemis.UI.Shared/Controls/Flyouts/MaterialIconPickerFlyout.cs +++ b/src/Artemis.UI.Shared/Controls/Flyouts/MaterialIconPickerFlyout.cs @@ -19,7 +19,20 @@ public sealed class MaterialIconPickerFlyout : Flyout protected override Control CreatePresenter() { _picker ??= new MaterialIconPicker.MaterialIconPicker(); - PickerFlyoutPresenter presenter = new() {Content = MaterialIconPicker}; + _picker.Flyout = this; + FlyoutPresenter presenter = new() {Content = MaterialIconPicker}; return presenter; } + + #region Overrides of FlyoutBase + + /// + protected override void OnClosed() + { + if (_picker != null) + _picker.Flyout = null; + base.OnClosed(); + } + + #endregion } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Controls/MaterialIconPicker/MaterialIconPicker.cs b/src/Artemis.UI.Shared/Controls/MaterialIconPicker/MaterialIconPicker.cs index e1e65c9fa..5777b21de 100644 --- a/src/Artemis.UI.Shared/Controls/MaterialIconPicker/MaterialIconPicker.cs +++ b/src/Artemis.UI.Shared/Controls/MaterialIconPicker/MaterialIconPicker.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reactive.Linq; using System.Text.RegularExpressions; using System.Windows.Input; +using Artemis.UI.Shared.Flyouts; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; @@ -28,16 +29,35 @@ public partial class MaterialIconPicker : TemplatedControl public static readonly StyledProperty ValueProperty = AvaloniaProperty.Register(nameof(Value), defaultBindingMode: BindingMode.TwoWay); + /// + /// Gets the command to execute when deleting stops. + /// + public static readonly DirectProperty SelectIconProperty = + AvaloniaProperty.RegisterDirect(nameof(SelectIcon), g => g.SelectIcon); + + private readonly ICommand _selectIcon; + private ItemsRepeater? _iconsContainer; + private SourceList? _iconsSource; private TextBox? _searchBox; private IDisposable? _sub; - private ItemsRepeater? _iconsContainer; - private readonly ICommand _selectIcon; + private readonly Dictionary _enumNames; /// public MaterialIconPicker() { - _selectIcon = ReactiveCommand.Create(i => Value = i); + _selectIcon = ReactiveCommand.Create(i => + { + Value = i; + Flyout?.Hide(); + }); + + // Build a list of enum names and values, this is required because a value may have more than one name + _enumNames = new Dictionary(); + MaterialIconKind[] values = Enum.GetValues(); + string[] names = Enum.GetNames(); + for (int index = 0; index < names.Length; index++) + _enumNames[names[index]] = values[index]; } /// @@ -49,12 +69,6 @@ public partial class MaterialIconPicker : TemplatedControl set => SetValue(ValueProperty, value); } - /// - /// Gets the command to execute when deleting stops. - /// - public static readonly DirectProperty SelectIconProperty = - AvaloniaProperty.RegisterDirect(nameof(SelectIcon), g => g.SelectIcon); - /// /// Gets the command to execute when deleting stops. /// @@ -64,16 +78,14 @@ public partial class MaterialIconPicker : TemplatedControl private init => SetAndRaise(SelectIconProperty, ref _selectIcon, value); } - #region Overrides of TemplatedControl + internal MaterialIconPickerFlyout? Flyout { get; set; } - /// - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + private void Setup() { - _searchBox = e.NameScope.Find("SearchBox"); - _iconsContainer = e.NameScope.Find("IconsContainer"); - if (_iconsContainer == null) + if (_searchBox == null || _iconsContainer == null) return; + // Build a list of values, they are not unique because a value with multiple names occurs once per name _iconsSource = new SourceList(); _iconsSource.AddRange(Enum.GetValues().Distinct()); _sub = _iconsSource.Connect() @@ -84,6 +96,25 @@ public partial class MaterialIconPicker : TemplatedControl _iconsContainer.ItemsSource = icons; } + #region Overrides of TemplatedControl + + /// + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + _searchBox = e.NameScope.Find("SearchBox"); + _iconsContainer = e.NameScope.Find("IconsContainer"); + + Setup(); + base.OnApplyTemplate(e); + } + + /// + protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + Setup(); + base.OnAttachedToLogicalTree(e); + } + /// protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { @@ -91,6 +122,9 @@ public partial class MaterialIconPicker : TemplatedControl _iconsSource = null; _sub?.Dispose(); _sub = null; + + if (_searchBox != null) + _searchBox.Text = ""; base.OnDetachedFromLogicalTree(e); } @@ -99,8 +133,11 @@ public partial class MaterialIconPicker : TemplatedControl if (string.IsNullOrWhiteSpace(text)) return _ => true; + // Strip out whitespace and find all matching enum values text = StripWhiteSpaceRegex().Replace(text, ""); - return data => data.ToString().Contains(text, StringComparison.InvariantCultureIgnoreCase); + HashSet values = _enumNames.Where(n => n.Key.Contains(text, StringComparison.OrdinalIgnoreCase)).Select(n => n.Value).ToHashSet(); + // Only show those that matched + return data => values.Contains(data); } [GeneratedRegex("\\s+")] diff --git a/src/Artemis.UI.Shared/Controls/MaterialIconPicker/MaterialIconPickerButton.cs b/src/Artemis.UI.Shared/Controls/MaterialIconPicker/MaterialIconPickerButton.cs index c05b7d74d..53c751ac3 100644 --- a/src/Artemis.UI.Shared/Controls/MaterialIconPicker/MaterialIconPickerButton.cs +++ b/src/Artemis.UI.Shared/Controls/MaterialIconPicker/MaterialIconPickerButton.cs @@ -31,7 +31,7 @@ public class MaterialIconPickerButton : TemplatedControl /// Gets or sets the desired flyout placement. /// public static readonly StyledProperty PlacementProperty = - AvaloniaProperty.Register(nameof(Placement)); + AvaloniaProperty.Register(nameof(Placement), PlacementMode.BottomEdgeAlignedLeft); private Button? _button; private MaterialIconPickerFlyout? _flyout; @@ -79,14 +79,21 @@ public class MaterialIconPickerButton : TemplatedControl if (_flyout == null) return; - // Logic here is taken from Fluent Avalonia's ColorPicker which also reuses the same control since it's large - _flyout.MaterialIconPicker.Value = Value; - - _flyout.Placement = Placement; - _flyout.ShowAt(_button != null ? _button : this); - _flyoutActive = true; - + MaterialIconPickerFlyout flyout = new(); + flyout.FlyoutPresenterClasses.Add("material-icon-picker-presenter"); + flyout.MaterialIconPicker.Value = Value; + flyout.Placement = Placement; + + flyout.Closed += FlyoutOnClosed; + flyout.ShowAt(this); FlyoutOpened?.Invoke(this, EventArgs.Empty); + + void FlyoutOnClosed(object? closedSender, EventArgs closedEventArgs) + { + Value = flyout.MaterialIconPicker.Value; + flyout.Closed -= FlyoutOnClosed; + FlyoutClosed?.Invoke(this, EventArgs.Empty); + } } #region Overrides of TemplatedControl diff --git a/src/Artemis.UI.Shared/Styles/Controls/GradientPickerButton.axaml b/src/Artemis.UI.Shared/Styles/Controls/GradientPickerButton.axaml index 13f5ae0a5..75aafc057 100644 --- a/src/Artemis.UI.Shared/Styles/Controls/GradientPickerButton.axaml +++ b/src/Artemis.UI.Shared/Styles/Controls/GradientPickerButton.axaml @@ -28,7 +28,6 @@ -