Skip to content

Commit

Permalink
Merge pull request #1768 from erri120/feat/1685-selected-panel
Browse files Browse the repository at this point in the history
Selected Panel Part #1
  • Loading branch information
erri120 authored Jul 16, 2024
2 parents 408cb50 + c069d5b commit f0963dc
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,15 @@ public interface IPanelViewModel : IViewModelInterface
/// </summary>
public ReactiveCommand<Unit, Unit> PopoutCommand { get; }

/// <summary>
/// Gets or sets whether the current panel is selected.
/// </summary>
public bool IsSelected { get; set; }

/// <summary>
/// Gets or sets whether the current panel is not the only panel in the workspace.
/// </summary>
public bool IsNotAlone { get; set; }
public bool IsAlone { get; set; }

/// <summary>
/// Gets or sets the logical bounds the panel.
Expand Down
5 changes: 3 additions & 2 deletions src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
x:Name="TabHeaderScrollViewer"
VerticalScrollBarVisibility="Disabled"
HorizontalScrollBarVisibility="Auto">
<Grid ColumnDefinitions="Auto, *">
<Grid ColumnDefinitions="Auto, Auto, *">
<StackPanel Grid.Column="0" Orientation="Horizontal">
<ItemsControl x:Name="TabHeaders" Height="52">
<ItemsControl.ItemsPanel>
Expand All @@ -42,7 +42,8 @@
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<Border Grid.Column="1" x:Name="AddTabButton1Container"
<Separator x:Name="OneTabLine" Grid.Column="1"/>
<Border Grid.Column="2" x:Name="AddTabButton1Container"
HorizontalAlignment="Stretch">
<Button x:Name="AddTabButton1"
Margin="12 12 10 12"
Expand Down
40 changes: 25 additions & 15 deletions src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelView.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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", ":alone")]
public partial class PanelView : ReactiveUserControl<IPanelViewModel>
{
private const double ScrollOffset = 250;
Expand All @@ -32,6 +34,19 @@ public PanelView()
.Subscribe()
.DisposeWith(disposables);

// panel selection
this.AddDisposableHandler(PointerPressedEvent, (_, _) =>
{
if (ViewModel is not null) ViewModel.IsSelected = true;
}, 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<ScrollChangedEventArgs>(
addHandler => TabHeaderScrollViewer.ScrollChanged += addHandler,
Expand Down Expand Up @@ -121,23 +136,18 @@ public PanelView()
.BindToView(this, view => view.TabHeaderScrollViewer.Offset)
.DisposeWith(disposables);

// styling:
// pseudo 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);

this.WhenAnyValue(view => view.ViewModel!.IsAlone)
.SubscribeWithErrorLogging(isAlone => PseudoClasses.Set(":alone", isAlone))
.DisposeWith(disposables);
});
}
Expand Down
18 changes: 8 additions & 10 deletions src/NexusMods.App.UI/WorkspaceSystem/Panel/PanelViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ public class PanelViewModel : AViewModel<IPanelViewModel>, IPanelViewModel
public ReactiveCommand<Unit, PanelId> CloseCommand { get; }
public ReactiveCommand<Unit, Unit> PopoutCommand { get; }

[Reactive]
public bool IsNotAlone { get; set; }
[Reactive] public bool IsSelected { get; set; } = true;

[Reactive] public bool IsAlone { get; set; }

[Reactive] private PanelTabId SelectedTabId { get; set; }

Expand All @@ -52,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);

Expand Down Expand Up @@ -111,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
Expand All @@ -128,22 +129,19 @@ 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)
{
tab.IsVisible = tab.Id == selectedTabId;
tab.Header.IsSelected = tab.Id == selectedTabId;
}
})
.Subscribe()
.DisposeWith(disposables);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public interface IWorkspaceViewModel : IViewModelInterface
/// </summary>
public bool IsActive { get; set; }

public IPanelViewModel SelectedPanel { get; }

public ReadOnlyObservableCollection<IPanelViewModel> Panels { get; }

public ReadOnlyObservableCollection<IPanelResizerViewModel> Resizers { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,15 @@ public class WorkspaceViewModel : AViewModel<IWorkspaceViewModel>, IWorkspaceVie
public WindowId WindowId => _workspaceController.WindowId;

/// <inheritdoc/>
public string Title { get; set; } = string.Empty;
[Reactive] public string Title { get; set; } = string.Empty;

/// <inheritdoc/>
public IWorkspaceContext Context { get; set; } = EmptyContext.Instance;

/// <inheritdoc/>
[Reactive] public IPanelViewModel SelectedPanel { get; private set; } = null!;

/// <inheritdoc/>
[Reactive] public bool IsActive { get; set; }

private readonly SourceCache<IPanelViewModel, PanelId> _panelSource = new(x => x.Id);
Expand Down Expand Up @@ -101,21 +105,50 @@ 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
_panelSource
.Connect()
.Count()
.Select(panelCount => panelCount > 1)
.Do(hasMultiplePanels =>
.SubscribeWithErrorLogging(hasMultiplePanels =>
{
for (var i = 0; i < Panels.Count; i++)
{
Panels[i].IsNotAlone = hasMultiplePanels;
Panels[i].IsAlone = !hasMultiplePanels;
}
})
.SubscribeWithErrorLogging()
.DisposeWith(disposables);

// Finished Resizing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,72 @@
<Setter Property="Padding" Value="0" />
</Style>

<Style Selector="^ Border#PanelBorder:not(.OneTab)">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="{StaticResource StrokeTranslucentWeak}"/>
</Style>

<Style Selector="^ Border#TabHeaderBorder">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="BorderBrush" Value="{StaticResource StrokeTranslucentWeak}"/>
<Setter Property="BorderBrush" Value="Transparent"/>

<Style Selector="^ Border#AddTabButton1Container, ^ Border#TabHeaderAddAndScrollBorder, ^ Border#TabHeaderSideAreaBorder, ^ Border#ScrollLeftButtonBorder">
<Setter Property="BorderThickness" Value="0 0 0 1"/>
<Setter Property="BorderBrush" Value="{StaticResource StrokeTranslucentWeak}" />
<Style Selector="^ Separator#OneTabLine">
<Setter Property="Margin" Value="0"/>
<Setter Property="Height" Value="28"/>
<Setter Property="Width" Value="1"/>
<Setter Property="Background" Value="{StaticResource StrokeTranslucentWeak}"/>
</Style>
</Style>

<!-- one tab -->
<Style Selector="^ Border#TabHeaderBorder.OneTab">
<Style Selector="^ ui|PanelTabHeaderView Border#Container">
<Setter Property="CornerRadius" Value="{StaticResource Rounded-tl-lg}"/>
<!-- single panel -->
<Style Selector="^:alone">
<Style Selector="^ Border#PanelBorder">
<Setter Property="BorderThickness" Value="2" />
<Setter Property="BorderBrush" Value="Transparent" />
</Style>
</Style>

<!-- multiple panels -->
<Style Selector="^:not(:alone)">
<!-- selected -->
<Style Selector="^:selected">
<Style Selector="^ Border#PanelBorder">
<Setter Property="BorderThickness" Value="2" />
<Setter Property="BorderBrush" Value="{StaticResource BrandNeutral600}" />
</Style>
</Style>

<!-- not selected -->
<Style Selector="^:not(:selected)">
<Style Selector="^ Border#PanelBorder">
<Setter Property="BorderThickness" Value="2" />
<Setter Property="BorderBrush" Value="Transparent" />
</Style>
</Style>
</Style>

<!-- one tab -->
<Style Selector="^:one-tab">
<Style Selector="^ Border#TabHeaderBorder">
<Setter Property="BorderThickness" Value="0 0 0 1" />
<Setter Property="BorderBrush" Value="{StaticResource StrokeTranslucentWeak}"/>
<Setter Property="BorderThickness" Value="0 0 1 0" />

<Style Selector="^ ui|PanelTabHeaderView Border#Container">
<Setter Property="CornerRadius" Value="{StaticResource Rounded-tl-lg}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="BorderBrush" Value="Transparent"/>
</Style>

<Style Selector="^ Separator#OneTabLine">
<Setter Property="IsVisible" Value="True"/>
</Style>
</Style>
</Style>

<!-- multiple tabs -->
<Style Selector="^ Border#TabHeaderBorder:not(.OneTab)">
<Style Selector="^:not(:one-tab) Border#TabHeaderBorder">
<Setter Property="CornerRadius" Value="{StaticResource Rounded-t-lg}" />
<Setter Property="Background" Value="{StaticResource SurfaceBaseBrush}" />

<Style Selector="^ Separator#OneTabLine">
<Setter Property="IsVisible" Value="False"/>
</Style>

<Style Selector="^ ItemsControl#TabHeaders">
<!-- every tab -->
<Style Selector="^ ContentPresenter:nth-child(n) ui|PanelTabHeaderView">
Expand All @@ -45,14 +81,8 @@
<Setter Property="BorderBrush" Value="{StaticResource StrokeTranslucentWeak}" />
</Style>

<!-- selected tab -->
<Style Selector="^ Border#Container.Selected">
<Setter Property="BorderThickness" Value="1 0 0 0" />
</Style>

<!-- not selected tab -->
<Style Selector="^ Border#Container:not(.Selected)">
<Setter Property="BorderThickness" Value="1 0 0 1" />
<Style Selector="^ Border#Container">
<Setter Property="BorderThickness" Value="0" />
</Style>
</Style>

Expand All @@ -62,27 +92,15 @@
<Setter Property="CornerRadius" Value="{StaticResource Rounded-tl-lg}" />
</Style>

<!-- selected tab -->
<Style Selector="^ Border#Container.Selected">
<Setter Property="BorderThickness" Value="0 0 0 0" />
</Style>

<!-- not selected tab -->
<Style Selector="^ Border#Container:not(.Selected)">
<Setter Property="BorderThickness" Value="0 0 0 1" />
<Style Selector="^ Border#Container">
<Setter Property="BorderThickness" Value="0" />
</Style>
</Style>

<!-- last tab -->
<Style Selector="^ ContentPresenter:nth-last-child(1) ui|PanelTabHeaderView">
<!-- selected tab -->
<Style Selector="^ Border#Container.Selected">
<Setter Property="BorderThickness" Value="1 0 1 0" />
</Style>

<!-- not selected tab -->
<Style Selector="^ Border#Container:not(.Selected)">
<Setter Property="BorderThickness" Value="1 0 1 1" />
<Style Selector="^ Border#Container">
<Setter Property="BorderThickness" Value="0" />
</Style>
</Style>
</Style>
Expand Down

0 comments on commit f0963dc

Please sign in to comment.