using ytLive.Helpers; using ytLive.Models; namespace ytLive.ViewModels; /// /// Preview undo/redo (creator directive 2026-11-01): Ctrl+Z and Ctrl+Shift+Z in edit mode. /// /// The stack lives on the view model rather than in PreviewPane 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. /// /// Gated on edit mode. 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. /// 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!; /// Call immediately BEFORE a layout mutation — once per gesture, never per mouse /// move, or a single drag eats the whole stack. 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); } /// Dropped on layout load: undoing across a reload would resurrect a layout the /// creator never had. private void ClearPreviewHistory() { _previewHistory.Clear(); NotifyHistoryChanged(); } }