Core - Rewrote profile configuration icons

WPF UI - Adjusted for the above rewrite 
Avalonia UI - Implemented profile icons
This commit is contained in:
Robert committed 2021-12-04 12:02:17 +01:00
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"
@@ -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}" />
@@ -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());
@@ -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;
@@ -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>
@@ -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);
}
}
}
@@ -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 -->