mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 19:36:28 +00:00
Core - Rewrote profile configuration icons
WPF UI - Adjusted for the above rewrite Avalonia UI - Implemented profile icons
This commit is contained in:
1 parent
4c5c785aa6
commit
a6f1b05c19
31 files changed
+659
-105
No files matched your search
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using Artemis.Core;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
@@ -48,20 +49,29 @@ namespace Artemis.UI.Shared.Controls
|
||||
|
||||
try
|
||||
{
|
||||
if (ConfigurationIcon.IconType == ProfileConfigurationIconType.SvgImage && ConfigurationIcon.FileIcon != null)
|
||||
if (ConfigurationIcon.IconType == ProfileConfigurationIconType.MaterialIcon)
|
||||
{
|
||||
SvgSource source = new();
|
||||
source.Load(ConfigurationIcon.FileIcon);
|
||||
Content = new SvgImage {Source = source};
|
||||
}
|
||||
else if (ConfigurationIcon.IconType == ProfileConfigurationIconType.MaterialIcon && ConfigurationIcon.MaterialIcon != null)
|
||||
{
|
||||
Content = Enum.TryParse(ConfigurationIcon.MaterialIcon, true, out MaterialIconKind parsedIcon)
|
||||
Content = Enum.TryParse(ConfigurationIcon.IconName, true, out MaterialIconKind parsedIcon)
|
||||
? new MaterialIcon {Kind = parsedIcon!}
|
||||
: new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
|
||||
return;
|
||||
}
|
||||
else if (ConfigurationIcon.IconType == ProfileConfigurationIconType.BitmapImage && ConfigurationIcon.FileIcon != null)
|
||||
Content = new Image {Source = new Bitmap(ConfigurationIcon.FileIcon)};
|
||||
|
||||
Stream? stream = ConfigurationIcon.GetIconStream();
|
||||
if (stream == null)
|
||||
{
|
||||
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
|
||||
return;
|
||||
}
|
||||
|
||||
if (ConfigurationIcon.IconType == ProfileConfigurationIconType.SvgImage)
|
||||
{
|
||||
SvgSource source = new();
|
||||
source.Load(stream);
|
||||
Content = new Image {Source = new SvgImage {Source = source}};
|
||||
}
|
||||
else if (ConfigurationIcon.IconType == ProfileConfigurationIconType.BitmapImage)
|
||||
Content = new Image {Source = new Bitmap(ConfigurationIcon.GetIconStream())};
|
||||
else
|
||||
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
|
||||
}
|
||||
@@ -83,10 +93,8 @@ namespace Artemis.UI.Shared.Controls
|
||||
if (ConfigurationIcon != null)
|
||||
ConfigurationIcon.PropertyChanged -= IconOnPropertyChanged;
|
||||
|
||||
if (Content is SvgImage svgImage)
|
||||
svgImage.Source?.Dispose();
|
||||
else if (Content is Image image)
|
||||
((Bitmap) image.Source).Dispose();
|
||||
if (Content is Image image && image.Source is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace Artemis.UI.Shared.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts an enum into a boolean.
|
||||
/// </summary>
|
||||
public class EnumToBooleanConverter : IValueConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object Convert(object? value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return Equals(value, parameter);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object? value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value?.Equals(true) == true ? parameter : BindingOperations.DoNothing;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ namespace Artemis.UI.Shared.Services.Interfaces
|
||||
/// <typeparam name="TViewModel">The view model type</typeparam>
|
||||
/// <typeparam name="TResult">The return type</typeparam>
|
||||
/// <returns>A task containing the return value of type <typeparamref name="TResult" /></returns>
|
||||
Task<TResult> ShowDialogAsync<TViewModel, TResult>(params (string name, object value)[] parameters) where TViewModel : DialogViewModelBase<TResult>;
|
||||
Task<TResult> ShowDialogAsync<TViewModel, TResult>(params (string name, object? value)[] parameters) where TViewModel : DialogViewModelBase<TResult>;
|
||||
|
||||
/// <summary>
|
||||
/// Shows a content dialog asking the user to confirm an action
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Artemis.UI.Shared.Services
|
||||
window.Show();
|
||||
}
|
||||
|
||||
public async Task<TResult> ShowDialogAsync<TViewModel, TResult>(params (string name, object value)[] parameters) where TViewModel : DialogViewModelBase<TResult>
|
||||
public async Task<TResult> ShowDialogAsync<TViewModel, TResult>(params (string name, object? value)[] parameters) where TViewModel : DialogViewModelBase<TResult>
|
||||
{
|
||||
IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
|
||||
TViewModel viewModel = _kernel.Get<TViewModel>(paramsArray)!;
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Styles.Resources>
|
||||
<VisualBrush x:Key="CheckerboardBrush" TileMode="Tile" Stretch="Uniform" DestinationRect="0,0,15,15">
|
||||
<VisualBrush.Visual>
|
||||
<Grid Width="15" Height="15" RowDefinitions="*,*" ColumnDefinitions="*,*">
|
||||
<Rectangle Grid.Row="0" Grid.Column="0" Fill="Black" Opacity="0.15" />
|
||||
<Rectangle Grid.Row="0" Grid.Column="1" />
|
||||
<Rectangle Grid.Row="1" Grid.Column="0" />
|
||||
<Rectangle Grid.Row="1" Grid.Column="1" Fill="Black" Opacity="0.15" />
|
||||
</Grid>
|
||||
</VisualBrush.Visual>
|
||||
</VisualBrush>
|
||||
</Styles.Resources>
|
||||
<StyleInclude Source="/Styles/Border.axaml" />
|
||||
<StyleInclude Source="/Styles/Button.axaml" />
|
||||
<StyleInclude Source="/Styles/TextBlock.axaml" />
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
</StackPanel>
|
||||
</Design.PreviewWith>
|
||||
|
||||
<Styles.Resources>
|
||||
<CornerRadius x:Key="CardCornerRadius">8</CornerRadius>
|
||||
</Styles.Resources>
|
||||
|
||||
<!-- Add Styles Here -->
|
||||
<Style Selector="Border.router-container">
|
||||
<Setter Property="Background" Value="{DynamicResource SolidBackgroundFillColorTertiary}" />
|
||||
@@ -25,13 +29,13 @@
|
||||
<Style Selector="Border.card">
|
||||
<Setter Property="Padding" Value="25" />
|
||||
<Setter Property="Background" Value="{DynamicResource ControlFillColorDefaultBrush}" />
|
||||
<Setter Property="CornerRadius" Value="10" />
|
||||
<Setter Property="CornerRadius" Value="{DynamicResource CardCornerRadius}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.card-condensed">
|
||||
<Setter Property="Padding" Value="15" />
|
||||
<Setter Property="Background" Value="{DynamicResource ControlFillColorDefaultBrush}" />
|
||||
<Setter Property="CornerRadius" Value="10" />
|
||||
<Setter Property="CornerRadius" Value="{DynamicResource CardCornerRadius}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Separator.card-separator">
|
||||
|
||||
@@ -51,5 +51,11 @@
|
||||
<Compile Update="Screens\Debugger\Tabs\Settings\DebugSettingsView.axaml.cs">
|
||||
<DependentUpon>DebugSettingsView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Screens\Root\Sidebar\ContentDialogs\SidebarCategoryEditView.axaml.cs">
|
||||
<DependentUpon>SidebarCategoryEditView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Screens\Root\Sidebar\Dialogs\ProfileConfigurationEditView.axaml.cs">
|
||||
<DependentUpon>ProfileConfigurationEditView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -9,20 +9,7 @@
|
||||
x:Class="Artemis.UI.Screens.Device.DeviceSettingsView">
|
||||
<Border Classes="card" Padding="0" Width="200" ClipToBounds="True" Margin="5">
|
||||
<Grid RowDefinitions="140,*,Auto">
|
||||
<Rectangle Grid.Row="0">
|
||||
<Rectangle.Fill>
|
||||
<VisualBrush TileMode="Tile" Stretch="Uniform" DestinationRect="0,0,15,15">
|
||||
<VisualBrush.Visual>
|
||||
<Grid Width="15" Height="15" RowDefinitions="*,*" ColumnDefinitions="*,*">
|
||||
<Rectangle Grid.Row="0" Grid.Column="0" Fill="Black" Opacity="0.15" />
|
||||
<Rectangle Grid.Row="0" Grid.Column="1" />
|
||||
<Rectangle Grid.Row="1" Grid.Column="0" />
|
||||
<Rectangle Grid.Row="1" Grid.Column="1" Fill="Black" Opacity="0.15" />
|
||||
</Grid>
|
||||
</VisualBrush.Visual>
|
||||
</VisualBrush>
|
||||
</Rectangle.Fill>
|
||||
</Rectangle>
|
||||
<Rectangle Grid.Row="0" Fill="{DynamicResource CheckerboardBrush}"/>
|
||||
<controls1:DeviceVisualizer VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="5"
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Root.Sidebar.Dialogs.SidebarCategoryCreateView">
|
||||
x:Class="Artemis.UI.Screens.Root.Sidebar.Dialogs.SidebarCategoryEditView">
|
||||
<StackPanel>
|
||||
<StackPanel.KeyBindings>
|
||||
<KeyBinding Gesture="Enter" Command="{Binding Confirm}" />
|
||||
+2
-2
@@ -5,9 +5,9 @@ using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Root.Sidebar.Dialogs
|
||||
{
|
||||
public class SidebarCategoryCreateView : ReactiveUserControl<SidebarCategoryCreateViewModel>
|
||||
public class SidebarCategoryEditView : ReactiveUserControl<SidebarCategoryEditViewModel>
|
||||
{
|
||||
public SidebarCategoryCreateView()
|
||||
public SidebarCategoryEditView()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.WhenActivated(_ => this.ClearAllDataValidationErrors());
|
||||
+2
-2
@@ -8,13 +8,13 @@ using ReactiveUI.Validation.Extensions;
|
||||
|
||||
namespace Artemis.UI.Screens.Root.Sidebar.Dialogs
|
||||
{
|
||||
public class SidebarCategoryCreateViewModel : ContentDialogViewModelBase
|
||||
public class SidebarCategoryEditViewModel : ContentDialogViewModelBase
|
||||
{
|
||||
private readonly IProfileService _profileService;
|
||||
private readonly ProfileCategory? _category;
|
||||
private string? _categoryName;
|
||||
|
||||
public SidebarCategoryCreateViewModel(IProfileService profileService, ProfileCategory? category)
|
||||
public SidebarCategoryEditViewModel(IProfileService profileService, ProfileCategory? category)
|
||||
{
|
||||
_profileService = profileService;
|
||||
_category = category;
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
|
||||
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Root.Sidebar.Dialogs"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:svg="clr-namespace:Avalonia.Svg.Skia;assembly=Avalonia.Svg.Skia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="850"
|
||||
x:Class="Artemis.UI.Screens.Root.Sidebar.Dialogs.ProfileConfigurationEditView"
|
||||
Title="{Binding DisplayName}"
|
||||
Icon="/Assets/Images/Logo/bow.ico"
|
||||
Width="800"
|
||||
Height="850">
|
||||
<Window.Resources>
|
||||
<converters:EnumToBooleanConverter x:Key="EnumBoolConverter" />
|
||||
</Window.Resources>
|
||||
<Grid Margin="16" RowDefinitions="*,Auto">
|
||||
<ScrollViewer>
|
||||
<StackPanel>
|
||||
<StackPanel.Styles>
|
||||
<Style Selector="TextBlock.label">
|
||||
<Setter Property="Margin" Value="0 10 0 5" />
|
||||
</Style>
|
||||
</StackPanel.Styles>
|
||||
|
||||
<Grid>
|
||||
<TextBlock Classes="h4" IsVisible="{Binding IsNew}">Add a new profile</TextBlock>
|
||||
<TextBlock Classes="h4" IsVisible="{Binding !IsNew}" Text="{Binding ProfileConfiguration.Name}" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Classes="h5">General</TextBlock>
|
||||
|
||||
<Border Classes="card" Margin="0 0 0 15">
|
||||
<StackPanel>
|
||||
<TextBlock Classes="label">Profile name</TextBlock>
|
||||
<TextBox Text="{Binding ProfileName}" />
|
||||
<TextBlock Classes="label">Module</TextBlock>
|
||||
<ComboBox SelectedItem="{Binding SelectedModule}" IsEnabled="{Binding Modules.Count}" Items="{Binding Modules}" HorizontalAlignment="Stretch">
|
||||
<ComboBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ComboBox.ItemsPanel>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:ProfileModuleViewModel}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:ArtemisIcon Icon="{Binding Icon}" Width="16" Height="16" Margin="0 0 5 0" />
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<Grid>
|
||||
<TextBlock Classes="subtitle" IsVisible="{Binding Modules.Count}">Optional and binds the profile to the selected module, making module data available</TextBlock>
|
||||
<TextBlock Classes="subtitle" IsVisible="{Binding !Modules.Count}">No available modules were found</TextBlock>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Classes="label">Icon type</TextBlock>
|
||||
<WrapPanel Orientation="Horizontal">
|
||||
<RadioButton Content="Material Icon"
|
||||
IsChecked="{Binding IconType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static core:ProfileConfigurationIconType.MaterialIcon}}" />
|
||||
<RadioButton Content="Bitmap image"
|
||||
IsChecked="{Binding IconType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static core:ProfileConfigurationIconType.BitmapImage}}" />
|
||||
<RadioButton Content="SVG image"
|
||||
IsChecked="{Binding IconType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static core:ProfileConfigurationIconType.SvgImage}}" />
|
||||
</WrapPanel>
|
||||
|
||||
<TextBlock Classes="label">Icon</TextBlock>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
IsVisible="{Binding IconType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static core:ProfileConfigurationIconType.BitmapImage}}">
|
||||
<Border Background="{DynamicResource CheckerboardBrush}" CornerRadius="{DynamicResource CardCornerRadius}" Width="98" Height="98">
|
||||
<Image Source="{Binding SelectedBitmapSource}" Margin="10"/>
|
||||
</Border>
|
||||
<Button Command="{Binding BrowseBitmapFile}"
|
||||
VerticalAlignment="Bottom"
|
||||
Margin="10 0"
|
||||
IsVisible="{Binding IconType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static core:ProfileConfigurationIconType.BitmapImage}}">
|
||||
Browse bitmap file
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
IsVisible="{Binding IconType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static core:ProfileConfigurationIconType.SvgImage}}">
|
||||
<Border Background="{DynamicResource CheckerboardBrush}" CornerRadius="{DynamicResource CardCornerRadius}" Width="98" Height="98">
|
||||
<Image Margin="10" Source="{Binding SelectedSvgSource}">
|
||||
|
||||
</Image>
|
||||
</Border>
|
||||
<Button Command="{Binding BrowseSvgFile}"
|
||||
VerticalAlignment="Bottom"
|
||||
Margin="10 0"
|
||||
IsVisible="{Binding IconType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static core:ProfileConfigurationIconType.SvgImage}}">
|
||||
Browse SVG file
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
IsVisible="{Binding IconType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static core:ProfileConfigurationIconType.MaterialIcon}}">
|
||||
<Border Background="{DynamicResource CheckerboardBrush}" CornerRadius="{DynamicResource CardCornerRadius}" Width="98" Height="98">
|
||||
<avalonia:MaterialIcon Kind="{Binding SelectedMaterialIcon.Icon}" Width="65" Height="65" />
|
||||
</Border>
|
||||
<ComboBox Items="{Binding MaterialIcons}"
|
||||
SelectedItem="{Binding SelectedMaterialIcon}"
|
||||
VirtualizationMode="Simple"
|
||||
VerticalAlignment="Bottom"
|
||||
IsTextSearchEnabled="True"
|
||||
Margin="10 0"
|
||||
Width="250"
|
||||
IsVisible="{Binding IconType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static core:ProfileConfigurationIconType.MaterialIcon}}">
|
||||
<ComboBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ComboBox.ItemsPanel>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate DataType="local:ProfileIconViewModel">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<avalonia:MaterialIcon Kind="{Binding Icon}" Margin="0 0 5 0" />
|
||||
<TextBlock Text="{Binding DisplayName}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<TextBlock Classes="h5">Keybindings</TextBlock>
|
||||
<TextBlock Classes="subtitle">You may set up hotkeys to activate/deactivate the profile</TextBlock>
|
||||
<Border Classes="card" Margin="0 5 0 15">
|
||||
<TextBlock>TODO</TextBlock>
|
||||
</Border>
|
||||
|
||||
<TextBlock Classes="h5">Activation conditions</TextBlock>
|
||||
<TextBlock Classes="subtitle">If you only want this profile to be active under certain conditions, configure those conditions below</TextBlock>
|
||||
<Border Classes="card" Margin="0 5 0 15">
|
||||
<TextBlock>TODO</TextBlock>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<Grid Grid.Row="1" ColumnDefinitions="*,Auto,Auto">
|
||||
<Button Grid.Column="0" Command="{Binding Import}">Import profile</Button>
|
||||
<Button Grid.Column="1" Margin="10" Classes="accent" Command="{Binding Confirm}">Confirm</Button>
|
||||
<Button Grid.Column="2" Command="{Binding Cancel}">Cancel</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</Window>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Root.Sidebar.Dialogs
|
||||
{
|
||||
public partial class ProfileConfigurationEditView : ReactiveWindow<ProfileConfigurationEditViewModel>
|
||||
{
|
||||
public ProfileConfigurationEditView()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services.Interfaces;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Svg.Skia;
|
||||
using Avalonia.Threading;
|
||||
using Castle.Core.Resource;
|
||||
using Material.Icons;
|
||||
using Newtonsoft.Json;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Root.Sidebar.Dialogs
|
||||
{
|
||||
public class ProfileConfigurationEditViewModel : DialogViewModelBase<bool>
|
||||
{
|
||||
private readonly ProfileCategory _profileCategory;
|
||||
private readonly IProfileService _profileService;
|
||||
private readonly IWindowService _windowService;
|
||||
private ProfileConfigurationIconType _iconType;
|
||||
private ObservableCollection<ProfileIconViewModel>? _materialIcons;
|
||||
private ProfileConfiguration _profileConfiguration;
|
||||
private string _profileName;
|
||||
private Bitmap? _selectedBitmapSource;
|
||||
private ProfileIconViewModel? _selectedMaterialIcon;
|
||||
private ProfileModuleViewModel? _selectedModule;
|
||||
private string? _selectedIconPath;
|
||||
private SvgImage? _selectedSvgSource;
|
||||
|
||||
public ProfileConfigurationEditViewModel(ProfileCategory profileCategory, ProfileConfiguration? profileConfiguration, IWindowService windowService,
|
||||
IProfileService profileService, IPluginManagementService pluginManagementService)
|
||||
{
|
||||
_profileCategory = profileCategory;
|
||||
_windowService = windowService;
|
||||
_profileService = profileService;
|
||||
_profileConfiguration = profileConfiguration ?? profileService.CreateProfileConfiguration(profileCategory, "New profile", Enum.GetValues<MaterialIconKind>().First().ToString());
|
||||
_profileName = _profileConfiguration.Name;
|
||||
_iconType = _profileConfiguration.Icon.IconType;
|
||||
|
||||
IsNew = profileConfiguration == null;
|
||||
DisplayName = IsNew ? "Artemis | Add profile" : "Artemis | Edit profile";
|
||||
Modules = new ObservableCollection<ProfileModuleViewModel>(
|
||||
pluginManagementService.GetFeaturesOfType<Module>().Where(m => !m.IsAlwaysAvailable).Select(m => new ProfileModuleViewModel(m))
|
||||
);
|
||||
|
||||
Dispatcher.UIThread.Post(LoadIcon, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
public bool IsNew { get; }
|
||||
|
||||
public ProfileConfiguration ProfileConfiguration
|
||||
{
|
||||
get => _profileConfiguration;
|
||||
set => this.RaiseAndSetIfChanged(ref _profileConfiguration, value);
|
||||
}
|
||||
|
||||
public string ProfileName
|
||||
{
|
||||
get => _profileName;
|
||||
set => this.RaiseAndSetIfChanged(ref _profileName, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<ProfileModuleViewModel> Modules { get; }
|
||||
|
||||
public ProfileModuleViewModel? SelectedModule
|
||||
{
|
||||
get => _selectedModule;
|
||||
set => this.RaiseAndSetIfChanged(ref _selectedModule, value);
|
||||
}
|
||||
|
||||
public async Task Import()
|
||||
{
|
||||
string[]? result = await _windowService.CreateOpenFileDialog()
|
||||
.HavingFilter(f => f.WithExtension("json").WithName("Artemis profile"))
|
||||
.ShowAsync();
|
||||
|
||||
if (result == null)
|
||||
return;
|
||||
|
||||
string json = await File.ReadAllTextAsync(result[0]);
|
||||
ProfileConfigurationExportModel? profileConfigurationExportModel = null;
|
||||
try
|
||||
{
|
||||
profileConfigurationExportModel = JsonConvert.DeserializeObject<ProfileConfigurationExportModel>(json, IProfileService.ExportSettings);
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
_windowService.ShowExceptionDialog("Import profile failed", e);
|
||||
}
|
||||
|
||||
if (profileConfigurationExportModel == null)
|
||||
{
|
||||
await _windowService.ShowConfirmContentDialog("Import profile", "Failed to import this profile, make sure it is a valid Artemis profile.", "Confirm", null);
|
||||
return;
|
||||
}
|
||||
|
||||
_profileService.ImportProfile(_profileCategory, profileConfigurationExportModel);
|
||||
Close(true);
|
||||
}
|
||||
|
||||
public async Task Confirm()
|
||||
{
|
||||
ProfileConfiguration.Name = ProfileName;
|
||||
ProfileConfiguration.Module = SelectedModule?.Module;
|
||||
await SaveIcon();
|
||||
|
||||
_profileService.SaveProfileConfigurationIcon(ProfileConfiguration);
|
||||
_profileService.SaveProfileCategory(_profileCategory);
|
||||
Close(true);
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
if (IsNew)
|
||||
_profileService.RemoveProfileConfiguration(_profileConfiguration);
|
||||
Close(false);
|
||||
}
|
||||
|
||||
#region Icon
|
||||
|
||||
public ProfileConfigurationIconType IconType
|
||||
{
|
||||
get => _iconType;
|
||||
set => this.RaiseAndSetIfChanged(ref _iconType, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<ProfileIconViewModel>? MaterialIcons
|
||||
{
|
||||
get => _materialIcons;
|
||||
set => this.RaiseAndSetIfChanged(ref _materialIcons, value);
|
||||
}
|
||||
|
||||
public ProfileIconViewModel? SelectedMaterialIcon
|
||||
{
|
||||
get => _selectedMaterialIcon;
|
||||
set => this.RaiseAndSetIfChanged(ref _selectedMaterialIcon, value);
|
||||
}
|
||||
|
||||
public Bitmap? SelectedBitmapSource
|
||||
{
|
||||
get => _selectedBitmapSource;
|
||||
set => this.RaiseAndSetIfChanged(ref _selectedBitmapSource, value);
|
||||
}
|
||||
|
||||
public SvgImage? SelectedSvgSource
|
||||
{
|
||||
get => _selectedSvgSource;
|
||||
set => this.RaiseAndSetIfChanged(ref _selectedSvgSource, value);
|
||||
}
|
||||
|
||||
private void LoadIcon()
|
||||
{
|
||||
// Preselect the icon based on streams if needed
|
||||
if (_profileConfiguration.Icon.IconType == ProfileConfigurationIconType.BitmapImage)
|
||||
{
|
||||
SelectedBitmapSource = new Bitmap(_profileConfiguration.Icon.GetIconStream());
|
||||
}
|
||||
else if (_profileConfiguration.Icon.IconType == ProfileConfigurationIconType.SvgImage)
|
||||
{
|
||||
SvgSource newSource = new();
|
||||
newSource.Load(_profileConfiguration.Icon.GetIconStream());
|
||||
SelectedSvgSource = new SvgImage {Source = newSource};
|
||||
}
|
||||
|
||||
// Prepare the contents of the dropdown box, it should be virtualized so no need to wait with this
|
||||
MaterialIcons = new ObservableCollection<ProfileIconViewModel>(Enum.GetValues<MaterialIconKind>()
|
||||
.Select(kind => new ProfileIconViewModel(kind))
|
||||
.DistinctBy(vm => vm.DisplayName)
|
||||
.OrderBy(vm => vm.DisplayName));
|
||||
|
||||
// Preselect the icon or fall back to a random one
|
||||
SelectedMaterialIcon = !IsNew && Enum.TryParse(_profileConfiguration.Icon.IconName, out MaterialIconKind enumValue)
|
||||
? MaterialIcons.FirstOrDefault(m => m.Icon == enumValue)
|
||||
: MaterialIcons.ElementAt(new Random().Next(0, MaterialIcons.Count - 1));
|
||||
}
|
||||
|
||||
private async Task SaveIcon()
|
||||
{
|
||||
if (IconType == ProfileConfigurationIconType.MaterialIcon && SelectedMaterialIcon != null)
|
||||
ProfileConfiguration.Icon.SetIconByName(SelectedMaterialIcon.Icon.ToString());
|
||||
else if (_selectedIconPath != null)
|
||||
{
|
||||
await using FileStream fileStream = File.OpenRead(_selectedIconPath);
|
||||
ProfileConfiguration.Icon.SetIconByStream(Path.GetFileName(_selectedIconPath), fileStream);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task BrowseBitmapFile()
|
||||
{
|
||||
string[]? result = await _windowService.CreateOpenFileDialog()
|
||||
.HavingFilter(f => f.WithExtension("png").WithExtension("jpg").WithExtension("bmp").WithName("Bitmap image"))
|
||||
.ShowAsync();
|
||||
|
||||
if (result == null)
|
||||
return;
|
||||
|
||||
SelectedBitmapSource = new Bitmap(result[0]);
|
||||
_selectedIconPath = result[0];
|
||||
}
|
||||
|
||||
public async Task BrowseSvgFile()
|
||||
{
|
||||
string[]? result = await _windowService.CreateOpenFileDialog()
|
||||
.HavingFilter(f => f.WithExtension("svg").WithName("SVG image"))
|
||||
.ShowAsync();
|
||||
|
||||
if (result == null)
|
||||
return;
|
||||
|
||||
SvgSource newSource = new();
|
||||
newSource.Load(result[0]);
|
||||
|
||||
SelectedSvgSource = new SvgImage {Source = newSource};
|
||||
_selectedIconPath = result[0];
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Artemis.UI.Shared;
|
||||
using Material.Icons;
|
||||
|
||||
namespace Artemis.UI.Screens.Root.Sidebar.Dialogs
|
||||
{
|
||||
public class ProfileIconViewModel : ViewModelBase
|
||||
{
|
||||
public ProfileIconViewModel(MaterialIconKind icon)
|
||||
{
|
||||
Icon = icon;
|
||||
DisplayName = icon.ToString();
|
||||
}
|
||||
|
||||
public MaterialIconKind Icon { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.UI.Shared;
|
||||
using Material.Icons;
|
||||
|
||||
namespace Artemis.UI.Screens.Root.Sidebar.Dialogs
|
||||
{
|
||||
public class ProfileModuleViewModel : ViewModelBase
|
||||
{
|
||||
public ProfileModuleViewModel(Module module)
|
||||
{
|
||||
Module = module;
|
||||
Name = module.Info.Name;
|
||||
Icon = module.Info.ResolvedIcon ?? MaterialIconKind.QuestionMark.ToString();
|
||||
Description = module.Info.Description;
|
||||
}
|
||||
|
||||
public string Icon { get; }
|
||||
public string Name { get; }
|
||||
public string? Description { get; }
|
||||
|
||||
public Module Module { get; }
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
<Grid x:Name="ContainerGrid" Margin="0 8 0 0" RowDefinitions="Auto,*">
|
||||
<Grid Grid.Row="0" Background="Transparent" ColumnDefinitions="Auto,Auto,*,Auto,Auto">
|
||||
<Grid Grid.Row="0" Background="Transparent" ColumnDefinitions="Auto,Auto,*,Auto,Auto,Auto">
|
||||
|
||||
<avalonia:MaterialIcon Classes.chevron-collapsed="{Binding ShowItems}"
|
||||
Kind="ChevronUp"
|
||||
@@ -84,11 +84,17 @@
|
||||
<ToggleButton Classes="category-button icon-button icon-button-small"
|
||||
Grid.Column="3"
|
||||
ToolTip.Tip="Suspend profile"
|
||||
Margin="2 0 0 0"
|
||||
Margin="5 0"
|
||||
IsChecked="{Binding IsSuspended}">
|
||||
<avalonia:MaterialIcon Kind="Pause" />
|
||||
</ToggleButton>
|
||||
|
||||
<Button Classes="category-button icon-button icon-button-small"
|
||||
Grid.Column="4"
|
||||
ToolTip.Tip="Add profile"
|
||||
HorizontalAlignment="Right"
|
||||
Command="{Binding AddProfile}">
|
||||
<avalonia:MaterialIcon Kind="Plus" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Border Grid.Row="1">
|
||||
|
||||
@@ -74,15 +74,21 @@ namespace Artemis.UI.Screens.Root.Sidebar
|
||||
{
|
||||
await _windowService.CreateContentDialog()
|
||||
.WithTitle("Edit category")
|
||||
.WithViewModel<SidebarCategoryCreateViewModel>(out var vm, ("category", ProfileCategory))
|
||||
.WithViewModel<SidebarCategoryEditViewModel>(out var vm, ("category", ProfileCategory))
|
||||
.HavingPrimaryButton(b => b.WithText("Confirm").WithCommand(vm.Confirm))
|
||||
.HavingSecondaryButton(b => b.WithText("Delete").WithCommand(vm.Delete))
|
||||
.WithCloseButtonText("Cancel")
|
||||
.WithDefaultButton(ContentDialogButton.Primary)
|
||||
.ShowAsync();
|
||||
|
||||
_sidebarViewModel.UpdateProfileCategories();
|
||||
}
|
||||
|
||||
public async Task AddProfile()
|
||||
{
|
||||
await _windowService.ShowDialogAsync<ProfileConfigurationEditViewModel, bool>(("profileCategory", ProfileCategory), ("profileConfiguration", null));
|
||||
}
|
||||
|
||||
private void CreateProfileViewModels()
|
||||
{
|
||||
ProfileConfigurations.Clear();
|
||||
|
||||
@@ -7,22 +7,8 @@
|
||||
xmlns:svg="clr-namespace:Avalonia.Svg.Skia;assembly=Avalonia.Svg.Skia"
|
||||
mc:Ignorable="d" d:DesignWidth="240" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Root.Sidebar.SidebarView">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="60" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" IsHitTestVisible="False">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid RowDefinitions="60,Auto,Auto,*,Auto,Auto">
|
||||
<Grid Grid.Row="0" IsHitTestVisible="False" ColumnDefinitions="Auto,*">
|
||||
<Image Grid.Column="0">
|
||||
<Image.Source>
|
||||
<svg:SvgImage Source="/Assets/Images/Logo/bow.svg" />
|
||||
|
||||
@@ -90,8 +90,9 @@ namespace Artemis.UI.Screens.Root.Sidebar
|
||||
{
|
||||
await _windowService.CreateContentDialog()
|
||||
.WithTitle("Add new category")
|
||||
.WithViewModel<SidebarCategoryCreateViewModel>(out var vm, ("category", null))
|
||||
.WithViewModel<SidebarCategoryEditViewModel>(out var vm, ("category", null))
|
||||
.HavingPrimaryButton(b => b.WithText("Confirm").WithCommand(vm.Confirm))
|
||||
.WithCloseButtonText("Cancel")
|
||||
.WithDefaultButton(ContentDialogButton.Primary)
|
||||
.ShowAsync();
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceInclude Source="/ArtemisTrayIcon.axaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<SolidColorBrush x:Key="Warning">Yellow</SolidColorBrush>
|
||||
</ResourceDictionary>
|
||||
</Styles.Resources>
|
||||
<!-- Third party styles -->
|
||||
|
||||
Reference in new issue
Block a user