]> O.S.I.I.S - jp/crow.git/commitdiff
simple label base
authorJean-Philippe Bruyère <jp_bruyere@hotmail.com>
Sat, 16 Jan 2021 09:32:53 +0000 (10:32 +0100)
committerJean-Philippe Bruyère <jp_bruyere@hotmail.com>
Sat, 16 Jan 2021 09:32:53 +0000 (10:32 +0100)
Crow/Default.style
Crow/src/Enums.cs
Crow/src/Mono.Cairo/Context.cs
Crow/src/Widgets/Label.cs
Crow/src/Widgets/TextBox.cs
Crow/src/Widgets/Widget.cs
Samples/common/SampleBase.cs

index 104b303c6efd80118512ddac2e1a6595b5842586..37de07d54794f3deba36315f05aac356d6255b05 100644 (file)
@@ -72,7 +72,8 @@ Button {
 Label {
        Height = "Fit";
        Width = "Fit";
-       Margin = "0";   
+       Margin = "0";
+       MouseCursor = "ibeam";
 }
 OldLabel {
        Height = "Fit";
index 55e3bfcbe8182af8b1d85f3572a5a99227b35477..ebbb8d223cb5d9577d7747076fb764e0a4f08495 100644 (file)
@@ -22,6 +22,13 @@ namespace Crow
         BottomRight = 0x0c,
                Center = 0x10,
                Undefined = 0x40
+    }
+       public enum TextAlignment
+    {
+               Left,
+               Right,
+               Center,
+               Justify
     }
     public enum HorizontalAlignment
     {
index 9ac41f9fb7c012f2a9164296f76f1561258a4964..a8dfac3438905807341081913b85998fdc8109ad 100644 (file)
@@ -66,9 +66,7 @@ namespace Crow.Cairo {
                        //
                        int ptr_size = Marshal.SizeOf (typeof (IntPtr));
 
-                       /*PlatformID platform = Environment.OSVersion.Platform;
-            RuntimeInformation.IsOSPlatform(OSPlatform.Linux); 
-            RuntimeInformation.OSArchitecture.HasFlag(Architecture.);
+                       PlatformID platform = Environment.OSVersion.Platform;
                        if (platform == PlatformID.Win32NT ||
                            platform == PlatformID.Win32S ||
                            platform == PlatformID.Win32Windows ||
@@ -76,10 +74,10 @@ namespace Crow.Cairo {
                            ptr_size == 4){
                                c_compiler_long_size = 4;
                                native_glyph_size = Marshal.SizeOf (typeof (NativeGlyph_4byte_longs));
-                       } else {*/
+                       } else {
                                c_compiler_long_size = 8;
                                native_glyph_size = Marshal.SizeOf (typeof (Glyph));
-                       //}
+                       }
                }
 
                public Context (Surface surface) : this (NativeMethods.cairo_create (surface.Handle), true)
@@ -904,8 +902,16 @@ namespace Crow.Cairo {
                        Span<byte> bytes = size > 512 ? new byte[size] : stackalloc byte[size];
                        int encodedBytes = Encoding.UTF8.GetBytes (s, bytes);
                        bytes[encodedBytes] = 0;
-                       
-                       NativeMethods.cairo_text_extents (handle, ref bytes.Slice(0, encodedBytes + 1).GetPinnableReference(), out extents);
+
+                       NativeMethods.cairo_text_extents (handle, ref bytes.Slice (0, encodedBytes + 1).GetPinnableReference (), out extents);
+               }
+               public void TextExtents (ReadOnlySpan<char> s, out TextExtents extents) {
+                       int size = s.Length * 4 + 1;
+                       Span<byte> bytes = size > 512 ? new byte[size] : stackalloc byte[size];
+                       int encodedBytes = Encoding.UTF8.GetBytes (s, bytes);
+                       bytes[encodedBytes] = 0;
+
+                       NativeMethods.cairo_text_extents (handle, ref bytes.Slice (0, encodedBytes + 1).GetPinnableReference (), out extents);
                }
                public void ShowText (Span<byte> bytes) =>
                        NativeMethods.cairo_show_text (handle, ref bytes.GetPinnableReference ());
index 5724c7ff4f06d8900b02d9fa0b10259bb1b385b6..cd01eb697908fbaa3e30bc479fd2d46bc2787cb4 100644 (file)
@@ -9,8 +9,48 @@ using Crow.Cairo;
 using System.Text.RegularExpressions;
 using System.ComponentModel;
 using System.Text;
+using System.Diagnostics;
 
 namespace Crow {
+       internal struct TextSpan
+    {
+               public readonly int Start;
+               public readonly int End;
+               public TextSpan (int start, int end) {
+                       Start = start;
+                       End = end;
+        }
+       }
+
+    [DebuggerDisplay ("{Line}, {Column}, {VisualCharXPosition}")]
+    internal struct CharLocation : IEquatable<CharLocation>
+    {
+               public readonly int Line;
+               public int Column;
+               public double VisualCharXPosition;
+               public CharLocation (int line, int column, double visualX = -1) {
+                       Line = line;
+                       Column = column;
+                       VisualCharXPosition = visualX;
+        }
+               public bool HasVisualX => Column >= 0 && VisualCharXPosition >= 0;
+
+               public static bool operator ==(CharLocation a, CharLocation b)
+                       => a.Equals (b);
+               public static bool operator != (CharLocation a, CharLocation b)
+                       => !a.Equals (b);
+               public bool Equals (CharLocation other) {
+                       return Column < 0 ?
+                               Line == other.Line && VisualCharXPosition == other.VisualCharXPosition :
+                               Line == other.Line && Column == other.Column;
+               }
+        public override bool Equals (object obj) => obj is CharLocation loc ? Equals(loc) : false;
+        public override int GetHashCode () {
+            return Column < 0 ?
+                               HashCode.Combine (Line, VisualCharXPosition) :
+                               HashCode.Combine (Line, Column);
+               }
+    }
        internal struct LineSpan
     {
                public readonly int Start;
@@ -20,6 +60,7 @@ namespace Crow {
                public int Length => End - Start;
                public int LengthIncludingLineBreak => EndIncludingLineBreak - Start;
                public int LineBreakLength => EndIncludingLineBreak - End;
+               public bool HasLineBreak => LineBreakLength > 0;
                public LineSpan (int start, int end, int endIncludingLineBreak) {
                        Start = start;
                        End = end;
@@ -42,6 +83,9 @@ namespace Crow {
                                str.AsSpan ().Slice (Start);
                }*/
        }
+       /// <summary>
+       /// Simple label widget possibly multiline but without tabulation handling.
+       /// </summary>
        public class Label : Widget
     {
                #region CTOR
@@ -49,6 +93,9 @@ namespace Crow {
                public Label(Interface iface, string style = null) : base (iface, style) { }
                #endregion
 
+               /// <summary>
+               /// Occurs when Text has changed.
+               /// </summary>
                public event EventHandler<TextChangeEventArgs> TextChanged;
 
                public virtual void OnTextChanged(Object sender, TextChangeEventArgs e)
@@ -61,30 +108,32 @@ namespace Crow {
 
                #region private and protected fields
                string _text;
-        Alignment _textAlignment;
+        TextAlignment _textAlignment;
                bool horizontalStretch;
                bool verticalStretch;
                bool _selectable;
                bool _multiline;
                Color selBackground;
                Color selForeground;
-               Point mouseLocalPos = -1;//mouse coord in widget space, filled only when clicked
-               int _currentCol;        //0 based cursor position in string
-               int _currentLine;
-               Point _selBegin = -1;   //selection start (row,column)
-               Point _selRelease = -1; //selection end (row,column)
-               double textCursorPos;   //cursor position in cairo units in widget client coord.
-               double SelStartCursorPos = -1;
-               double SelEndCursorPos = -1;
-               bool SelectionInProgress = false;
-
-        protected Rectangle rText;
+
+               //Point mouseLocalPos = -1;//mouse coord in widget space, filled only when clicked
+
+               
+               CharLocation? hoverLoc = null;
+               CharLocation? currentLoc = null;
+               CharLocation? selectionStart = null;    //selection start (row,column)
+               
+               
+
+        //protected Rectangle rText;
                protected float widthRatio = 1f;
                protected float heightRatio = 1f;
                protected FontExtents fe;
                protected TextExtents te;
                #endregion
-
+               /// <summary>
+               /// Background color for selected text inside this label.
+               /// </summary>
                [DefaultValue("SteelBlue")]
                public virtual Color SelectionBackground {
                        get { return selBackground; }
@@ -96,6 +145,9 @@ namespace Crow {
                                RegisterForRedraw ();
                        }
                }
+               /// <summary>
+               /// Selected text color inside this label.
+               /// </summary>
                [DefaultValue("White")]
                public virtual Color SelectionForeground {
                        get { return selForeground; }
@@ -107,8 +159,11 @@ namespace Crow {
                                RegisterForRedraw ();
                        }
                }
-               [DefaultValue(Alignment.Left)]
-               public Alignment TextAlignment
+               /// <summary>
+               /// If measure is not 'Fit', align text inside the bounds of this label.
+               /// </summary>
+               [DefaultValue(TextAlignment.Left)]              
+               public TextAlignment TextAlignment
         {
             get { return _textAlignment; }
             set {
@@ -119,6 +174,9 @@ namespace Crow {
                                NotifyValueChangedAuto (_textAlignment);
                        }
         }
+               /// <summary>
+               /// If Width is not 'Fit', Stretch text through the whole slot width.
+               /// </summary>
                [DefaultValue(false)]
                public virtual bool HorizontalStretch {
                        get { return horizontalStretch; }
@@ -130,6 +188,9 @@ namespace Crow {
                                NotifyValueChangedAuto (horizontalStretch);
                        }
                }
+               /// <summary>
+               /// If Height is not 'Fit', stretch text through the whole slot height.
+               /// </summary>
                [DefaultValue(false)]
                public virtual bool VerticalStretch {
                        get { return verticalStretch; }
@@ -141,6 +202,10 @@ namespace Crow {
                                NotifyValueChangedAuto (verticalStretch);
                        }
                }
+               /// <summary>
+               /// Text to display in this label. May include linebreaks if Multiline is 'true'.
+               /// If Multiline is false, linebreaks will be treated as unrecognized unicode char.
+               /// </summary>
                [DefaultValue("label")]
         public string Text
         {
@@ -158,7 +223,8 @@ namespace Crow {
                                RegisterForGraphicUpdate ();
             }
         }
-               [DefaultValue(false)]
+               
+               /*[DefaultValue(false)]
                public bool Selectable
                {
                        get => _selectable;
@@ -172,7 +238,11 @@ namespace Crow {
                                SelRelease = -1;
                                RegisterForRedraw ();
                        }
-               }
+               }*/
+               /// <summary>
+               /// If 'true', linebreaks will be interpreted. If 'false', linebreaks are threated as unprintable
+               /// unicode characters.
+               /// </summary>
                [DefaultValue(false)]
                public bool Multiline
                {
@@ -186,7 +256,7 @@ namespace Crow {
                                RegisterForGraphicUpdate();
                        }
                }
-               [DefaultValue(0)]
+               /*[DefaultValue(0)]
                public int CurrentColumn{
                        get { return _currentCol; }
                        set {
@@ -224,11 +294,6 @@ namespace Crow {
                                        gr.FontOptions = Interface.FontRenderingOptions;
                                        gr.Antialias = Interface.Antialias;
                                        try {
-                                               //string l = lines [_currentLine].GetSubString (_text);
-                                               ;
-                                               /*if (_currentCol < l.Length)
-                                                       l = l.Remove (Math.Min (_currentCol, l.Length));
-                                               l = l.Replace ("\t", new String (' ', Interface.TAB_SIZE));*/
                                                return (int)Math.Ceiling (gr.TextExtents (_text.GetLine (lines[_currentLine], _currentCol), Interface.TAB_SIZE).XAdvance);
                                        } catch {
                                                System.Diagnostics.Debug.WriteLine ("xpos measuring fault in label");
@@ -331,34 +396,6 @@ namespace Crow {
                [XmlIgnore]public bool selectionIsEmpty
                { get { return SelRelease < 0; } }
 
-               LineSpan[] lines;
-               void getLines () {
-                       if (string.IsNullOrEmpty (_text)) {
-                               lines = new LineSpan[] { new LineSpan (0, 0, 0) };
-                               return;
-                       }
-                       if (!_multiline) {
-                               lines = new LineSpan[] { new LineSpan (0, _text.Length, _text.Length) };
-                               return;
-                       }
-
-                       List<LineSpan> _lines = new List<LineSpan> ();
-                       bool lineBreak = false;
-                       int start = 0, end = 0;
-            for (int i = 0; i < _text.Length; i++) {
-                               if (_text[i].IsAnyLineBreakCharacter()) {
-                                       if (lineBreak)
-                                               continue;
-                                       lineBreak = true;
-                                       end = i;
-                }else if (lineBreak) {
-                                       _lines.Add (new LineSpan (start, end, i));
-                                       start = end = i;
-                                       lineBreak = false;
-                }
-            }
-                       lines = _lines.ToArray();                       
-               }
                /// <summary>
                /// Moves cursor one char to the left.
                /// </summary>
@@ -425,7 +462,7 @@ namespace Crow {
                                CurrentColumn--;
                                Text = _text.Remove (lines[CurrentLine].Start + CurrentColumn, 1);                              
                        } else {
-                               /*int linesToRemove = selectionEnd.Y - selectionStart.Y + 1;
+                               int linesToRemove = selectionEnd.Y - selectionStart.Y + 1;
                                int l = selectionStart.Y;
 
                                if (linesToRemove > 0) {
@@ -440,7 +477,7 @@ namespace Crow {
                                        lines [l] = lines [l].Remove (selectionStart.X, selectionEnd.X - selectionStart.X);
                                CurrentColumn = selectionStart.X;
                                SelBegin = -1;
-                               SelRelease = -1;*/
+                               SelRelease = -1;
                        }
                        //OnTextChanged (this, new TextChangeEventArgs (Text));
                }
@@ -450,7 +487,7 @@ namespace Crow {
                /// <param name="str">String.</param>
                protected void Insert(string str)
                {
-                       /*if (!selectionIsEmpty)
+                       if (!selectionIsEmpty)
                                this.DeleteChar ();
                        if (_multiline) {
                                string[] strLines = Regex.Split (str, "\r\n|\r|\n|" + @"\\n").ToArray();
@@ -470,21 +507,70 @@ namespace Crow {
                                lines [CurrentLine] = lines [CurrentLine].Insert (CurrentColumn, str);
                                CurrentColumn += str.Length;
                        }
-                       OnTextChanged (this, new TextChangeEventArgs (Text));*/
+                       OnTextChanged (this, new TextChangeEventArgs (Text));
                }
                /// <summary>
                /// Insert a line break.
                /// </summary>
                protected void InsertLineBreak()
                {
-                       /*lines.Insert(CurrentLine + 1, lines[CurrentLine].Substring(CurrentColumn));
+                       lines.Insert(CurrentLine + 1, lines[CurrentLine].Substring(CurrentColumn));
                        lines [CurrentLine] = lines [CurrentLine].Substring (0, CurrentColumn);
                        CurrentLine++;
                        CurrentColumn = 0;
-                       OnTextChanged (this, new TextChangeEventArgs (Text));*/
+                       OnTextChanged (this, new TextChangeEventArgs (Text));
                }
+               */
                bool textMeasureIsUpToDate = false;
                Size cachedTextSize = default(Size);
+               LineSpan[] lines;
+               void getLines () {
+                       if (string.IsNullOrEmpty (_text)) {
+                               lines = new LineSpan[] { new LineSpan (0, 0, 0) };
+                               return;
+                       }
+                       if (!_multiline) {
+                               lines = new LineSpan[] { new LineSpan (0, _text.Length, _text.Length) };
+                               return;
+                       }
+
+                       List<LineSpan> _lines = new List<LineSpan> ();                  
+                       int start = 0, i = 0;
+                       while (i < _text.Length) {
+                               char c = _text[i];
+                               if (c == '\r') {
+                                       if (++i < _text.Length) {
+                                               if (_text[i] == '\n')
+                                                       _lines.Add (new LineSpan (start, i - 1, ++i));
+                                               else
+                                                       _lines.Add (new LineSpan (start, i - 1, i));
+                                       } else
+                                               _lines.Add (new LineSpan (start, i - 1, i));
+                                       start = i;
+                               } else if (c == '\n') {
+                                       if (++i < _text.Length) {
+                                               if (_text[i] == '\r')
+                                                       _lines.Add (new LineSpan (start, i - 1, ++i));
+                                               else
+                                                       _lines.Add (new LineSpan (start, i - 1, i));
+                                       } else
+                                               _lines.Add (new LineSpan (start, i - 1, i));
+                                       start = i;
+
+                               } else if (c == '\u0085' || c == '\u2028' || c == '\u2029')
+                                       _lines.Add (new LineSpan (start, i - 1, i));
+                               else
+                                       i++;
+                       }
+
+                       if (start < i)
+                               _lines.Add (new LineSpan (start, _text.Length, _text.Length));
+                       else
+                               _lines.Add (new LineSpan (_text.Length, _text.Length, _text.Length));
+
+                       lines = _lines.ToArray ();
+               }
+
 
                #region GraphicObject overrides
                public override int measureRawSize(LayoutingType lt)
@@ -506,24 +592,36 @@ namespace Crow {
 
                                        cachedTextSize.Height = (int)Math.Ceiling ((fe.Ascent+fe.Descent) * Math.Max (1, lines.Length));
 
-                                       try {
-                                               TextExtents tmp = default;
-                                               for (int i = 0; i < lines.Length; i++) {                                                        
-                                                       gr.TextExtents (_text.GetLine (lines[i]), Interface.TAB_SIZE, out tmp);
-                                                       if (lines[i].LengthInPixel < 0)
-                                                               lines[i].LengthInPixel = (int)tmp.XAdvance;
-                                                       if (tmp.XAdvance > te.XAdvance)
-                                                               te = tmp;
+                                       TextExtents tmp = default;
+                                       int longestLine = 0;
+                                       for (int i = 0; i < lines.Length; i++) {                                                        
+                                               if (lines[i].LengthInPixel < 0) {
+                                                       if (lines[i].Length == 0 && lines[i].HasLineBreak)
+                                                               lines[i].LengthInPixel = 10;// (int)Math.Ceiling (fe.MaxXAdvance);
+                                                       else {
+                                                               gr.TextExtents (_text.GetLine (lines[i]), Interface.TAB_SIZE, out tmp);
+                                                               lines[i].LengthInPixel = (int)Math.Ceiling (tmp.XAdvance);
+                                                       }
                                                }
-                                               cachedTextSize.Width = (int)Math.Ceiling (te.XAdvance);
-                                               textMeasureIsUpToDate = true;
-                                       } catch {                                                       
-                                               return -1;
-                                       }                                       
+                                               if (lines[i].LengthInPixel > lines[longestLine].LengthInPixel)
+                                                       longestLine = i;
+                                       }
+                                       cachedTextSize.Width = lines[longestLine].LengthInPixel;
+                                       textMeasureIsUpToDate = true;                           
                                }
                        }
                        return Margin * 2 + (lt == LayoutingType.Height ? cachedTextSize.Height : cachedTextSize.Width);
                }
+
+               double getX (int clientWidth, ref LineSpan ls) {
+                       switch (TextAlignment) {
+                       case TextAlignment.Right:
+                               return clientWidth - ls.LengthInPixel;                          
+                       case TextAlignment.Center:
+                               return clientWidth / 2 - ls.LengthInPixel / 2;
+                       }
+                       return 0;
+               }
                protected override void onDraw (Context gr)
                {
                        base.onDraw (gr);
@@ -533,136 +631,41 @@ namespace Crow {
                        gr.FontOptions = Interface.FontRenderingOptions;
                        gr.Antialias = Interface.Antialias;
 
-                       gr.Save ();
-                       gr.Translate (xTranslation, 0);
-
-                       rText = new Rectangle(new Size(
-                               measureRawSize(LayoutingType.Width), measureRawSize(LayoutingType.Height)));
-                       rText.Width -= 2 * Margin;
-                       rText.Height -= 2 * Margin;
-
-                       widthRatio = 1f;
-                       heightRatio = 1f;
+                       gr.Save ();                     
 
                        Rectangle cb = ClientRectangle;
 
-                       rText.X = cb.X;
-                       rText.Y = cb.Y;
+                       fe = gr.FontExtents;
+                       int lineHeight = (int)(fe.Ascent + fe.Descent);
 
-                       if (horizontalStretch) {
-                               widthRatio = (float)cb.Width / (float)rText.Width;
-                               if (!verticalStretch)
-                                       heightRatio = widthRatio;
-                       }
+                       CharLocation selStart = default, selEnd = default;
 
-                       if (verticalStretch) {
-                               heightRatio = (float)cb.Height / (float)rText.Height;
-                               if (!horizontalStretch)
-                                       widthRatio = heightRatio;
+                       if (HasFocus) {
+                               updateLocation (gr, cb.Width, ref currentLoc);
+                               if (selectionStart.HasValue) { 
+                                       updateLocation (gr, cb.Width, ref selectionStart);
+                                       if (currentLoc.Value.Line < selectionStart.Value.Line) {
+                                               selStart = currentLoc.Value;
+                                               selEnd = selectionStart.Value;
+                                       }else if (currentLoc.Value.Line > selectionStart.Value.Line) {
+                                               selStart = selectionStart.Value;
+                                               selEnd = currentLoc.Value;
+                                       } else if (currentLoc.Value.Column < selectionStart.Value.Column) {
+                                               selStart = currentLoc.Value;
+                                               selEnd = selectionStart.Value;
+                                       } else {
+                                               selStart = selectionStart.Value;
+                                               selEnd = currentLoc.Value;
+                                       }
+                               } else {
+                                       Foreground.SetAsSource (IFace, gr);
+                                       gr.LineWidth = 1.0;
+                                       gr.MoveTo (0.5 + currentLoc.Value.VisualCharXPosition + cb.X, cb.Y + currentLoc.Value.Line * lineHeight);
+                                       gr.LineTo (0.5 + currentLoc.Value.VisualCharXPosition + cb.X, cb.Y + (currentLoc.Value.Line + 1) * lineHeight);
+                                       gr.Stroke ();
+                               }
                        }
 
-                       rText.Width = (int)(widthRatio * (float)rText.Width);
-                       rText.Height = (int)(heightRatio * (float)rText.Height);
-
-                       switch (TextAlignment)
-                       {
-                       case Alignment.TopLeft:     //ok
-                               rText.X = cb.X;
-                               rText.Y = cb.Y;
-                               break;
-                       case Alignment.Top:   //ok
-                               rText.Y = cb.Y;
-                               rText.X = cb.X + cb.Width / 2 - rText.Width / 2;
-                               break;
-                       case Alignment.TopRight:    //ok
-                               rText.Y = cb.Y;
-                               rText.X = cb.Right - rText.Width;
-                               break;
-                       case Alignment.Left://ok
-                               rText.X = cb.X;
-                               rText.Y = cb.Y + cb.Height / 2 - rText.Height / 2;
-                               break;
-                       case Alignment.Right://ok
-                               rText.X = cb.X + cb.Width - rText.Width;
-                               rText.Y = cb.Y + cb.Height / 2 - rText.Height / 2;
-                               break;
-                       case Alignment.Bottom://ok
-                               rText.X = cb.Width / 2 - rText.Width / 2;
-                               rText.Y = cb.Height - rText.Height;
-                               break;
-                       case Alignment.BottomLeft://ok
-                               rText.X = cb.X;
-                               rText.Y = cb.Bottom - rText.Height;
-                               break;
-                       case Alignment.BottomRight://ok
-                               rText.Y = cb.Bottom - rText.Height;
-                               rText.X = cb.Right - rText.Width;
-                               break;
-                       case Alignment.Center://ok
-                               rText.X = cb.X + cb.Width / 2 - rText.Width / 2;
-                               //rText.Y = cb.Y + cb.Height / 2 - rText.Height / 2;
-                               rText.Y = cb.Y + (int)Math.Floor((double)cb.Height / 2.0 - (double)rText.Height / 2.0);
-                               break;
-                       }
-
-                       //gr.FontMatrix = new Matrix(widthRatio * (float)Font.Size, 0, 0, heightRatio * (float)Font.Size, 0, 0);
-                       fe = gr.FontExtents;
-
-                       #region draw text cursor
-                       if (HasFocus && Selectable)
-                       {
-                               if (mouseLocalPos >= 0)
-                               {
-                                       computeTextCursor(gr);
-
-                                       if (SelectionInProgress)
-                                       {
-                                               if (SelBegin < 0){
-                                                       SelBegin = new Point(CurrentColumn, CurrentLine);
-                                                       SelStartCursorPos = textCursorPos;
-                                                       SelRelease = -1;
-                                               }else{
-                                                       SelRelease = new Point(CurrentColumn, CurrentLine);
-                                                       if (SelRelease == SelBegin)
-                                                               SelRelease = -1;
-                                                       else
-                                                               SelEndCursorPos = textCursorPos;
-                                               }
-                                       }else
-                                               computeTextCursorPosition(gr);
-                               }else
-                                       computeTextCursorPosition(gr);
-
-                               Foreground.SetAsSource (IFace, gr);
-                               gr.LineWidth = 1.0;
-                               gr.MoveTo (0.5 + textCursorPos + rText.X, rText.Y + CurrentLine * (fe.Ascent+fe.Descent));
-                               gr.LineTo (0.5 + textCursorPos + rText.X, rText.Y + (CurrentLine + 1) * (fe.Ascent+fe.Descent));
-                               gr.Stroke();
-                       }
-                       #endregion
-
-                       //****** debug selection *************
-                       //                      if (SelRelease >= 0) {
-                       //                              new SolidColor(Color.DarkGreen).SetAsSource(gr);
-                       //                              Rectangle R = new Rectangle (
-                       //                                                   rText.X + (int)SelEndCursorPos - 3,
-                       //                                                   rText.Y + (int)(SelRelease.Y * (fe.Ascent+fe.Descent)),
-                       //                                                   6,
-                       //                                                   (int)(fe.Ascent+fe.Descent));
-                       //                              gr.Rectangle (R);
-                       //                              gr.Fill ();
-                       //                      }
-                       //                      if (SelBegin >= 0) {
-                       //                              new SolidColor(Color.DarkRed).SetAsSource(gr);
-                       //                              Rectangle R = new Rectangle (
-                       //                                      rText.X + (int)SelStartCursorPos - 3,
-                       //                                      rText.Y + (int)(SelBegin.Y * (fe.Ascent+fe.Descent)),
-                       //                                      6,
-                       //                                      (int)(fe.Ascent+fe.Descent));
-                       //                              gr.Rectangle (R);
-                       //                              gr.Fill ();
-                       //                      }
-                       //*******************
                        if (string.IsNullOrEmpty (_text)) {
                                gr.Restore ();
                                return;
@@ -671,77 +674,75 @@ namespace Crow {
                        Foreground.SetAsSource (IFace, gr);
 
                        TextExtents extents;
-                       Span<byte> bytes = stackalloc byte[128];
+                       Span<byte> bytes = stackalloc byte[128];                        
 
                        for (int i = 0; i < lines.Length; i++) {
-                               if (lines[i].Length == 0)
-                                       continue;
-                               
-                               int size = lines[i].Length * 4 + 1;
-                               if (bytes.Length < size)
-                                       bytes = size > 512 ? new byte[size] : stackalloc byte[size];
-                               
-                               int encodedBytes = Encoding.UTF8.GetBytes (_text.GetLine (lines[i]), bytes);
-                               bytes[encodedBytes] = 0;
-
-                               if (lines[i].LengthInPixel < 0) {
-                                       gr.TextExtents (bytes.Slice (0, encodedBytes), out extents);
-                                       lines[i].LengthInPixel = (int)extents.XAdvance;
+                               int encodedBytes = -1;
+                               if (lines[i].Length > 0) {
+
+                                       int size = lines[i].Length * 4 + 1;
+                                       if (bytes.Length < size)
+                                               bytes = size > 512 ? new byte[size] : stackalloc byte[size];
+
+                                       encodedBytes = Encoding.UTF8.GetBytes (_text.GetLine (lines[i]), bytes);
+                                       bytes[encodedBytes++] = 0;
+
+                                       if (lines[i].LengthInPixel < 0) {
+                                               if (lines[i].Length == 0 && lines[i].HasLineBreak)
+                                                       lines[i].LengthInPixel = 10;// (int)Math.Ceiling (fe.MaxXAdvance);
+                                               else {
+                                                       gr.TextExtents (bytes.Slice (0, encodedBytes), out extents);
+                                                       lines[i].LengthInPixel = (int)extents.XAdvance;
+                                               }
+                                       }
                                }
-                               //string l = lines [i].Replace ("\t", new String (' ', Interface.TAB_SIZE));
-                               
+
                                Rectangle lineRect = new Rectangle (
-                                       rText.X,
-                                       rText.Y + i * (int)(fe.Ascent+fe.Descent),
+                                       Width.IsFit ? cb.X : (int)getX (cb.Width, ref lines[i]) + cb.X,
+                                       cb.Y + i * lineHeight,
                                        lines[i].LengthInPixel,
-                                       (int)(fe.Ascent+fe.Descent));
-
-                               //                              if (TextAlignment == Alignment.Center ||
-                               //                                      TextAlignment == Alignment.Top ||
-                               //                                      TextAlignment == Alignment.Bottom)
-                               //                                      lineRect.X += (rText.Width - lineLength) / 2;
-                               //                              else if (TextAlignment == Alignment.Right ||
-                               //                                      TextAlignment == Alignment.TopRight ||
-                               //                                      TextAlignment == Alignment.BottomRight)
-                               //                                      lineRect.X += (rText.Width - lineLength);
-
-                               gr.MoveTo (lineRect.X,(double)rText.Y + fe.Ascent + (fe.Ascent+fe.Descent) * i) ;
-                gr.ShowText (bytes.Slice (0, encodedBytes));
-                               //gr.Fill ();
-
-                               if (Selectable) {
-                                       if (SelRelease >= 0 && i >= selectionStart.Y && i <= selectionEnd.Y) {
-                                               gr.SetSource (selBackground);
-
-                                               Rectangle selRect = lineRect;
+                                       lineHeight);
 
-                                               int cpStart = (int)SelStartCursorPos,
-                                               cpEnd = (int)SelEndCursorPos;
-
-                                               if (SelBegin.Y > SelRelease.Y) {
-                                                       cpStart = cpEnd;
-                                                       cpEnd = (int)SelStartCursorPos;
-                                               }
+                               if (encodedBytes > 0) {
+                                       gr.MoveTo (lineRect.X, lineRect.Y + fe.Ascent);
+                                       gr.ShowText (bytes.Slice (0, encodedBytes));
+                               }
 
-                                               if (i == selectionStart.Y) {
-                                                       selRect.Width -= cpStart;
-                                                       selRect.Left += cpStart;
-                                               }
-                                               if (i == selectionEnd.Y)
-                                                       selRect.Width -= (lines[i].LengthInPixel - cpEnd);
+                               if (HasFocus && selectionStart.HasValue) {
+                                       Rectangle selRect = lineRect;
+                                       if (_multiline) {
+                                               if (i >= selStart.Line && i <= selEnd.Line) {
+                                                       if (selStart.Line == selEnd.Line) {
+                                                               selRect.X = (int)selStart.VisualCharXPosition + cb.X;
+                                                               selRect.Width = (int)(selEnd.VisualCharXPosition - selStart.VisualCharXPosition);
+                                                       } else if (i == selStart.Line) {
+                                                               int newX = (int)selStart.VisualCharXPosition + cb.X;
+                                                               selRect.Width -= (newX - selRect.X);
+                                                               selRect.X = newX;
+                                                       } else if (i == selEnd.Line) {
+                                                               selRect.Width = (int)selEnd.VisualCharXPosition - selRect.X;
+                                                       }
+                                               } else
+                                                       continue;
+                    } else {
+                                               selRect.X = (int)selStart.VisualCharXPosition + cb.X;
+                                               selRect.Width = (int)(selEnd.VisualCharXPosition - selStart.VisualCharXPosition);                                               
+                                       }
 
-                                               gr.Rectangle (selRect);
+                                       gr.SetSource (selBackground);
+                                       gr.Rectangle (selRect);
+                                       if (encodedBytes < 0)
+                                               gr.Fill ();
+                                       else {
                                                gr.FillPreserve ();
                                                gr.Save ();
                                                gr.Clip ();
                                                gr.SetSource (SelectionForeground);
-                                               gr.MoveTo (lineRect.X, rText.Y + fe.Ascent + (fe.Ascent+fe.Descent) * i);
+                                               gr.MoveTo (lineRect.X, lineRect.Y + fe.Ascent);
                                                gr.ShowText (bytes.Slice (0, encodedBytes));
-                                               gr.Fill ();
                                                gr.Restore ();
-
-                                               Foreground.SetAsSource (IFace, gr);
                                        }
+                                       Foreground.SetAsSource (IFace, gr);
                                }
                        }
 
@@ -750,81 +751,75 @@ namespace Crow {
                #endregion
 
                #region Mouse handling
-               void updatemouseLocalPos(Point mpos){
-                       mouseLocalPos = mpos - ScreenCoordinates(Slot).TopLeft - ClientRectangle.TopLeft;
-                       mouseLocalPos.X -= xTranslation;
-                       if (mouseLocalPos.X < 0)
-                               mouseLocalPos.X = 0;
-                       if (mouseLocalPos.Y < 0)
-                               mouseLocalPos.Y = 0;
-               }
                protected override void onFocused (object sender, EventArgs e)
                {
                        base.onFocused (sender, e);
-
-                       if (!_selectable)
-                               return;
-                       SelBegin = new Point(0,0);
-                       SelRelease = new Point (lines.LastOrDefault ().Length, lines.Length-1);
+                       if (currentLoc == null) {
+                               selectionStart = new CharLocation (0, 0);                               
+                               currentLoc = new CharLocation (lines.Length - 1, lines[lines.Length - 1].Length);
+                       }
                        RegisterForRedraw ();
                }
                protected override void onUnfocused (object sender, EventArgs e)
                {
                        base.onUnfocused (sender, e);
-
-                       SelBegin = -1;
-                       SelRelease = -1;
                        RegisterForRedraw ();
                }
                public override void onMouseMove (object sender, MouseMoveEventArgs e)
                {
                        base.onMouseMove (sender, e);
 
-                       if (!(SelectionInProgress && HasFocus && _selectable))
-                               return;
-
-                       updatemouseLocalPos (e.Position);
+                       Point mouseLocalPos = e.Position - ScreenCoordinates (Slot).TopLeft - ClientRectangle.TopLeft;
+                       int hoverLine = _multiline ?
+                               (int)Math.Min (Math.Max (0, Math.Floor (mouseLocalPos.Y / (fe.Ascent + fe.Descent))), lines.Length - 1) : 0;
+                       hoverLoc = new CharLocation (hoverLine, -1, mouseLocalPos.X);
 
-                       RegisterForRedraw();
+                       if (HasFocus && IFace.IsDown (Glfw.MouseButton.Left)) {
+                               currentLoc = hoverLoc;
+                               RegisterForRedraw ();                           
+                       }
                }
+               Point grabMousePos = -1;
                public override void onMouseDown (object sender, MouseButtonEventArgs e)
                {
-                       if (HasFocus) {
-                               if (_selectable) {
-                                       updatemouseLocalPos (e.Position);
-                                       SelBegin = -1;
-                                       SelRelease = -1;
-                                       SelectionInProgress = true;
-                                       RegisterForRedraw ();//TODO:should put it in properties
-                               }
+                       if (e.Button == Glfw.MouseButton.Left) {                                
+                               if (HasFocus) {
+                                       grabMousePos = e.Position - ScreenCoordinates (Slot).TopLeft - ClientRectangle.TopLeft;
+                                       if (IFace.Shift)
+                                               currentLoc = hoverLoc;
+                                       else
+                                               currentLoc = selectionStart = hoverLoc;
+                                       RegisterForRedraw ();
+                                       e.Handled = true;
+                               }                                       
                        }
+                       base.onMouseDown (sender, e);
 
                        //done at the end to set 'hasFocus' value after testing it
-                       base.onMouseDown (sender, e);
                }
                public override void onMouseUp (object sender, MouseButtonEventArgs e)
                {
                        base.onMouseUp (sender, e);
-                       if (!(HasFocus || _selectable))
-                               return;
-                       if (!SelectionInProgress)
+                       if (e.Button != Glfw.MouseButton.Left)
                                return;
-
-                       updatemouseLocalPos (e.Position);
-                       SelectionInProgress = false;
+                       Point mouseLocalPos = e.Position - ScreenCoordinates (Slot).TopLeft - ClientRectangle.TopLeft;
+                       if (mouseLocalPos == grabMousePos) {
+                               selectionStart = null;                          
+                               e.Handled = true;                               
+                       }
                        RegisterForRedraw ();
                }
                public override void onMouseDoubleClick (object sender, MouseButtonEventArgs e)
                {
                        base.onMouseDoubleClick (sender, e);
-                       if (!(this.HasFocus || _selectable))
+                       /*if (!(this.HasFocus || _selectable))
                                return;
                        
                        GotoWordStart ();
                        SelBegin = CurrentPosition;
                        GotoWordEnd ();
                        SelRelease = CurrentPosition;
-                       SelectionInProgress = false;
+                       SelectionInProgress = false;*/
                        RegisterForRedraw ();
                }
                #endregion
@@ -833,80 +828,49 @@ namespace Crow {
                /// Update Current Column, line and TextCursorPos
                /// from mouseLocalPos
                /// </summary>
-               void computeTextCursor(Context gr)
+               void updateLocation(Context gr, int clientWidth, ref CharLocation? location)
                {
-                       TextExtents te;
-
-                       double cPos = 0f;
-
-                       CurrentLine = (int)(mouseLocalPos.Y / (fe.Ascent+fe.Descent));
+                       if (location == null)
+                               return;
+                       CharLocation loc = location.Value;
+                       if (loc.HasVisualX)
+                               return;
+                       LineSpan ls = lines[loc.Line];
+                       ReadOnlySpan<char> curLine = _text.GetLine (lines[loc.Line]);
+                       double cPos = Width.IsFit ? 0 : getX (clientWidth, ref ls);
                        
-                       //fix cu
-                       if (CurrentLine >= lines.Length)
-                               CurrentLine = lines.Length - 1;
-
-                       LineSpan ls = lines[CurrentLine];
-                       ReadOnlySpan<char> curLine = _text.GetLine (lines[CurrentLine]);
-
-                       switch (TextAlignment) {
-                       case Alignment.Center:
-                       case Alignment.Top:
-                       case Alignment.Bottom:
-                               cPos += ClientRectangle.Width - gr.TextExtents (curLine, Interface.TAB_SIZE).Width / 2.0;
-                               break;
-                       case Alignment.Right:
-                       case Alignment.TopRight:
-                       case Alignment.BottomRight:
-                               cPos += ClientRectangle.Width - gr.TextExtents (curLine, Interface.TAB_SIZE).Width;
-                               break;
+                       if (loc.Column >= 0) {
+                               loc.VisualCharXPosition = gr.TextExtents (curLine.Slice (0, loc.Column), Interface.TAB_SIZE).XAdvance + cPos;
+                               location = loc;
+                               return;
                        }
 
-                       
-                       for (int i = 0; i < lines[CurrentLine].Length; i++)
-                       {
-                               /*string c = .Substring (i, 1);
-                               if (c == "\t")
-                                       c = new string (' ', Interface.TAB_SIZE);
+                       TextExtents te;                 
+                       Span<byte> bytes = stackalloc byte[5];//utf8 single char buffer + '\0'
 
-                               te = gr.TextExtents(c);
+                       for (int i = 0; i < ls.Length; i++)
+                       {
+                               int encodedBytes = Encoding.UTF8.GetBytes (curLine.Slice (i, 1), bytes);
+                               bytes[encodedBytes] = 0;
 
+                               gr.TextExtents (bytes, out te);
                                double halfWidth = te.XAdvance / 2;
 
-                               if (mouseLocalPos.X <= cPos + halfWidth)
+                               if (loc.VisualCharXPosition <= cPos + halfWidth)
                                {
-                                       CurrentColumn = i;
-                                       textCursorPos = cPos;
-                                       mouseLocalPos = -1;
+                                       loc.Column = i;
+                                       loc.VisualCharXPosition = cPos;
+                                       location = loc;
                                        return;
                                }
 
-                               cPos += te.XAdvance;*/
-                       }
-                       CurrentColumn = lines[CurrentLine].Length;
-                       textCursorPos = cPos;
-
-                       //reset mouseLocalPos
-                       mouseLocalPos = -1;
-               }
-               /// <summary> Computes offsets in cairo units </summary>
-               void computeTextCursorPosition(Context gr)
-               {
-                       if (SelBegin >= 0)
-                               SelStartCursorPos = GetXFromTextPointer (gr, SelBegin);
-                       if (SelRelease >= 0)
-                               SelEndCursorPos = GetXFromTextPointer (gr, SelRelease);
-                       textCursorPos = GetXFromTextPointer (gr, new Point(CurrentColumn, CurrentLine));
-               }
-               /// <summary> Compute x offset in cairo unit from text position </summary>
-               double GetXFromTextPointer(Context gr, Point pos)
-               {
-                       try {
-                               /*string l = lines [pos.Y].Substring (0, pos.X).
-                                       Replace ("\t", new String (' ', Interface.TAB_SIZE));*/
-                               return gr.TextExtents (_text.GetLine(lines[pos.Y], pos.X), Interface.TAB_SIZE).XAdvance;
-                       } catch{
-                               return -1;
+                               cPos += te.XAdvance;
                        }
+                       loc.Column = ls.Length;
+                       loc.VisualCharXPosition = cPos;
+                       location = loc;
                }
+
+
     }
 }
index 31b513a6f378b4e9471454fdd4753a45e54a2d1a..aa916999295c4a5b9c679e35fea07ee97c046250 100644 (file)
@@ -7,7 +7,7 @@ using Glfw;
 
 namespace Crow
 {
-       public class TextBox : Label
+       public class TextBox : OldLabel
     {
                #region CTOR
                protected TextBox() {}
index 23329c762d9ca5d497efeb4a82a57e432c6e62c8..afaeebeb4bcb6bea83a50ea193fde7175691f684 100644 (file)
@@ -678,7 +678,7 @@ namespace Crow
                /// </summary>
                [DesignCategory ("Behaviour")][DefaultValue(false)]
                public virtual bool Focusable {
-                       get { return focusable; }
+                       get => focusable;
                        set {
                                if (focusable == value)
                                        return;
@@ -690,7 +690,7 @@ namespace Crow
                /// True when this control has the focus, only one control per interface may have it.
                /// </summary>
                [XmlIgnore]public virtual bool HasFocus {
-                       get { return hasFocus; }
+                       get => hasFocus;
                        set {
                                if (value == hasFocus)
                                        return;
@@ -709,7 +709,7 @@ namespace Crow
                /// be used for other two states periferic action.
                /// </summary>
                [XmlIgnore]public virtual bool IsActive {
-                       get { return isActive; }
+                       get => isActive;
                        internal set {
                                if (value == isActive)
                                        return;
@@ -722,7 +722,7 @@ namespace Crow
                /// true if this control has the pointer hover
                /// </summary>
                [XmlIgnore]public virtual bool IsHover {
-                       get { return isHover; }
+                       get => isHover;
                        internal set {
                                if (value == isHover)
                                        return;
@@ -746,7 +746,7 @@ namespace Crow
                /// </summary>
                [DesignCategory ("Behaviour")][DefaultValue(false)]
                public virtual bool MouseRepeat {
-                       get { return mouseRepeat; }
+                       get => mouseRepeat;
                        set {
                                if (mouseRepeat == value)
                                        return;
@@ -794,7 +794,7 @@ namespace Crow
                                NotifyValueChangedAuto (mouseCursor);
                                this.RegisterForRedraw ();
 
-                               if (isHover)
+                               if (Focusable && IsHover)
                                        IFace.MouseCursor = mouseCursor;
                        }
                }
@@ -2009,7 +2009,8 @@ namespace Crow
                }
                public virtual void onMouseEnter(object sender, MouseMoveEventArgs e)
                {
-                       IFace.MouseCursor = MouseCursor;                        
+                       if (Focusable)
+                               IFace.MouseCursor = MouseCursor;                        
 
                        if (IFace.DragAndDropOperation != null) {
                                Widget g = this;
index bdc6608b1c3d4b9909256a83f13014de47291bbf..cb24dad62c83deb5f3091a94b80feb25f5e9e13a 100644 (file)
@@ -39,7 +39,21 @@ namespace Crow
                VerticalAlignment currentVAlign;
 
                DirectoryInfo curDir = new DirectoryInfo (Path.GetDirectoryName (Assembly.GetEntryAssembly ().Location));
-               public FileSystemInfo[] CurDirectory => curDir.GetFileSystemInfos (); 
+               public FileSystemInfo[] CurDirectory => curDir.GetFileSystemInfos ();
+               public string MultilineText =
+                       $"Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit. Sed non risus.\n\nSuspendisse lectus tortor,";
+               //public string MultilineText = $"a\n";
+               TextAlignment textAlignment = TextAlignment.Left;
+               public TextAlignment TextAlignment {
+                       get => textAlignment;
+                       set {
+                               if (textAlignment == value)
+                                       return;
+                               textAlignment = value;
+                               NotifyValueChanged (textAlignment);
+                       }
+               }
+
                public int IntValue {
                        get => intValue;
                        set {
@@ -248,11 +262,11 @@ namespace Crow
                                boolVal = value;
                                NotifyValueChanged (boolVal);
                        }
-               }
+               }        
 
-               #endregion
+        #endregion
 
-               protected override void OnInitialized ()
+        protected override void OnInitialized ()
                {
                        Commands = new List<Command> {
                                new Command(() => MessageBox.ShowModal(this, MessageBox.Type.Information, "context menu 1 clicked")) { Caption = "Action 1" },