using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace Bro.Common.ImageCanvas { public partial class ZoomAndPanControl { private readonly Stack _undoStack = new Stack(); private readonly Stack _redoStack = new Stack(); private UndoRedoStackItem _viewportZoomCache; /// /// Record the previous zoom level, so that we can return to it. /// public void SaveZoom() { _viewportZoomCache = CreateUndoRedoStackItem(); if (_undoStack.Any() && _viewportZoomCache.Equals(_undoStack.Peek())) return; _undoStack.Push(_viewportZoomCache); _redoStack.Clear(); _undoZoomCommand?.RaiseCanExecuteChanged(); _redoZoomCommand?.RaiseCanExecuteChanged(); } /// /// Record the last saved zoom level, so that we can return to it if no activity for 750 milliseconds /// public void DelayedSaveZoom750Miliseconds() { if (_timer750Miliseconds?.Running != true) _viewportZoomCache = CreateUndoRedoStackItem(); (_timer750Miliseconds ?? (_timer750Miliseconds = new KeepAliveTimer(TimeSpan.FromMilliseconds(740), () => { if (_undoStack.Any() && _viewportZoomCache.Equals(_undoStack.Peek())) return; _undoStack.Push(_viewportZoomCache); _redoStack.Clear(); _undoZoomCommand?.RaiseCanExecuteChanged(); _redoZoomCommand?.RaiseCanExecuteChanged(); }))).Nudge(); } private KeepAliveTimer _timer750Miliseconds; /// /// Record the last saved zoom level, so that we can return to it if no activity for 1550 milliseconds /// public void DelayedSaveZoom1500Miliseconds() { if (!_timer1500Miliseconds?.Running != true) _viewportZoomCache = CreateUndoRedoStackItem(); (_timer1500Miliseconds ?? (_timer1500Miliseconds = new KeepAliveTimer(TimeSpan.FromMilliseconds(1500), () => { if (_undoStack.Any() && _viewportZoomCache.Equals(_undoStack.Peek())) return; _undoStack.Push(_viewportZoomCache); _redoStack.Clear(); _undoZoomCommand?.RaiseCanExecuteChanged(); _redoZoomCommand?.RaiseCanExecuteChanged(); }))).Nudge(); } private KeepAliveTimer _timer1500Miliseconds; private UndoRedoStackItem CreateUndoRedoStackItem() { return new UndoRedoStackItem(ContentOffsetX, ContentOffsetY, ContentViewportWidth, ContentViewportHeight, InternalViewportZoom); } /// /// Jump back to the previous zoom level, saving current zoom to Redo Stack. /// private void UndoZoom() { _viewportZoomCache = CreateUndoRedoStackItem(); if (!_undoStack.Any() || !_viewportZoomCache.Equals(_undoStack.Peek())) _redoStack.Push(_viewportZoomCache); _viewportZoomCache = _undoStack.Pop(); AnimatedZoomTo(_viewportZoomCache.Zoom, _viewportZoomCache.Rect); SetScrollViewerFocus(); _undoZoomCommand?.RaiseCanExecuteChanged(); _redoZoomCommand?.RaiseCanExecuteChanged(); } /// /// Jump back to the most recent zoom level saved on redo stack. /// private void RedoZoom() { _viewportZoomCache = CreateUndoRedoStackItem(); if (!_redoStack.Any() || !_viewportZoomCache.Equals(_redoStack.Peek())) _undoStack.Push(_viewportZoomCache); _viewportZoomCache = _redoStack.Pop(); AnimatedZoomTo(_viewportZoomCache.Zoom, _viewportZoomCache.Rect); SetScrollViewerFocus(); _undoZoomCommand?.RaiseCanExecuteChanged(); _redoZoomCommand?.RaiseCanExecuteChanged(); } private bool CanUndoZoom => _undoStack.Any(); private bool CanRedoZoom => _redoStack.Any(); /// /// Command to implement Undo /// public ICommand UndoZoomCommand => _undoZoomCommand ?? (_undoZoomCommand = new RelayCommand(UndoZoom, () => CanUndoZoom)); private RelayCommand _undoZoomCommand; /// /// Command to implement Redo /// public ICommand RedoZoomCommand => _redoZoomCommand ?? (_redoZoomCommand = new RelayCommand(RedoZoom, () => CanRedoZoom)); private RelayCommand _redoZoomCommand; private class UndoRedoStackItem { public UndoRedoStackItem(Rect rect, double zoom) { Rect = rect; Zoom = zoom; } public UndoRedoStackItem(double offsetX, double offsetY, double width, double height, double zoom) { Rect = new Rect(offsetX, offsetY, width, height); Zoom = zoom; } public Rect Rect { get; } public double Zoom { get; } public override string ToString() { return $"Rectangle {{{Rect.X},{Rect.X}}}, Zoom {Zoom}"; } public bool Equals(UndoRedoStackItem obj) { return Zoom.IsWithinOnePercent(obj.Zoom) && Rect.Equals(obj.Rect); } } private void SetScrollViewerFocus() { var scrollViewer = _content.FindParentControl(); if (scrollViewer != null) { Keyboard.Focus(scrollViewer); scrollViewer.Focus(); } } } }