aboutsummaryrefslogtreecommitdiffstatsci
path: root/Samples/common/src/Editor.cs
diff options
context:
space:
mode:
authorJean-Philippe Bruyère <jp_bruyere@hotmail.com>2021-06-05 16:52:53 +0200
committerJean-Philippe Bruyère <jp_bruyere@hotmail.com>2021-06-05 16:52:53 +0200
commitf738cf0f78c57750cce8fd5744825b0591f24bb8 (patch)
tree47c3313d26151e53cbef822ac602132fb77b88dd /Samples/common/src/Editor.cs
parenta515e31998f64f537e29cd30fdb7e9bfc7d4a02f (diff)
ItemsScroller for list, layouting changes, editor suggestions
Diffstat (limited to 'Samples/common/src/Editor.cs')
-rw-r--r--Samples/common/src/Editor.cs401
1 files changed, 401 insertions, 0 deletions
diff --git a/Samples/common/src/Editor.cs b/Samples/common/src/Editor.cs
new file mode 100644
index 00000000..b9b0ae90
--- /dev/null
+++ b/Samples/common/src/Editor.cs
@@ -0,0 +1,401 @@
+// Copyright (c) 2013-2021 Bruyère Jean-Philippe <jp_bruyere@hotmail.com>
+//
+// This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
+
+using System;
+using Glfw;
+using Crow.Text;
+using System.Collections.Generic;
+using Crow.Cairo;
+using System.Threading.Tasks;
+using System.Linq;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+
+namespace Crow
+{
+ public class Editor : TextBox {
+ XmlSource source;
+ object TokenMutex = new object();
+
+ void parse () {
+ XmlSource tmp = new XmlSource(_text);
+ lock(TokenMutex)
+ source = tmp;
+ RegisterForGraphicUpdate();
+ }
+ protected override void onInitialized(object sender, EventArgs e)
+ {
+ base.onInitialized(sender, e);
+
+ }
+ ListBox overlay;
+ List<String> suggestions;
+ public List<String> Suggestions {
+ get => suggestions;
+ set {
+ suggestions = value;
+ NotifyValueChangedAuto (suggestions);
+ if (suggestions == null || suggestions.Count == 0)
+ hideOverlay ();
+ else
+ showOverlay ();
+ }
+ }
+ bool suggestionsActive => overlay != null && overlay.IsVisible;
+ Token currentToken;
+ SyntaxNode currentNode;
+ string[] allWidgetNames = typeof (Widget).Assembly.GetExportedTypes ().Where(t=>typeof(Widget).IsAssignableFrom (t))
+ .Select (s => s.Name).ToArray ();
+
+
+ string [] getAllCrowTypeMembers (string crowTypeName) {
+ Type crowType = IML.Instantiator.GetWidgetTypeFromName (crowTypeName);
+ return crowType.GetMembers (BindingFlags.Public | BindingFlags.Instance).
+ Where (m=>((m is PropertyInfo pi && pi.CanWrite) || (m is EventInfo)) &&
+ m.GetCustomAttribute<XmlIgnoreAttribute>() == null).Select (mb=>mb.Name).ToArray();
+ }
+ public override void OnTextChanged(object sender, TextChangeEventArgs e)
+ {
+ base.OnTextChanged(sender, e);
+ //Task.Run(()=>parse());
+
+ parse();
+ tryGetSuggestions ();
+
+ //Console.WriteLine ($"{pos}: {suggestionTok.AsString (_text)} {suggestionTok}");
+ }
+ void tryGetSuggestions () {
+ if (!currentLoc.HasValue)
+ return;
+ int pos = lines.GetAbsolutePosition (CurrentLoc.Value);
+ currentToken = source.FindTokenIncludingPosition (pos);
+ currentNode = source.FindNodeIncludingPosition (pos);
+
+ Console.WriteLine ($"Current Token: {currentToken} Current Node: {currentNode}");
+
+ if (currentToken.Type == TokenType.ElementOpen) {
+ Suggestions = new List<string> (allWidgetNames);
+ } else if (currentToken.Type == TokenType.ElementName) {
+ Suggestions = allWidgetNames.Where (s => s.StartsWith (currentToken.AsString (_text), StringComparison.OrdinalIgnoreCase)).ToList ();
+ } else if (currentNode is AttributeSyntax attribNode) {
+ if (currentNode.Parent is ElementTagSyntax eltTag) {
+ if (currentToken.Type == TokenType.AttributeName && eltTag.NameToken.HasValue) {
+ Suggestions = getAllCrowTypeMembers (eltTag.NameToken.Value.AsString (_text))
+ .Where (s => s.StartsWith (currentToken.AsString (_text), StringComparison.OrdinalIgnoreCase)).ToList ();
+ }
+ }
+ } else if (currentNode is ElementStartTagSyntax eltStartTag) {
+ Suggestions = getAllCrowTypeMembers (eltStartTag.NameToken.Value.AsString (_text)).ToList ();
+ } else {
+ /*SyntaxNode curNode = source.FindNodeIncludingPosition (pos);
+ Console.WriteLine ($"Current Node: {curNode}");
+ if (curNode is ElementStartTagSyntax eltStartTag &&
+ (currentToken.Type != TokenType.ClosingSign && currentToken.Type != TokenType.EmptyElementClosing && currentToken.Type != TokenType.Unknown)) {
+ Suggestions = getAllCrowTypeMembers (eltStartTag.NameToken.Value.AsString (_text)).ToList ();
+ } else*/
+ hideOverlay ();
+ }
+ }
+ void showOverlay () {
+ lock (IFace.UpdateMutex) {
+ if (overlay == null) {
+ overlay = IFace.LoadIMLFragment<ListBox>(@"
+ <ListBox Style='suggestionsListBox' Data='{Suggestions}' />
+ ");
+ overlay.DataSource = this;
+ overlay.Loaded += (sender, arg) => (sender as ListBox).SelectedIndex = 0;
+ } else
+ overlay.IsVisible = true;
+ overlay.RegisterForLayouting(LayoutingType.Sizing);
+ }
+ }
+ void hideOverlay () {
+ if (overlay == null)
+ return;
+ overlay.IsVisible = false;
+ }
+ void completeToken () {
+ string selectedSugg = overlay.SelectedItem as string;
+ if (selectedSugg == null)
+ return;
+ if (currentToken.Type == TokenType.ElementOpen || currentToken.Type == TokenType.WhiteSpace)
+ update (new TextChange (currentToken.End, 0, selectedSugg));
+ else
+ update (new TextChange (currentToken.Start, currentToken.Length, selectedSugg));
+ hideOverlay ();
+ }
+ public override void onMouseDown (object sender, MouseButtonEventArgs e) {
+ hideOverlay ();
+ base.onMouseDown (sender, e);
+ }
+ public override void onKeyDown(object sender, KeyEventArgs e)
+ {
+ if (suggestionsActive) {
+ switch (e.Key) {
+ case Key.Escape:
+ hideOverlay ();
+ return;
+ case Key.Left:
+ case Key.Right:
+ hideOverlay ();
+ break;
+ case Key.End:
+ case Key.Home:
+ case Key.Down:
+ case Key.Up:
+ case Key.PageDown:
+ case Key.PageUp:
+ overlay.onKeyDown (this, e);
+ return;
+ case Key.Tab:
+ completeToken ();
+ return;
+ }
+ } else if (e.Key == Key.Space && IFace.Ctrl) {
+ tryGetSuggestions ();
+ return;
+ }
+
+ TextSpan selection = Selection;
+ if (e.Key == Key.Tab && !selection.IsEmpty) {
+ int lineStart = lines.GetLocation (selection.Start).Line;
+ int lineEnd = lines.GetLocation (selection.End).Line;
+
+ if (IFace.Shift) {
+ for (int l = lineStart; l <= lineEnd; l++) {
+ if (Text[lines[l].Start] == '\t')
+ update (new TextChange (lines[l].Start, 1, ""));
+ else if (Char.IsWhiteSpace (Text[lines[l].Start])) {
+ int i = 1;
+ while (i < lines[l].Length && i < Interface.TAB_SIZE && Char.IsWhiteSpace (Text[i]))
+ i++;
+ update (new TextChange (lines[l].Start, i, ""));
+ }
+ }
+
+ }else{
+ for (int l = lineStart; l <= lineEnd; l++)
+ update (new TextChange (lines[l].Start, 0, "\t"));
+ }
+
+ selectionStart = new CharLocation (lineStart, 0);
+ CurrentLoc = new CharLocation (lineEnd, lines[lineEnd].Length);
+
+ return;
+ }
+ base.onKeyDown(sender, e);
+ }
+
+ protected override void drawContent (Context gr) {
+ try {
+ lock(TokenMutex) {
+ if (source == null || source.Tokens.Length == 0) {
+ base.drawContent (gr);
+ return;
+ }
+
+ Rectangle cb = ClientRectangle;
+ fe = gr.FontExtents;
+ double lineHeight = fe.Ascent + fe.Descent;
+
+ CharLocation selStart = default, selEnd = default;
+ bool selectionNotEmpty = false;
+
+ if (HasFocus) {
+ if (currentLoc?.Column < 0) {
+ updateLocation (gr, cb.Width, ref currentLoc);
+ NotifyValueChanged ("CurrentColumn", CurrentColumn);
+ } else
+ updateLocation (gr, cb.Width, ref currentLoc);
+
+ if (overlay != null && overlay.IsVisible) {
+ Point p = new Point((int)currentLoc.Value.VisualCharXPosition - ScrollX, (int)(lineHeight * (currentLoc.Value.Line + 1) - ScrollY));
+ if (p.Y < 0 || p.X < 0)
+ hideOverlay ();
+ else {
+ p += ScreenCoordinates (Slot).TopLeft;
+ overlay.Left = p.X;
+ overlay.Top = p.Y;
+ }
+ }
+ if (selectionStart.HasValue) {
+ updateLocation (gr, cb.Width, ref selectionStart);
+ if (CurrentLoc.Value != selectionStart.Value)
+ selectionNotEmpty = true;
+ }
+ if (selectionNotEmpty) {
+ 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
+ IFace.forceTextCursor = true;
+ }
+
+ double spacePixelWidth = gr.TextExtents (" ").XAdvance;
+ int x = 0, y = 0;
+ double pixX = cb.Left;
+
+
+ Foreground.SetAsSource (IFace, gr);
+ gr.Translate (-ScrollX, -ScrollY);
+
+
+ ReadOnlySpan<char> sourceBytes = source.Source.AsSpan();
+ Span<byte> bytes = stackalloc byte[128];
+ TextExtents extents;
+ int tokPtr = 0;
+ Token tok = source.Tokens[tokPtr];
+ bool multilineToken = false;
+
+ ReadOnlySpan<char> buff = sourceBytes;
+
+
+ for (int i = 0; i < lines.Count; i++) {
+ //if (!cancelLinePrint (lineHeight, lineHeight * y, cb.Height)) {
+
+ if (multilineToken) {
+ if (tok.End < lines[i].End) {//last incomplete line of multiline token
+ buff = sourceBytes.Slice (lines[i].Start, tok.End - lines[i].Start);
+ } else {//print full line
+ buff = sourceBytes.Slice (lines[i].Start, lines[i].Length);
+ }
+ }
+
+ while (tok.Start < lines[i].End) {
+ if (!multilineToken) {
+ if (tok.End > lines[i].End) {//first line of multiline
+ multilineToken = true;
+ buff = sourceBytes.Slice (tok.Start, lines[i].End - tok.Start);
+ } else
+ buff = sourceBytes.Slice (tok.Start, tok.Length);
+
+ if (tok.Type.HasFlag (TokenType.Punctuation))
+ gr.SetSource(Colors.DarkGrey);
+ else if (tok.Type.HasFlag (TokenType.Trivia))
+ gr.SetSource(Colors.DimGrey);
+ else if (tok.Type == TokenType.ElementName) {
+ gr.SetSource(Colors.Green);
+ }else if (tok.Type == TokenType.AttributeName) {
+ gr.SetSource(Colors.Blue);
+ }else if (tok.Type == TokenType.AttributeValue) {
+ gr.SetSource(Colors.OrangeRed);
+ }else if (tok.Type == TokenType.EqualSign) {
+ gr.SetSource(Colors.Black);
+ }else if (tok.Type == TokenType.PI_Target) {
+ gr.SetSource(Colors.DarkSlateBlue);
+ }else {
+ gr.SetSource(Colors.Red);
+ }
+ }
+
+ int size = buff.Length * 4 + 1;
+ if (bytes.Length < size)
+ bytes = size > 512 ? new byte[size] : stackalloc byte[size];
+
+ int encodedBytes = Crow.Text.Encoding.ToUtf8 (buff, bytes);
+
+ if (encodedBytes > 0) {
+ bytes[encodedBytes++] = 0;
+ gr.TextExtents (bytes.Slice (0, encodedBytes), out extents);
+ gr.MoveTo (pixX, lineHeight * y + fe.Ascent);
+ gr.ShowText (bytes.Slice (0, encodedBytes));
+ pixX += extents.XAdvance;
+ x += buff.Length;
+ }
+
+ if (multilineToken) {
+ if (tok.End < lines[i].End)//last incomplete line of multiline token
+ multilineToken = false;
+ else
+ break;
+ }
+
+ if (++tokPtr >= source.Tokens.Length)
+ break;
+ tok = source.Tokens[tokPtr];
+ }
+
+ if (HasFocus && selectionNotEmpty) {
+ RectangleD lineRect = new RectangleD (cb.X, lineHeight * y + cb.Top, pixX, lineHeight);
+ RectangleD selRect = lineRect;
+
+ if (i >= selStart.Line && i <= selEnd.Line) {
+ if (selStart.Line == selEnd.Line) {
+ selRect.X = selStart.VisualCharXPosition + cb.X;
+ selRect.Width = selEnd.VisualCharXPosition - selStart.VisualCharXPosition;
+ } else if (i == selStart.Line) {
+ double newX = selStart.VisualCharXPosition + cb.X;
+ selRect.Width -= (newX - selRect.X) - 10.0;
+ selRect.X = newX;
+ } else if (i == selEnd.Line)
+ selRect.Width = selEnd.VisualCharXPosition - selRect.X + cb.X;
+ else
+ selRect.Width += 10.0;
+
+ buff = sourceBytes.Slice(lines[i].Start, lines[i].Length);
+ int size = buff.Length * 4 + 1;
+ if (bytes.Length < size)
+ bytes = size > 512 ? new byte[size] : stackalloc byte[size];
+
+ int encodedBytes = Crow.Text.Encoding.ToUtf8 (buff, bytes);
+
+ gr.SetSource (SelectionBackground);
+ gr.Rectangle (selRect);
+ if (encodedBytes < 0)
+ gr.Fill ();
+ else {
+ gr.FillPreserve ();
+ gr.Save ();
+ gr.Clip ();
+ gr.SetSource (SelectionForeground);
+ gr.MoveTo (lineRect.X, lineRect.Y + fe.Ascent);
+ gr.ShowText (bytes.Slice (0, encodedBytes));
+ gr.Restore ();
+ }
+ Foreground.SetAsSource (IFace, gr);
+ }
+ }
+
+ if (!multilineToken) {
+ if (++tokPtr >= source.Tokens.Length)
+ break;
+ tok = source.Tokens[tokPtr];
+ }
+
+ x = 0;
+ pixX = 0;
+
+ y++;
+
+
+ /* } else if (tok2.Type == TokenType.Tabulation) {
+ int spaceRounding = x % tabSize;
+ int spaces = spaceRounding == 0 ?
+ tabSize * tok2.Length :
+ spaceRounding + tabSize * (tok2.Length - 1);
+ x += spaces;
+ pixX += spacePixelWidth * spaces;
+ continue;
+ } else if (tok2.Type == TokenType.WhiteSpace) {
+ x += tok2.Length;
+ pixX += spacePixelWidth * tok2.Length;*/
+ }
+ //gr.Translate (ScrollX, ScrollY);
+ }
+ } catch {
+
+ }
+ }
+ }
+} \ No newline at end of file