From 66ca2f8ffec9906be6432f7c99b25713d43bd407 Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 19 Sep 2023 22:15:36 -0400 Subject: [PATCH] Add dock view state save/load from vm --- .../Controls/Dock/DockUserControlBase.cs | 48 ++++++++++++- .../Inference/LoadViewStateEventArgs.cs | 11 +++ .../Inference/SaveViewStateEventArgs.cs | 12 ++++ .../Models/Inference/ViewState.cs | 13 ++++ .../Base/InferenceTabViewModelBase.cs | 68 +++++++++++++++++++ .../Views/InferencePage.axaml | 8 +++ 6 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 StabilityMatrix.Avalonia/Models/Inference/LoadViewStateEventArgs.cs create mode 100644 StabilityMatrix.Avalonia/Models/Inference/SaveViewStateEventArgs.cs create mode 100644 StabilityMatrix.Avalonia/Models/Inference/ViewState.cs diff --git a/StabilityMatrix.Avalonia/Controls/Dock/DockUserControlBase.cs b/StabilityMatrix.Avalonia/Controls/Dock/DockUserControlBase.cs index 7f4b18ef..034a6ad3 100644 --- a/StabilityMatrix.Avalonia/Controls/Dock/DockUserControlBase.cs +++ b/StabilityMatrix.Avalonia/Controls/Dock/DockUserControlBase.cs @@ -1,4 +1,8 @@ using System; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Threading.Tasks; +using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Threading; @@ -6,6 +10,8 @@ using Dock.Avalonia.Controls; using Dock.Model; using Dock.Model.Avalonia.Json; using Dock.Model.Core; +using StabilityMatrix.Avalonia.Models.Inference; +using StabilityMatrix.Avalonia.ViewModels.Base; namespace StabilityMatrix.Avalonia.Controls.Dock; @@ -34,7 +40,43 @@ public abstract class DockUserControlBase : DropTargetUserControlBase } } - protected virtual void LoadDockLayout(string data) + /// + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + // Attach handlers for view state saving and loading + if (DataContext is InferenceTabViewModelBase vm) + { + vm.SaveViewStateRequested += (_, args) => + { + var saveTcs = new TaskCompletionSource(); + + Dispatcher.UIThread.Post(() => + { + var state = new ViewState { DockLayout = SaveDockLayout() }; + saveTcs.SetResult(state); + }); + + args.StateTask ??= saveTcs.Task; + }; + + vm.LoadViewStateRequested += (_, args) => + { + if (args.State.DockLayout is { } layout) + { + LoadDockLayout(layout); + } + }; + } + } + + protected void LoadDockLayout(JsonObject data) + { + LoadDockLayout(data.ToJsonString()); + } + + protected void LoadDockLayout(string data) { if (BaseDock is null) return; @@ -46,8 +88,8 @@ public abstract class DockUserControlBase : DropTargetUserControlBase } } - protected virtual string SaveDockLayout() + protected string? SaveDockLayout() { - return BaseDock is null ? string.Empty : DockSerializer.Serialize(BaseDock.Layout); + return BaseDock is null ? null : DockSerializer.Serialize(BaseDock.Layout); } } diff --git a/StabilityMatrix.Avalonia/Models/Inference/LoadViewStateEventArgs.cs b/StabilityMatrix.Avalonia/Models/Inference/LoadViewStateEventArgs.cs new file mode 100644 index 00000000..ae906de4 --- /dev/null +++ b/StabilityMatrix.Avalonia/Models/Inference/LoadViewStateEventArgs.cs @@ -0,0 +1,11 @@ +using System; + +namespace StabilityMatrix.Avalonia.Models.Inference; + +/// +/// Event for view models requesting to load their view state from a provided state +/// +public class LoadViewStateEventArgs : EventArgs +{ + public required ViewState State { get; init; } +} diff --git a/StabilityMatrix.Avalonia/Models/Inference/SaveViewStateEventArgs.cs b/StabilityMatrix.Avalonia/Models/Inference/SaveViewStateEventArgs.cs new file mode 100644 index 00000000..454da7db --- /dev/null +++ b/StabilityMatrix.Avalonia/Models/Inference/SaveViewStateEventArgs.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading.Tasks; + +namespace StabilityMatrix.Avalonia.Models.Inference; + +/// +/// Event for view models requesting to get their view state for saving +/// +public class SaveViewStateEventArgs : EventArgs +{ + public Task? StateTask { get; set; } +} diff --git a/StabilityMatrix.Avalonia/Models/Inference/ViewState.cs b/StabilityMatrix.Avalonia/Models/Inference/ViewState.cs new file mode 100644 index 00000000..f736fdd8 --- /dev/null +++ b/StabilityMatrix.Avalonia/Models/Inference/ViewState.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +namespace StabilityMatrix.Avalonia.Models.Inference; + +/// +/// Model for view states of inference tabs +/// +[JsonSerializable(typeof(ViewState))] +public class ViewState +{ + public string? DockLayout { get; set; } +} diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs index c13c8b67..dfa9d322 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Base/InferenceTabViewModelBase.cs @@ -2,11 +2,15 @@ using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; +using System.Threading.Tasks; +using AsyncAwaitBestPractices; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; using StabilityMatrix.Avalonia.Models; +using StabilityMatrix.Avalonia.Models.Inference; using StabilityMatrix.Avalonia.ViewModels.Inference; using StabilityMatrix.Core.Models.Database; using StabilityMatrix.Core.Models.FileInterfaces; @@ -44,6 +48,70 @@ public abstract partial class InferenceTabViewModelBase /// Control? IPersistentViewProvider.AttachedPersistentView { get; set; } + #region Weak Events + + private WeakEventManager? loadViewStateRequestedEventManager; + + public event EventHandler LoadViewStateRequested + { + add + { + loadViewStateRequestedEventManager ??= new WeakEventManager(); + loadViewStateRequestedEventManager.AddEventHandler(value); + } + remove => loadViewStateRequestedEventManager?.RemoveEventHandler(value); + } + + protected void LoadViewState(LoadViewStateEventArgs args) => + loadViewStateRequestedEventManager?.RaiseEvent(this, args, nameof(LoadViewStateRequested)); + + private WeakEventManager? saveViewStateRequestedEventManager; + + public event EventHandler SaveViewStateRequested + { + add + { + saveViewStateRequestedEventManager ??= new WeakEventManager(); + saveViewStateRequestedEventManager.AddEventHandler(value); + } + remove => saveViewStateRequestedEventManager?.RemoveEventHandler(value); + } + + protected async Task SaveViewState() + { + var eventArgs = new SaveViewStateEventArgs(); + saveViewStateRequestedEventManager?.RaiseEvent( + this, + eventArgs, + nameof(SaveViewStateRequested) + ); + + if (eventArgs.StateTask is not { } stateTask) + { + throw new InvalidOperationException( + "SaveViewStateRequested event handler did not set the StateTask property" + ); + } + + return await stateTask; + } + + #endregion + + [RelayCommand] + private async Task DebugSaveViewState() + { + var state = await SaveViewState(); + if (state.DockLayout is { } layout) + { + await DialogHelper.CreateJsonDialog(layout).ShowAsync(); + } + else + { + await DialogHelper.CreateTaskDialog("Failed", "No layout data").ShowAsync(); + } + } + protected virtual void Dispose(bool disposing) { if (disposing) diff --git a/StabilityMatrix.Avalonia/Views/InferencePage.axaml b/StabilityMatrix.Avalonia/Views/InferencePage.axaml index 24f43546..59adb39a 100644 --- a/StabilityMatrix.Avalonia/Views/InferencePage.axaml +++ b/StabilityMatrix.Avalonia/Views/InferencePage.axaml @@ -212,6 +212,14 @@ Command="{Binding MenuSaveAsCommand}" InputGesture="Ctrl+Shift+S" Text="Save As..." /> + + + + + +