From: Jean-Philippe Bruyère Date: Mon, 15 Feb 2021 20:04:39 +0000 (+0100) Subject: ShowCase undo/redo X-Git-Tag: v0.9.5-beta~68 X-Git-Url: https://git.osiis.dedyn.io/?a=commitdiff_plain;h=882c9d1ad4114f99f1b129b02109980382d72f82;p=jp%2Fcrow.git ShowCase undo/redo --- diff --git a/Samples/ShowCase/ShowCase.cs b/Samples/ShowCase/ShowCase.cs index 23b7b151..fa1bcaa0 100644 --- a/Samples/ShowCase/ShowCase.cs +++ b/Samples/ShowCase/ShowCase.cs @@ -10,6 +10,9 @@ using Crow.IML; using System.Runtime.CompilerServices; using Glfw; using System.Diagnostics; +using Crow.Text; +using System.Collections.Generic; +using Encoding = System.Text.Encoding; namespace ShowCase { @@ -37,8 +40,6 @@ namespace ShowCase NotifyValueChanged (CurrentDir); } } - - string source, origSource; public string CurrentFile { get { return Configuration.Global.Get (nameof (CurrentFile)); } set { @@ -49,50 +50,85 @@ namespace ShowCase } } + public Command CMDNew, CMDOpen, CMDSave, CMDSaveAs, CMDQuit, CMDShowLeftPane, + CMDUndo, CMDRedo, CMDCut, CMDCopy, CMDPaste, CMDHelp, CMDAbout, CMDOptions; + + const string _defaultFileName = "unnamed.txt"; + string source = ""; + int dirtyUndoLevel; + TextBox editor; + Stopwatch reloadChrono = Stopwatch.StartNew (); + + public new bool IsDirty { get { return undoStack.Count != dirtyUndoLevel; } } public string Source { get => source; set { if (source == value) return; - source = value; - if (!reloadChrono.IsRunning) - reloadChrono.Restart (); - - CMDSave.CanExecute = source != origSource; + if (!reloadChrono.IsRunning) + reloadChrono.Restart (); NotifyValueChanged (source); } } + public CommandGroup EditorCommands => new CommandGroup (CMDUndo, CMDRedo, CMDCut, CMDCopy, CMDPaste, CMDSave, CMDSaveAs); + Stack undoStack = new Stack (); + Stack redoStack = new Stack (); - public static Picture IcoNew = new SvgPicture ("#Icons.blank-file.svg"); - public static Picture IcoOpen = new SvgPicture ("#Icons.open.svg"); - public static Picture IcoSave = new SvgPicture ("#Icons.save.svg"); - public static Picture IcoSaveAs = new SvgPicture ("#Icons.save.svg"); - public static Picture IcoQuit = new SvgPicture ("#Icons.sign-out.svg"); - public static Picture IcoUndo = new SvgPicture ("#Icons.undo.svg"); - public static Picture IcoRedo = new SvgPicture ("#Icons.redo.svg"); - - public static Picture IcoCut = new SvgPicture ("#Icons.scissors.svg"); - public static Picture IcoCopy = new SvgPicture ("#Icons.copy-file.svg"); - public static Picture IcoPaste = new SvgPicture ("#Icons.paste-on-document.svg"); - - public Command CMDNew, CMDSave, CMDSaveAs, CMDUndo, CMDRedo, CMDCut, CMDCopy, CMDPaste; - public CommandGroup ContextCommands => new CommandGroup (CMDNew, CMDSave, CMDSaveAs); + void undo () { + if (undoStack.TryPop (out TextChange tch)) { + redoStack.Push (tch.Inverse (source)); + CMDRedo.CanExecute = true; + apply (tch); + editor.SetCursorPosition (tch.End + tch.ChangedText.Length); + } + if (undoStack.Count == 0) + CMDUndo.CanExecute = false; + } + void redo () { + if (redoStack.TryPop (out TextChange tch)) { + undoStack.Push (tch.Inverse (source)); + CMDUndo.CanExecute = true; + apply (tch); + editor.SetCursorPosition (tch.End + tch.ChangedText.Length); + } + if (redoStack.Count == 0) + CMDRedo.CanExecute = false; + } + bool disableTextChangedEvent = false; + void apply (TextChange change) { + Span tmp = stackalloc char[source.Length + (change.ChangedText.Length - change.Length)]; + ReadOnlySpan src = source.AsSpan (); + src.Slice (0, change.Start).CopyTo (tmp); + if (!string.IsNullOrEmpty (change.ChangedText)) + change.ChangedText.AsSpan ().CopyTo (tmp.Slice (change.Start)); + src.Slice (change.End).CopyTo (tmp.Slice (change.Start + change.ChangedText.Length)); + disableTextChangedEvent = true; + Source = tmp.ToString (); + disableTextChangedEvent = false; + NotifyValueChanged ("IsDirty", IsDirty); + } + void initCommands () { - CMDNew = new Command (new Action (onNewFile)) { Caption = "New", Icon = "#Icons.blank-file.svg", CanExecute = true }; + CMDNew = new Command (new Action (onNewFile)) { Caption = "New", Icon = "#Icons.blank-file.svg", CanExecute = true }; CMDSave = new Command (new Action (onSave)) { Caption = "Save", Icon = "#Icons.save.svg", CanExecute = false }; CMDSaveAs = new Command (new Action (onSaveAs)) { Caption = "Save As...", Icon = "#Icons.save.svg", CanExecute = true }; - /*CMDUndo = new Command (new Action (undo)) { Caption = "Undo", Icon = IcoUndo, CanExecute = false }; - CMDRedo = new Command (new Action (redo)) { Caption = "Redo", Icon = IcoRedo, CanExecute = false }; - CMDCut = new Command (new Action (cut)) { Caption = "Cut", Icon = IcoCut, CanExecute = false }; - CMDCopy = new Command (new Action (copy)) { Caption = "Copy", Icon = IcoCopy, CanExecute = false }; - CMDPaste = new Command (new Action (paste)) { Caption = "Paste", Icon = IcoPaste, CanExecute = false };*/ - } + CMDQuit = new Command (new Action (() => base.Quit ())) { Caption = "Quit", Icon = "#Icons.exit.svg", CanExecute = true }; + CMDUndo = new Command (new Action (undo)) { Caption = "Undo", Icon = "#Icons.undo.svg", CanExecute = false }; + CMDRedo = new Command (new Action (redo)) { Caption = "Redo", Icon = "#Icons.redo.svg", CanExecute = false }; + CMDCut = new Command (new Action (() => Quit ())) { Caption = "Cut", Icon = "#Icons.scissors.svg", CanExecute = false }; + CMDCopy = new Command (new Action (() => Quit ())) { Caption = "Copy", Icon = "#Icons.copy-file.svg", CanExecute = false }; + CMDPaste = new Command (new Action (() => Quit ())) { Caption = "Paste", Icon = "#Icons.paste-on-document.svg", CanExecute = false }; - public new bool IsDirty { - get => !string.Equals(origSource, source); + } + void onNewFile () { + if (IsDirty) { + MessageBox mb = MessageBox.ShowModal (this, MessageBox.Type.YesNo, "Current file has unsaved changes, are you sure?"); + mb.Yes += (sender, e) => newFile (); + } else + newFile (); } void onSave () { @@ -131,17 +167,12 @@ namespace ShowCase save (); } - void onNewFile () { - if (IsDirty) { - MessageBox mb = MessageBox.ShowModal (this, MessageBox.Type.YesNo, "Current file has unsaved changes, are you sure?"); - mb.Yes += (sender, e) => newFile (); - } else - newFile (); - } void newFile() { - origSource = ""; - Source = ""; + disableTextChangedEvent = true; + Source = @"