diff options
| author | Jean-Philippe Bruyère <jp_bruyere@hotmail.com> | 2021-06-05 16:52:53 +0200 |
|---|---|---|
| committer | Jean-Philippe Bruyère <jp_bruyere@hotmail.com> | 2021-06-05 16:52:53 +0200 |
| commit | f738cf0f78c57750cce8fd5744825b0591f24bb8 (patch) | |
| tree | 47c3313d26151e53cbef822ac602132fb77b88dd /Samples/common/src | |
| parent | a515e31998f64f537e29cd30fdb7e9bfc7d4a02f (diff) | |
ItemsScroller for list, layouting changes, editor suggestions
Diffstat (limited to 'Samples/common/src')
| -rw-r--r-- | Samples/common/src/Editor.cs | 401 | ||||
| -rw-r--r-- | Samples/common/src/ImlParsing/Syntax.cs | 310 | ||||
| -rw-r--r-- | Samples/common/src/ImlParsing/Token.cs | 40 | ||||
| -rw-r--r-- | Samples/common/src/ImlParsing/TokenType.cs | 38 | ||||
| -rw-r--r-- | Samples/common/src/ImlParsing/XmlSource.cs | 260 | ||||
| -rw-r--r-- | Samples/common/src/SampleBase.cs | 383 |
6 files changed, 1432 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 diff --git a/Samples/common/src/ImlParsing/Syntax.cs b/Samples/common/src/ImlParsing/Syntax.cs new file mode 100644 index 00000000..fd410eaf --- /dev/null +++ b/Samples/common/src/ImlParsing/Syntax.cs @@ -0,0 +1,310 @@ +// 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 System.Collections.Generic; +using System.Linq; + +namespace Crow +{ + public class SyntaxException : Exception { + public readonly Token Token; + public SyntaxException(string message, Token token = default, Exception innerException = null) + : base (message, innerException) { + Token = token; + } + } + public class SyntaxAnalyser { + XmlSource source; + IEnumerable<Token> tokens => source.Tokens; + public SyntaxNode Root => CurrentNode; + public SyntaxAnalyser (XmlSource source) { + this.source = source; + //tokens = source.Tokens.Where (t => !t.Type.HasFlag (TokenType.Trivia)); + } + + + /*List<SyntaxNode> readAttributes (IEnumerator<Token> iter) { + List<SyntaxNode> attributes = new List<SyntaxNode> (10); + while (iter.MoveNext ()) { + switch (iter.Current.Type) { + case TokenType.EmptyElementClosing: + case TokenType.ClosingSign: + return attributes; + case TokenType.AttributeName: + Token attribName = iter.Current; + moveNextOrThrow (iter); + Token equalSign = accept (iter, TokenType.EqualSign); + moveNextOrThrow (iter); + Token attribValue = accept (iter, TokenType.AttributeValue); + attributes.Add (new AttributeSyntax (source, attribName, equalSign, attribValue)); + break; + } + } + throw new SyntaxException ("Unexpected end of source"); + } + + Token accept (IEnumerator<Token> iter, TokenType acceptedTokenType) { + if (iter.Current.Type == acceptedTokenType) + return iter.Current; + else + throw new SyntaxException ("Unexpected token", iter.Current); + } + void moveNextOrThrow (IEnumerator<Token> iter) { + if (!iter.MoveNext ()) + throw new SyntaxException ("Unexpected end of source"); + } + ElementStartTagSyntax readElementStart (IEnumerator<Token> iter) { + Token eltOpen = iter.Current; + moveNextOrThrow (iter); + Token eltName = accept (iter, TokenType.ElementName); + + List<SyntaxNode> attributes = readAttributes (iter); + + if (iter.Current.Type == TokenType.EmptyElementClosing || iter.Current.Type == TokenType.ClosingSign) + return new ElementStartTagSyntax(source, eltOpen, eltName, iter.Current, attributes); + + throw new SyntaxException ("Unexpected token", iter.Current); + } + ElementEndTagSyntax readElementEnd (IEnumerator<Token> iter) { + Token eltOpen = iter.Current; + moveNextOrThrow (iter); + Token eltName = accept (iter, TokenType.ElementName); + moveNextOrThrow (iter); + return new ElementEndTagSyntax(source, eltOpen, eltName, accept (iter, TokenType.ClosingSign)); + }*/ + + SyntaxNode CurrentNode; + Token previousTok; + IEnumerator<Token> iter; + public List<SyntaxException> Exceptions { get; private set; } + + void finishCurrentNode () { + + } + + public void Process () { + Exceptions = new List<SyntaxException> (); + CurrentNode = new IMLRootSyntax (source); + previousTok = default; + iter = tokens.GetEnumerator (); + + bool notEndOfSource = iter.MoveNext (); + while (notEndOfSource) { + if (!iter.Current.Type.HasFlag (TokenType.Trivia)) { + if (CurrentNode is ElementStartTagSyntax tag) { + if (iter.Current.Type == TokenType.AttributeName) { + AttributeSyntax attribute = new AttributeSyntax (iter.Current); + attribute.NameToken = iter.Current; + CurrentNode = CurrentNode.AddChild (attribute); + } else if (iter.Current.Type == TokenType.ElementName) + tag.NameToken = iter.Current; + else if (iter.Current.Type == TokenType.ClosingSign) { + tag.EndToken = iter.Current; + CurrentNode = tag.Parent; + CurrentNode.RemoveChild (tag); + CurrentNode = CurrentNode.AddChild (new ElementSyntax (tag)); + } else if (iter.Current.Type == TokenType.EmptyElementClosing) { + tag.EndToken = iter.Current; + CurrentNode = tag.Parent; + CurrentNode.RemoveChild (tag); + CurrentNode.AddChild (new EmptyElementSyntax (tag)); + } else { + Exceptions.Add (new SyntaxException ("Unexpected Token", iter.Current)); + CurrentNode.EndToken = previousTok; + CurrentNode = CurrentNode.Parent; + continue; + } + } else if (CurrentNode is ElementSyntax elt) { + if (iter.Current.Type == TokenType.ElementOpen) + CurrentNode = CurrentNode.AddChild (new ElementStartTagSyntax (iter.Current)); + else if (iter.Current.Type == TokenType.EndElementOpen) { + elt.EndTag = new ElementEndTagSyntax (iter.Current); + CurrentNode = elt.AddChild (elt.EndTag); + } + } else if (CurrentNode is AttributeSyntax attrib) { + if (iter.Current.Type == TokenType.EqualSign) + if (attrib.EqualToken.HasValue) + Exceptions.Add (new SyntaxException ("Extra equal sign in attribute syntax", iter.Current)); + else + attrib.EqualToken = iter.Current; + else if (iter.Current.Type == TokenType.AttributeValue) { + attrib.ValueToken = attrib.EndToken = iter.Current; + CurrentNode = CurrentNode.Parent; + } else { + Exceptions.Add (new SyntaxException ("Unexpected Token", iter.Current)); + CurrentNode.EndToken = previousTok; + CurrentNode = CurrentNode.Parent; + continue; + } + } else if (CurrentNode is ElementEndTagSyntax eltEndTag) { + if (iter.Current.Type == TokenType.ElementName) + eltEndTag.NameToken = iter.Current; + else if (iter.Current.Type == TokenType.ClosingSign) { + eltEndTag.EndToken = eltEndTag.Parent.EndToken = iter.Current; + CurrentNode = eltEndTag.Parent.Parent; + } else { + Exceptions.Add (new SyntaxException ("Unexpected Token", iter.Current)); + eltEndTag.EndToken = eltEndTag.Parent.EndToken = previousTok; + CurrentNode = CurrentNode.Parent.Parent; + continue; + } + } else if (CurrentNode is IMLRootSyntax) { + switch (iter.Current.Type) { + case TokenType.ElementOpen: + CurrentNode = CurrentNode.AddChild (new ElementStartTagSyntax (iter.Current)); + break; + case TokenType.PI_Start: + CurrentNode = CurrentNode.AddChild (new ProcessingInstructionSyntax (iter.Current)); + break; + default: + Exceptions.Add (new SyntaxException ("Unexpected Token", iter.Current)); + break; + } + } else if (CurrentNode is ProcessingInstructionSyntax pi) { + if (iter.Current.Type == TokenType.PI_Target) + pi.NameToken = iter.Current; + else if (iter.Current.Type == TokenType.PI_End) { + pi.EndToken = iter.Current; + CurrentNode = CurrentNode.Parent; + } else if (iter.Current.Type == TokenType.AttributeName) { + AttributeSyntax attribute = new AttributeSyntax (iter.Current); + attribute.NameToken = iter.Current; + CurrentNode = CurrentNode.AddChild (attribute); + } else { + Exceptions.Add (new SyntaxException ("Unexpected Token", iter.Current)); + pi.EndToken = previousTok; + CurrentNode = CurrentNode.Parent; + continue; + } + } + } + + previousTok = iter.Current; + notEndOfSource = iter.MoveNext (); + } + while (CurrentNode.Parent != null) { + if (!CurrentNode.EndToken.HasValue) + CurrentNode.EndToken = previousTok; + CurrentNode = CurrentNode.Parent; + } + } + } + public class SyntaxNode { + public SyntaxNode Parent { get; private set; } + List<SyntaxNode> children = new List<SyntaxNode> (); + + public readonly Token StartToken; + public Token? EndToken { get; internal set; } + public SyntaxNode (Token tokStart, Token? tokEnd = null) { + StartToken = tokStart; + EndToken = tokEnd; + } + + public virtual bool IsComplete => EndToken.HasValue; + + internal SyntaxNode AddChild (SyntaxNode child) { + children.Add (child); + child.Parent = this; + return child; + } + internal void RemoveChild (SyntaxNode child) { + children.Remove (child); + child.Parent = null; + } + public T GetChild<T> () => children.OfType<T> ().FirstOrDefault (); + public SyntaxNode FindNodeIncludingPosition (int pos) { + foreach (SyntaxNode node in children) { + if (node.Contains (pos)) + return node.FindNodeIncludingPosition (pos); + } + return this; + } + public T FindNodeIncludingPosition<T> (int pos) { + foreach (SyntaxNode node in children) { + if (node.Contains (pos)) + return node.FindNodeIncludingPosition<T> (pos); + } + + return this is T tt ? tt : default; + } + public virtual IMLRootSyntax Root => Parent.Root; + public XmlSource Source => Root.source; + public bool Contains (int pos) => + EndToken.HasValue ? + StartToken.Start <= pos && EndToken.Value.End >= pos : false; + + public void Dump (int level = 0) { + Console.WriteLine ($"{new string('\t', level)}{this}"); + foreach (SyntaxNode node in children) + node.Dump (level + 1); + } + public override string ToString() => $"{this.GetType().Name}: {StartToken} -> {EndToken}"; + } + public class IMLRootSyntax : SyntaxNode { + internal readonly XmlSource source; + public override IMLRootSyntax Root => this; + public IMLRootSyntax (XmlSource source) + : base (source.Tokens.FirstOrDefault (), source.Tokens.LastOrDefault ()) { + this.source = source; + } + } + public class ProcessingInstructionSyntax : SyntaxNode { + public Token PIStartToken => StartToken; + public Token? PIEndToken => EndToken.HasValue && EndToken.Value.Type == TokenType.PI_End ? EndToken : null; + public Token? NameToken { get; internal set; } + public override bool IsComplete => base.IsComplete & NameToken.HasValue; + public ProcessingInstructionSyntax (Token startTok) + : base (startTok) { + } + } + + public abstract class ElementTagSyntax : SyntaxNode { + public Token OpenToken => StartToken; + public Token? NameToken { get; internal set; } + public Token? CloseToken => EndToken.HasValue && EndToken.Value.Type == TokenType.ClosingSign ? EndToken : null; + public override bool IsComplete => base.IsComplete & NameToken.HasValue & CloseToken.HasValue; + protected ElementTagSyntax (Token startTok) + : base (startTok) { + } + } + public class ElementStartTagSyntax : ElementTagSyntax { + public ElementStartTagSyntax (Token startTok) + : base (startTok) { + } + } + public class ElementEndTagSyntax : ElementTagSyntax { + public ElementEndTagSyntax (Token startTok) + : base (startTok) { + } + } + + public class EmptyElementSyntax : SyntaxNode { + public readonly ElementStartTagSyntax StartTag; + public EmptyElementSyntax (ElementStartTagSyntax startNode) : base (startNode.StartToken, startNode.EndToken) { + StartTag = startNode; + AddChild (StartTag); + } + } + + public class ElementSyntax : SyntaxNode { + public readonly ElementStartTagSyntax StartTag; + public ElementEndTagSyntax EndTag { get; internal set; } + + public override bool IsComplete => base.IsComplete & StartTag.IsComplete & (EndTag != null && EndTag.IsComplete); + + public ElementSyntax (ElementStartTagSyntax startTag) + : base (startTag.StartToken) { + StartTag = startTag; + AddChild (StartTag); + } + } + + public class AttributeSyntax : SyntaxNode { + public Token? NameToken { get; internal set; } + public Token? EqualToken { get; internal set; } + public Token? ValueToken { get; internal set; } + public AttributeSyntax (Token startTok) : base (startTok) {} + public override bool IsComplete => base.IsComplete & NameToken.HasValue & EqualToken.HasValue & ValueToken.HasValue; + } +}
\ No newline at end of file diff --git a/Samples/common/src/ImlParsing/Token.cs b/Samples/common/src/ImlParsing/Token.cs new file mode 100644 index 00000000..46389df4 --- /dev/null +++ b/Samples/common/src/ImlParsing/Token.cs @@ -0,0 +1,40 @@ +// 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 Crow.Text; +using System.Diagnostics.CodeAnalysis; + +namespace Crow +{ + public struct Token : IComparable<Token>, IEquatable<Token> { + public readonly TokenType Type; + public int Start; + public readonly int Length; + public int End => Start + Length; + public TextSpan Span => new TextSpan (Start, End); + public string AsString (ReadOnlySpan<char> source) + => source.Slice (Start, Length).ToString(); + + public Token (TokenType type, int pos) { + Type = type; + Start = pos; + Length = 1; + } + public Token (TokenType type, int start, int end) { + Type = type; + Start = start; + Length = end - start; + } + + public int CompareTo([AllowNull] Token other) + => Start - other.Start; + public bool Equals([AllowNull] Token other) + => Type == other.Type && Start == other.Start && Length == other.Length; + public override bool Equals(object obj) + => obj is Token other ? Equals (other) : false; + public override int GetHashCode() => HashCode.Combine (Type, Start, Length); + public override string ToString() => $"{Type}:{Start},{Length};"; + } +}
\ No newline at end of file diff --git a/Samples/common/src/ImlParsing/TokenType.cs b/Samples/common/src/ImlParsing/TokenType.cs new file mode 100644 index 00000000..f9d7bcc6 --- /dev/null +++ b/Samples/common/src/ImlParsing/TokenType.cs @@ -0,0 +1,38 @@ +// 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; + +namespace Crow +{ + [Flags] + public enum TokenType { + Unknown, + Trivia = 0x0100, + WhiteSpace = 0x4100, + Tabulation = 0x4101, + LineBreak = 0x4102, + LineComment = 0x0103, + BlockCommentStart = 0x0104, + BlockComment = 0x0105, + BlockCommentEnd = 0x0106, + Name = 0x0200, + ElementName = 0x0201, + AttributeName = 0x0202, + PI_Target = 0x0203, + Punctuation = 0x0400, + PI_Start = 0x0401,// '<?' + PI_End = 0x0402,// '?>' + Operator = 0x0800, + EqualSign = 0x0801, + AttributeValue = 0x2000, + Keyword = 0x1000, + ElementOpen = 0x0403,// '<' + EndElementOpen = 0x0404,// '</' + EmptyElementClosing = 0x0405,// '/>' + ClosingSign = 0x0406,// '>' + DTDObjectOpen = 0x04A0,// '<!' + Content, + } +}
\ No newline at end of file diff --git a/Samples/common/src/ImlParsing/XmlSource.cs b/Samples/common/src/ImlParsing/XmlSource.cs new file mode 100644 index 00000000..f520dcf3 --- /dev/null +++ b/Samples/common/src/ImlParsing/XmlSource.cs @@ -0,0 +1,260 @@ +// 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 Crow.Text; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Crow +{ + public class XmlSource { + public Token[] Tokens; + public readonly string Source; + + SyntaxNode RootNode; + + public XmlSource (string _source) { + Source = _source; + Tokenizer tokenizer = new Tokenizer(); + Tokens = tokenizer.Tokenize (Source); + + SyntaxAnalyser syntaxAnalyser = new SyntaxAnalyser (this); + Stopwatch sw = Stopwatch.StartNew (); + syntaxAnalyser.Process (); + sw.Stop(); + + foreach (Token t in Tokens) + Console.WriteLine ($"{t,-40} {Source.AsSpan(t.Start, t.Length).ToString()}"); + syntaxAnalyser.Root.Dump(); + Console.WriteLine ($"Syntax Analysis done in {sw.ElapsedMilliseconds}(ms) {sw.ElapsedTicks}(ticks)"); + foreach (SyntaxException ex in syntaxAnalyser.Exceptions) + Console.WriteLine ($"{ex}"); + + RootNode = syntaxAnalyser.Root; + } + + public Token FindTokenIncludingPosition (int pos) { + if (Tokens == null || Tokens.Length == 0) + return default; + int idx = Array.BinarySearch (Tokens, 0, Tokens.Length, new Token () {Start = pos}); + + return idx == 0 ? Tokens[0] : idx < 0 ? Tokens[~idx - 1] : Tokens[idx - 1]; + } + public SyntaxNode FindNodeIncludingPosition (int pos) { + if (RootNode == null) + return null; + if (!RootNode.Contains (pos)) + return null; + return RootNode.FindNodeIncludingPosition (pos); + } + public T FindNodeIncludingPosition<T> (int pos) { + if (RootNode == null) + return default; + if (!RootNode.Contains (pos)) + return default; + return RootNode.FindNodeIncludingPosition<T> (pos); + } + + public class TokenizerException : Exception { + public readonly int Position; + public TokenizerException(string message, int position, Exception innerException = null) + : base (message, innerException) { + Position = position; + } + } + + class Tokenizer { + enum States + { + Init,//first statement of prolog, xmldecl should only apear in this state + prolog,//misc before doctypedecl + ProcessingInstrucitons, + DTD, + DTDObject,//doctype finished + Xml, + StartTag,//inside start tag + Content,//after start tag with no closing slash + EndTag + } + + States curState = States.Init; + List<Token> Toks = new List<Token>(100); + + public Tokenizer () {} + + void skipWhiteSpaces (ref SpanCharReader reader) { + while(!reader.EndOfSpan) { + switch (reader.Peak) { + case '\x85': + case '\x2028': + case '\xA': + reader.Read(); + addTok (ref reader, TokenType.LineBreak); + break; + case '\xD': + reader.Read(); + if (reader.IsNextCharIn ('\xA', '\x85')) + reader.Read(); + addTok (ref reader, TokenType.LineBreak); + break; + case '\x20': + case '\x9': + char c = reader.Read(); + while (reader.TryPeak (c)) + reader.Read(); + addTok (ref reader, c == '\x20' ? TokenType.WhiteSpace : TokenType.Tabulation); + break; + default: + return; + } + } + } + bool readName (ref SpanCharReader reader) { + if (reader.EndOfSpan) + return false; + char c = reader.Peak; + if (char.IsLetter(c) || c == '_' || c == ':') { + reader.Advance (); + while (reader.TryPeak (ref c)) { + if (!(char.IsLetterOrDigit(c) || c == '.' || c == '-' || c == '\xB7')) + return true; + reader.Advance (); + } + return true; + } + return false; + } + + int startOfTok; + void addTok (ref SpanCharReader reader, TokenType tokType) { + if (reader.CurrentPosition == startOfTok) + return; + Toks.Add (new Token(tokType, startOfTok, reader.CurrentPosition)); + startOfTok = reader.CurrentPosition; + } + public Token[] Tokenize (string source) { + SpanCharReader reader = new SpanCharReader(source); + + startOfTok = 0; + int curObjectLevel = 0; + curState = States.Init; + + while(!reader.EndOfSpan) { + + skipWhiteSpaces (ref reader); + + if (reader.EndOfSpan) + break; + + switch (reader.Peak) { + case '<': + reader.Advance (); + if (reader.TryPeak ('?')) { + reader.Advance (); + addTok (ref reader, TokenType.PI_Start); + readName (ref reader); + addTok (ref reader, TokenType.PI_Target); + curState = States.ProcessingInstrucitons; + } else if (reader.TryPeak ('!')) { + reader.Advance (); + if (reader.TryPeak ("--")) { + reader.Advance (2); + addTok (ref reader, TokenType.BlockCommentStart); + if (reader.TryReadUntil ("-->")) { + addTok (ref reader, TokenType.BlockComment); + reader.Advance (3); + addTok (ref reader, TokenType.BlockCommentEnd); + } else if (reader.TryPeak ("-->")) { + reader.Advance (3); + addTok (ref reader, TokenType.BlockCommentEnd); + } + } else { + addTok (ref reader, TokenType.DTDObjectOpen); + if (readName (ref reader)) { + addTok (ref reader, TokenType.Keyword); + curState = States.DTDObject; + } + } + } else if (reader.TryPeak('/')) { + reader.Advance (); + addTok (ref reader, TokenType.EndElementOpen); + if (readName (ref reader)) { + addTok (ref reader, TokenType.ElementName); + if (reader.TryPeak('>')) { + reader.Advance (); + addTok (ref reader, TokenType.ClosingSign); + + if (--curObjectLevel > 0) + curState = States.Content; + else + curState = States.Xml; + } + } + }else{ + addTok (ref reader, TokenType.ElementOpen); + if (readName (ref reader)) { + addTok (ref reader, TokenType.ElementName); + curState = States.StartTag; + } + } + break; + case '?': + reader.Advance (); + if (reader.TryPeak ('>')){ + reader.Advance (); + addTok (ref reader, TokenType.PI_End); + }else + addTok (ref reader, TokenType.Unknown); + curState = States.prolog; + break; + case '\'': + case '"': + char q = reader.Read(); + if (reader.TryReadUntil (q)) + reader.Advance (); + addTok (ref reader, TokenType.AttributeValue); + break; + case '=': + reader.Advance(); + addTok (ref reader, TokenType.EqualSign); + break; + case '>': + reader.Advance(); + addTok (ref reader, TokenType.ClosingSign); + curObjectLevel++; + curState = States.Content; + break; + case '/': + reader.Advance(); + if (reader.TryRead ('>')) { + addTok (ref reader, TokenType.EmptyElementClosing); + if (--curObjectLevel > 0) + curState = States.Content; + else + curState = States.Xml; + }else + addTok (ref reader, TokenType.Unknown); + break; + default: + if (curState == States.StartTag || curState == States.ProcessingInstrucitons) { + if (readName(ref reader)) + addTok (ref reader, TokenType.AttributeName); + else if (reader.TryAdvance()) + addTok (ref reader, TokenType.Unknown); + } else { + reader.TryReadUntil ('<'); + addTok (ref reader, TokenType.Content); + } + break; + } + } + + return Toks.ToArray(); + } + + } + + } +}
\ No newline at end of file diff --git a/Samples/common/src/SampleBase.cs b/Samples/common/src/SampleBase.cs new file mode 100644 index 00000000..1e849d6f --- /dev/null +++ b/Samples/common/src/SampleBase.cs @@ -0,0 +1,383 @@ +using Crow; +using Glfw; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.NetworkInformation; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Crow +{ + public class SampleBase : Interface + { +#if NETCOREAPP + static IntPtr resolveUnmanaged(Assembly assembly, String libraryName) + { + + switch (libraryName) + { + case "glfw3": + return NativeLibrary.Load("glfw", assembly, null); + case "rsvg-2.40": + return NativeLibrary.Load("rsvg-2", assembly, null); + } + Console.WriteLine($"[UNRESOLVE] {assembly} {libraryName}"); + return IntPtr.Zero; + } + + static SampleBase() + { + System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()).ResolvingUnmanagedDll += resolveUnmanaged; + } +#endif + public SampleBase(IntPtr hWin) : base(800, 600, hWin) { } + public SampleBase() : base(800, 600, true, true) { } + public SampleBase(int width, int height, bool startUIThread, bool createSurface) : + base(width, height, startUIThread, createSurface) + { + + } + + public Version CrowVersion => Assembly.GetAssembly(typeof(Widget)).GetName().Version; + + #region Test values for Binding + public CommandGroup Commands, AllCommands; + public CommandGroup EditCommands = new CommandGroup("Edit Commands", + new Command("Edit command 1", (sender) => MessageBox.ShowModal((sender as Widget).IFace, MessageBox.Type.Information, "Edit comand 1 clicked")), + new Command("Edit command 2 a bit longer", (sender) => MessageBox.ShowModal((sender as Widget).IFace, MessageBox.Type.Information, "Edit comand 2 clicked"), null, false), + new Command("Edit command three", (sender) => MessageBox.ShowModal((sender as Widget).IFace, MessageBox.Type.Information, "Edit comand 3 clicked")) + ); + public CommandGroup FileCommands = new CommandGroup("File Commands", + new Command("File command 1", (sender) => MessageBox.ShowModal((sender as Widget).IFace, MessageBox.Type.Information, "File comand 1 clicked")), + new Command("File command 2 a bit longer", (sender) => MessageBox.ShowModal((sender as Widget).IFace, MessageBox.Type.Information, "File comand 2 clicked")), + new Command("File command three", (sender) => MessageBox.ShowModal((sender as Widget).IFace, MessageBox.Type.Information, "File comand 3 clicked")) + ); + + void initCommands() + { + Commands = new CommandGroup("commands msg boxes", + new Command("Action 1", () => MessageBox.ShowModal(this, MessageBox.Type.Information, "context menu 1 clicked")), + new Command("Action two", () => MessageBox.ShowModal(this, MessageBox.Type.Information, "context menu 2 clicked"), null, false), + new Command("Action three", () => MessageBox.ShowModal(this, MessageBox.Type.Information, "context menu 3 clicked")) + ); + AllCommands = new CommandGroup ("All Commands", + FileCommands, + EditCommands, + new CommandGroup ("Combined commands", FileCommands, EditCommands), + new Command("Action A", () => MessageBox.ShowModal(this, MessageBox.Type.Information, "context menu A clicked")) + ); + } + public int intValue = 500; + VerticalAlignment currentVAlign; + + DirectoryInfo curDir = new DirectoryInfo(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)); + public FileSystemInfo[] CurDirectory => curDir.GetFileSystemInfos(); + public string MultilineText = + $"Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit. Sed non risus.\n\nSuspendisse lectus tortor,\nLorem 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 + { + if (IntValue == value) + return; + intValue = value; + NotifyValueChanged("IntValue", intValue); + } + } + public VerticalAlignment CurrentVAlign + { + get => currentVAlign; + set + { + if (currentVAlign == value) + return; + currentVAlign = value; + NotifyValueChanged("CurrentVAlign", currentVAlign); + } + } + void onSpinnerValueChange(object sender, ValueChangeEventArgs e) + { + if (e.MemberName != "Value") + return; + intValue = Convert.ToInt32(e.NewValue); + } + void change_alignment(object sender, EventArgs e) + { + RadioButton rb = sender as RadioButton; + if (rb == null) + return; + NotifyValueChanged("alignment", Enum.Parse(typeof(Alignment), rb.Caption)); + } + public IEnumerable<String> List2 = new List<string>(new string[] + { + "string1", + "string2", + "string3", + "string4", + "string5", + "string6", + "string7", + "string8", + "string8", + "string8", + "string8", + "string8", + "string8", + "string9" + } + ); + public IEnumerable<String> TestList2 + { + set + { + List2 = value; + NotifyValueChanged("TestList2", testList); + } + get { return List2; } + } + public class TestClass + { + public string Prop1 { get; set; } + public string Prop2 { get; set; } + + public override string ToString() + => $"{Prop1}, {Prop2}"; + + public void OnValidateCommand(Object sender, ValidateEventArgs e) + { + Console.WriteLine($"Validation: {e.ValidatedText}"); + } + } + public class TestClassVC : IValueChange + { + public event EventHandler<ValueChangeEventArgs> ValueChanged; + public void NotifyValueChanged(object _value, [CallerMemberName] string caller = null) + => ValueChanged.Raise(this, new ValueChangeEventArgs(caller, _value)); + string prop1, prop2; + public string Prop1 + { + get => prop1; + set + { + if (prop1 == value) + return; + prop1 = value; + NotifyValueChanged(prop1); + } + } + public string Prop2 + { + get => prop2; + set + { + if (prop2 == value) + return; + prop2 = value; + NotifyValueChanged(prop2); + } + + + + } + + public override string ToString() + => $"{Prop1}, {Prop2}"; + + } + TestClass tcInstance = new TestClass() { Prop1 = "instance 0 prop1 value", Prop2 = "instance 0 prop2 value" }; + TestClassVC tcVCInstance;// = new TestClassVC () { Prop1 = "instance 0 prop1 value", Prop2 = "instance 0 prop2 value" }; + TestClass tcInstance1 = new TestClass() { Prop1 = "instance 1 prop1 value", Prop2 = "instance 1 prop2 value" }; + TestClassVC tcVCInstance1 = new TestClassVC() { Prop1 = "instance 1 prop1 value", Prop2 = "instance 1 prop2 value" }; + TestClass tcInstance2 = new TestClass() { Prop1 = "instance 2 prop1 value", Prop2 = "instance 2 prop2 value" }; + TestClassVC tcVCInstance2 = new TestClassVC() { Prop1 = "instance 2 prop1 value", Prop2 = "instance 2 prop2 value" }; + + public TestClass TcInstance + { + get => tcInstance; + set + { + if (tcInstance == value) + return; + tcInstance = value; + NotifyValueChanged(tcInstance); + } + } + public TestClassVC TcVCInstance + { + get => tcVCInstance; + set + { + if (tcVCInstance == value) + return; + tcVCInstance = value; + NotifyValueChanged(tcVCInstance); + } + } + + void tcInstance_ChangeProperties_MouseClick(object sender, MouseButtonEventArgs e) + { + } + public void tcInstance_ChangeInstance_MouseClick(object sender, MouseButtonEventArgs e) + { + if (TcInstance == tcInstance1) + TcInstance = tcInstance2; + else + TcInstance = tcInstance1; + } + void tcVCInstance_ChangeInstance_MouseClick(object sender, MouseButtonEventArgs e) + { + TcVCInstance = new TestClassVC() { Prop1 = "prop1 value changed", Prop2 = "prop2 value changed" }; + } + + public IEnumerable<TestClass> List3 = new List<TestClass>(new TestClass[] + { + new TestClass { Prop1 = "string1", Prop2="prop2-1" }, + new TestClass { Prop1 = "string2", Prop2="prop2-2" }, + new TestClass { Prop1 = "string3", Prop2="prop2-3" }, + } + ); + public IEnumerable<string> TestList3Props1 => List3.Select(sc => sc.Prop1).ToList(); + public IEnumerable<TestClass> TestList3 + { + set + { + List3 = value; + NotifyValueChanged("TestList3", testList); + } + get { return List3; } + } + string testString; + public string TestString + { + get => testString; + set + { + if (testString == value) + return; + testString = value; + + NotifyValueChanged(testString); + } + } + string prop1; + public string TestList3SelProp1 + { + get => prop1; + set + { + if (prop1 == value) + return; + prop1 = value; + + NotifyValueChanged(prop1); + } + } + + string selString; + public string TestList2SelectedString + { + get => selString; + set + { + if (selString == value) + return; + selString = value; + NotifyValueChanged(selString); + } + } + + + IList<Colors> testList = (IList<Colors>)FastEnumUtility.FastEnum.GetValues<Colors>().ToList();//.ColorDic.Values//.OrderBy(c=>c.Hue) + //.ThenBy(c=>c.Value).ThenBy(c=>c.Saturation) + //.ToList (); + public IList<Colors> TestList + { + set + { + testList = value; + NotifyValueChanged("TestList", testList); + } + get { return testList; } + } + void OnClear(object sender, MouseButtonEventArgs e) => TestList = null; + void OnLoadList(object sender, MouseButtonEventArgs e) => TestList = (IList<Colors>)FastEnumUtility.FastEnum.GetValues<Colors>().ToList(); + + + string curSources = ""; + public string CurSources + { + get { return curSources; } + set + { + if (value == curSources) + return; + curSources = value; + NotifyValueChanged(curSources); + } + } + bool boolVal = true; + public bool BoolVal + { + get { return boolVal; } + set + { + if (boolVal == value) + return; + boolVal = value; + NotifyValueChanged(boolVal); + } + } + + + + #endregion + + protected override void OnInitialized() + { + initCommands(); + base.OnInitialized(); + } + + public override bool OnKeyDown(Key key) + { + + switch (key) + { + case Key.F5: + Load("Interfaces/Divers/testFileDialog.crow").DataSource = this; + return true; + case Key.F6: + Load("Interfaces/Divers/0.crow").DataSource = this; + return true; + case Key.F7: + Load("Interfaces/Divers/perfMeasures.crow").DataSource = this; + return true; + case Key.F2: + if (IsKeyDown(Key.LeftShift)) + DbgLogger.Reset(); + DbgLogger.Save(this); + return true; + } + return base.OnKeyDown(key); + } + } +}
\ No newline at end of file |