Skip to content

Commit

Permalink
Merge pull request #2323 from Nexus-Mods/loadorder-v2-TreeDataGrid-ba…
Browse files Browse the repository at this point in the history
…ckend

Loadorder v2 tree data grid backend part 6
  • Loading branch information
Al12rs authored Dec 2, 2024
2 parents b847ad3 + 41025cb commit f90e63f
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ public static void ApplyChanges<TKey, TValue>(this ObservableList<TValue> list,
switch (change.Reason)
{
case ChangeReason.Add:
list.Add(change.Current);
var insertIndex = Math.Clamp(change.CurrentIndex, 0, list.Count);
list.Insert(insertIndex, change.Current);
break;
case ChangeReason.Remove:
list.Remove(change.Current);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ public abstract class TreeDataGridAdapter<TModel, TKey> : ReactiveR3Object
public BindableReactiveProperty<bool> IsSourceEmpty { get; } = new(value: true);

public ObservableHashSet<TModel> SelectedModels { get; private set; } = [];

private ObservableList<TModel> Roots { get; set; } = [];
protected ObservableList<TModel> Roots { get; private set; } = [];
private ISynchronizedView<TModel, TModel> RootsView { get; }
private INotifyCollectionChangedSynchronizedViewList<TModel> RootsCollectionChangedView { get; }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Reactive;
using NexusMods.Abstractions.Games;
using NexusMods.App.UI.Controls;
using ReactiveUI;

Expand All @@ -9,7 +8,7 @@ public class LoadOrderItemDesignModel : TreeDataGridItemModel<ILoadOrderItemMode
{
public ReactiveCommand<Unit, Unit> MoveUp { get; } = ReactiveCommand.Create(() => { });
public ReactiveCommand<Unit, Unit> MoveDown { get; } = ReactiveCommand.Create(() => { });
public int SortIndex { get; set; }
public int SortIndex { get; set; }
public string DisplayName { get; set; } = "Display Name";
public string ModName { get; set; } = "Mod Name";
public bool IsActive { get; set; }
Expand Down
84 changes: 61 additions & 23 deletions src/NexusMods.App.UI/Pages/Sorting/LoadOrder/LoadOrderItemModel.cs
Original file line number Diff line number Diff line change
@@ -1,47 +1,47 @@
using System.Reactive;
using System.ComponentModel;
using System.Reactive.Disposables;
using NexusMods.Abstractions.Games;
using NexusMods.App.UI.Controls;
using R3;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using CompositeDisposable = System.Reactive.Disposables.CompositeDisposable;
using Observable = System.Reactive.Linq.Observable;
using Unit = System.Reactive.Unit;

namespace NexusMods.App.UI.Pages.Sorting;

public class LoadOrderItemModel : TreeDataGridItemModel<ILoadOrderItemModel, Guid>, ILoadOrderItemModel
{
private CompositeDisposable _disposables = new();
private readonly CompositeDisposable _disposables = new();
private readonly IObservable<ListSortDirection> _sortDirectionObservable;
private readonly IObservable<int> _lastIndexObservable;
private ListSortDirection _sortDirection;
public ISortableItem InnerItem { get; }
public ReactiveCommand<Unit, Unit> MoveUp { get; }
public ReactiveCommand<Unit, Unit> MoveDown { get; }

public ReactiveUI.ReactiveCommand<Unit, Unit> MoveUp { get; }
public ReactiveUI.ReactiveCommand<Unit, Unit> MoveDown { get; }
public int SortIndex { get; }
public string DisplayName { get; }

[Reactive] public string ModName { get; private set; }
[Reactive] public bool IsActive { get; private set; }

public LoadOrderItemModel(ISortableItem sortableItem)
public LoadOrderItemModel(
ISortableItem sortableItem,
IObservable<ListSortDirection> sortDirectionObservable,
IObservable<int> lastIndexObservable,
Subject<MoveUpDownCommandPayload> commandSubject)
{
InnerItem = sortableItem;
SortIndex = sortableItem.SortIndex;
DisplayName = sortableItem.DisplayName;

IsActive = sortableItem.IsActive;
ModName = sortableItem.ModName;

MoveUp = ReactiveCommand.CreateFromTask(async () =>
{
await sortableItem.SortableItemProvider.SetRelativePosition(InnerItem, delta: 1);
return Unit.Default;
}
);

MoveDown = ReactiveCommand.CreateFromTask(async () =>
{
await sortableItem.SortableItemProvider.SetRelativePosition(InnerItem, delta: -1);
return Unit.Default;
}
);

_sortDirectionObservable = sortDirectionObservable;
_lastIndexObservable = lastIndexObservable;

this.WhenAnyValue(vm => vm.InnerItem.IsActive)
.Subscribe(value => IsActive = value)
Expand All @@ -50,10 +50,48 @@ public LoadOrderItemModel(ISortableItem sortableItem)
this.WhenAnyValue(vm => vm.InnerItem.ModName)
.Subscribe(value => ModName = value)
.DisposeWith(_disposables);

_sortDirectionObservable
.BindTo(this, vm => vm._sortDirection)
.DisposeWith(_disposables);

var sortIndexObservable = this.WhenAnyValue(vm => vm.SortIndex);
var canExecuteUp = Observable.CombineLatest(
sortIndexObservable,
_sortDirectionObservable,
_lastIndexObservable,
(sortIndex, sortDirection, lastIndex) =>
sortDirection == ListSortDirection.Ascending ? sortIndex > 0 : sortIndex < lastIndex
);

var canExecuteDown = Observable.CombineLatest(
sortIndexObservable,
_sortDirectionObservable,
_lastIndexObservable,
(sortIndex, sortDirection, lastIndex) =>
sortDirection == ListSortDirection.Ascending ? sortIndex < lastIndex : sortIndex > 0
);

MoveUp = ReactiveUI.ReactiveCommand.Create(() =>
{
var delta = _sortDirection == ListSortDirection.Ascending ? -1 : +1;
commandSubject.OnNext(new MoveUpDownCommandPayload(this, delta));
},
canExecuteUp
);

MoveDown = ReactiveUI.ReactiveCommand.Create(() =>
{
var delta = _sortDirection == ListSortDirection.Ascending ? +1 : -1;
commandSubject.OnNext(new MoveUpDownCommandPayload(this, delta));
},
canExecuteDown
);
}


private bool _isDisposed;

protected override void Dispose(bool disposing)
{
if (!_isDisposed)
Expand Down
39 changes: 15 additions & 24 deletions src/NexusMods.App.UI/Pages/Sorting/LoadOrder/LoadOrderView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,22 @@
xmlns:sorting="clr-namespace:NexusMods.App.UI.Pages.Sorting"
xmlns:alerts="clr-namespace:NexusMods.App.UI.Controls.Alerts"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"
x:Class="NexusMods.App.UI.Pages.Sorting.LoadOrderView">
x:Class="NexusMods.App.UI.Pages.Sorting.LoadOrderView"
x:DataType="sorting:ILoadOrderViewModel">
<Design.DataContext>
<sorting:LoadOrderDesignViewModel />
</Design.DataContext>

<controls:EmptyState x:Name="EmptyState"
Header="{Binding EmptyStateMessageTitle}">
<controls:EmptyState x:Name="EmptyState">

<controls:EmptyState.Subtitle>
<TextBlock Text="{Binding EmptyStateMessageContents}" />
<TextBlock x:Name="EmptySpaceMessageTextBlock"/>
</controls:EmptyState.Subtitle>

<Grid RowDefinitions="Auto, Auto, *">
<Border Grid.Row="0" Classes="Toolbar">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
<ComboBox SelectedIndex="0" Classes="Secondary Compact">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<icons:UnifiedIcon Value="{x:Static icons:IconValues.Sort}" Size="20" />
<TextBlock Text="Ascending (1st top)" Theme="{StaticResource BodyMDNormalTheme}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox x:Name="SortDirectionComboBox" SelectedIndex="0" Classes="Secondary Compact">
<ComboBoxItem>
<StackPanel Orientation="Horizontal">
<icons:UnifiedIcon Value="{x:Static icons:IconValues.SortAscending}" Size="20" />
Expand Down Expand Up @@ -66,18 +58,17 @@
<StackPanel Grid.Row="1" Spacing="24" Margin="24">
<alerts:Alert x:Name="LoadOrderAlert"
Severity="Info"
Title="{Binding InfoAlertTitle}"
Body="{Binding InfoAlertMessage}"
ShowDismiss="True" />
ShowDismiss="True"
Title="{CompiledBinding InfoAlertHeading}"
Body="{CompiledBinding InfoAlertMessage }"/>

<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock Text="Last Loaded REDmod File Wins"
Theme="{StaticResource HeadingXSSemiTheme}" />
<controls:StandardButton ShowIcon="IconOnly"
<TextBlock x:Name="TitleTextBlock" Theme="{StaticResource HeadingXSSemiTheme}" />
<controls:StandardButton x:Name="InfoAlertButton"
ShowIcon="IconOnly"
LeftIcon="{x:Static icons:IconValues.Info}"
Type="Tertiary"
Fill="None"
Command="{Binding InfoAlertCommand}"/>
Fill="None" />
</StackPanel>
</StackPanel>

Expand All @@ -90,7 +81,7 @@
Margin="0,8,0,8"
Value="{x:Static icons:IconValues.Trophy}"
Size="20"
ToolTip.Tip="{Binding TrophyToolTip}" />
ToolTip.Tip="{CompiledBinding TrophyToolTip}" />

<Grid RowDefinitions="Auto, *, Auto" HorizontalAlignment="Center">
<icons:UnifiedIcon Grid.Row="0" x:Name="ArrowUpIcon"
Expand Down Expand Up @@ -120,7 +111,7 @@

<StackPanel Orientation="Horizontal" Spacing="12">
<controls:StandardButton x:Name="UpButton"
Command="{CompiledBinding MoveDown}"
Command="{CompiledBinding MoveUp}"
LeftIcon="{x:Static icons:IconValues.ArrowUp}"
ShowIcon="IconOnly"
Size="Medium"
Expand All @@ -140,7 +131,7 @@
HorizontalAlignment="Center" />
</Border>
<controls:StandardButton x:Name="DownButton"
Command="{CompiledBinding MoveUp}"
Command="{CompiledBinding MoveDown}"
LeftIcon="{x:Static icons:IconValues.ArrowDown}"
ShowIcon="IconOnly"
Size="Medium"
Expand Down
68 changes: 54 additions & 14 deletions src/NexusMods.App.UI/Pages/Sorting/LoadOrder/LoadOrderView.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System.ComponentModel;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia.Controls;
using Avalonia.ReactiveUI;
using NexusMods.App.UI.Controls;
using NexusMods.App.UI.Helpers;
using NexusMods.App.UI.Extensions;
using ReactiveUI;

namespace NexusMods.App.UI.Pages.Sorting;
Expand All @@ -21,40 +22,79 @@ public LoadOrderView()
SortOrderTreeDataGrid,
vm => vm.Adapter
);


// TreeDataGrid Source
this.OneWayBind(ViewModel,
vm => vm.Adapter.Source.Value,
view => view.SortOrderTreeDataGrid.Source
)
.DisposeWith(disposables);

this.WhenAnyValue(view => view.ViewModel!.SortDirectionCurrent)
.Subscribe(sortCurrentDirection =>
// Trophy bar
this.WhenAnyValue(view => view.ViewModel!.IsWinnerTop)
.Subscribe(isWinnerTop =>
{
var isAscending = sortCurrentDirection == ListSortDirection.Ascending;
ArrowUpIcon.IsVisible = !isAscending;
ArrowDownIcon.IsVisible = isAscending;
DockPanel.SetDock(TrophyIcon, isWinnerTop ? Dock.Top : Dock.Bottom);
TrophyBarPanel.Classes.ToggleIf("IsWinnerTop", isWinnerTop);
TrophyBarPanel.Classes.ToggleIf("IsWinnerBottom", !isWinnerTop);
}
)
.DisposeWith(disposables);

this.WhenAnyValue(view => view.ViewModel!.IsWinnerTop)
.Subscribe(isWinnerTop =>
// Trophy bar arrow
this.WhenAnyValue(view => view.ViewModel!.SortDirectionCurrent)
.Subscribe(sortCurrentDirection =>
{
DockPanel.SetDock(TrophyIcon, isWinnerTop ? Dock.Top : Dock.Bottom);
TrophyBarPanel.Classes.Add(isWinnerTop ? "IsWinnerTop" : "IsWinnerBottom");
var isAscending = sortCurrentDirection == ListSortDirection.Ascending;
ArrowUpIcon.IsVisible = !isAscending;
ArrowDownIcon.IsVisible = isAscending;
}
)
.DisposeWith(disposables);

// empty state
// Empty state
this.OneWayBind(ViewModel,
vm => vm.Adapter.IsSourceEmpty.Value,
view => view.EmptyState.IsActive)
.DisposeWith(disposables);

// alert
this.OneWayBind(ViewModel, vm => vm.AlertSettingsWrapper, view => view.LoadOrderAlert.AlertSettings)
// Empty state Header
this.OneWayBind(ViewModel,
vm => vm.EmptyStateMessageTitle,
view => view.EmptyState.Header)
.DisposeWith(disposables);

// Empty state Message
this.OneWayBind(ViewModel,
vm => vm.EmptyStateMessageContents,
view => view.EmptySpaceMessageTextBlock.Text)
.DisposeWith(disposables);

// SortDirection -> ComboBox
this.WhenAnyValue(view => view.ViewModel!.SortDirectionCurrent)
.Select(sortDirection => sortDirection == ListSortDirection.Ascending ? 0 : 1)
.BindToView(this, view => view.SortDirectionComboBox.SelectedIndex)
.DisposeWith(disposables);

// ComboBox -> SortDirection
this.WhenAnyValue(view => view.SortDirectionComboBox.SelectedIndex)
.Select(index => index == 0 ? ListSortDirection.Ascending : ListSortDirection.Descending)
.BindTo(ViewModel, vm => vm.SortDirectionCurrent)
.DisposeWith(disposables);

// Title
this.OneWayBind(ViewModel, vm => vm.InfoAlertTitle,
view => view.TitleTextBlock.Text)
.DisposeWith(disposables);

// Alert settings
this.OneWayBind(ViewModel, vm => vm.AlertSettingsWrapper,
view => view.LoadOrderAlert.AlertSettings)
.DisposeWith(disposables);

// Alert Command
this.OneWayBind(ViewModel, vm => vm.InfoAlertCommand,
view => view.InfoAlertButton.Command)
.DisposeWith(disposables);
}
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reactive;
using System.Reactive.Disposables;
using Avalonia.Controls.Models.TreeDataGrid;
Expand Down
Loading

0 comments on commit f90e63f

Please sign in to comment.