From 4c46f05e036ab23c8ddf98d61fdb7f50df8cf831 Mon Sep 17 00:00:00 2001 From: erri120 Date: Mon, 15 Jul 2024 13:45:44 +0200 Subject: [PATCH 1/5] Add IsSelected --- .../WorkspaceSystem/Panel/IPanelViewModel.cs | 5 +++ .../WorkspaceSystem/Panel/PanelView.axaml.cs | 41 ++++++++++++------- .../WorkspaceSystem/Panel/PanelViewModel.cs | 5 ++- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Panel/IPanelViewModel.cs b/src/NexusMods.App.UI/WorkspaceSystem/Panel/IPanelViewModel.cs index 14232c5a17..6069ba8e1e 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/Panel/IPanelViewModel.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/Panel/IPanelViewModel.cs @@ -38,6 +38,11 @@ public interface IPanelViewModel : IViewModelInterface /// public ReactiveCommand PopoutCommand { get; } + /// + /// Gets or sets whether the current panel is selected. + /// + public bool IsSelected { get; set; } + /// /// Gets or sets whether the current panel is not the only panel in the workspace. /// diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml.cs b/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml.cs index 4ef81a2238..8f3f2d6220 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml.cs @@ -2,12 +2,14 @@ using System.Reactive.Linq; using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.Metadata; using Avalonia.Interactivity; using Avalonia.ReactiveUI; using ReactiveUI; namespace NexusMods.App.UI.WorkspaceSystem; +[PseudoClasses(":one-tab", ":selected")] public partial class PanelView : ReactiveUserControl { private const double ScrollOffset = 250; @@ -32,6 +34,24 @@ public PanelView() .Subscribe() .DisposeWith(disposables); + // panel selection + this.AddDisposableHandler(PointerEnteredEvent, (_, _) => + { + if (ViewModel is not null) ViewModel.IsSelected = true; + }, routes: RoutingStrategies.Direct | RoutingStrategies.Bubble, handledEventsToo: true).DisposeWith(disposables); + + this.AddDisposableHandler(PointerExitedEvent, (_, _) => + { + if (ViewModel is not null) ViewModel.IsSelected = false; + }, routes: RoutingStrategies.Direct, handledEventsToo: true).DisposeWith(disposables); + + this.WhenAnyValue(view => view.IsKeyboardFocusWithin) + .SubscribeWithErrorLogging(value => + { + if (ViewModel is not null) ViewModel.IsSelected = value; + }) + .DisposeWith(disposables); + // update scroll buttons and AddTab button (show left aligned or right aligned, depending on the scrollbar visibility) Observable.FromEventPattern( addHandler => TabHeaderScrollViewer.ScrollChanged += addHandler, @@ -121,23 +141,14 @@ public PanelView() .BindToView(this, view => view.TabHeaderScrollViewer.Offset) .DisposeWith(disposables); - // styling: + // classes this.WhenAnyValue(view => view.ViewModel!.Tabs.Count) .Select(count => count == 1) - .Do(hasOneTab => - { - if (hasOneTab) - { - TabHeaderBorder.Classes.Add("OneTab"); - PanelBorder.Classes.Add("OneTab"); - } - else - { - TabHeaderBorder.Classes.Remove("OneTab"); - PanelBorder.Classes.Remove("OneTab"); - } - }) - .SubscribeWithErrorLogging() + .SubscribeWithErrorLogging(hasOneTab => PseudoClasses.Set(":one-tab", hasOneTab)) + .DisposeWith(disposables); + + this.WhenAnyValue(view => view.ViewModel!.IsSelected) + .SubscribeWithErrorLogging(isSelected => PseudoClasses.Set(":selected", isSelected)) .DisposeWith(disposables); }); } diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelViewModel.cs b/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelViewModel.cs index 49f598a1b7..cee5974902 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelViewModel.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelViewModel.cs @@ -39,8 +39,9 @@ public class PanelViewModel : AViewModel, IPanelViewModel public ReactiveCommand CloseCommand { get; } public ReactiveCommand PopoutCommand { get; } - [Reactive] - public bool IsNotAlone { get; set; } + [Reactive] public bool IsSelected { get; set; } + + [Reactive] public bool IsNotAlone { get; set; } [Reactive] private PanelTabId SelectedTabId { get; set; } From 97a4ced222ff3c7e8ac4ea0573d08605e08b3123 Mon Sep 17 00:00:00 2001 From: erri120 Date: Mon, 15 Jul 2024 15:10:05 +0200 Subject: [PATCH 2/5] Styling --- .../WorkspaceSystem/Panel/IPanelViewModel.cs | 2 +- .../WorkspaceSystem/Panel/PanelView.axaml.cs | 17 ++-- .../WorkspaceSystem/Panel/PanelViewModel.cs | 4 +- .../Workspace/WorkspaceViewModel.cs | 2 +- .../WorkspaceSystem/PanelViewStyles.axaml | 81 ++++++++++--------- 5 files changed, 53 insertions(+), 53 deletions(-) diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Panel/IPanelViewModel.cs b/src/NexusMods.App.UI/WorkspaceSystem/Panel/IPanelViewModel.cs index 6069ba8e1e..7d9fb03bde 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/Panel/IPanelViewModel.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/Panel/IPanelViewModel.cs @@ -46,7 +46,7 @@ public interface IPanelViewModel : IViewModelInterface /// /// Gets or sets whether the current panel is not the only panel in the workspace. /// - public bool IsNotAlone { get; set; } + public bool IsAlone { get; set; } /// /// Gets or sets the logical bounds the panel. diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml.cs b/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml.cs index 8f3f2d6220..16ae206619 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml.cs @@ -9,7 +9,7 @@ namespace NexusMods.App.UI.WorkspaceSystem; -[PseudoClasses(":one-tab", ":selected")] +[PseudoClasses(":one-tab", ":selected", ":alone")] public partial class PanelView : ReactiveUserControl { private const double ScrollOffset = 250; @@ -43,14 +43,7 @@ public PanelView() this.AddDisposableHandler(PointerExitedEvent, (_, _) => { if (ViewModel is not null) ViewModel.IsSelected = false; - }, routes: RoutingStrategies.Direct, handledEventsToo: true).DisposeWith(disposables); - - this.WhenAnyValue(view => view.IsKeyboardFocusWithin) - .SubscribeWithErrorLogging(value => - { - if (ViewModel is not null) ViewModel.IsSelected = value; - }) - .DisposeWith(disposables); + }, routes: RoutingStrategies.Direct | RoutingStrategies.Bubble, handledEventsToo: true).DisposeWith(disposables); // update scroll buttons and AddTab button (show left aligned or right aligned, depending on the scrollbar visibility) Observable.FromEventPattern( @@ -141,7 +134,7 @@ public PanelView() .BindToView(this, view => view.TabHeaderScrollViewer.Offset) .DisposeWith(disposables); - // classes + // pseudo classes this.WhenAnyValue(view => view.ViewModel!.Tabs.Count) .Select(count => count == 1) .SubscribeWithErrorLogging(hasOneTab => PseudoClasses.Set(":one-tab", hasOneTab)) @@ -150,6 +143,10 @@ public PanelView() this.WhenAnyValue(view => view.ViewModel!.IsSelected) .SubscribeWithErrorLogging(isSelected => PseudoClasses.Set(":selected", isSelected)) .DisposeWith(disposables); + + this.WhenAnyValue(view => view.ViewModel!.IsAlone) + .SubscribeWithErrorLogging(isAlone => PseudoClasses.Set(":alone", isAlone)) + .DisposeWith(disposables); }); } } diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelViewModel.cs b/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelViewModel.cs index cee5974902..39a7526277 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelViewModel.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelViewModel.cs @@ -41,7 +41,7 @@ public class PanelViewModel : AViewModel, IPanelViewModel [Reactive] public bool IsSelected { get; set; } - [Reactive] public bool IsNotAlone { get; set; } + [Reactive] public bool IsAlone { get; set; } [Reactive] private PanelTabId SelectedTabId { get; set; } @@ -53,7 +53,7 @@ public PanelViewModel(IWorkspaceController workspaceController, PageFactoryContr _workspaceController = workspaceController; _factoryController = factoryController; - var canExecute = this.WhenAnyValue(vm => vm.IsNotAlone); + var canExecute = this.WhenAnyValue(vm => vm.IsAlone).Select(b => !b); PopoutCommand = ReactiveCommand.Create(() => { }, canExecute); CloseCommand = ReactiveCommand.Create(() => Id, canExecute); diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs b/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs index 22c0f48302..32443809a9 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs @@ -112,7 +112,7 @@ public WorkspaceViewModel( { for (var i = 0; i < Panels.Count; i++) { - Panels[i].IsNotAlone = hasMultiplePanels; + Panels[i].IsAlone = !hasMultiplePanels; } }) .SubscribeWithErrorLogging() diff --git a/src/Themes/NexusMods.Themes.NexusFluentDark/Styles/UserControls/WorkspaceSystem/PanelViewStyles.axaml b/src/Themes/NexusMods.Themes.NexusFluentDark/Styles/UserControls/WorkspaceSystem/PanelViewStyles.axaml index adf533edca..636adbec78 100644 --- a/src/Themes/NexusMods.Themes.NexusFluentDark/Styles/UserControls/WorkspaceSystem/PanelViewStyles.axaml +++ b/src/Themes/NexusMods.Themes.NexusFluentDark/Styles/UserControls/WorkspaceSystem/PanelViewStyles.axaml @@ -7,33 +7,54 @@ - - - - - + + + + + + + + - - - - - - @@ -62,27 +77,15 @@ - - - - - - - - From 43118b6a4b32182ef593550163b78fd367eabf21 Mon Sep 17 00:00:00 2001 From: erri120 Date: Mon, 15 Jul 2024 16:38:22 +0200 Subject: [PATCH 3/5] Panel selection functionality --- .../WorkspaceSystem/Panel/PanelView.axaml.cs | 10 +++-- .../WorkspaceSystem/Panel/PanelViewModel.cs | 13 +++---- .../Workspace/IWorkspaceViewModel.cs | 2 + .../Workspace/WorkspaceViewModel.cs | 39 +++++++++++++++++-- 4 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml.cs b/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml.cs index 16ae206619..aa388d7900 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml.cs @@ -40,10 +40,12 @@ public PanelView() if (ViewModel is not null) ViewModel.IsSelected = true; }, routes: RoutingStrategies.Direct | RoutingStrategies.Bubble, handledEventsToo: true).DisposeWith(disposables); - this.AddDisposableHandler(PointerExitedEvent, (_, _) => - { - if (ViewModel is not null) ViewModel.IsSelected = false; - }, routes: RoutingStrategies.Direct | RoutingStrategies.Bubble, handledEventsToo: true).DisposeWith(disposables); + this.WhenAnyValue(view => view.IsKeyboardFocusWithin) + .Where(isFocused => isFocused) + .SubscribeWithErrorLogging(_ => + { + if (ViewModel is not null) ViewModel.IsSelected = true; + }).DisposeWith(disposables); // update scroll buttons and AddTab button (show left aligned or right aligned, depending on the scrollbar visibility) Observable.FromEventPattern( diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelViewModel.cs b/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelViewModel.cs index 39a7526277..2b81973d14 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelViewModel.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelViewModel.cs @@ -39,7 +39,7 @@ public class PanelViewModel : AViewModel, IPanelViewModel public ReactiveCommand CloseCommand { get; } public ReactiveCommand PopoutCommand { get; } - [Reactive] public bool IsSelected { get; set; } + [Reactive] public bool IsSelected { get; set; } = true; [Reactive] public bool IsAlone { get; set; } @@ -112,14 +112,14 @@ public PanelViewModel(IWorkspaceController workspaceController, PageFactoryContr else SelectedTabId = Tabs[removedIndex].Id; }) - .Subscribe() + .SubscribeWithErrorLogging() .DisposeWith(disposables); // handle the close command on tabs _tabsList .Connect() .MergeMany(item => item.Header.CloseTabCommand) - .Subscribe(CloseTab) + .SubscribeWithErrorLogging(CloseTab) .DisposeWith(disposables); // handle when a tab gets selected @@ -129,14 +129,12 @@ public PanelViewModel(IWorkspaceController workspaceController, PageFactoryContr .WhenPropertyChanged(item => item.Header.IsSelected) .Where(propertyValue => propertyValue.Value) .Select(propertyValue => propertyValue.Sender.Id) - // NOTE(erri120): this throws an exception, see #751 - // .BindToVM(this, vm => vm.SelectedTabId) - .Subscribe(selectedTabId => SelectedTabId = selectedTabId) + .BindToVM(this, vm => vm.SelectedTabId) .DisposeWith(disposables); // 2) update the visibility of the tabs this.WhenAnyValue(vm => vm.SelectedTabId) - .Do(selectedTabId => + .SubscribeWithErrorLogging(selectedTabId => { foreach (var tab in Tabs) { @@ -144,7 +142,6 @@ public PanelViewModel(IWorkspaceController workspaceController, PageFactoryContr tab.Header.IsSelected = tab.Id == selectedTabId; } }) - .Subscribe() .DisposeWith(disposables); }); } diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Workspace/IWorkspaceViewModel.cs b/src/NexusMods.App.UI/WorkspaceSystem/Workspace/IWorkspaceViewModel.cs index 2fee5d284b..a61b84743d 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/Workspace/IWorkspaceViewModel.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/Workspace/IWorkspaceViewModel.cs @@ -33,6 +33,8 @@ public interface IWorkspaceViewModel : IViewModelInterface /// public bool IsActive { get; set; } + public IPanelViewModel SelectedPanel { get; } + public ReadOnlyObservableCollection Panels { get; } public ReadOnlyObservableCollection Resizers { get; } diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs b/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs index 32443809a9..80bbf24a13 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs @@ -27,11 +27,15 @@ public class WorkspaceViewModel : AViewModel, IWorkspaceVie public WindowId WindowId => _workspaceController.WindowId; /// - public string Title { get; set; } = string.Empty; + [Reactive] public string Title { get; set; } = string.Empty; /// public IWorkspaceContext Context { get; set; } = EmptyContext.Instance; + /// + [Reactive] public IPanelViewModel SelectedPanel { get; private set; } = null!; + + /// [Reactive] public bool IsActive { get; set; } private readonly SourceCache _panelSource = new(x => x.Id); @@ -101,6 +105,36 @@ public WorkspaceViewModel( .SubscribeWithErrorLogging(ClosePanel) .DisposeWith(disposables); + // handle when a tab gets removed + _panelSource + .Connect() + .ForEachChange(itemChange => + { + if (itemChange.Reason != ChangeReason.Remove) return; + if (!itemChange.Current.IsSelected) return; + SelectedPanel = Panels.First(); + }) + .SubscribeWithErrorLogging() + .DisposeWith(disposables); + + // selecting a panel + _panelSource + .Connect() + .WhenPropertyChanged(panel => panel.IsSelected) + .Where(propertyValue => propertyValue.Value) + .Select(propertyValue => propertyValue.Sender) + .BindToVM(this, vm => vm.SelectedPanel); + + this.WhenAnyValue(vm => vm.SelectedPanel) + .SubscribeWithErrorLogging(selectedPanel => + { + foreach (var panel in Panels) + { + panel.IsSelected = panel.Id == selectedPanel.Id; + } + }) + .DisposeWith(disposables); + // TODO: popout command // Disabling certain features when there is only one panel @@ -108,14 +142,13 @@ public WorkspaceViewModel( .Connect() .Count() .Select(panelCount => panelCount > 1) - .Do(hasMultiplePanels => + .SubscribeWithErrorLogging(hasMultiplePanels => { for (var i = 0; i < Panels.Count; i++) { Panels[i].IsAlone = !hasMultiplePanels; } }) - .SubscribeWithErrorLogging() .DisposeWith(disposables); // Finished Resizing From 3934361c5cd1124d89cadc3fc85fc0957f70610e Mon Sep 17 00:00:00 2001 From: erri120 Date: Mon, 15 Jul 2024 17:24:19 +0200 Subject: [PATCH 4/5] Use pointer pressed --- src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml.cs b/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml.cs index aa388d7900..239cf6fac4 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml.cs @@ -35,7 +35,7 @@ public PanelView() .DisposeWith(disposables); // panel selection - this.AddDisposableHandler(PointerEnteredEvent, (_, _) => + this.AddDisposableHandler(PointerPressedEvent, (_, _) => { if (ViewModel is not null) ViewModel.IsSelected = true; }, routes: RoutingStrategies.Direct | RoutingStrategies.Bubble, handledEventsToo: true).DisposeWith(disposables); From c069d5b06a7f4f7e2cf88fafb0b1e09197dc51af Mon Sep 17 00:00:00 2001 From: erri120 Date: Tue, 16 Jul 2024 13:09:35 +0200 Subject: [PATCH 5/5] Add separator --- .../WorkspaceSystem/Panel/PanelView.axaml | 5 +++-- .../WorkspaceSystem/PanelViewStyles.axaml | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml b/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml index ba77d2accb..fb0d73eb3d 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml +++ b/src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml @@ -25,7 +25,7 @@ x:Name="TabHeaderScrollViewer" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto"> - + @@ -42,7 +42,8 @@ - +