Sidebar - Added subitems support

This commit is contained in:
Robert committed 2023-07-07 22:55:02 +02:00
1 parent 229d93901b
commit 99a365be0b
33 files changed
+572 -162

No files matched your search

@@ -99,7 +99,6 @@
</Style>
<Style Selector="ListBox.sidebar-listbox ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="(i:Interaction.Behaviors)">
<i:BehaviorCollectionTemplate>
<i:BehaviorCollection>
@@ -76,8 +76,8 @@
x:Name="ProfileIcon"
VerticalAlignment="Center"
ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}"
Width="20"
Height="20"
Width="22"
Height="22"
Margin="0 0 5 0">
<shared:ProfileConfigurationIcon.Transitions>
<Transitions>
@@ -4,11 +4,11 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:vm="clr-namespace:Artemis.UI.Screens.Sidebar;assembly=Artemis.UI"
x:DataType="vm:SidebarScreenViewModel"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Sidebar.SidebarScreenView">
<StackPanel Orientation="Horizontal">
<avalonia:MaterialIcon Kind="{CompiledBinding Icon}" Width="16" Height="16" />
<TextBlock FontSize="12" Margin="10 0" VerticalAlignment="Center" Text="{CompiledBinding DisplayName}" />
x:Class="Artemis.UI.Screens.Sidebar.SidebarScreenView"
x:DataType="vm:SidebarScreenViewModel">
<StackPanel Orientation="Horizontal" Background="Transparent" PointerReleased="InputElement_OnPointerReleased" DoubleTapped="InputElement_OnDoubleTapped">
<avalonia:MaterialIcon Kind="{CompiledBinding Icon}" Width="18" Height="18" />
<TextBlock Margin="10 0" VerticalAlignment="Center" FontSize="13" Text="{CompiledBinding DisplayName}" />
</StackPanel>
</UserControl>
@@ -1,13 +1,25 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Sidebar;
public partial class SidebarScreenView : UserControl
public partial class SidebarScreenView : ReactiveUserControl<SidebarScreenViewModel>
{
public SidebarScreenView()
{
InitializeComponent();
}
private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
// if (ViewModel != null)
// ViewModel.IsExpanded = !ViewModel.IsExpanded;
}
private void InputElement_OnDoubleTapped(object? sender, TappedEventArgs e)
{
e.Handled = true;
}
}
@@ -1,25 +1,71 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Avalonia.Threading;
using Material.Icons;
using ReactiveUI;
namespace Artemis.UI.Screens.Sidebar;
public class SidebarScreenViewModel : ViewModelBase
{
public SidebarScreenViewModel(MaterialIconKind icon, string displayName, string path)
private bool _isExpanded;
public SidebarScreenViewModel(MaterialIconKind icon, string displayName, string path, string? rootPath = null, ObservableCollection<SidebarScreenViewModel>? screens = null)
{
Icon = icon;
Path = path;
RootPath = rootPath ?? path;
DisplayName = displayName;
Screens = screens ?? new ObservableCollection<SidebarScreenViewModel>();
}
public MaterialIconKind Icon { get; }
public string Path { get; }
public string RootPath { get; }
public ObservableCollection<SidebarScreenViewModel> Screens { get; }
public bool IsExpanded
{
get => _isExpanded;
set => RaiseAndSetIfChanged(ref _isExpanded, value);
}
public bool Matches(string? path)
{
if (path == null)
return false;
return path.StartsWith(Path, StringComparison.InvariantCultureIgnoreCase);
return path.StartsWith(RootPath, StringComparison.InvariantCultureIgnoreCase);
}
public SidebarScreenViewModel? GetMatch(string path)
{
foreach (SidebarScreenViewModel sidebarScreenViewModel in Screens)
{
SidebarScreenViewModel? match = sidebarScreenViewModel.GetMatch(path);
if (match != null)
return match;
}
return Screens.FirstOrDefault(s => s.Matches(path));
}
public void ExpandIfRequired(SidebarScreenViewModel selected)
{
if (selected == this && Screens.Any())
{
IsExpanded = true;
return;
}
if (Screens.Contains(selected))
IsExpanded = true;
foreach (SidebarScreenViewModel sidebarScreenViewModel in Screens)
sidebarScreenViewModel.ExpandIfRequired(selected);
}
}
@@ -3,7 +3,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:sidebar="clr-namespace:Artemis.UI.Screens.Sidebar"
mc:Ignorable="d" d:DesignWidth="240" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Sidebar.SidebarView"
@@ -20,11 +20,23 @@
</Grid>
<!-- Built-in screens -->
<ListBox Classes="sidebar-listbox"
Grid.Row="1"
Margin="10 2"
ItemsSource="{CompiledBinding SidebarScreens}"
SelectedItem="{CompiledBinding SelectedSidebarScreen}" />
<TreeView Grid.Row="1"
Margin="10 2"
ItemsSource="{CompiledBinding SidebarScreen.Screens}"
SelectedItem="{CompiledBinding SelectedScreen}"
ItemContainerTheme="{StaticResource MenuTreeViewItem}">
<TreeView.Styles>
<Style Selector="TreeViewItem">
<Setter Property="IsExpanded" Value="{CompiledBinding IsExpanded, Mode=TwoWay, DataType=sidebar:SidebarScreenViewModel}" />
</Style>
</TreeView.Styles>
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{CompiledBinding Screens}">
<ContentControl Content="{CompiledBinding}" />
</TreeDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<Border Grid.Row="2" Margin="8" Height="1" Background="{DynamicResource ButtonBorderBrush}"></Border>
<!-- Categories -->
@@ -41,51 +53,51 @@
<!-- Bottom buttons -->
<Border Grid.Row="4" Margin="8" Height="1" Background="{DynamicResource ButtonBorderBrush}"></Border>
<WrapPanel Grid.Row="5" Orientation="Horizontal" HorizontalAlignment="Left" Margin="5 0 5 5">
<controls:HyperlinkButton Classes="icon-button"
Width="44"
Height="44"
ToolTip.Tip="View website"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=sidebar">
<ui:HyperlinkButton Classes="icon-button"
Width="44"
Height="44"
ToolTip.Tip="View website"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=sidebar">
<avalonia:MaterialIcon Kind="Web" Width="20" Height="20" />
</controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button"
Width="44"
Height="44"
ToolTip.Tip="View GitHub repository"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://github.com/Artemis-RGB/Artemis">
</ui:HyperlinkButton>
<ui:HyperlinkButton Classes="icon-button"
Width="44"
Height="44"
ToolTip.Tip="View GitHub repository"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://github.com/Artemis-RGB/Artemis">
<avalonia:MaterialIcon Kind="Github" Width="20" Height="20" />
</controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button"
Width="44"
Height="44"
ToolTip.Tip="View Wiki"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=sidebar">
</ui:HyperlinkButton>
<ui:HyperlinkButton Classes="icon-button"
Width="44"
Height="44"
ToolTip.Tip="View Wiki"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&amp;mtm_kwd=sidebar">
<avalonia:MaterialIcon Kind="BookOpenOutline" Width="20" Height="20" />
</controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button"
Width="44"
Height="44"
ToolTip.Tip="Join our Discord"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://discord.gg/S3MVaC9">
</ui:HyperlinkButton>
<ui:HyperlinkButton Classes="icon-button"
Width="44"
Height="44"
ToolTip.Tip="Join our Discord"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://discord.gg/S3MVaC9">
<avalonia:MaterialIcon Kind="Chat" Width="20" Height="20" />
</controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button"
Width="44"
Height="44"
ToolTip.Tip="View donation options"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://wiki.artemis-rgb.com/en/donating?mtm_campaign=artemis&amp;mtm_kwd=sidebar">
</ui:HyperlinkButton>
<ui:HyperlinkButton Classes="icon-button"
Width="44"
Height="44"
ToolTip.Tip="View donation options"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://wiki.artemis-rgb.com/en/donating?mtm_campaign=artemis&amp;mtm_kwd=sidebar">
<avalonia:MaterialIcon Kind="Gift" Width="20" Height="20" />
</controls:HyperlinkButton>
</ui:HyperlinkButton>
</WrapPanel>
</Grid>
</UserControl>
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
@@ -23,34 +24,38 @@ namespace Artemis.UI.Screens.Sidebar;
public class SidebarViewModel : ActivatableViewModelBase
{
public const string ROOT_SCREEN = "root";
private readonly IRouter _router;
private readonly IWindowService _windowService;
private SidebarScreenViewModel? _selectedSidebarScreen;
private ReadOnlyObservableCollection<SidebarCategoryViewModel> _sidebarCategories = new(new ObservableCollection<SidebarCategoryViewModel>());
private SidebarScreenViewModel? _selectedScreen;
public SidebarViewModel(IRouter router, IProfileService profileService, IWindowService windowService, ISidebarVmFactory sidebarVmFactory)
{
_router = router;
_windowService = windowService;
SidebarScreens = new ObservableCollection<SidebarScreenViewModel>
SidebarScreen = new SidebarScreenViewModel(MaterialIconKind.Abacus, ROOT_SCREEN, "", null, new ObservableCollection<SidebarScreenViewModel>()
{
new(MaterialIconKind.Home, "Home", "home"),
#if DEBUG
new(MaterialIconKind.TestTube, "Workshop", "workshop"),
#endif
new(MaterialIconKind.HomeOutline, "Home", "home"),
new(MaterialIconKind.TestTube, "Workshop", "workshop", null, new ObservableCollection<SidebarScreenViewModel>
{
new(MaterialIconKind.FolderVideo, "Profiles", "workshop/profiles/1", "workshop/profiles"),
new(MaterialIconKind.KeyboardVariant, "Layouts", "workshop/layouts/1", "workshop/layouts"),
}),
new(MaterialIconKind.Devices, "Surface Editor", "surface-editor"),
new(MaterialIconKind.Cog, "Settings", "settings")
};
new(MaterialIconKind.SettingsOutline, "Settings", "settings")
});
AddCategory = ReactiveCommand.CreateFromTask(ExecuteAddCategory);
this.WhenAnyValue(vm => vm.SelectedScreen).WhereNotNull().Subscribe(NavigateToScreen);
this.WhenAnyValue(vm => vm.SelectedScreen).WhereNotNull().Subscribe(s => SidebarScreen.ExpandIfRequired(s));
SourceList<ProfileCategory> profileCategories = new();
this.WhenAnyValue(vm => vm.SelectedSidebarScreen).WhereNotNull().Subscribe(NavigateToScreen);
this.WhenActivated(d =>
{
_router.CurrentPath.WhereNotNull().Subscribe(r => SelectedSidebarScreen = SidebarScreens.FirstOrDefault(s => s.Matches(r))).DisposeWith(d);
_router.CurrentPath.WhereNotNull().Subscribe(r => SelectedScreen = SidebarScreen.GetMatch(r)).DisposeWith(d);
Observable.FromEventPattern<ProfileCategoryEventArgs>(x => profileService.ProfileCategoryAdded += x, x => profileService.ProfileCategoryAdded -= x)
.Subscribe(e => profileCategories.Add(e.EventArgs.ProfileCategory))
@@ -75,11 +80,17 @@ public class SidebarViewModel : ActivatableViewModelBase
.DisposeWith(d);
SidebarCategories = categoryViewModels;
SelectedSidebarScreen = SidebarScreens.First();
});
SelectedScreen = SidebarScreen.Screens.First();
}
public ObservableCollection<SidebarScreenViewModel> SidebarScreens { get; }
public SidebarScreenViewModel SidebarScreen { get; }
public SidebarScreenViewModel? SelectedScreen
{
get => _selectedScreen;
set => RaiseAndSetIfChanged(ref _selectedScreen, value);
}
public ReadOnlyObservableCollection<SidebarCategoryViewModel> SidebarCategories
{
@@ -87,12 +98,6 @@ public class SidebarViewModel : ActivatableViewModelBase
set => RaiseAndSetIfChanged(ref _sidebarCategories, value);
}
public SidebarScreenViewModel? SelectedSidebarScreen
{
get => _selectedSidebarScreen;
set => RaiseAndSetIfChanged(ref _selectedSidebarScreen, value);
}
public ReactiveCommand<Unit, Unit> AddCategory { get; }
private async Task ExecuteAddCategory()
@@ -112,7 +117,7 @@ public class SidebarViewModel : ActivatableViewModelBase
{
try
{
await _router.Navigate(sidebarScreenViewModel.Path, new RouterNavigationOptions {IgnoreOnPartialMatch = true});
await _router.Navigate(sidebarScreenViewModel.Path);
}
catch (Exception e)
{