aboutsummaryrefslogtreecommitdiffstatsci
path: root/Samples/common
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
parenta515e31998f64f537e29cd30fdb7e9bfc7d4a02f (diff)
ItemsScroller for list, layouting changes, editor suggestions
Diffstat (limited to 'Samples/common')
-rw-r--r--Samples/common/samples.style8
-rw-r--r--Samples/common/src/Editor.cs (renamed from Samples/common/Editor.cs)412
-rw-r--r--Samples/common/src/ImlParsing/Syntax.cs310
-rw-r--r--Samples/common/src/ImlParsing/Token.cs40
-rw-r--r--Samples/common/src/ImlParsing/TokenType.cs38
-rw-r--r--Samples/common/src/ImlParsing/XmlSource.cs260
-rw-r--r--Samples/common/src/SampleBase.cs (renamed from Samples/common/SampleBase.cs)0
-rw-r--r--Samples/common/ui/Interfaces/Divers/0.crow2
-rw-r--r--Samples/common/ui/Interfaces/Divers/fitListWithStretchedItems.crow10
-rw-r--r--Samples/common/ui/Interfaces/Divers/temp.crow4
-rw-r--r--Samples/common/ui/Interfaces/Experimental/multiColorPick2.crow20
-rw-r--r--Samples/common/ui/templates/ColorPicker.template2
-rw-r--r--Samples/common/ui/templates/Suggestions.template13
13 files changed, 824 insertions, 295 deletions
diff --git a/Samples/common/samples.style b/Samples/common/samples.style
index 7c33bd9d..8a43f3a0 100644
--- a/Samples/common/samples.style
+++ b/Samples/common/samples.style
@@ -80,3 +80,11 @@ valignStyle2 {
smallLabel {
Font="consolas, 8";
}
+suggestionsListBox {
+ Template = "#ui.Suggestions.template";
+ Width = "Fit";
+ Height = "Fit";
+ MaximumSize = "300, 120";
+ Background = "Jet";
+ UseLoadingThread = "false";
+}
diff --git a/Samples/common/Editor.cs b/Samples/common/src/Editor.cs
index 95de4569..b9b0ae90 100644
--- a/Samples/common/Editor.cs
+++ b/Samples/common/src/Editor.cs
@@ -1,6 +1,4 @@
-using System.Security.Principal;
-using System.Threading;
-// Copyright (c) 2013-2019 Bruyère Jean-Philippe <jp_bruyere@hotmail.com>
+// 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)
@@ -11,272 +9,11 @@ using System.Collections.Generic;
using Crow.Cairo;
using System.Threading.Tasks;
using System.Linq;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
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,
- }
-
- public struct 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 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 override string ToString() => $"{Type},{Start} {Length}";
- }
- public class XmlSource {
- public Token[] Tokens;
- public readonly string Source;
-
- public XmlSource (string _source) {
- Source = _source;
- Tokenizer tokenizer = new Tokenizer();
- Tokens = tokenizer.Tokenize (Source);
-
- /*foreach (Token t in Tokens)
- Console.WriteLine ($"{t,-40} {Source.AsSpan(t.Start, t.Length).ToString()}");*/
- }
- 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);
- } else
- addTok (ref reader, TokenType.Unknown);
- 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();
- }
-
- }
-
- }
public class Editor : TextBox {
XmlSource source;
object TokenMutex = new object();
@@ -292,18 +29,134 @@ namespace Crow
base.onInitialized(sender, e);
}
- Widget overlay;
+ 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());
+ //Task.Run(()=>parse());
+
parse();
+ tryGetSuggestions ();
- /*if (overlay == null && HasFocus)
- overlay = IFace.LoadIMLFragment(@"<Widget Width='50' Height='50' Background='Jet'/>");*/
+ //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;
@@ -332,8 +185,7 @@ namespace Crow
return;
}
base.onKeyDown(sender, e);
- }
- int tabSize = 4;
+ }
protected override void drawContent (Context gr) {
try {
@@ -357,11 +209,15 @@ namespace Crow
} else
updateLocation (gr, cb.Width, ref currentLoc);
- if (overlay != null) {
- Point p = new Point((int)currentLoc.Value.VisualCharXPosition, (int)(lineHeight * (currentLoc.Value.Line + 1)));
- p += ScreenCoordinates (Slot).TopLeft;
- overlay.Left = p.X;
- overlay.Top = p.Y;
+ 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);
@@ -535,7 +391,7 @@ namespace Crow
x += tok2.Length;
pixX += spacePixelWidth * tok2.Length;*/
}
- gr.Translate (ScrollX, ScrollY);
+ //gr.Translate (ScrollX, ScrollY);
}
} catch {
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/SampleBase.cs b/Samples/common/src/SampleBase.cs
index 1e849d6f..1e849d6f 100644
--- a/Samples/common/SampleBase.cs
+++ b/Samples/common/src/SampleBase.cs
diff --git a/Samples/common/ui/Interfaces/Divers/0.crow b/Samples/common/ui/Interfaces/Divers/0.crow
index 49cb6605..f84ad20a 100644
--- a/Samples/common/ui/Interfaces/Divers/0.crow
+++ b/Samples/common/ui/Interfaces/Divers/0.crow
@@ -92,7 +92,7 @@
</VerticalStack>
<Splitter />
<VerticalStack Width="30%" Margin="5">
- <ListBox Name="colorList" Data="{TestList}" Margin="5" ItemTemplate="Interfaces/colorItem.crow" />
+ <ListBox Focusable="true" Name="colorList" Data="{TestList}" Margin="5" ItemTemplate="Interfaces/colorItem.crow" />
</VerticalStack>
</HorizontalStack>
</Window> \ No newline at end of file
diff --git a/Samples/common/ui/Interfaces/Divers/fitListWithStretchedItems.crow b/Samples/common/ui/Interfaces/Divers/fitListWithStretchedItems.crow
index c2342c6b..6fd59f14 100644
--- a/Samples/common/ui/Interfaces/Divers/fitListWithStretchedItems.crow
+++ b/Samples/common/ui/Interfaces/Divers/fitListWithStretchedItems.crow
@@ -1,14 +1,18 @@
<?xml version="1.0"?>
-<ListBox Data="{TestList}" Width="Fit" >
+<ListBox Data="{TestList}" Width="Fit" Focusable="true" >
<Template>
- <Scroller Height="Stretched" Background="DarkRed" Margin="1">
+ <Scroller Name="ItemsScroller" Height="Stretched" Background="Onyx" Margin="10">
<VerticalStack Name="ItemsContainer" Orientation="Horizontal" VerticalAlignment="Top" Height="Fit" Background="Grey" Margin="20"/>
</Scroller>
</Template>
<ItemTemplate>
<!--<Label Text="{}" TextAlignment="Center" Font="mono, 9" Foreground="Black" Margin="10" Width="Stretched" Background="{}"/>-->
- <Label Text="{}" TextAlignment="Center" Font="mono, 9" Foreground="Black" Margin="10" Background="{}" Width="Stretched" />
+ <ListItem Width="Stretched" Margin="1" CornerRadius="5"
+ Selected="{Background=Yellow}"
+ Unselected="{Background=Transparent}">
+ <Label Text="{}" TextAlignment="Center" Font="mono, 9" Foreground="Black" Margin="6" Background="{}" Width="Stretched" />
+ </ListItem>
</ItemTemplate>
</ListBox>
diff --git a/Samples/common/ui/Interfaces/Divers/temp.crow b/Samples/common/ui/Interfaces/Divers/temp.crow
new file mode 100644
index 00000000..633bd600
--- /dev/null
+++ b/Samples/common/ui/Interfaces/Divers/temp.crow
@@ -0,0 +1,4 @@
+<VerticalStack>
+ <Label Text='Hello World' Background='MediumSeaGreen' Margin='10'/>
+ <CheckBox I
+</VerticalStack> \ No newline at end of file
diff --git a/Samples/common/ui/Interfaces/Experimental/multiColorPick2.crow b/Samples/common/ui/Interfaces/Experimental/multiColorPick2.crow
index 39721689..0d3c04e7 100644
--- a/Samples/common/ui/Interfaces/Experimental/multiColorPick2.crow
+++ b/Samples/common/ui/Interfaces/Experimental/multiColorPick2.crow
@@ -3,18 +3,14 @@
<Template>
<Popper Margin="0" Caption="{./CurrentColor}" Background="{./Background}" >
<Template>
- <CheckBox Margin="0" Caption="{./Caption}" IsChecked="{²./IsPopped}" Background="{./Background}">
- <Template>
- <HorizontalStack Margin="3" Spacing="3" Background="{./Background}">
- <Border Width="18" Height="12" CornerRadius="3"
- Background="{../../../../CurrentColor}">
- </Border>
- <Label Width="Stretched" Text="{./Caption}" />
- </HorizontalStack>
- </Template>/>{}
- </CheckBox>
- </Template>
- <TabView MinimumSize="{../../MinimumPopupSize}" Width="Fit" Height="Fit" >
+ <HorizontalStack Margin="3" Spacing="3" Background="{./Background}">
+ <Border Width="18" Height="12" CornerRadius="3"
+ Background="{../../../CurrentColor}">
+ </Border>
+ <Label Width="Stretched" Text="{./Caption}" />
+ </HorizontalStack>
+ </Template>/>
+ <TabView MinimumSize="{../MinimumPopupSize}" Width="200" Height="200" >
<ColorPicker Name="HSV" CurrentColor="{²../../../CurrentColor}" Background="Onyx"/>
<ColorPicker IsVisible="false" Name="Names" CurrentColor="{²../../../CurrentColor}" Height="Stretched" Background="Onyx">
<Template>
diff --git a/Samples/common/ui/templates/ColorPicker.template b/Samples/common/ui/templates/ColorPicker.template
index ca7ddda4..b7c0ce4b 100644
--- a/Samples/common/ui/templates/ColorPicker.template
+++ b/Samples/common/ui/templates/ColorPicker.template
@@ -8,7 +8,7 @@
<Label Width="Stretched" Text="{./Caption}" />
</HorizontalStack>
</Template>
- <TabView MinimumSize="{../../MinimumPopupSize}" Width="Fit" Height="Fit" >
+ <TabView MinimumSize="{../MinimumPopupSize}" Width="200" Height="200" >
<ColorPicker Name="HSV" CurrentColor="{²../../../CurrentColor}" Background="Onyx"/>
<ColorPicker IsVisible="false" Name="Names" CurrentColor="{²../../../CurrentColor}" Height="Stretched" Background="Onyx">
<Template>
diff --git a/Samples/common/ui/templates/Suggestions.template b/Samples/common/ui/templates/Suggestions.template
new file mode 100644
index 00000000..5a4349d7
--- /dev/null
+++ b/Samples/common/ui/templates/Suggestions.template
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<Border BorderWidth="1" Background="{./Background}" Height="Stretched">
+ <!--<HorizontalStack Margin="1">-->
+ <Scroller Name="ItemsScroller" Margin="2">
+ <VerticalStack Height="Fit" MinimumSize="10,10"
+ Name="ItemsContainer" Margin="0" VerticalAlignment="Top"/>
+ </Scroller>
+ <!--<ScrollBar Name="scrollbar1" Value="{²../ItemsScroller.ScrollY}"
+ LargeIncrement="{../ItemsScroller.PageHeight}" SmallIncrement="30" CursorSize="{../ItemsScroller.ChildHeightRatio}"
+ Maximum="{../ItemsScroller.MaxScrollY}" Orientation="Vertical"
+ Width="12" />
+ </HorizontalStack>-->
+</Border>