From b948613fd357410ad195ecfe8c9f202f63431e1a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jean-Philippe=20Bruy=C3=A8re?= Date: Thu, 28 Jan 2021 09:38:30 +0100 Subject: [PATCH] Crow.Text namespace, new TextBox using TextChange structure --- Crow/Crow.csproj | 2 +- Crow/src/EventArgs/TextChangeEventArgs.cs | 7 +- Crow/src/ExtensionsMethods.cs | 9 +- Crow/src/IML/Instantiator.cs | 6 +- Crow/src/Interface.cs | 7 +- Crow/src/Text/CharLocation.cs | 35 ++ Crow/src/Text/Text.cs | 119 +++++++ Crow/src/Text/TextChange.cs | 20 ++ Crow/src/Text/TextLine.cs | 49 +++ Crow/src/Text/TextLineCollection.cs | 110 ++++++ Crow/src/Text/TextSpan.cs | 19 ++ Crow/src/Widgets/Label.cs | 313 +++++------------- Crow/src/Widgets/OldLabel.cs | 10 +- Crow/src/Widgets/OldTextBox.cs | 2 +- Crow/src/Widgets/TextBox.cs | 161 +++------ Crow/src/Widgets/Widget.cs | 2 +- Samples/PerfTests/Program.cs | 17 +- .../PerfTests/Properties/launchSettings.json | 2 +- 18 files changed, 516 insertions(+), 374 deletions(-) create mode 100644 Crow/src/Text/CharLocation.cs create mode 100644 Crow/src/Text/Text.cs create mode 100644 Crow/src/Text/TextChange.cs create mode 100644 Crow/src/Text/TextLine.cs create mode 100644 Crow/src/Text/TextLineCollection.cs create mode 100644 Crow/src/Text/TextSpan.cs diff --git a/Crow/Crow.csproj b/Crow/Crow.csproj index ce2bd530..a3c9e805 100644 --- a/Crow/Crow.csproj +++ b/Crow/Crow.csproj @@ -23,7 +23,7 @@ True true $(NoWarn);1591;1587;1570;1572;1573;1574 - DESIGN_MODE;MEASURE_TIME + _DESIGN_MODE;_MEASURE_TIME false false App.config diff --git a/Crow/src/EventArgs/TextChangeEventArgs.cs b/Crow/src/EventArgs/TextChangeEventArgs.cs index 33ca405c..a51ce29f 100644 --- a/Crow/src/EventArgs/TextChangeEventArgs.cs +++ b/Crow/src/EventArgs/TextChangeEventArgs.cs @@ -24,17 +24,18 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +using Crow.Text; using System; namespace Crow { public class TextChangeEventArgs: EventArgs { - public String Text; + public TextChange Change; - public TextChangeEventArgs (string _newValue) : base() + public TextChangeEventArgs (TextChange _newValue) : base() { - Text = _newValue; + Change = _newValue; } } } diff --git a/Crow/src/ExtensionsMethods.cs b/Crow/src/ExtensionsMethods.cs index a40b25fc..4961ea90 100644 --- a/Crow/src/ExtensionsMethods.cs +++ b/Crow/src/ExtensionsMethods.cs @@ -2,6 +2,7 @@ // // This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +using Crow.Text; using System; using System.IO; using System.Linq; @@ -196,24 +197,24 @@ namespace Crow internal static bool IsAnyLineBreakCharacter (this char c) => c == '\n' || c == '\r' || c == '\u0085' || c == '\u2028' || c == '\u2029'; - internal static ReadOnlySpan GetLine (this string str, LineSpan ls) { + internal static ReadOnlySpan GetLine (this string str, TextLine ls) { if (ls.Start >= str.Length) return "".AsSpan (); return str.AsSpan ().Slice (ls.Start, ls.Length); } - internal static ReadOnlySpan GetLine (this string str, LineSpan ls, int offset) { + internal static ReadOnlySpan GetLine (this string str, TextLine ls, int offset) { int start = ls.Start + offset; if (start >= str.Length) return "".AsSpan (); return str.AsSpan ().Slice (start, ls.Length); } - internal static ReadOnlySpan GetLineIncludingLineBreak (this string str, LineSpan ls) { + internal static ReadOnlySpan GetLineIncludingLineBreak (this string str, TextLine ls) { if (ls.Start >= str.Length) return "".AsSpan (); return str.AsSpan ().Slice (ls.Start, ls.LengthIncludingLineBreak); } - internal static ReadOnlySpan GetLineIncludingLineBreak (this string str, LineSpan ls, int offset) { + internal static ReadOnlySpan GetLineIncludingLineBreak (this string str, TextLine ls, int offset) { int start = ls.Start + offset; if (start >= str.Length) return "".AsSpan (); diff --git a/Crow/src/IML/Instantiator.cs b/Crow/src/IML/Instantiator.cs index 12a8bef2..3a249a1e 100644 --- a/Crow/src/IML/Instantiator.cs +++ b/Crow/src/IML/Instantiator.cs @@ -231,11 +231,7 @@ namespace Crow.IML { ctx.curLine += li.LineNumber - 1; #endif - string tmpXml = reader.ReadOuterXml (); - - - XmlTextReader r = reader as XmlTextReader; - + string tmpXml = reader.ReadOuterXml (); if (ctx.nodesStack.Peek().HasTemplate) emitTemplateLoad (ctx, tmpXml); diff --git a/Crow/src/Interface.cs b/Crow/src/Interface.cs index 11417dec..1c55ad38 100644 --- a/Crow/src/Interface.cs +++ b/Crow/src/Interface.cs @@ -209,7 +209,7 @@ namespace Crow #endregion - public string WindowTitle { + public string WindowTitle { set => Glfw3.SetWindowTitle (hWin, value); } @@ -219,7 +219,6 @@ namespace Crow } public virtual void InterfaceThread () { - while (!Glfw3.WindowShouldClose (hWin)) { Update (); Thread.Sleep (UPDATE_INTERVAL); @@ -767,9 +766,9 @@ namespace Crow DiscardQueue = new Queue (LayoutingQueue.Count); //Debug.WriteLine ("======= Layouting queue start ======="); - LayoutingQueueItem lqi; + while (LayoutingQueue.Count > 0) { - lqi = LayoutingQueue.Dequeue (); + LayoutingQueueItem lqi = LayoutingQueue.Dequeue (); lqi.ProcessLayouting (); } LayoutingQueue = DiscardQueue; diff --git a/Crow/src/Text/CharLocation.cs b/Crow/src/Text/CharLocation.cs new file mode 100644 index 00000000..8a8b9784 --- /dev/null +++ b/Crow/src/Text/CharLocation.cs @@ -0,0 +1,35 @@ +using System; +using System.Diagnostics; + +namespace Crow.Text +{ + [DebuggerDisplay ("{Line}, {Column}, {VisualCharXPosition}")] + public 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); + } + } +} diff --git a/Crow/src/Text/Text.cs b/Crow/src/Text/Text.cs new file mode 100644 index 00000000..4ebb6e59 --- /dev/null +++ b/Crow/src/Text/Text.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Crow.Text +{ + public class Text + { + char[] buffer; + int length; + TextLine[] lines; + + public Text (string text, int capacity = -1) { + if (string.IsNullOrEmpty (text)) { + buffer = new char[capacity > 0 ? capacity : 0]; + length = 0; + } else { + if (capacity >= text.Length) { + buffer = new char[capacity]; + text.AsSpan ().CopyTo (buffer.AsSpan ()); + } else + buffer = text.ToCharArray (); + + length = text.Length; + updateLines (); + } + } + public Text (int capacity) { + buffer = new char[capacity]; + length = 0; + } + + void updateLines () { + if (length == 0) { + lines = new TextLine[] { new TextLine (0, 0, 0) }; + return; + } + + List _lines = new List (); + int start = 0, i = 0; + while (i < length) { + char c = buffer[i]; + if (c == '\r') { + if (++i < length) { + if (buffer[i] == '\n') + _lines.Add (new TextLine (start, i - 1, ++i)); + else + _lines.Add (new TextLine (start, i - 1, i)); + } else + _lines.Add (new TextLine (start, i - 1, i)); + start = i; + } else if (c == '\n') { + if (++i < length) { + if (buffer[i] == '\r') + _lines.Add (new TextLine (start, i - 1, ++i)); + else + _lines.Add (new TextLine (start, i - 1, i)); + } else + _lines.Add (new TextLine (start, i - 1, i)); + start = i; + + } else if (c == '\u0085' || c == '\u2028' || c == '\u2029') + _lines.Add (new TextLine (start, i - 1, i)); + else + i++; + } + + if (start < i) + _lines.Add (new TextLine (start, length, length)); + else + _lines.Add (new TextLine (length, length, length)); + + lines = _lines.ToArray (); + } + + public void Append (ReadOnlySpan str) { + + if (length + str.Length > buffer.Length) { + char[] newbuff = new char[length + str.Length]; + Span tmp = newbuff.AsSpan (); + buffer.AsSpan ().CopyTo (tmp); + str.CopyTo (tmp.Slice (length)); + buffer = newbuff; + } else { + str.CopyTo (buffer.AsSpan ().Slice (length)); + } + length = length + str.Length; + } + public void Insert (ReadOnlySpan str, int start) { + if (length + str.Length > buffer.Length) { + char[] newbuff = new char[length + str.Length]; + Span tmp = newbuff.AsSpan (); + buffer.AsSpan ().Slice (0, start).CopyTo (tmp); + tmp = tmp.Slice (start); + str.CopyTo (tmp); + tmp = tmp.Slice (str.Length); + buffer.AsSpan ().Slice (start, length - start).CopyTo (tmp); + buffer = newbuff; + } else { + buffer.AsSpan ().Slice (start, length - start).CopyTo (buffer.AsSpan ().Slice (start + str.Length)); + str.CopyTo (buffer.AsSpan ().Slice (start, str.Length)); + } + length = length + str.Length; + } + public void Remove (int start, int length) { + Span tmp = buffer.AsSpan (); + int end = Math.Min (this.length, start + length); + if (end < this.length) + tmp.Slice (end, this.length - end).CopyTo (tmp.Slice (start, this.length - end)); + this.length = start + (this.length - end); + } + + public void Update (TextChange change) { + + } + + public override string ToString () => new string (buffer, 0, length); + } +} diff --git a/Crow/src/Text/TextChange.cs b/Crow/src/Text/TextChange.cs new file mode 100644 index 00000000..636c732b --- /dev/null +++ b/Crow/src/Text/TextChange.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Crow.Text +{ + public struct TextChange + { + public readonly int Start; + public readonly int Length; + public readonly string ChangedText; + + public int End => Start + Length; + public TextChange (int position, int length, string changedText) { + Start = position; + Length = length; + ChangedText = changedText; + } + } +} diff --git a/Crow/src/Text/TextLine.cs b/Crow/src/Text/TextLine.cs new file mode 100644 index 00000000..53190748 --- /dev/null +++ b/Crow/src/Text/TextLine.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace Crow.Text +{ + [DebuggerDisplay ("{Start}, {Length}, {LengthInPixel}")] + public struct TextLine : IComparable + { + public int Start; + public int Length; + public int LengthIncludingLineBreak; + public int LengthInPixel; + public int End => Start + Length; + public int EndIncludingLineBreak => Start + LengthIncludingLineBreak; + public int LineBreakLength => LengthIncludingLineBreak - Length; + public bool HasLineBreak => LineBreakLength > 0; + public TextLine (int start, int end, int endIncludingLineBreak) { + Start = start; + Length = end - start; + LengthIncludingLineBreak = endIncludingLineBreak - start; + LengthInPixel = -1; + } + public TextLine (int start) { + Start = start; + Length = 0; + LengthIncludingLineBreak = 0; + LengthInPixel = -1; + } + public TextLine WithStartOffset (int start) => new TextLine (Start + start, End, EndIncludingLineBreak); + + public int CompareTo (TextLine other) => Start - other.Start; + /*public ReadOnlySpan GetSubString (string str) { + if (Start >= str.Length) + return "".AsSpan(); + return str.Length - Start < Length ? + str.AsSpan().Slice (Start, Length) : + str.AsSpan().Slice (Start); +} +public ReadOnlySpan GetSubStringIncludingLineBreak (string str) { + if (Start >= str.Length) + return "".AsSpan (); + return (str.Length - Start < LengthIncludingLineBreak) ? + str.AsSpan ().Slice (Start, LengthIncludingLineBreak) : + str.AsSpan ().Slice (Start); +}*/ + } +} diff --git a/Crow/src/Text/TextLineCollection.cs b/Crow/src/Text/TextLineCollection.cs new file mode 100644 index 00000000..a505409f --- /dev/null +++ b/Crow/src/Text/TextLineCollection.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace Crow.Text +{ + public class LineCollection : IList + { + TextLine[] lines; + int length; + + #region CTOR + public LineCollection (int capacity) { + lines = new TextLine[capacity]; + length = 0; + } + public LineCollection (TextLine[] _lines, int capacity = -1) { + if (capacity >= _lines.Length) { + lines = new TextLine[capacity]; + _lines.AsSpan ().CopyTo (lines); + } else + lines =_lines; + + length = _lines.Length; + } + #endregion + + public int GetAbsolutePosition (CharLocation loc) => lines[loc.Line].Start + loc.Column; + public CharLocation GetLocation (int absolutePosition) { + TextLine tl = new TextLine (absolutePosition); + int result = lines.AsSpan (0, length).BinarySearch (tl); + if (result < 0) { + result = ~result; + return result == 0 ? + new CharLocation (0, absolutePosition) : + new CharLocation (result - 1, absolutePosition - lines[result - 1].Start); + } + return new CharLocation (result, absolutePosition - lines[result].Start); + } + public void UpdateLineLengthInPixel (int index, int lengthInPixel) { + lines[index].LengthInPixel = lengthInPixel; + } + public int Count => length; + public bool IsReadOnly => false; + + public TextLine this[int index] { get => lines[index]; set => lines[index] = value; } + + public void Add (TextLine item) { + if (lines.Length < length + 1) { + TextLine[] tmp = new TextLine[length + 4]; + lines.AsSpan ().CopyTo (tmp); + lines = tmp; + } + lines[length] = item; + length++; + } + + public void Clear () { + length = 0; + } + + public bool Contains (TextLine item) => Array.IndexOf (lines, item) >= 0; + + public void CopyTo (TextLine[] array, int arrayIndex) { + lines.AsSpan (0, length).CopyTo (array.AsSpan (arrayIndex)); + } + + public bool Remove (TextLine item) { + int idx = Array.IndexOf (lines, item); + if (idx < 0) + return false; + if (idx + 1 < length) + lines.AsSpan (idx + 1, length - idx - 1).CopyTo (lines.AsSpan (idx)); + length--; + return true; + } + public IEnumerator GetEnumerator () => new LineEnumerator (this); + IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); + + public int IndexOf (TextLine item) { + throw new NotImplementedException (); + } + + public void Insert (int index, TextLine item) { + throw new NotImplementedException (); + } + + public void RemoveAt (int index) { + throw new NotImplementedException (); + } + + public class LineEnumerator : IEnumerator + { + TextLine[] lines; + int length, position = -1; + public LineEnumerator (LineCollection coll) { + lines = coll.lines; + length = coll.length; + } + public TextLine Current => lines[position]; + object IEnumerator.Current => Current; + public void Dispose () { } + public bool MoveNext () => ++position < length; + public void Reset () { + position = -1; + } + } + } +} diff --git a/Crow/src/Text/TextSpan.cs b/Crow/src/Text/TextSpan.cs new file mode 100644 index 00000000..486d8e23 --- /dev/null +++ b/Crow/src/Text/TextSpan.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Crow.Text +{ + public struct TextSpan + { + public readonly int Start; + public readonly int End; + public TextSpan (int start, int end) { + Start = start; + End = end; + } + + public bool IsEmpty => Start == End; + public int Length => End - Start; + } +} diff --git a/Crow/src/Widgets/Label.cs b/Crow/src/Widgets/Label.cs index 486f58dc..fc261cb5 100644 --- a/Crow/src/Widgets/Label.cs +++ b/Crow/src/Widgets/Label.cs @@ -12,162 +12,9 @@ using System.Text; using System.Diagnostics; using Glfw; using System.Collections; +using Crow.Text; 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 class LineCollection : ICollection - { - LineSpan[] lines; - - public int Count => throw new NotImplementedException (); - - public bool IsReadOnly => throw new NotImplementedException (); - - public void Add (LineSpan item) { - throw new NotImplementedException (); - } - - public void Clear () { - throw new NotImplementedException (); - } - - public bool Contains (LineSpan item) { - throw new NotImplementedException (); - } - - public void CopyTo (LineSpan[] array, int arrayIndex) { - throw new NotImplementedException (); - } - - public IEnumerator GetEnumerator () { - throw new NotImplementedException (); - } - - public bool Remove (LineSpan item) { - throw new NotImplementedException (); - } - - IEnumerator IEnumerable.GetEnumerator () { - throw new NotImplementedException (); - } - - /*public LineSpan this[int index] { - get => lines[index]; - set => lines[index] = value; - } - public int Count => lines.Length; - public bool IsReadOnly => false; - - public void Add (LineSpan item) { - - } - - public void Clear () { - throw new NotImplementedException (); - } - - public bool Contains (LineSpan item) { - throw new NotImplementedException (); - } - - public void CopyTo (LineSpan[] array, int arrayIndex) { - throw new NotImplementedException (); - } - - public IEnumerator GetEnumerator () { - throw new NotImplementedException (); - } - - public int IndexOf (LineSpan item) { - throw new NotImplementedException (); - } - - public void Insert (int index, LineSpan item) { - throw new NotImplementedException (); - } - - public bool Remove (LineSpan item) { - throw new NotImplementedException (); - } - - public void RemoveAt (int index) { - throw new NotImplementedException (); - } - - IEnumerator IEnumerable.GetEnumerator () { - throw new NotImplementedException (); - }*/ - } - internal struct LineSpan - { - public int Start; - public int Length; - public int LengthIncludingLineBreak; - public int LengthInPixel; - public int End => Start + Length; - public int EndIncludingLineBreak => Start + LengthIncludingLineBreak; - public int LineBreakLength => LengthIncludingLineBreak - Length; - public bool HasLineBreak => LineBreakLength > 0; - public LineSpan (int start, int end, int endIncludingLineBreak) { - Start = start; - Length = end - start; - LengthIncludingLineBreak = endIncludingLineBreak - start; - LengthInPixel = -1; - } - public LineSpan WithStartOffset (int start) => new LineSpan (Start + start, End, EndIncludingLineBreak); - /*public ReadOnlySpan GetSubString (string str) { - if (Start >= str.Length) - return "".AsSpan(); - return str.Length - Start < Length ? - str.AsSpan().Slice (Start, Length) : - str.AsSpan().Slice (Start); - } - public ReadOnlySpan GetSubStringIncludingLineBreak (string str) { - if (Start >= str.Length) - return "".AsSpan (); - return (str.Length - Start < LengthIncludingLineBreak) ? - str.AsSpan ().Slice (Start, LengthIncludingLineBreak) : - str.AsSpan ().Slice (Start); - }*/ - } /// /// Simple label widget possibly multiline but without tabulation handling. /// @@ -184,15 +31,13 @@ namespace Crow { public event EventHandler TextChanged; public virtual void OnTextChanged(Object sender, TextChangeEventArgs e) - { - textMeasureIsUpToDate = false; - NotifyValueChanged ("Text", Text); + { TextChanged.Raise (this, e); } //TODO:change protected to private #region private and protected fields - string _text; + protected string _text; TextAlignment _textAlignment; bool horizontalStretch; bool verticalStretch; @@ -204,9 +49,9 @@ namespace Crow { //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 CharLocation? hoverLoc = null; + protected CharLocation? currentLoc = null; + protected CharLocation? selectionStart = null; //selection start (row,column) protected FontExtents fe; protected TextExtents te; @@ -239,18 +84,6 @@ namespace Crow { RegisterForRedraw (); } } - void resetLocationXs () { - if (currentLoc.HasValue) { - CharLocation cl = currentLoc.Value; - cl.VisualCharXPosition = -1; - currentLoc = cl; - } - if (selectionStart.HasValue) { - CharLocation cl = selectionStart.Value; - cl.VisualCharXPosition = -1; - selectionStart = cl; - } - } /// /// If measure is not 'Fit', align text inside the bounds of this label. /// @@ -281,18 +114,21 @@ namespace Crow { if (_text.AsSpan ().SequenceEqual (value.AsSpan ())) return; + int oldTextLength = string.IsNullOrEmpty (_text) ? 0 : _text.Length; _text = value; getLines (); - OnTextChanged (this, new TextChangeEventArgs (Text)); + textMeasureIsUpToDate = false; + NotifyValueChanged ("Text", Text); + OnTextChanged (this, new TextChangeEventArgs (new TextChange (0, oldTextLength, _text))); RegisterForGraphicUpdate (); } } /// /// If 'true', linebreaks will be interpreted. If 'false', linebreaks are threated as unprintable - /// unicode characters. + /// unicode characters. Default value is 'False'. /// [DefaultValue(false)] public bool Multiline @@ -467,7 +303,7 @@ namespace Crow { targetColumn = -1; CharLocation loc = currentLoc.Value; if (loc.Column == lines[loc.Line].Length) { - if (loc.Line == lines.Length - 1) + if (loc.Line == lines.Count - 1) return false; currentLoc = new CharLocation (loc.Line + 1, 0); } else @@ -495,7 +331,7 @@ namespace Crow { } public bool MoveDown () { CharLocation loc = currentLoc.Value; - if (loc.Line == lines.Length - 1) + if (loc.Line == lines.Count - 1) return false; if (loc.Column > lines[loc.Line + 1].Length) { @@ -511,22 +347,8 @@ namespace Crow { return true; } + /* - /// - /// Moves cursor one char to the right. - /// - /// true if move succeed - public bool MoveRight(){ - int tmp = _currentCol + 1; - if (tmp > lines [_currentLine].Length){ - if (CurrentLine == lines.Length - 1) - return false; - CurrentLine++; - CurrentColumn = 0; - } else - CurrentColumn = tmp; - return true; - } public void GotoWordStart(){ CurrentColumn--; //skip white spaces @@ -624,54 +446,96 @@ namespace Crow { */ bool textMeasureIsUpToDate = false; Size cachedTextSize = default(Size); - LineSpan[] lines; - void getLines () { + protected LineCollection lines; + protected void getLines () { if (string.IsNullOrEmpty (_text)) { - lines = new LineSpan[] { new LineSpan (0, 0, 0) }; + lines = new LineCollection(new TextLine[] { new TextLine (0, 0, 0) }); return; } if (!_multiline) { - lines = new LineSpan[] { new LineSpan (0, _text.Length, _text.Length) }; + lines = new LineCollection (new TextLine[] { new TextLine (0, _text.Length, _text.Length) }); return; } - List _lines = new List (); + 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)); + _lines.Add (new TextLine (start, i - 1, ++i)); else - _lines.Add (new LineSpan (start, i - 1, i)); + _lines.Add (new TextLine (start, i - 1, i)); } else - _lines.Add (new LineSpan (start, i - 1, i)); + _lines.Add (new TextLine (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)); + _lines.Add (new TextLine (start, i - 1, ++i)); else - _lines.Add (new LineSpan (start, i - 1, i)); + _lines.Add (new TextLine (start, i - 1, i)); } else - _lines.Add (new LineSpan (start, i - 1, i)); + _lines.Add (new TextLine (start, i - 1, i)); start = i; } else if (c == '\u0085' || c == '\u2028' || c == '\u2029') - _lines.Add (new LineSpan (start, i - 1, i)); + _lines.Add (new TextLine (start, i - 1, i)); else i++; } if (start < i) - _lines.Add (new LineSpan (start, _text.Length, _text.Length)); + _lines.Add (new TextLine (start, _text.Length, _text.Length)); else - _lines.Add (new LineSpan (_text.Length, _text.Length, _text.Length)); + _lines.Add (new TextLine (_text.Length, _text.Length, _text.Length)); - lines = _lines.ToArray (); + lines = new LineCollection (_lines.ToArray ()); + } + void resetLocationXs () { + if (currentLoc.HasValue) { + CharLocation cl = currentLoc.Value; + cl.VisualCharXPosition = -1; + currentLoc = cl; + } + if (selectionStart.HasValue) { + CharLocation cl = selectionStart.Value; + cl.VisualCharXPosition = -1; + selectionStart = cl; + } + } + double getX (int clientWidth, TextLine ls) { + switch (TextAlignment) { + case TextAlignment.Right: + return clientWidth - ls.LengthInPixel; + case TextAlignment.Center: + return clientWidth / 2 - ls.LengthInPixel / 2; + } + return 0; } + protected TextSpan Selection { + get { + CharLocation selStart = currentLoc.Value, selEnd = currentLoc.Value; + if (selectionStart.HasValue) { + 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; + } + } + return new TextSpan (lines.GetAbsolutePosition (selStart), lines.GetAbsolutePosition (selEnd)); + } + } #region GraphicObject overrides public override int measureRawSize(LayoutingType lt) @@ -691,17 +555,17 @@ namespace Crow { fe = gr.FontExtents; te = new TextExtents (); - cachedTextSize.Height = (int)Math.Ceiling ((fe.Ascent+fe.Descent) * Math.Max (1, lines.Length)); + cachedTextSize.Height = (int)Math.Ceiling ((fe.Ascent+fe.Descent) * Math.Max (1, lines.Count)); TextExtents tmp = default; int longestLine = 0; - for (int i = 0; i < lines.Length; i++) { + for (int i = 0; i < lines.Count; i++) { if (lines[i].LengthInPixel < 0) { if (lines[i].Length == 0) - lines[i].LengthInPixel = 0;// (int)Math.Ceiling (fe.MaxXAdvance); + lines.UpdateLineLengthInPixel (i, 0);// (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); + lines.UpdateLineLengthInPixel (i, (int)Math.Ceiling (tmp.XAdvance)); } } if (lines[i].LengthInPixel > lines[longestLine].LengthInPixel) @@ -714,15 +578,6 @@ namespace Crow { 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); @@ -783,7 +638,7 @@ namespace Crow { TextExtents extents; Span bytes = stackalloc byte[128]; - for (int i = 0; i < lines.Length; i++) { + for (int i = 0; i < lines.Count; i++) { int encodedBytes = -1; if (lines[i].Length > 0) { @@ -796,12 +651,12 @@ namespace Crow { if (lines[i].LengthInPixel < 0) { gr.TextExtents (bytes.Slice (0, encodedBytes), out extents); - lines[i].LengthInPixel = (int)extents.XAdvance; + lines.UpdateLineLengthInPixel (i, (int)extents.XAdvance); } } Rectangle lineRect = new Rectangle ( - Width.IsFit && !Multiline ? cb.X : (int)getX (cb.Width, ref lines[i]) + cb.X, + Width.IsFit && !Multiline ? cb.X : (int)getX (cb.Width, lines[i]) + cb.X, cb.Y + i * lineHeight, lines[i].LengthInPixel, lineHeight); @@ -860,7 +715,7 @@ namespace Crow { base.onFocused (sender, e); if (currentLoc == null) { selectionStart = new CharLocation (0, 0); - currentLoc = new CharLocation (lines.Length - 1, lines[lines.Length - 1].Length); + currentLoc = new CharLocation (lines.Count - 1, lines[lines.Count - 1].Length); } RegisterForRedraw (); } @@ -875,7 +730,7 @@ namespace Crow { 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; + (int)Math.Min (Math.Max (0, Math.Floor (mouseLocalPos.Y / (fe.Ascent + fe.Descent))), lines.Count - 1) : 0; hoverLoc = new CharLocation (hoverLine, -1, mouseLocalPos.X); if (HasFocus && IFace.IsDown (Glfw.MouseButton.Left)) { @@ -946,7 +801,7 @@ namespace Crow { break; case Key.End: checkShift (); - int l = IFace.Ctrl ? lines.Length - 1 : currentLoc.Value.Line; + int l = IFace.Ctrl ? lines.Count - 1 : currentLoc.Value.Line; currentLoc = new CharLocation (l, lines[l].Length); RegisterForRedraw (); break; @@ -1008,9 +863,9 @@ namespace Crow { CharLocation loc = location.Value; if (loc.HasVisualX) return; - LineSpan ls = lines[loc.Line]; - ReadOnlySpan curLine = _text.GetLine (lines[loc.Line]); - double cPos = Width.IsFit && !Multiline ? 0 : getX (clientWidth, ref ls); + TextLine ls = lines[loc.Line]; + ReadOnlySpan curLine = _text.GetLine (ls); + double cPos = Width.IsFit && !Multiline ? 0 : getX (clientWidth, ls); if (loc.Column >= 0) { loc.VisualCharXPosition = gr.TextExtents (curLine.Slice (0, loc.Column), Interface.TAB_SIZE).XAdvance + cPos; diff --git a/Crow/src/Widgets/OldLabel.cs b/Crow/src/Widgets/OldLabel.cs index 080bc5a0..ed87d712 100644 --- a/Crow/src/Widgets/OldLabel.cs +++ b/Crow/src/Widgets/OldLabel.cs @@ -128,7 +128,7 @@ namespace Crow { lines = getLines; - OnTextChanged (this, new TextChangeEventArgs (Text)); + OnTextChanged (this, new TextChangeEventArgs (default)); RegisterForGraphicUpdate (); } } @@ -380,7 +380,7 @@ namespace Crow { lines [CurrentLine] += lines [CurrentLine + 1]; lines.RemoveAt (CurrentLine + 1); - OnTextChanged (this, new TextChangeEventArgs (Text)); + OnTextChanged (this, new TextChangeEventArgs (default)); return; } CurrentColumn--; @@ -403,7 +403,7 @@ namespace Crow { SelBegin = -1; SelRelease = -1; } - OnTextChanged (this, new TextChangeEventArgs (Text)); + OnTextChanged (this, new TextChangeEventArgs (default)); } /// /// Insert new string at caret position, should be sure no line break is inside. @@ -426,7 +426,7 @@ namespace Crow { lines [CurrentLine] = lines [CurrentLine].Insert (CurrentColumn, str); CurrentColumn += str.Length; } - OnTextChanged (this, new TextChangeEventArgs (Text)); + OnTextChanged (this, new TextChangeEventArgs (default)); } /// /// Insert a line break. @@ -437,7 +437,7 @@ namespace Crow { lines [CurrentLine] = lines [CurrentLine].Substring (0, CurrentColumn); CurrentLine++; CurrentColumn = 0; - OnTextChanged (this, new TextChangeEventArgs (Text)); + OnTextChanged (this, new TextChangeEventArgs (default)); } bool textMeasureIsUpToDate = false; Size cachedTextSize = default(Size); diff --git a/Crow/src/Widgets/OldTextBox.cs b/Crow/src/Widgets/OldTextBox.cs index 8c0fce1f..f209746f 100644 --- a/Crow/src/Widgets/OldTextBox.cs +++ b/Crow/src/Widgets/OldTextBox.cs @@ -59,7 +59,7 @@ namespace Crow if (Multiline) InsertLineBreak (); else - OnTextChanged(this,new TextChangeEventArgs(Text)); + OnTextChanged(this,new TextChangeEventArgs(default)); break; case Key.Escape: Text = ""; diff --git a/Crow/src/Widgets/TextBox.cs b/Crow/src/Widgets/TextBox.cs index f33f89d0..3564ee49 100644 --- a/Crow/src/Widgets/TextBox.cs +++ b/Crow/src/Widgets/TextBox.cs @@ -3,7 +3,9 @@ // This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) using Crow.Cairo; +using Crow.Text; using Glfw; +using System; namespace Crow { @@ -19,131 +21,49 @@ namespace Crow public override void onKeyDown (object sender, KeyEventArgs e) { Key key = e.Key; - + TextSpan selection = Selection; switch (key) { - /*case Key.Backspace: - if (CurrentPosition == 0) - return; - DeleteChar(); - break; - case Key.Delete: - if (selectionIsEmpty) { - if (!MoveRight ()) + case Key.Backspace: + if (selection.Length == 0) { + if (selection.Start == 0) return; - }else if (IFace.Shift) - IFace.Clipboard = SelectedText; - DeleteChar (); - break; - case Key.KeypadEnter: - case Key.Enter: - if (!selectionIsEmpty) - DeleteChar (); - if (Multiline) - InsertLineBreak (); - else - OnTextChanged(this,new TextChangeEventArgs(Text)); + update (new TextChange (selection.Start - 1, 1, "")); + } else + update (new TextChange (selection.Start, selection.Length, "")); break; - case Key.Escape: - Text = ""; - CurrentColumn = 0; - SelRelease = -1; - break; - case Key.Home: - if (IFace.Shift) { - if (selectionIsEmpty) - SelBegin = new Point (CurrentColumn, CurrentLine); - if (IFace.Ctrl) - CurrentLine = 0; - CurrentColumn = 0; - SelRelease = new Point (CurrentColumn, CurrentLine); - break; - } - SelRelease = -1; - if (IFace.Ctrl) - CurrentLine = 0; - CurrentColumn = 0; - break; - case Key.End: - if (IFace.Shift) { - if (selectionIsEmpty) - SelBegin = CurrentPosition; - if (IFace.Ctrl) - CurrentLine = int.MaxValue; - CurrentColumn = int.MaxValue; - SelRelease = CurrentPosition; - break; + case Key.Delete: + if (selection.Length == 0) { + if (selection.Start == Text.Length) + return; + update (new TextChange (selection.Start, 1, "")); + } else { + if (IFace.Shift) + IFace.Clipboard = Text.AsSpan(selection.Start, selection.End).ToString(); + update (new TextChange (selection.Start, selection.Length, "")); } - SelRelease = -1; - if (IFace.Ctrl) - CurrentLine = int.MaxValue; - CurrentColumn = int.MaxValue; break; case Key.Insert: if (IFace.Shift) - this.Insert (IFace.Clipboard); - else if (IFace.Ctrl && !selectionIsEmpty) - IFace.Clipboard = this.SelectedText; - break; - case Key.Left: - if (IFace.Shift) { - if (selectionIsEmpty) - SelBegin = new Point(CurrentColumn, CurrentLine); - if (IFace.Ctrl) - GotoWordStart (); - else if (!MoveLeft ()) - return; - SelRelease = CurrentPosition; - break; - } - SelRelease = -1; - if (IFace.Ctrl) - GotoWordStart (); - else - MoveLeft(); + update (new TextChange (selection.Start, selection.Length, IFace.Clipboard)); + else if (IFace.Ctrl && !selection.IsEmpty) + IFace.Clipboard = Text.AsSpan (selection.Start, selection.End).ToString (); break; - case Key.Right: - if (IFace.Shift) { - if (selectionIsEmpty) - SelBegin = CurrentPosition; - if (IFace.Ctrl) - GotoWordEnd (); - else if (!MoveRight ()) - return; - SelRelease = CurrentPosition; - break; - } - SelRelease = -1; - if (IFace.Ctrl) - GotoWordEnd (); + case Key.KeypadEnter: + case Key.Enter: + if (Multiline) + update (new TextChange (selection.Start, selection.Length, "\n")); else - MoveRight (); - break; - case Key.Up: - if (IFace.Shift) { - if (selectionIsEmpty) - SelBegin = CurrentPosition; - CurrentLine--; - SelRelease = CurrentPosition; - break; - } - SelRelease = -1; - CurrentLine--; + OnTextChanged(this,new TextChangeEventArgs(default)); break; - case Key.Down: - if (IFace.Shift) { - if (selectionIsEmpty) - SelBegin = CurrentPosition; - CurrentLine++; - SelRelease = CurrentPosition; - break; - } - SelRelease = -1; - CurrentLine++; + case Key.Escape: + selectionStart = null; + currentLoc = lines.GetLocation (selection.Start); + RegisterForRedraw (); + break; + case Key.Tab: + update (new TextChange (selection.Start, selection.Length, "\t")); break; - case Key.Tab: - this.Insert ("\t"); - break;*/ default: base.onKeyDown (sender, e); break; @@ -154,6 +74,9 @@ namespace Crow { base.onKeyPress (sender, e); + TextSpan selection = Selection; + update (new TextChange (selection.Start, selection.Length, e.KeyChar.ToString ())); + /*Insert (e.KeyChar.ToString()); SelRelease = -1; @@ -162,5 +85,17 @@ namespace Crow RegisterForGraphicUpdate();*/ } #endregion + + void update(TextChange change) { + Span tmp = stackalloc char[Text.Length + (change.ChangedText.Length - change.Length)]; + ReadOnlySpan src = Text.AsSpan (); + src.Slice (0, change.Start).CopyTo (tmp); + change.ChangedText.AsSpan ().CopyTo (tmp.Slice (change.Start)); + src.Slice (change.End).CopyTo (tmp.Slice (change.Start + change.ChangedText.Length)); + Text = tmp.ToString (); + + selectionStart = null; + currentLoc = lines.GetLocation (change.Start + change.ChangedText.Length); + } } } diff --git a/Crow/src/Widgets/Widget.cs b/Crow/src/Widgets/Widget.cs index e76c807f..8a844ed0 100644 --- a/Crow/src/Widgets/Widget.cs +++ b/Crow/src/Widgets/Widget.cs @@ -1212,7 +1212,7 @@ namespace Crow { if (pi.GetSetMethod () == null) return; - XmlIgnoreAttribute xia = (XmlIgnoreAttribute)pi.GetCustomAttribute (typeof (XmlIgnoreAttribute)); + XmlIgnoreAttribute xia = pi.GetCustomAttribute (); if (xia != null) return; diff --git a/Samples/PerfTests/Program.cs b/Samples/PerfTests/Program.cs index dcc412ed..d6ada8b4 100644 --- a/Samples/PerfTests/Program.cs +++ b/Samples/PerfTests/Program.cs @@ -32,7 +32,7 @@ namespace PerfTests System.Runtime.Loader.AssemblyLoadContext.Default.ResolvingUnmanagedDll+=resolveUnmanaged; } #endif - readonly int count = 1, updateCycles = 0; + readonly int count = 10, updateCycles = 0; readonly bool screenOutput = false; readonly string inDirectory = null;//directory to test readonly string outFilePath; @@ -68,7 +68,7 @@ namespace PerfTests Console.WriteLine ("-i,--input:\n\tInput directory to search recursively for '.crow' file to test. If ommitted, builtin unit tests are performs"); Console.WriteLine ("-w,--width:\n\toutput surface width, not displayed on screen."); Console.WriteLine ("-h,--height:\n\toutput surface height, not displayed on screen."); - Console.WriteLine ("-c,--count:\n\trepeat each test 'c' times."); + Console.WriteLine ("-c,--count:\n\trepeat each test 'c' times. (default = 10, minimum = 5"); Console.WriteLine ("-b,--begin:\n\tStarting stage for measures, may be the stage name or stage index"); foreach (Stage s in Enum.GetValues(typeof(Stage))) { @@ -76,7 +76,7 @@ namespace PerfTests } Console.WriteLine ("-e,--end:\n\tEnding stage for measures, may be the stage name or stage index"); Console.WriteLine ("-r,--reset:\n\tenable clear iterators after each test file."); - Console.WriteLine ("-u,--update:\n\tmeasure 'n' update cycle with DateTime.Now string notified."); + Console.WriteLine ("-u,--update:\n\tmeasure 'n' update cycle with elapsed ticks string notified. (default = 0)"); Console.WriteLine ("-s,--screen:\n\tenable output to screen."); Console.WriteLine ("--help:\n\tthis help message."); } @@ -100,7 +100,7 @@ namespace PerfTests break; case "-c": case "--count": - count = int.Parse (args[i++]); + count = Math.Max(5, int.Parse (args[i++])); break; case "-w": case "--width": @@ -511,10 +511,13 @@ namespace PerfTests public static void Main (string [] args) { - try { + + try { using (TestInterface iface = new TestInterface (args)) { - iface.PerformUnitTests (); - iface.PerformTests (); + if (string.IsNullOrEmpty(iface.inDirectory)) + iface.PerformUnitTests (); + else + iface.PerformTests (); } } catch (Exception) { } diff --git a/Samples/PerfTests/Properties/launchSettings.json b/Samples/PerfTests/Properties/launchSettings.json index 49325a0c..0d2830a2 100644 --- a/Samples/PerfTests/Properties/launchSettings.json +++ b/Samples/PerfTests/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "PerfTests": { "commandName": "Project", - "commandLineArgs": "-c 20 -i Interfaces/PerfLabels" + "commandLineArgs": "-c 10 -b 0 -e 1 " } } } \ No newline at end of file -- 2.47.3