feat(ui): gear to the rail, EDIT on the panel edge, preview undo/redo
Three creator directives (2026-11-01), done ahead of the surge. **Gear moved to the preview rail** — first tab, orange (Brush.Attention), gear icon only. Left-click opens the same app menu the top bar gear owned (Settings / Bug / Feature / About), right-click goes straight to App Settings; both behaviours moved with it, so nothing lost an entry point. TopBar keeps the wordmark and EDIT. **EDIT right edge aligned to the layers panel.** The work area is margin 8 + a 360px column, the top bar is padding 16, so the shared edge is 352 inside the bar. TopBar column 0 is now a fixed 352 grid with EDIT right-aligned in it — alignment by arithmetic instead of by eye. **Preview undo/redo: Ctrl+Z / Ctrl+Shift+Z.** Models/PreviewHistory snapshots element REFERENCES plus geometry, visibility, order and membership — a deep clone would mirror every property of a dozen element subclasses and quietly miss the next one added. One snapshot per gesture (pushed at drag/resize start, never in the move loop), bounded at 50, redo survives multiple undos, cleared on layout load. Gated on EditingActive: undo while the preview shows program output would rewrite a scene nobody is looking at. Honest scope, in the type's doc comment: layout edits only. Colour/opacity/border/font edits are not captured yet, so Ctrl+Z after one of those restores the last layout change. 6 tests in PreviewHistoryTests; 19 pass across it and the neighbouring UI classes; Debug and Release both build at 0 warnings.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
using ytLive.Helpers;
|
||||
using ytLive.Models;
|
||||
|
||||
namespace ytLive.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// Preview undo/redo (creator directive 2026-11-01): Ctrl+Z and Ctrl+Shift+Z in edit mode.
|
||||
///
|
||||
/// <para>The stack lives on the view model rather than in <c>PreviewPane</c> because the edit
|
||||
/// surface is not one control — a layer can be moved by a drag, removed from the layers list, or
|
||||
/// hidden from the properties panel — and undo has to cover all of them or it is a lie.</para>
|
||||
///
|
||||
/// <para><b>Gated on edit mode.</b> Undo only means something while the preview is the edit
|
||||
/// surface; with editing off the preview shows program output and a stray Ctrl+Z would silently
|
||||
/// rewrite a scene the creator isn't looking at.</para>
|
||||
/// </summary>
|
||||
public partial class MainViewModel
|
||||
{
|
||||
private readonly PreviewHistory _previewHistory = new();
|
||||
|
||||
public bool CanUndoPreview => EditingActive && _previewHistory.CanUndo;
|
||||
public bool CanRedoPreview => EditingActive && _previewHistory.CanRedo;
|
||||
|
||||
public RelayCommand UndoPreviewCommand { get; private set; } = null!;
|
||||
public RelayCommand RedoPreviewCommand { get; private set; } = null!;
|
||||
|
||||
/// <summary>Call immediately BEFORE a layout mutation — once per gesture, never per mouse
|
||||
/// move, or a single drag eats the whole stack.</summary>
|
||||
internal void PushPreviewUndo()
|
||||
{
|
||||
if (!EditingActive) return;
|
||||
_previewHistory.Capture(Scenes);
|
||||
NotifyHistoryChanged();
|
||||
}
|
||||
|
||||
private void UndoPreview()
|
||||
{
|
||||
if (!EditingActive || !_previewHistory.Undo(Scenes)) return;
|
||||
AfterHistoryStep();
|
||||
}
|
||||
|
||||
private void RedoPreview()
|
||||
{
|
||||
if (!EditingActive || !_previewHistory.Redo(Scenes)) return;
|
||||
AfterHistoryStep();
|
||||
}
|
||||
|
||||
private void AfterHistoryStep()
|
||||
{
|
||||
// Restored geometry has to reach the compositor: the baked region is cached per scene
|
||||
// and keyed on element identity, so a pure value change is invisible to it until the
|
||||
// cache is dropped. Without this, undo moves the model and the picture stays put.
|
||||
foreach (var scene in Scenes) _sceneGraph.InvalidateBake(scene);
|
||||
ScheduleSave();
|
||||
NotifyHistoryChanged();
|
||||
}
|
||||
|
||||
private void NotifyHistoryChanged()
|
||||
{
|
||||
OnPropertyChanged(nameof(CanUndoPreview));
|
||||
OnPropertyChanged(nameof(CanRedoPreview));
|
||||
}
|
||||
|
||||
private void InitPreviewHistory()
|
||||
{
|
||||
// RelayCommand queries CommandManager.RequerySuggested, so no explicit invalidation is
|
||||
// needed when CanExecute changes.
|
||||
UndoPreviewCommand = new RelayCommand(_ => UndoPreview(), _ => CanUndoPreview);
|
||||
RedoPreviewCommand = new RelayCommand(_ => RedoPreview(), _ => CanRedoPreview);
|
||||
}
|
||||
|
||||
/// <summary>Dropped on layout load: undoing across a reload would resurrect a layout the
|
||||
/// creator never had.</summary>
|
||||
private void ClearPreviewHistory()
|
||||
{
|
||||
_previewHistory.Clear();
|
||||
NotifyHistoryChanged();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user