From: Jean-Philippe Bruyère Date: Sat, 16 Jan 2021 09:32:53 +0000 (+0100) Subject: simple label base X-Git-Tag: v0.9.5-beta~101 X-Git-Url: https://git.osiis.dedyn.io/?a=commitdiff_plain;h=ff03f75e02af643215d74fa7c99cdc983e048ce7;p=jp%2Fcrow.git simple label base --- diff --git a/Crow/Default.style b/Crow/Default.style index 104b303c..37de07d5 100644 --- a/Crow/Default.style +++ b/Crow/Default.style @@ -72,7 +72,8 @@ Button { Label { Height = "Fit"; Width = "Fit"; - Margin = "0"; + Margin = "0"; + MouseCursor = "ibeam"; } OldLabel { Height = "Fit"; diff --git a/Crow/src/Enums.cs b/Crow/src/Enums.cs index 55e3bfcb..ebbb8d22 100644 --- a/Crow/src/Enums.cs +++ b/Crow/src/Enums.cs @@ -22,6 +22,13 @@ namespace Crow BottomRight = 0x0c, Center = 0x10, Undefined = 0x40 + } + public enum TextAlignment + { + Left, + Right, + Center, + Justify } public enum HorizontalAlignment { diff --git a/Crow/src/Mono.Cairo/Context.cs b/Crow/src/Mono.Cairo/Context.cs index 9ac41f9f..a8dfac34 100644 --- a/Crow/src/Mono.Cairo/Context.cs +++ b/Crow/src/Mono.Cairo/Context.cs @@ -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 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 s, out TextExtents extents) { + int size = s.Length * 4 + 1; + Span 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 bytes) => NativeMethods.cairo_show_text (handle, ref bytes.GetPinnableReference ()); diff --git a/Crow/src/Widgets/Label.cs b/Crow/src/Widgets/Label.cs index 5724c7ff..cd01eb69 100644 --- a/Crow/src/Widgets/Label.cs +++ b/Crow/src/Widgets/Label.cs @@ -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 + { + 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); }*/ } + /// + /// Simple label widget possibly multiline but without tabulation handling. + /// public class Label : Widget { #region CTOR @@ -49,6 +93,9 @@ namespace Crow { public Label(Interface iface, string style = null) : base (iface, style) { } #endregion + /// + /// Occurs when Text has changed. + /// public event EventHandler 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 - + /// + /// Background color for selected text inside this label. + /// [DefaultValue("SteelBlue")] public virtual Color SelectionBackground { get { return selBackground; } @@ -96,6 +145,9 @@ namespace Crow { RegisterForRedraw (); } } + /// + /// Selected text color inside this label. + /// [DefaultValue("White")] public virtual Color SelectionForeground { get { return selForeground; } @@ -107,8 +159,11 @@ namespace Crow { RegisterForRedraw (); } } - [DefaultValue(Alignment.Left)] - public Alignment TextAlignment + /// + /// If measure is not 'Fit', align text inside the bounds of this label. + /// + [DefaultValue(TextAlignment.Left)] + public TextAlignment TextAlignment { get { return _textAlignment; } set { @@ -119,6 +174,9 @@ namespace Crow { NotifyValueChangedAuto (_textAlignment); } } + /// + /// If Width is not 'Fit', Stretch text through the whole slot width. + /// [DefaultValue(false)] public virtual bool HorizontalStretch { get { return horizontalStretch; } @@ -130,6 +188,9 @@ namespace Crow { NotifyValueChangedAuto (horizontalStretch); } } + /// + /// If Height is not 'Fit', stretch text through the whole slot height. + /// [DefaultValue(false)] public virtual bool VerticalStretch { get { return verticalStretch; } @@ -141,6 +202,10 @@ namespace Crow { NotifyValueChangedAuto (verticalStretch); } } + /// + /// 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. + /// [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 (); } - } + }*/ + /// + /// If 'true', linebreaks will be interpreted. If 'false', linebreaks are threated as unprintable + /// unicode characters. + /// [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 _lines = new List (); - 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(); - } /// /// Moves cursor one char to the left. /// @@ -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 { /// String. 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)); } /// /// Insert a line break. /// 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 _lines = new List (); + 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 bytes = stackalloc byte[128]; + Span 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 /// - 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 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 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 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; - } - /// Computes offsets in cairo units - 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)); - } - /// Compute x offset in cairo unit from text position - 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; } + + } } diff --git a/Crow/src/Widgets/TextBox.cs b/Crow/src/Widgets/TextBox.cs index 31b513a6..aa916999 100644 --- a/Crow/src/Widgets/TextBox.cs +++ b/Crow/src/Widgets/TextBox.cs @@ -7,7 +7,7 @@ using Glfw; namespace Crow { - public class TextBox : Label + public class TextBox : OldLabel { #region CTOR protected TextBox() {} diff --git a/Crow/src/Widgets/Widget.cs b/Crow/src/Widgets/Widget.cs index 23329c76..afaeebeb 100644 --- a/Crow/src/Widgets/Widget.cs +++ b/Crow/src/Widgets/Widget.cs @@ -678,7 +678,7 @@ namespace Crow /// [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. /// [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. /// [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 /// [XmlIgnore]public virtual bool IsHover { - get { return isHover; } + get => isHover; internal set { if (value == isHover) return; @@ -746,7 +746,7 @@ namespace Crow /// [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; diff --git a/Samples/common/SampleBase.cs b/Samples/common/SampleBase.cs index bdc6608b..cb24dad6 100644 --- a/Samples/common/SampleBase.cs +++ b/Samples/common/SampleBase.cs @@ -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 { new Command(() => MessageBox.ShowModal(this, MessageBox.Type.Information, "context menu 1 clicked")) { Caption = "Action 1" },