]> O.S.I.I.S - jp/crow.git/commitdiff
Splitter simplification, source header in src/Text, Table wip
authorJean-Philippe Bruyère <jp_bruyere@hotmail.com>
Fri, 26 Mar 2021 16:07:42 +0000 (17:07 +0100)
committerJean-Philippe Bruyère <jp_bruyere@hotmail.com>
Fri, 26 Mar 2021 16:07:42 +0000 (17:07 +0100)
18 files changed:
Crow/src/EventArgs/SelectedTextChangeEventArgs.cs [new file with mode: 0644]
Crow/src/EventArgs/SelectionChangeEventArgs.cs
Crow/src/Text/CharLocation.cs
Crow/src/Text/Encoding.cs
Crow/src/Text/Extensions.cs
Crow/src/Text/IEditableTextWidget.cs
Crow/src/Text/SpanCharReader.cs
Crow/src/Text/Text.cs
Crow/src/Text/TextChange.cs
Crow/src/Text/TextLine.cs
Crow/src/Text/TextLineCollection.cs
Crow/src/Text/TextSpan.cs
Crow/src/Widgets/Splitter.cs
Crow/src/Widgets/Table.cs
Crow/src/Widgets/TableRow.cs
Crow/src/Widgets/Widget.cs
Samples/ShowCase/ShowCase.cs
Samples/ShowCase/ui/showcase.crow

diff --git a/Crow/src/EventArgs/SelectedTextChangeEventArgs.cs b/Crow/src/EventArgs/SelectedTextChangeEventArgs.cs
new file mode 100644 (file)
index 0000000..91ceac1
--- /dev/null
@@ -0,0 +1,28 @@
+// Copyright (c) 2013-2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+
+using Crow.Text;
+using System;
+
+namespace Crow
+{
+       /// <summary>
+       /// Occurs in the TextBox widget when the text has changed.
+       /// </summary>
+       public class SelectedTextChangeEventArgs: EventArgs
+       {
+               /// <summary>
+               /// The TextChange structure representing the change.
+               /// </summary>
+               public TextSpan Old;
+               public TextSpan New;
+
+               public SelectedTextChangeEventArgs (TextSpan old, TextSpan _new) : base()
+               {
+                       Old = old;
+                       New = _new;
+               }
+       }
+}
+
index a2097f124755997991f87c8a55dc288ff145d70e..c45c880a8a4ee43298f556a7c8e05fd2075727a3 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (c) 2013-2020  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+// Copyright (c) 2013-2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
 //
 // This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
 
index 8db2ef64ad799454ac2a7dccbb1cd2d7b1335f17..cfc537c603740d3a25b841e344479c278e4e4e9b 100644 (file)
@@ -1,4 +1,7 @@
-using System;
+// Copyright (c) 2013-2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
 using System.Diagnostics;
 
 namespace Crow.Text
index 7196e44c5548bcb76ff0ce9a40349833bcd542dc..b02e4565fddcbf2265f776d47094af58087d8d12 100644 (file)
@@ -1,4 +1,7 @@
-using System;
+// Copyright (c) 2013-2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
 using System.Collections.Generic;
 using System.Text;
 
index 7f5a53b7a2daa68256f8835c647a0c74cf594d70..b0b87180e48e77d35943a9f3f8022e939d0edfd2 100644 (file)
@@ -1,4 +1,7 @@
-using System;
+// Copyright (c) 2013-2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
 
 namespace Crow.Text
 {
index 20092acbce914d286ad545281ebe72b984379cda..19f3ebd07b3d42d0b2c68d58e0afd677f702f723 100644 (file)
@@ -1,3 +1,6 @@
+// Copyright (c) 2013-2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
 using System;
 using Crow.Cairo;
 
index b8f42565456592ff58d11f0b840c220fee63b17a..bab2c4b7c8c942507f30a3bea72e28bb6d84c539 100644 (file)
@@ -1,4 +1,7 @@
-using System;
+// Copyright (c) 2013-2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
 using System.Collections.Generic;
 using System.Text;
 
index 4ebb6e59fb06f7a60ccea1438bf17c7d176d2eb5..70a3c9c977b8157d310068ed4ae13e1bd55e71b0 100644 (file)
@@ -1,4 +1,7 @@
-using System;
+// Copyright (c) 2013-2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
 using System.Collections.Generic;
 using System.Text;
 
index deecc5e5359bdc026cbbbaab0f745932d9b032b3..696699c11d8932cf3009d6f63313002b60a41327 100644 (file)
@@ -1,4 +1,7 @@
-using System;
+// Copyright (c) 2013-2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
 using System.Collections.Generic;
 using System.Text;
 
index a574d659c1af4827ffa1f9cc4221524f4ce4bb1a..e3565083cc50a84543ca1899911e805b1fe4dc8b 100644 (file)
@@ -1,4 +1,7 @@
-using System;
+// Copyright (c) 2013-2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Text;
index b64c75e234b3397fa8ebfe36931bb4f6a56a2fe8..19696867d08aa13a63ea94b673da0105b7576b72 100644 (file)
@@ -1,4 +1,7 @@
-using System;
+// Copyright (c) 2013-2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Text;
index 486d8e23f3d10cb98a94ce36686a586dee813a2f..0ea0a6381cb737e3dce20149bcde9957532658d6 100644 (file)
@@ -1,10 +1,13 @@
-using System;
+// Copyright (c) 2013-2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+using System;
 using System.Collections.Generic;
 using System.Text;
 
 namespace Crow.Text
 {
-       public struct TextSpan
+       public struct TextSpan : IEquatable<TextSpan>
        {
                public readonly int Start;
                public readonly int End;
@@ -15,5 +18,17 @@ namespace Crow.Text
 
                public bool IsEmpty => Start == End;
                public int Length => End - Start;
+
+               public bool Equals(TextSpan other)
+                       => Start == other.Start && End == other.End;
+               public override bool Equals(object obj)
+                       => obj is TextSpan ts ? Equals(ts) : false;
+
+               public override int GetHashCode()
+                       => HashCode.Combine(Start, End);
+               public static bool operator ==(TextSpan left, TextSpan right)
+                       => left.Equals (right);         
+               public static bool operator !=(TextSpan left, TextSpan right)
+                       => !left.Equals (right);                
        }
 }
index 99db30c1b417a199ff02acbfaf124e3a90898a6a..b8d7a2fc6852be0ae68d0c31bcd44bbcd3ccbba7 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (c) 2013-2020  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
+// Copyright (c) 2013-2021  Jean-Philippe Bruyère <jp_bruyere@hotmail.com>
 //
 // This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
 
@@ -33,27 +33,9 @@ namespace Crow
                        }
                }
 
-               Unit u1, u2;
-               int init1 = -1, init2 = -1, delta = 0, min1, min2, max1 , max2;
-               Widget go1 = null, go2 = null;
-
-               void initSplit(Measure m1, int size1, Measure m2, int size2){
-                       if (m1 != Measure.Stretched) {
-                               init1 = size1;
-                               u1 = m1.Units;
-                       }
-                       if (m2 != Measure.Stretched) {
-                               init2 = size2;
-                               u2 = m2.Units;
-                       }
-               }
-               void convertSizeInPix(Widget g1){
-
-               }
-
                #region GraphicObject override
                public override ILayoutable Parent {
-                       get { return base.Parent; }
+                       get => base.Parent;
                        set {
                                if (value != null) {                    
                                        GenericStack gs = value as GenericStack;
@@ -72,109 +54,42 @@ namespace Crow
                        else
                                IFace.MouseCursor = MouseCursor.sb_v_double_arrow;
                }
-               public override void onMouseDown (object sender, MouseButtonEventArgs e)
-               {               
-                       go1 = go2 = null;
-                       init1 = init2 = -1;
-                       delta = 0;
-
+                               
+               public override void onMouseMove (object sender, MouseMoveEventArgs e)
+               {                                               
                        GenericStack gs = Parent as GenericStack;
+                       Point m = gs.ScreenPointToLocal (e.Position);
                        int ptrSplit = gs.Children.IndexOf (this);
-                       if (ptrSplit == 0 || ptrSplit == gs.Children.Count - 1)
-                               return;
-
-                       go1 = gs.Children [ptrSplit - 1];
-                       go2 = gs.Children [ptrSplit + 1];
-
-                       if (gs.Orientation == Orientation.Horizontal) {
-                               initSplit (go1.Width, go1.Slot.Width, go2.Width, go2.Slot.Width);
-                               min1 = go1.MinimumSize.Width;
-                               min2 = go2.MinimumSize.Width;
-                               max1 = go1.MaximumSize.Width;
-                               max2 = go2.MaximumSize.Width;
-                               if (init1 >= 0)
-                                       go1.Width = init1;
-                               if (init2 >= 0)
-                                       go2.Width = init2;
-                       } else {
-                               initSplit (go1.Height, go1.Slot.Height, go2.Height, go2.Slot.Height);
-                               min1 = go1.MinimumSize.Height;
-                               min2 = go2.MinimumSize.Height;
-                               max1 = go1.MaximumSize.Height;
-                               max2 = go2.MaximumSize.Height;
-                               if (init1 >= 0)
-                                       go1.Height = init1;
-                               if (init2 >= 0)
-                                       go2.Height = init2;
-                       }
-                       e.Handled = true;
-                       base.onMouseDown (sender, e);
-               }
-               public override void onMouseMove (object sender, MouseMoveEventArgs e)
-               {
-                       e.Handled = true;
-                       base.onMouseMove (sender, e);
-
-                       if (IsActive && go1 != null && go2 != null) {
-                               GenericStack gs = Parent as GenericStack;
-                               int newDelta = delta, size1 = init1, size2 = init2;
-                               if (gs.Orientation == Orientation.Horizontal) {
-                                       newDelta -= e.XDelta;
-                                       if (size1 < 0)
-                                               size1 = go1.Slot.Width + delta;
-                                       if (size2 < 0)
-                                               size2 = go2.Slot.Width - delta;
-                               } else {
-                                       newDelta -= e.YDelta;
-                                       if (size1 < 0)
-                                               size1 = go1.Slot.Height + delta;
-                                       if (size2 < 0)
-                                               size2 = go2.Slot.Height - delta;
-                               }
 
-                               if (size1 - newDelta < min1 || (max1 > 0 && size1 - newDelta > max1) ||
-                                       size2 + newDelta < min2 || (max2 > 0 && size2 + newDelta > max2))
-                                       return;
-
-                               delta = newDelta;
-
-                               if (gs.Orientation == Orientation.Horizontal) {
-                                       if (init1 >= 0)
-                                               go1.Width = init1 - delta;
-                                       if (init2 >= 0)
-                                               go2.Width = init2 + delta;
+                       if (IFace.IsDown (Glfw.MouseButton.Left) && ptrSplit > 0 && ptrSplit < gs.Children.Count - 1) {
+                               Widget w0 = gs.Children[ptrSplit - 1];
+                               Widget w1 = gs.Children[ptrSplit + 1];
+                               if (gs.Orientation == Orientation.Horizontal) {                                 
+                                       int x = m.X - Slot.Width / 2 - gs.Spacing;
+
+                                       if (x > w0.Slot.Left + w0.MinimumSize.Width &&
+                                               x + Slot.Width + 2 * gs.Spacing < w1.Slot.Right - w1.MinimumSize.Width) {
+                                               w0.Width = x - w0.Slot.X;       
+                                               x += Slot.Width + 2 * gs.Spacing;
+                                               w1.Width = w1.Slot.Right - x;                                           
+                                       }
                                } else {
-                                       if (init1 >= 0)
-                                               go1.Height = init1 - delta;
-                                       if (init2 >= 0)
-                                               go2.Height = init2 + delta;
+                                       int y = m.Y - Slot.Height / 2 - gs.Spacing;
+
+                                       if (y > w0.Slot.Top + w0.MinimumSize.Height &&
+                                               y + Slot.Height + 2 * gs.Spacing < w1.Slot.Bottom - w1.MinimumSize.Height) {
+                                               w0.Height = y - w0.Slot.Top;    
+                                               y += Slot.Height + 2 * gs.Spacing;
+                                               w1.Height = w1.Slot.Bottom - y;                                         
+                                       }
                                }
-
+                               e.Handled = true;
                        }
-               }
-               public override void onMouseUp (object sender, MouseButtonEventArgs e)
-               {
-                       base.onMouseUp (sender, e);
-
-                       GenericStack gs = Parent as GenericStack;
 
-                       if (init1 >= 0 && u1 == Unit.Percent) {
-                               if (gs.Orientation == Orientation.Horizontal)
-                                       go1.Width = new Measure ((int)Math.Ceiling (
-                                               go1.Width.Value * 100.0 / (double)(gs.Slot.Width - 2 * gs.Margin)), Unit.Percent);
-                               else
-                                       go1.Height = new Measure ((int)Math.Ceiling (
-                                               go1.Height.Value * 100.0 / (double)gs.Slot.Height), Unit.Percent);
-                       }
-                       if (init2 >= 0 && u2 == Unit.Percent) {
-                               if (gs.Orientation == Orientation.Horizontal)
-                                       go2.Width = new Measure ((int)Math.Floor (
-                                               go2.Width.Value * 100.0 / (double)(gs.Slot.Width - 2 * gs.Margin)), Unit.Percent);
-                               else
-                                       go2.Height = new Measure ((int)Math.Floor (
-                                               go2.Height.Value * 100.0 / (double)gs.Slot.Height), Unit.Percent);
-                       }
+                       
+                       base.onMouseMove (sender, e);
                }
+               
                public override bool UpdateLayout (LayoutingType layoutType)
                {
                        GenericStack gs = Parent as GenericStack;
@@ -190,21 +105,7 @@ namespace Crow
                                        Height = Measure.Stretched;
                        }
                        return base.UpdateLayout (layoutType);
-               }
-               public override bool PointIsIn (ref Point m)
-               {
-                       if (!(Visible & IsEnabled)||IsDragged)
-                               return false;
-                       if (!Parent.PointIsIn(ref m))
-                               return false;
-                       m -= (Parent.getSlot().Position + Parent.ClientRectangle.Position) ;
-                       Rectangle r = Slot;
-                       if (Width == Measure.Stretched)
-                               r.Inflate (0, 5);
-                       else
-                               r.Inflate (5, 0);
-                       return r.ContainsOrIsEqual (m); 
-               }
+               }               
                #endregion
        }
 }
index 3719e3e22f50cef0a772c6eba9bbe31bddf4c8e5..0beafb561ac29b103c3bafc79460a06362072096 100644 (file)
@@ -21,7 +21,7 @@ namespace Crow
 
                string caption;
                Measure width = Measure.Fit;
-               //public int ComputedWidth;
+               public int ComputedWidth;
                public Widget LargestWidget;
 
                public string Caption {
@@ -70,18 +70,6 @@ namespace Crow
                //int lineWidth;
                ObservableList<Column> columns = new ObservableList<Column>();
 
-               /*[DefaultValue (1)]
-               public int LineWidth {
-                       get => lineWidth;
-                       set {
-                               if (lineWidth == value)
-                                       return;
-                               lineWidth = value;
-                               NotifyValueChangedAuto (lineWidth);
-                               RegisterForLayouting (LayoutingType.Sizing | LayoutingType.ArrangeChildren);
-                       }
-               }*/
-
                public ObservableList<Column> Columns {
                        get => columns;
                        set {
@@ -106,6 +94,7 @@ namespace Crow
                                layoutType &= (~(LayoutingType.X|LayoutingType.Width));                 
                }*/
 
+               //overriden to prevent search for largest child, all the rows as the same total width.
                public override void ComputeChildrenPositions () {
                        int d = 0;
                        childrenRWLock.EnterReadLock();
@@ -123,6 +112,7 @@ namespace Crow
                        RegisteredLayoutings &= (~layoutType);
 
                        if (layoutType == LayoutingType.Width) {
+                               //propagate column.width to each row's children
                                foreach (TableRow row in Children) {
                                        for (int i = 0; i < Columns.Count && i < row.Children.Count; i++) 
                                                row.Children[i].Width = Columns[i].Width;
index a251b6f99a8de4a36a745717d0760ff292d76c12..dbb076619ac8d3809c7b5effa998c835a1b032bf 100644 (file)
@@ -34,8 +34,6 @@ namespace Crow
                }
                #endregion
 
-               int spacing;
-
                public Table Table => Parent as Table;          
 
                /*public override void ChildrenLayoutingConstraints(ILayoutable layoutable, ref LayoutingType layoutType)
@@ -56,21 +54,25 @@ namespace Crow
                                return;
                        int spacing = Table.Spacing;
                        ObservableList<Column> cols = Table.Columns;
-                       
-                       //int d = 0;
+                                               
                        Widget first = Children[0];
                        TableRow firstRow = Table.Children[0] as TableRow;
+
                        if (firstRow == this) {
                                base.ComputeChildrenPositions();
                                return;
                        }                       
-                       childrenRWLock.EnterReadLock();
+                       childrenRWLock.EnterReadLock();                 
+                       
                        for (int i = 0; i < Children.Count && i < firstRow.Children.Count; i++)
                        {
-                               Widget w = Children[i];                         
-                               w.Slot.X = firstRow.Children[i].Slot.X;
-                               setChildWidth (w, firstRow.Children[i].Slot.Width);                             
-                               //d += spacing + firstRow.Children[i].Slot.Width;
+                               Widget w = Children[i];
+                               /*if (i < cols.Count && cols[i].Width.IsFit && cols[i].LargestWidget != null) {
+                                       w.Slot.X
+                               }else{*/
+                                       w.Slot.X = firstRow.Children[i].Slot.X;
+                                       setChildWidth (w, firstRow.Children[i].Slot.Width);                             
+                               //}
                        }
 
                        childrenRWLock.ExitReadLock();
@@ -87,8 +89,7 @@ namespace Crow
                        if (layoutType == LayoutingType.Width) {
                                if (firstRow.RegisteredLayoutings.HasFlag (LayoutingType.Width))
                                        return false;
-                               if (this != firstRow) {
-                                       //contentSize = firstRow.contentSize;
+                               if (this != firstRow) {                                 
                                        Slot.Width = firstRow.Slot.Width;
                                        if (Slot.Width != LastSlots.Width) {
                                                IsDirty = true;
@@ -119,10 +120,27 @@ namespace Crow
                        }
                        return base.measureRawSize (lt);
                }*/
-               public override void OnLayoutChanges(LayoutingType layoutType)
-               {
-                       base.OnLayoutChanges(layoutType);
-               }
+               /*public override void OnChildLayoutChanges (object sender, LayoutingEventArgs arg) {
+                       if (arg.LayoutType == LayoutingType.Width) {
+                               Widget w = sender as Widget;
+                               TableRow firstRow = Table.Children[0] as TableRow;
+                               int c = Children.IndexOf(w);                            
+                               if (c < Table.Columns.Count) {
+                                       Column col = Table.Columns[c];
+                                       if (col.Width.IsFit) {
+                                               if (col.LargestWidget == null || w.Slot.Width > col.ComputedWidth) {
+                                                       col.ComputedWidth = w.Slot.Width;
+                                                       col.LargestWidget = w;
+                                               } else if (w == col.LargestWidget)
+                                                       Console.WriteLine ("must search for largest widget");
+                                       }else if (w == firstRow)
+                                               col.ComputedWidth = w.Slot.Width;
+
+                               }
+                               Console.WriteLine ($"ROW:{Table.Children.IndexOf(this)} COL:{c} {w.LastSlots.Width} -> {w.Slot.Width} ");
+                       }
+                       base.OnChildLayoutChanges (sender, arg);
+               }*/
                /*public override void OnChildLayoutChanges (object sender, LayoutingEventArgs arg) {
                        Widget go = sender as Widget;
                        TableRow row = go.Parent as TableRow;
@@ -159,6 +177,7 @@ namespace Crow
                                                splitPos -= Spacing;
                                                Table.Columns[splitIndex].Width = splitPos - firstRow.Children[splitIndex].Slot.Left;
                                                Table.RegisterForLayouting (LayoutingType.Width);
+                                               e.Handled = true;
                                        }
                                        //Console.WriteLine ($"left:{firstRow.Children[splitIndex].Slot.Left} right:{firstRow.Children[splitIndex+1].Slot.Right} cb.X:{cb.X} splitPos:{splitPos} m:{m}");                               
                                } else {
@@ -169,7 +188,6 @@ namespace Crow
                                                if (m.X >= r.Right) {
                                                        r = Children[i+1].Slot;
                                                        if (m.X <= r.Left && Table.Columns.Count - 1 > i ) {
-                                                               Console.WriteLine ($"Set cursor Table row on mouse move. {m}");
                                                                IFace.MouseCursor = MouseCursor.sb_h_double_arrow;
                                                                splitIndex = i;
                                                                e.Handled = true;
@@ -177,10 +195,8 @@ namespace Crow
                                                        }
                                                }
                                        }
-                                       if (splitIndex < 0) {
+                                       if (splitIndex < 0 && IFace.MouseCursor == MouseCursor.sb_h_double_arrow)
                                                IFace.MouseCursor = MouseCursor.top_left_arrow;
-                                               Console.WriteLine ($"RESet cursor Table row on mouse move. {m}");
-                                       }
                                }
                        }
                        base.onMouseMove(sender, e);
index 908755809aa6b00f801901e6f2d16a8f04825704..29424df3fafe6d1cfd642444bfc70af217e8592d 100644 (file)
@@ -634,7 +634,7 @@ namespace Crow
                                if (width == value)
                                        return;
                                if (value.IsFixed) {
-                                       if (value < minimumSize.Width || (value > maximumSize.Width && maximumSize.Width > 0))
+                                       if (value < minimumSize.Width || (maximumSize.Width > 0 && value > maximumSize.Width))
                                                return;
                                }
                                width = value;
@@ -657,7 +657,7 @@ namespace Crow
                                if (height == value)
                                        return;
                                if (value.IsFixed) {
-                                       if (value < minimumSize.Height || (value > maximumSize.Height && maximumSize.Height > 0))
+                                       if (value < minimumSize.Height || (maximumSize.Height > 0 && value > maximumSize.Height))
                                                return;
                                }
                                height = value;
index 60e9ea3695439b5dca90cbfcfe811ac6d9fe61c8..0628695fbdd252eaaad244d93cc95287cbc58254 100644 (file)
@@ -56,27 +56,31 @@ namespace ShowCase
                                        CMDUndo, CMDRedo, CMDCut, CMDCopy, CMDPaste, CMDHelp, CMDAbout, CMDOptions;
 
                const string _defaultFileName = "unnamed.txt";
-               string source = "";
-               int dirtyUndoLevel;
+               string source = "", origSource;         
                TextBox editor;
                Stopwatch reloadChrono = new Stopwatch ();
 
-               public new bool IsDirty { get { return undoStack.Count != dirtyUndoLevel; } }
+               public new bool IsDirty => source != origSource;
                public string Source {
                        get => source;
                        set {
                                if (source == value)
                                        return;
                                source = value;
+                               CMDSave.CanExecute = IsDirty;
                                if (!reloadChrono.IsRunning)
                                        reloadChrono.Restart ();                                
                                NotifyValueChanged (source);
+                               NotifyValueChanged ("IsDirty", IsDirty);
                        }
                }
                public CommandGroup EditorCommands => new CommandGroup (CMDUndo, CMDRedo, CMDCut, CMDCopy, CMDPaste, CMDSave, CMDSaveAs);
 
                Stack<TextChange> undoStack = new Stack<TextChange> ();
                Stack<TextChange> redoStack = new Stack<TextChange> ();
+               TextSpan selection;
+               string SelectedText =>  
+                               selection.IsEmpty ? "" : Source.AsSpan (selection.Start, selection.Length).ToString ();
 
                void undo () {
                        if (undoStack.TryPop (out TextChange tch)) {
@@ -98,6 +102,16 @@ namespace ShowCase
                        if (redoStack.Count == 0)
                                CMDRedo.CanExecute = false;
                }
+               void cut () {
+                       copy ();
+                       applyChange (new TextChange (selection.Start, selection.Length, ""));
+               }
+               void copy () {
+                       Clipboard = SelectedText;
+               }
+               void paste () {                 
+                       applyChange (new TextChange (selection.Start, selection.Length, Clipboard));
+               }
                bool disableTextChangedEvent = false;
                void apply (TextChange change) {
                        Span<char> tmp = stackalloc char[source.Length + (change.ChangedText.Length - change.Length)];
@@ -108,8 +122,7 @@ namespace ShowCase
                        src.Slice (change.End).CopyTo (tmp.Slice (change.Start + change.ChangedText.Length));
                        disableTextChangedEvent = true;
                        Source = tmp.ToString ();
-                       disableTextChangedEvent = false;
-                       NotifyValueChanged ("IsDirty", IsDirty);
+                       disableTextChangedEvent = false;                        
                }       
                
                void initCommands ()
@@ -120,9 +133,9 @@ namespace ShowCase
                        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 };
+                       CMDCut = new Command (new Action (() => cut ())) { Caption = "Cut", Icon = "#Icons.scissors.svg", CanExecute = false };
+                       CMDCopy = new Command (new Action (() => copy ())) { Caption = "Copy", Icon = "#Icons.copy-file.svg", CanExecute = false };
+                       CMDPaste = new Command (new Action (() => paste ())) { Caption = "Paste", Icon = "#Icons.paste-on-document.svg", CanExecute = false };
 
                }
                void onNewFile () {
@@ -190,8 +203,9 @@ namespace ShowCase
                                byte [] buff = Encoding.UTF8.GetBytes (source);
                                s.Write (buff, 0, buff.Length);
                        }
-                       dirtyUndoLevel = undoStack.Count;
+                       origSource = source;
                        NotifyValueChanged ("IsDirty", IsDirty);
+                       CMDSave.CanExecute = false;
                }
 
                void reloadFromFile () {
@@ -200,7 +214,7 @@ namespace ShowCase
                        if (File.Exists (CurrentFile)) {
                                using (Stream s = new FileStream (CurrentFile, FileMode.Open)) {
                                        using (StreamReader sr = new StreamReader (s))
-                                               Source = sr.ReadToEnd ();
+                                               Source = origSource = sr.ReadToEnd ();
                                }
                        }
                        disableTextChangedEvent = false;
@@ -235,8 +249,7 @@ namespace ShowCase
                        undoStack.Clear ();
                        redoStack.Clear ();
                        CMDUndo.CanExecute = false;
-                       CMDRedo.CanExecute = false;
-                       dirtyUndoLevel = 0;
+                       CMDRedo.CanExecute = false;                     
                }
                void showError (Exception ex) {
                        NotifyValueChanged ("ErrorMessage", (object)ex.Message);
@@ -274,18 +287,31 @@ namespace ShowCase
                void onTextChanged (object sender, TextChangeEventArgs e) {
                        if (disableTextChangedEvent)
                                return;
-                       undoStack.Push (e.Change.Inverse (source));
+                       applyChange (e.Change);
+               }
+               void applyChange (TextChange change) {
+                       undoStack.Push (change.Inverse (source));
                        redoStack.Clear ();
                        CMDUndo.CanExecute = true;
                        CMDRedo.CanExecute = false;
-                       apply (e.Change);
+                       apply (change);
+               }
+               
+               void onSelectedTextChanged (object sender, EventArgs e) {                       
+                       selection = (sender as Label).Selection;
+                       Console.WriteLine($"selection:{selection.Start} length:{selection.Length}");
+                       CMDCut.CanExecute = CMDCopy.CanExecute = !selection.IsEmpty;
                }
                void textView_KeyDown (object sender, Crow.KeyEventArgs e) {
-                       if (Ctrl && e.Key == Glfw.Key.W) {
-                               if (Shift)
-                                       CMDRedo.Execute ();
-                               else
-                                       CMDUndo.Execute ();
+                       if (Ctrl) {
+                               if (e.Key == Glfw.Key.W) {
+                                       if (Shift)
+                                               CMDRedo.Execute ();
+                                       else
+                                               CMDUndo.Execute ();
+                               } else if (e.Key == Glfw.Key.S) {
+                                       onSave ();
+                               }
                        }
                }
 
index 6217373cf9dfcf7096a4277be0d34d3f51fc5b06..5b0e467c9db665de8e663245d6fda75e314dbaba 100644 (file)
@@ -19,6 +19,9 @@
                                <Button Style="IcoButton" Command="{CMDSaveAs}" />
                                <Button Style="IcoButton" Command="{CMDUndo}" />
                                <Button Style="IcoButton" Command="{CMDRedo}" />
+                               <Button Style="IcoButton" Command="{CMDCut}" />
+                               <Button Style="IcoButton" Command="{CMDCopy}" />
+                               <Button Style="IcoButton" Command="{CMDPaste}" />
                                <Widget Width="Stretched"/>
                                <Label Text="Line:" Foreground="Grey"/>
                                <Label Text="{../../tb.CurrentLine}"  Margin="2"/>
@@ -26,8 +29,9 @@
                                <Label Text="{../../tb.CurrentColumn}" Margin="2"/>
                        </HorizontalStack>
                        <HorizontalStack>
-                               <TextBox Name="tb" Text="{Source}" Multiline="true" Font="consolas, 12" Focusable="true" Height="Stretched" Width="Stretched"                                            
-                                                TextChanged="onTextChanged" KeyDown="textView_KeyDown" ContextCommands="{ContextCommands}"/>
+                               <TextBox Name="tb" Text="{Source}" Multiline="true" Font="consolas, 12" Focusable="true" Height="Stretched" Width="Stretched"                                           
+                                               TextChanged="onTextChanged" KeyDown="textView_KeyDown" ContextCommands="{EditorCommands}"/>
+                                               <!--SelectionChanged="onSelectedTextChanged"-->
                                <ScrollBar Value="{²../tb.ScrollY}"
                                                LargeIncrement="{../tb.PageHeight}" SmallIncrement="1"
                                                CursorRatio="{../tb.ChildHeightRatio}" Maximum="{../tb.MaxScrollY}" />
                        <ScrollBar Style="HScrollBar" Value="{²../tb.ScrollX}"
                                        LargeIncrement="{../tb.PageWidth}" SmallIncrement="1"
                                        CursorRatio="{../tb.ChildWidthRatio}" Maximum="{../tb.MaxScrollX}" />
-                       <Label Text="{CurrentFile}" Width="Stretched"/>
+                       <HorizontalStack Height="Fit">
+                               <Widget Width="10" Height="10" Background="RoyalBlue" Visible="{IsDirty}"/>
+                               <Label Text="{CurrentFile}" Width="Stretched"/>
+                       </HorizontalStack>                      
                        <Label Visible="{ShowError}" Text="{ErrorMessage}" Background="Red" Foreground="White" Width="Stretched" Margin="2"
                                Multiline="true"/>
                </VerticalStack>