using Crow.Cairo;
using System.Text.RegularExpressions;
using System.ComponentModel;
+using System.Text;
namespace Crow {
+ internal struct LineSpan
+ {
+ public readonly int Start;
+ public readonly int End;
+ public readonly int EndIncludingLineBreak;
+ public int Length => End - Start;
+ public int LengthIncludingLineBreak => EndIncludingLineBreak - Start;
+ public int LineBreakLength => EndIncludingLineBreak - End;
+ public LineSpan (int start, int end, int endIncludingLineBreak) {
+ Start = start;
+ End = end;
+ EndIncludingLineBreak = endIncludingLineBreak;
+ }
+ public LineSpan WithStartOffset (int start) => new LineSpan (Start + start, End, EndIncludingLineBreak);
+ /*public ReadOnlySpan<char> 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<char> GetSubStringIncludingLineBreak (string str) {
+ if (Start >= str.Length)
+ return "".AsSpan ();
+ return (str.Length - Start < LengthIncludingLineBreak) ?
+ str.AsSpan ().Slice (Start, LengthIncludingLineBreak) :
+ str.AsSpan ().Slice (Start);
+ }*/
+ }
public class Label : Widget
{
#region CTOR
//TODO:change protected to private
#region private and protected fields
- string _text = "label";
+ string _text;
Alignment _textAlignment;
bool horizontalStretch;
bool verticalStretch;
[DefaultValue("label")]
public string Text
{
- get {
- return lines == null ?
- _text : lines.Aggregate((i, j) => i + Interface.LineBreak + j);
- }
+ get => _text;
set
{
- if (string.Equals (value, _text, StringComparison.Ordinal))
+ if (_text.AsSpan ().SequenceEqual (value.AsSpan ()))
return;
_text = value;
- if (string.IsNullOrEmpty(_text))
- _text = "";
-
lines = getLines;
OnTextChanged (this, new TextChangeEventArgs (Text));
[DefaultValue(false)]
public bool Selectable
{
- get { return _selectable; }
+ get => _selectable;
set
{
if (value == _selectable)
[DefaultValue(false)]
public bool Multiline
{
- get { return _multiline; }
+ get => _multiline;
set
{
if (value == _multiline)
gr.FontOptions = Interface.FontRenderingOptions;
gr.Antialias = Interface.Antialias;
try {
- string l = lines [_currentLine];
- if (_currentCol < l.Length)
+ //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 (l).XAdvance);
+ 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");
return 0;
/// <summary>
/// return char at CurrentLine, CurrentColumn
/// </summary>
- [XmlIgnore]protected Char CurrentChar
- {
- get {
- return lines [CurrentLine][CurrentColumn];
- }
- }
+ [XmlIgnore]protected Char CurrentChar => _text[lines[CurrentLine].Start + CurrentColumn];
/// <summary>
/// ordered selection start and end positions in char units
/// </summary>
if (SelRelease < 0 || SelBegin < 0)
return "";
if (selectionStart.Y == selectionEnd.Y)
- return lines [selectionStart.Y].Substring (selectionStart.X, selectionEnd.X - selectionStart.X);
- string tmp = "";
- tmp = lines [selectionStart.Y].Substring (selectionStart.X);
- for (int l = selectionStart.Y + 1; l < selectionEnd.Y; l++) {
- tmp += Interface.LineBreak + lines [l];
- }
- tmp += Interface.LineBreak + lines [selectionEnd.Y].Substring (0, selectionEnd.X);
- return tmp;
+ return _text.Substring (lines[selectionStart.Y].Start + selectionStart.X, selectionEnd.X - selectionStart.X);
+
+ StringBuilder tmp = new StringBuilder (_text.GetLineIncludingLineBreak (lines[selectionStart.Y], selectionStart.X).ToString ());
+ for (int l = selectionStart.Y + 1; l < selectionEnd.Y; l++)
+ tmp.Append (_text.GetLineIncludingLineBreak (lines[l]).ToString ());
+ tmp.Append (_text.Substring (lines[selectionEnd.Y].Start, selectionEnd.X));
+ return tmp.ToString ();
}
}
[XmlIgnore]public bool selectionIsEmpty
{ get { return SelRelease < 0; } }
- List<string> lines;
- List<string> getLines {
+ List<LineSpan> lines;
+ List<LineSpan> getLines {
get {
- return _multiline ?
- Regex.Split (_text, "\r\n|\r|\n|\\\\n").ToList() :
- new List<string>(new string[] { _text });
+ if (string.IsNullOrEmpty(_text))
+ return new List<LineSpan> (new LineSpan[] { new LineSpan (0, 0, 0) });
+ if (!_multiline)
+ return new List<LineSpan> (new LineSpan[] { new LineSpan (0, _text.Length, _text.Length) });
+ 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] == '\r' || _text[i] == '\n') {
+ if (lineBreak)
+ continue;
+ lineBreak = true;
+ end = i;
+ }else if (lineBreak) {
+ lines.Add (new LineSpan (start, end, i));
+ start = end = i;
+ lineBreak = false;
+ }
+ }
+ return lines;
}
}
/// <summary>
//skip white spaces
while (!char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn > 0)
CurrentColumn--;
- while (char.IsLetterOrDigit (lines [CurrentLine] [CurrentColumn]) && CurrentColumn > 0)
+ while (char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn > 0)
CurrentColumn--;
if (!char.IsLetterOrDigit (this.CurrentChar))
CurrentColumn++;
return;
CurrentLine--;
CurrentColumn = lines [CurrentLine].Length;
- lines [CurrentLine] += lines [CurrentLine + 1];
- lines.RemoveAt (CurrentLine + 1);
- OnTextChanged (this, new TextChangeEventArgs (Text));
+ Text = _text.Remove (lines[CurrentLine].End, lines[CurrentLine].LineBreakLength);
return;
}
CurrentColumn--;
- lines [CurrentLine] = lines [CurrentLine].Remove (CurrentColumn, 1);
+ 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) {
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));
+ //OnTextChanged (this, new TextChangeEventArgs (Text));
}
/// <summary>
/// Insert new string at caret position, should be sure no line break is inside.
/// <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();
CurrentColumn += strLines[i].Length;
}
} else {
+ ReadOnlySpan<char> a = _text.AsSpan ().Slice (0 ,lines[CurrentLine].Start + CurrentColumn);
+ ReadOnlySpan<char> b = _text.AsSpan ().Slice (lines[CurrentLine].Start + CurrentColumn);
+
+ _text = string.Concat( (a, str.AsSpan(), b);
+
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);
cachedTextSize.Height = (int)Math.Ceiling ((fe.Ascent+fe.Descent) * Math.Max (1, lines.Count));
try {
- for (int i = 0; i < lines.Count; i++) {
- string l = lines[i].Replace ("\t", new String (' ', Interface.TAB_SIZE));
-
- TextExtents tmp = gr.TextExtents (l);
-
+ TextExtents tmp = default;
+ for (int i = 0; i < lines.Count; i++) {
+ gr.TextExtents (_text.GetLine (lines[i]), Interface.TAB_SIZE, out tmp);
if (tmp.XAdvance > te.XAdvance)
te = tmp;
}
#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 (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;
+ }
for (int i = 0; i < lines.Count; i++) {
- string l = lines [i].Replace ("\t", new String (' ', Interface.TAB_SIZE));
- int lineLength = (int)gr.TextExtents (l).XAdvance;
+ if (lines[i].Length == 0)
+ continue;
+ ReadOnlySpan<char> chars = _text.GetLine (lines[i]);
+
+ //string l = lines [i].Replace ("\t", new String (' ', Interface.TAB_SIZE));
+ int lineLength = (int)gr.TextExtents (chars, Interface.TAB_SIZE).XAdvance;
Rectangle lineRect = new Rectangle (
rText.X,
rText.Y + i * (int)(fe.Ascent+fe.Descent),
// TextAlignment == Alignment.TopRight ||
// TextAlignment == Alignment.BottomRight)
// lineRect.X += (rText.Width - lineLength);
- if (string.IsNullOrWhiteSpace (l))
- continue;
Foreground.SetAsSource (IFace, gr);
gr.MoveTo (lineRect.X,(double)rText.Y + fe.Ascent + (fe.Ascent+fe.Descent) * i) ;
- gr.ShowText (l);
+ gr.ShowText (chars, Interface.TAB_SIZE);
gr.Fill ();
if (Selectable) {
gr.Clip ();
gr.SetSource (SelectionForeground);
gr.MoveTo (lineRect.X, rText.Y + fe.Ascent + (fe.Ascent+fe.Descent) * i);
- gr.ShowText (l);
+ gr.ShowText (chars, Interface.TAB_SIZE);
gr.Fill ();
gr.Restore ();
}
double cPos = 0f;
CurrentLine = (int)(mouseLocalPos.Y / (fe.Ascent+fe.Descent));
-
+
//fix cu
if (CurrentLine >= lines.Count)
CurrentLine = lines.Count - 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(lines [CurrentLine]).Width/2.0;
+ 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(lines [CurrentLine]).Width;
+ cPos += ClientRectangle.Width - gr.TextExtents (curLine, Interface.TAB_SIZE).Width;
break;
}
+
for (int i = 0; i < lines[CurrentLine].Length; i++)
{
- string c = lines [CurrentLine].Substring (i, 1);
+ /*string c = .Substring (i, 1);
if (c == "\t")
c = new string (' ', Interface.TAB_SIZE);
return;
}
- cPos += te.XAdvance;
+ cPos += te.XAdvance;*/
}
CurrentColumn = lines[CurrentLine].Length;
textCursorPos = cPos;
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 (l).XAdvance;
+ /*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;
}