Browse Source

Cursor and lower level control building

Krzysztof Krysiński 2 months ago
parent
commit
c6f942189a
43 changed files with 347 additions and 140 deletions
  1. 5 42
      samples/Sample7_FlyUI/WindowContentElement.cs
  2. 6 3
      src/PixiEditor.Extensions.CommonApi/FlyUI/ByteMap.cs
  3. 72 0
      src/PixiEditor.Extensions.CommonApi/FlyUI/Cursor.cs
  4. 1 1
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/Align.cs
  5. 3 2
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/Border.cs
  6. 1 1
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/Button.cs
  7. 1 1
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/Center.cs
  8. 1 1
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/CheckBox.cs
  9. 4 2
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/Column.cs
  10. 3 2
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/Container.cs
  11. 21 2
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/ControlDefinition.cs
  12. 4 4
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/Hyperlink.cs
  13. 3 2
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/Icon.cs
  14. 4 3
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/Image.cs
  15. 10 6
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/LayoutElement.cs
  16. 3 1
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/MultiChildLayoutElement.cs
  17. 3 2
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/Padding.cs
  18. 4 2
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/Row.cs
  19. 4 0
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/SingleChildLayoutElement.cs
  20. 6 8
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/StatefulElement.cs
  21. 15 1
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/StatelessElement.cs
  22. 4 3
      src/PixiEditor.Extensions.Sdk/Api/FlyUI/Text.cs
  23. BIN
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll
  24. BIN
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Extensions.MSPackageBuilder.dll
  25. 25 0
      src/PixiEditor.Extensions/FlyUI/Converters/CursorToAvaloniaCursorConverter.cs
  26. 4 4
      src/PixiEditor.Extensions/FlyUI/Elements/Align.cs
  27. 3 3
      src/PixiEditor.Extensions/FlyUI/Elements/Border.cs
  28. 2 2
      src/PixiEditor.Extensions/FlyUI/Elements/Column.cs
  29. 2 2
      src/PixiEditor.Extensions/FlyUI/Elements/Container.cs
  30. 5 5
      src/PixiEditor.Extensions/FlyUI/Elements/Hyperlink.cs
  31. 3 3
      src/PixiEditor.Extensions/FlyUI/Elements/Icon.cs
  32. 3 3
      src/PixiEditor.Extensions/FlyUI/Elements/Image.cs
  33. 5 1
      src/PixiEditor.Extensions/FlyUI/Elements/LayoutBuilder.cs
  34. 48 4
      src/PixiEditor.Extensions/FlyUI/Elements/LayoutElement.cs
  35. 2 2
      src/PixiEditor.Extensions/FlyUI/Elements/Padding.cs
  36. 2 2
      src/PixiEditor.Extensions/FlyUI/Elements/Row.cs
  37. 3 4
      src/PixiEditor.Extensions/FlyUI/Elements/StatefulElement.cs
  38. 5 0
      src/PixiEditor.Extensions/FlyUI/Elements/StatelessElement.cs
  39. 3 3
      src/PixiEditor.Extensions/FlyUI/Elements/Text.cs
  40. 1 1
      src/PixiEditor.Extensions/FlyUI/IPropertyDeserializable.cs
  41. 0 1
      src/PixiEditor/Helpers/Behaviours/ClearFocusOnClickBehavior.cs
  42. 36 11
      tests/PixiEditor.Extensions.Sdk.Tests/NativeControlSerializationTest.cs
  43. 17 0
      tests/PixiEditor.Extensions.Sdk.Tests/WindowContentElement.cs

+ 5 - 42
samples/Sample7_FlyUI/WindowContentElement.cs

@@ -1,4 +1,5 @@
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
+using PixiEditor.Extensions.CommonApi.FlyUI;
 using PixiEditor.Extensions.CommonApi.FlyUI.Events;
 using PixiEditor.Extensions.CommonApi.FlyUI.Events;
 using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
 using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
 using PixiEditor.Extensions.Sdk;
 using PixiEditor.Extensions.Sdk;
@@ -12,50 +13,12 @@ public class WindowContentElement : StatelessElement
 {
 {
     public PopupWindow Window { get; set; }
     public PopupWindow Window { get; set; }
 
 
-    public override ControlDefinition BuildNative()
+    public override ILayoutElement<ControlDefinition> Build()
     {
     {
-        Layout layout = new Layout(body:
-            new Container(margin: Edges.All(25), child:
-                new Column(
-                    crossAxisAlignment: CrossAxisAlignment.Center,
-                    mainAxisAlignment: MainAxisAlignment.SpaceEvenly,
-                    children:
-                    [
-                        new Center(
-                            new Text(
-                                "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vitae neque nibh. Duis sed pharetra dolor. Donec dui sapien, aliquam id sodales in, ornare et urna. Mauris nunc odio, sagittis eget lectus at, imperdiet ornare quam. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam euismod pellentesque blandit. Vestibulum sagittis, ligula non finibus lobortis, dolor lacus consectetur turpis, id facilisis ligula dolor vitae augue.",
-                                wrap: TextWrap.Wrap,
-                                textStyle: new TextStyle(fontSize: 16))
-                        ),
-                        new Align(
-                            alignment: Alignment.CenterRight,
-                            child: new Text("- Paulo Coelho, The Alchemist (1233)", textStyle: new TextStyle(fontStyle: FontStyle.Italic))
-                        ),
-                        new Container(
-                            margin: Edges.Symmetric(25, 0),
-                            backgroundColor: Color.FromRgba(25, 25, 25, 255),
-                            child: new Column(
-                                new Image(
-                                    "/Pizza.png",
-                                    filterQuality: FilterQuality.None,
-                                    width: 256, height: 256))
-                        ),
-                        new CheckBox(new Text("heloo"),
-                            onCheckedChanged: args =>
-                            {
-                                PixiEditorExtension.Api.Logger.Log(((CheckBox)args.Sender).IsChecked
-                                    ? "Checked"
-                                    : "Unchecked");
-                            }),
-                        new Center(
-                            new Button(
-                                child: new Text("Close"), onClick: _ => { Window.Close(); }))
-                    ]
-                )
-            )
+        Border layout = new Border(child:
+            new Container(margin: Edges.All(25))
         );
         );
 
 
-        return layout.BuildNative();
+        return layout;
     }
     }
-
 }
 }

+ 6 - 3
src/PixiEditor.Extensions.CommonApi/FlyUI/ByteMap.cs

@@ -1,11 +1,13 @@
-using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
-
-namespace PixiEditor.Extensions.CommonApi.FlyUI;
+namespace PixiEditor.Extensions.CommonApi.FlyUI;
 
 
 public static class ByteMap
 public static class ByteMap
 {
 {
     public static byte GetTypeByteId(Type type)
     public static byte GetTypeByteId(Type type)
     {
     {
+        if (type == null)
+        {
+            return 255;
+        }
         if (type == typeof(int))
         if (type == typeof(int))
         {
         {
             return 0;
             return 0;
@@ -64,6 +66,7 @@ public static class ByteMap
             7 => typeof(char),
             7 => typeof(char),
             8 => typeof(string),
             8 => typeof(string),
             9 => typeof(byte[]),
             9 => typeof(byte[]),
+            255 => null,
             _ => throw new Exception($"Unknown unmanaged type id: {id}")
             _ => throw new Exception($"Unknown unmanaged type id: {id}")
         };
         };
     }
     }

+ 72 - 0
src/PixiEditor.Extensions.CommonApi/FlyUI/Cursor.cs

@@ -0,0 +1,72 @@
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
+
+namespace PixiEditor.Extensions.CommonApi.FlyUI;
+
+public struct Cursor : IStructProperty
+{
+    private const int Version = 1; // Serialization version, increment when changing the struct
+    public BuiltInCursor? BuiltInCursor { get; set; }
+    public bool IsCustom => BuiltInCursor == null;
+
+    public Cursor(BuiltInCursor builtInCursor)
+    {
+        BuiltInCursor = builtInCursor;
+    }
+
+    byte[] IStructProperty.Serialize()
+    {
+        var data = new List<byte>();
+        data.Add(Version);
+        data.Add(BuiltInCursor != null ? (byte)1 : (byte)0);
+        if (BuiltInCursor != null)
+        {
+            data.Add((byte)BuiltInCursor.Value);
+        }
+
+        return data.ToArray();
+    }
+
+    void IStructProperty.Deserialize(byte[] data)
+    {
+        int version = data[0];
+
+        int index = 1;
+        if (data[index] == 1)
+        {
+            index++;
+            BuiltInCursor = (BuiltInCursor)data[index];
+        }
+        else
+        {
+            BuiltInCursor = null;
+        }
+    }
+}
+
+public enum BuiltInCursor
+{
+    Arrow,
+    IBeam,
+    Wait,
+    Cross,
+    UpArrow,
+    SizeWestEast,
+    SizeNorthSouth,
+    SizeAll,
+    No,
+    Hand,
+    AppStarting,
+    Help,
+    TopSide,
+    BottomSide,
+    LeftSide,
+    RightSide,
+    TopLeftCorner,
+    TopRightCorner,
+    BottomLeftCorner,
+    BottomRightCorner,
+    DragMove,
+    DragCopy,
+    DragLink,
+    None,
+}

+ 1 - 1
src/PixiEditor.Extensions.Sdk/Api/FlyUI/Align.cs

@@ -7,7 +7,7 @@ public class Align : SingleChildLayoutElement
 {
 {
     public Alignment Alignment { get; set; }
     public Alignment Alignment { get; set; }
 
 
-    public Align(Alignment alignment = Alignment.TopLeft, LayoutElement child = null)
+    public Align(Alignment alignment = Alignment.TopLeft, LayoutElement child = null, Cursor? cursor = null) : base(cursor)
     {
     {
         Child = child;
         Child = child;
         Alignment = alignment;
         Alignment = alignment;

+ 3 - 2
src/PixiEditor.Extensions.Sdk/Api/FlyUI/Border.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
+using PixiEditor.Extensions.CommonApi.FlyUI;
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
 
 
 namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 
 
@@ -22,7 +23,7 @@ public class Border : SingleChildLayoutElement
     public Border(LayoutElement child = null, Color color = default, Edges thickness = default,
     public Border(LayoutElement child = null, Color color = default, Edges thickness = default,
         Edges cornerRadius = default, Edges padding = default, Edges margin = default, double width = -1,
         Edges cornerRadius = default, Edges padding = default, Edges margin = default, double width = -1,
         double height = -1,
         double height = -1,
-        Color backgroundColor = default)
+        Color backgroundColor = default, Cursor? cursor = null) : base(cursor)
     {
     {
         Child = child;
         Child = child;
         Color = color;
         Color = color;

+ 1 - 1
src/PixiEditor.Extensions.Sdk/Api/FlyUI/Button.cs

@@ -11,7 +11,7 @@ public class Button : SingleChildLayoutElement
         remove => RemoveEvent(nameof(Click), value);
         remove => RemoveEvent(nameof(Click), value);
     }
     }
 
 
-    public Button(ILayoutElement<ControlDefinition> child = null, ElementEventHandler onClick = null)
+    public Button(ILayoutElement<ControlDefinition> child = null, ElementEventHandler onClick = null, Cursor? cursor = null) : base(cursor)
     {
     {
         Child = child;
         Child = child;
         if (onClick != null)
         if (onClick != null)

+ 1 - 1
src/PixiEditor.Extensions.Sdk/Api/FlyUI/Center.cs

@@ -4,7 +4,7 @@ namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 
 
 public class Center : SingleChildLayoutElement
 public class Center : SingleChildLayoutElement
 {
 {
-    public Center(ILayoutElement<ControlDefinition> child)
+    public Center(ILayoutElement<ControlDefinition> child, Cursor? cursor = null) : base(cursor)
     {
     {
         Child = child;
         Child = child;
     }
     }

+ 1 - 1
src/PixiEditor.Extensions.Sdk/Api/FlyUI/CheckBox.cs

@@ -13,7 +13,7 @@ public class CheckBox : SingleChildLayoutElement
 
 
     public bool IsChecked { get; set; }
     public bool IsChecked { get; set; }
 
 
-    public CheckBox(ILayoutElement<ControlDefinition> child = null, ElementEventHandler onCheckedChanged = null)
+    public CheckBox(ILayoutElement<ControlDefinition> child = null, ElementEventHandler onCheckedChanged = null, Cursor? cursor = null) : base(cursor)
     {
     {
         Child = child;
         Child = child;
         
         

+ 4 - 2
src/PixiEditor.Extensions.Sdk/Api/FlyUI/Column.cs

@@ -1,4 +1,6 @@
-namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
+using PixiEditor.Extensions.CommonApi.FlyUI;
+
+namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 
 
 public class Column : MultiChildLayoutElement
 public class Column : MultiChildLayoutElement
 {
 {
@@ -8,7 +10,7 @@ public class Column : MultiChildLayoutElement
     public Column(
     public Column(
         MainAxisAlignment mainAxisAlignment = MainAxisAlignment.Start,
         MainAxisAlignment mainAxisAlignment = MainAxisAlignment.Start,
         CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.Start,
         CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.Start,
-        LayoutElement[] children = null)
+        LayoutElement[] children = null, Cursor? cursor = null) : base(cursor)
     {
     {
         MainAxisAlignment = mainAxisAlignment;
         MainAxisAlignment = mainAxisAlignment;
         CrossAxisAlignment = crossAxisAlignment;
         CrossAxisAlignment = crossAxisAlignment;

+ 3 - 2
src/PixiEditor.Extensions.Sdk/Api/FlyUI/Container.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
+using PixiEditor.Extensions.CommonApi.FlyUI;
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
 
 
 namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 
 
@@ -10,7 +11,7 @@ public class Container : SingleChildLayoutElement
     public double Width { get; set; }
     public double Width { get; set; }
     public double Height { get; set; }
     public double Height { get; set; }
 
 
-    public Container(LayoutElement child = null, Edges margin = default, Color backgroundColor = default, double width = -1, double height = -1)
+    public Container(LayoutElement child = null, Edges margin = default, Color backgroundColor = default, double width = -1, double height = -1, Cursor? cursor = null) : base(cursor)
     {
     {
         Margin = margin;
         Margin = margin;
         BackgroundColor = backgroundColor;
         BackgroundColor = backgroundColor;

+ 21 - 2
src/PixiEditor.Extensions.Sdk/Api/FlyUI/ControlDefinition.cs

@@ -27,8 +27,27 @@ public class ControlDefinition
         InternalAddProperty(value);
         InternalAddProperty(value);
     }
     }
 
 
+    public void InsertProperty<T>(int at, T value)
+    {
+        InternalAddProperty(value);
+        if (at < 0 || at >= Properties.Count)
+        {
+            throw new ArgumentOutOfRangeException(nameof(at), "Index out of range");
+        }
+
+        var property = Properties[^1];
+        Properties.RemoveAt(Properties.Count - 1);
+        Properties.Insert(at, property);
+    }
+
     private void InternalAddProperty(object value)
     private void InternalAddProperty(object value)
     {
     {
+        if (value is null)
+        {
+            Properties.Add((null, null));
+            return;
+        }
+
         if (value is string s)
         if (value is string s)
         {
         {
             AddStringProperty(s);
             AddStringProperty(s);
@@ -132,10 +151,10 @@ public class ControlDefinition
         bytes.AddRange(Encoding.UTF8.GetBytes(structProperty.GetType().Name));
         bytes.AddRange(Encoding.UTF8.GetBytes(structProperty.GetType().Name));
 
 
         byte[] structBytes = structProperty.Serialize();
         byte[] structBytes = structProperty.Serialize();
-        
+
         bytes.AddRange(BitConverter.GetBytes(structBytes.Length));
         bytes.AddRange(BitConverter.GetBytes(structBytes.Length));
         bytes.AddRange(structBytes);
         bytes.AddRange(structBytes);
-        
+
         return bytes;
         return bytes;
     }
     }
 
 

+ 4 - 4
src/PixiEditor.Extensions.Sdk/Api/FlyUI/Hyperlink.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
+using PixiEditor.Extensions.CommonApi.FlyUI;
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
 
 
 namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 
 
@@ -6,12 +7,12 @@ public class Hyperlink : Text
 {
 {
     public string Url { get; set; }
     public string Url { get; set; }
 
 
-    public Hyperlink(string url, string text, TextWrap textWrap = TextWrap.None, TextStyle? textStyle = null) : base(text, textWrap, textStyle)
+    public Hyperlink(string url, string text, TextWrap textWrap = TextWrap.None, TextStyle? textStyle = null, Cursor? cursor = null) : base(text, textWrap, textStyle, cursor)
     {
     {
         Url = url;
         Url = url;
     }
     }
 
 
-    public override ControlDefinition BuildNative()
+    protected override ControlDefinition CreateControl()
     {
     {
         ControlDefinition hyperlink = new ControlDefinition(UniqueId, "Hyperlink");
         ControlDefinition hyperlink = new ControlDefinition(UniqueId, "Hyperlink");
         hyperlink.AddProperty(Value);
         hyperlink.AddProperty(Value);
@@ -19,7 +20,6 @@ public class Hyperlink : Text
         hyperlink.AddProperty(TextStyle);
         hyperlink.AddProperty(TextStyle);
         hyperlink.AddProperty(Url);
         hyperlink.AddProperty(Url);
 
 
-        BuildPendingEvents(hyperlink);
         return hyperlink;
         return hyperlink;
     }
     }
 }
 }

+ 3 - 2
src/PixiEditor.Extensions.Sdk/Api/FlyUI/Icon.cs

@@ -1,14 +1,15 @@
+using PixiEditor.Extensions.CommonApi.FlyUI;
 using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
 using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
 
 
 namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 
 
-public class Icon : StatelessElement
+public class Icon : LayoutElement
 {
 {
     public string IconName { get; set; }
     public string IconName { get; set; }
     public double Size { get; set; } = 16;
     public double Size { get; set; } = 16;
     public Color Color { get; set; } = Colors.White;
     public Color Color { get; set; } = Colors.White;
 
 
-    public Icon(string iconName, double size = 16, Color? color = null)
+    public Icon(string iconName, double size = 16, Color? color = null, Cursor? cursor = null) : base(cursor)
     {
     {
         IconName = iconName;
         IconName = iconName;
         Size = size;
         Size = size;

+ 4 - 3
src/PixiEditor.Extensions.Sdk/Api/FlyUI/Image.cs

@@ -1,9 +1,10 @@
-using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
+using PixiEditor.Extensions.CommonApi.FlyUI;
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
 using PixiEditor.Extensions.Sdk.Bridge;
 using PixiEditor.Extensions.Sdk.Bridge;
 
 
 namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 
 
-public class Image : StatelessElement
+public class Image : LayoutElement
 {
 {
     private string source = null!;
     private string source = null!;
 
 
@@ -28,7 +29,7 @@ public class Image : StatelessElement
     public FillMode FillMode { get; set; }
     public FillMode FillMode { get; set; }
     public FilterQuality FilterQuality { get; set; }
     public FilterQuality FilterQuality { get; set; }
 
 
-    public Image(string source, double width = -1, double height = -1, FillMode fillMode = FillMode.Uniform, FilterQuality filterQuality = FilterQuality.Unspecified)
+    public Image(string source, double width = -1, double height = -1, FillMode fillMode = FillMode.Uniform, FilterQuality filterQuality = FilterQuality.Unspecified, Cursor? cursor = null) : base(cursor)
     {
     {
         Source = source;
         Source = source;
         Width = width;
         Width = width;

+ 10 - 6
src/PixiEditor.Extensions.Sdk/Api/FlyUI/LayoutElement.cs

@@ -33,22 +33,26 @@ public abstract class LayoutElement : ILayoutElement<ControlDefinition>
         remove => RemoveEvent(nameof(PointerReleased), value);
         remove => RemoveEvent(nameof(PointerReleased), value);
     }
     }
 
 
+    public Cursor? Cursor { get; set; }
+
+    public LayoutElement(Cursor? cursor)
+    {
+        Cursor = cursor;
+        UniqueId = LayoutElementIdGenerator.GetNextId();
+        LayoutElementsStore.AddElement(UniqueId, this);
+    }
+
     public virtual ControlDefinition BuildNative()
     public virtual ControlDefinition BuildNative()
     {
     {
         ControlDefinition control = CreateControl();
         ControlDefinition control = CreateControl();
 
 
+        control.InsertProperty(0, Cursor);
         BuildPendingEvents(control);
         BuildPendingEvents(control);
         return control;
         return control;
     }
     }
 
 
     protected abstract ControlDefinition CreateControl();
     protected abstract ControlDefinition CreateControl();
 
 
-    public LayoutElement()
-    {
-        UniqueId = LayoutElementIdGenerator.GetNextId();
-        LayoutElementsStore.AddElement(UniqueId, this);
-    }
-
     ~LayoutElement()
     ~LayoutElement()
     {
     {
         LayoutElementsStore.RemoveElement(UniqueId);
         LayoutElementsStore.RemoveElement(UniqueId);

+ 3 - 1
src/PixiEditor.Extensions.Sdk/Api/FlyUI/MultiChildLayoutElement.cs

@@ -12,5 +12,7 @@ public abstract class MultiChildLayoutElement : LayoutElement, IMultiChildLayout
 
 
     public List<LayoutElement> Children { get; set; }
     public List<LayoutElement> Children { get; set; }
 
 
-
+    public MultiChildLayoutElement(Cursor? cursor = null) : base(cursor)
+    {
+    }
 }
 }

+ 3 - 2
src/PixiEditor.Extensions.Sdk/Api/FlyUI/Padding.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
+using PixiEditor.Extensions.CommonApi.FlyUI;
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
 
 
 namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 
 
@@ -6,7 +7,7 @@ public class Padding : SingleChildLayoutElement
 {
 {
     public Edges Edges { get; set; } = Edges.All(0);
     public Edges Edges { get; set; } = Edges.All(0);
     
     
-    public Padding(LayoutElement child = null, Edges edges = default)
+    public Padding(LayoutElement child = null, Edges edges = default, Cursor? cursor = null) : base(cursor)
     {
     {
         Edges = edges;
         Edges = edges;
         Child = child;
         Child = child;

+ 4 - 2
src/PixiEditor.Extensions.Sdk/Api/FlyUI/Row.cs

@@ -1,4 +1,6 @@
-namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
+using PixiEditor.Extensions.CommonApi.FlyUI;
+
+namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 
 
 public class Row : MultiChildLayoutElement
 public class Row : MultiChildLayoutElement
 {
 {
@@ -15,7 +17,7 @@ public class Row : MultiChildLayoutElement
     public Row(
     public Row(
         MainAxisAlignment mainAxisAlignment = MainAxisAlignment.Start,
         MainAxisAlignment mainAxisAlignment = MainAxisAlignment.Start,
         CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.Start,
         CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.Start,
-        LayoutElement[] children = null)
+        LayoutElement[] children = null, Cursor? cursor = null) : base(cursor)
     {
     {
         MainAxisAlignment = mainAxisAlignment;
         MainAxisAlignment = mainAxisAlignment;
         CrossAxisAlignment = crossAxisAlignment;
         CrossAxisAlignment = crossAxisAlignment;

+ 4 - 0
src/PixiEditor.Extensions.Sdk/Api/FlyUI/SingleChildLayoutElement.cs

@@ -5,4 +5,8 @@ namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 public abstract class SingleChildLayoutElement : LayoutElement, ISingleChildLayoutElement<ControlDefinition>
 public abstract class SingleChildLayoutElement : LayoutElement, ISingleChildLayoutElement<ControlDefinition>
 {
 {
     public ILayoutElement<ControlDefinition> Child { get; set; }
     public ILayoutElement<ControlDefinition> Child { get; set; }
+
+    public SingleChildLayoutElement(Cursor? cursor = null) : base(cursor)
+    {
+    }
 }
 }

+ 6 - 8
src/PixiEditor.Extensions.Sdk/Api/FlyUI/StatefulElement.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Extensions.CommonApi.FlyUI.State;
+using PixiEditor.Extensions.CommonApi.FlyUI;
+using PixiEditor.Extensions.CommonApi.FlyUI.State;
 
 
 namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 
 
@@ -6,6 +7,10 @@ public abstract class StatefulElement<TState> : LayoutElement, IStatefulElement<
 {
 {
     private TState state;
     private TState state;
 
 
+    protected StatefulElement(Cursor? cursor) : base(cursor)
+    {
+    }
+
     IState<ControlDefinition> IStatefulElement<ControlDefinition>.State
     IState<ControlDefinition> IStatefulElement<ControlDefinition>.State
     {
     {
         get
         get
@@ -26,13 +31,6 @@ public abstract class StatefulElement<TState> : LayoutElement, IStatefulElement<
 
 
     public TState State => (TState)((IStatefulElement<ControlDefinition>)this).State;
     public TState State => (TState)((IStatefulElement<ControlDefinition>)this).State;
 
 
-    public override ControlDefinition BuildNative()
-    {
-        var statefulContainer = CreateControl();
-        BuildPendingEvents(statefulContainer);
-        return statefulContainer;
-    }
-
     protected override ControlDefinition CreateControl()
     protected override ControlDefinition CreateControl()
     {
     {
         ControlDefinition controlDefinition = State.Build().BuildNative();
         ControlDefinition controlDefinition = State.Build().BuildNative();

+ 15 - 1
src/PixiEditor.Extensions.Sdk/Api/FlyUI/StatelessElement.cs

@@ -5,8 +5,22 @@ namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 
 
 public abstract class StatelessElement : LayoutElement, IStatelessElement<ControlDefinition>
 public abstract class StatelessElement : LayoutElement, IStatelessElement<ControlDefinition>
 {
 {
-    public ILayoutElement<ControlDefinition> Build()
+    protected StatelessElement() : base(null)
+    {
+    }
+
+    public virtual ILayoutElement<ControlDefinition> Build()
     {
     {
         return this;
         return this;
     }
     }
+
+    public override ControlDefinition BuildNative()
+    {
+        return CreateControl();
+    }
+
+    protected override ControlDefinition CreateControl()
+    {
+        return Build().BuildNative();
+    }
 }
 }

+ 4 - 3
src/PixiEditor.Extensions.Sdk/Api/FlyUI/Text.cs

@@ -1,8 +1,9 @@
-using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
+using PixiEditor.Extensions.CommonApi.FlyUI;
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
 
 
 namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 namespace PixiEditor.Extensions.Sdk.Api.FlyUI;
 
 
-public class Text : StatelessElement
+public class Text : LayoutElement
 {
 {
     public string Value { get; set; }
     public string Value { get; set; }
     
     
@@ -10,7 +11,7 @@ public class Text : StatelessElement
 
 
     public TextStyle TextStyle { get; set; }
     public TextStyle TextStyle { get; set; }
     
     
-    public Text(string value, TextWrap wrap = TextWrap.None, TextStyle? textStyle = null)
+    public Text(string value, TextWrap wrap = TextWrap.None, TextStyle? textStyle = null, Cursor? cursor = null) : base(cursor)
     {
     {
         Value = value;
         Value = value;
         TextWrap = wrap;
         TextWrap = wrap;

BIN
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Api.CGlueMSBuild.dll


BIN
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Extensions.MSPackageBuilder.dll


+ 25 - 0
src/PixiEditor.Extensions/FlyUI/Converters/CursorToAvaloniaCursorConverter.cs

@@ -0,0 +1,25 @@
+using System.Globalization;
+using Avalonia.Data.Converters;
+using Avalonia.Input;
+using PixiEditor.Extensions.CommonApi.FlyUI;
+using Cursor = PixiEditor.Extensions.CommonApi.FlyUI.Cursor;
+
+namespace PixiEditor.Extensions.FlyUI.Converters;
+
+internal class CursorToAvaloniaCursorConverter : IValueConverter
+{
+    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+    {
+        if (value is Cursor cursor)
+        {
+            return new Avalonia.Input.Cursor((StandardCursorType)(cursor.BuiltInCursor ?? BuiltInCursor.None));
+        }
+
+        return null;
+    }
+
+    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+    {
+        throw new NotImplementedException();
+    }
+}

+ 4 - 4
src/PixiEditor.Extensions/FlyUI/Elements/Align.cs

@@ -6,7 +6,7 @@ using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
 
 
 namespace PixiEditor.Extensions.FlyUI.Elements;
 namespace PixiEditor.Extensions.FlyUI.Elements;
 
 
-public class Align : SingleChildLayoutElement, IPropertyDeserializable
+public class Align : SingleChildLayoutElement
 {
 {
     private Panel _panel; 
     private Panel _panel; 
     public Alignment Alignment { get; set; }
     public Alignment Alignment { get; set; }
@@ -76,12 +76,12 @@ public class Align : SingleChildLayoutElement, IPropertyDeserializable
         };
         };
     }
     }
 
 
-    public void DeserializeProperties(ImmutableList<object> values)
+    protected override void DeserializeControlProperties(List<object> values)
     {
     {
         Alignment = (Alignment)values.FirstOrDefault();
         Alignment = (Alignment)values.FirstOrDefault();
     }
     }
-    
-    IEnumerable<object> IPropertyDeserializable.GetProperties()
+
+    protected override IEnumerable<object> GetControlProperties()
     {
     {
         yield return Alignment;
         yield return Alignment;
     }
     }

+ 3 - 3
src/PixiEditor.Extensions/FlyUI/Elements/Border.cs

@@ -9,7 +9,7 @@ using PixiEditor.Extensions.FlyUI.Converters;
 
 
 namespace PixiEditor.Extensions.FlyUI.Elements;
 namespace PixiEditor.Extensions.FlyUI.Elements;
 
 
-public class Border : SingleChildLayoutElement, IPropertyDeserializable
+public class Border : SingleChildLayoutElement
 {
 {
     private Avalonia.Controls.Border border;
     private Avalonia.Controls.Border border;
 
 
@@ -99,7 +99,7 @@ public class Border : SingleChildLayoutElement, IPropertyDeserializable
         border.Child = null;
         border.Child = null;
     }
     }
 
 
-    public IEnumerable<object> GetProperties()
+    protected override IEnumerable<object> GetControlProperties()
     {
     {
         yield return Color;
         yield return Color;
         yield return Thickness;
         yield return Thickness;
@@ -111,7 +111,7 @@ public class Border : SingleChildLayoutElement, IPropertyDeserializable
         yield return Height;
         yield return Height;
     }
     }
 
 
-    public void DeserializeProperties(ImmutableList<object> values)
+    protected override void DeserializeControlProperties(List<object> values)
     {
     {
         Color = (Color)values.ElementAtOrDefault(0, default(Color));
         Color = (Color)values.ElementAtOrDefault(0, default(Color));
         Thickness = (Edges)values.ElementAtOrDefault(1, default(Edges));
         Thickness = (Edges)values.ElementAtOrDefault(1, default(Edges));

+ 2 - 2
src/PixiEditor.Extensions/FlyUI/Elements/Column.cs

@@ -68,13 +68,13 @@ public class Column : MultiChildLayoutElement, IPropertyDeserializable
         return panel;
         return panel;
     }
     }
 
 
-    public IEnumerable<object> GetProperties()
+    protected override IEnumerable<object> GetControlProperties()
     {
     {
         yield return MainAxisAlignment;
         yield return MainAxisAlignment;
         yield return CrossAxisAlignment;
         yield return CrossAxisAlignment;
     }
     }
 
 
-    public void DeserializeProperties(ImmutableList<object> values)
+    protected override void DeserializeControlProperties(List<object> values)
     {
     {
         if (values.Count < 2)
         if (values.Count < 2)
             return;
             return;

+ 2 - 2
src/PixiEditor.Extensions/FlyUI/Elements/Container.cs

@@ -77,7 +77,7 @@ public class Container : SingleChildLayoutElement, IPropertyDeserializable
         _panel.Children.Clear();
         _panel.Children.Clear();
     }
     }
 
 
-    public IEnumerable<object> GetProperties()
+    protected override IEnumerable<object> GetControlProperties()
     {
     {
         yield return Margin;
         yield return Margin;
 
 
@@ -87,7 +87,7 @@ public class Container : SingleChildLayoutElement, IPropertyDeserializable
         yield return Height;
         yield return Height;
     }
     }
 
 
-    public void DeserializeProperties(ImmutableList<object> values)
+    protected override void DeserializeControlProperties(List<object> values)
     {
     {
         Margin = (Edges)values.ElementAtOrDefault(0, default(Edges));
         Margin = (Edges)values.ElementAtOrDefault(0, default(Edges));
         BackgroundColor = (Color)values.ElementAtOrDefault(1, default(Color));
         BackgroundColor = (Color)values.ElementAtOrDefault(1, default(Color));

+ 5 - 5
src/PixiEditor.Extensions/FlyUI/Elements/Hyperlink.cs

@@ -16,9 +16,9 @@ public class Hyperlink : Text
         Url = url;
         Url = url;
     }
     }
 
 
-    public override Control BuildNative()
+    protected override Control CreateNativeControl()
     {
     {
-        TextBlock hyperlink = (TextBlock)base.BuildNative();
+        TextBlock hyperlink = (TextBlock)base.CreateNativeControl();
 
 
         Binding urlBinding = new Binding() { Source = this, Path = nameof(Url), };
         Binding urlBinding = new Binding() { Source = this, Path = nameof(Url), };
 
 
@@ -27,7 +27,7 @@ public class Hyperlink : Text
         return hyperlink;
         return hyperlink;
     }
     }
 
 
-    public override IEnumerable<object> GetProperties()
+    protected override IEnumerable<object> GetControlProperties()
     {
     {
         yield return Value;
         yield return Value;
         yield return TextWrap;
         yield return TextWrap;
@@ -35,9 +35,9 @@ public class Hyperlink : Text
         yield return Url;
         yield return Url;
     }
     }
 
 
-    public override void DeserializeProperties(ImmutableList<object> values)
+    protected override void DeserializeControlProperties(List<object> values)
     {
     {
-        base.DeserializeProperties(values);
+        base.DeserializeControlProperties(values);
         Url = (string)values[3];
         Url = (string)values[3];
     }
     }
 }
 }

+ 3 - 3
src/PixiEditor.Extensions/FlyUI/Elements/Icon.cs

@@ -10,7 +10,7 @@ using Colors = PixiEditor.Extensions.CommonApi.FlyUI.Properties.Colors;
 
 
 namespace PixiEditor.Extensions.FlyUI.Elements;
 namespace PixiEditor.Extensions.FlyUI.Elements;
 
 
-public class Icon : StatelessElement, IPropertyDeserializable
+public class Icon : LayoutElement
 {
 {
     private double size = 16;
     private double size = 16;
     private string iconName = string.Empty;
     private string iconName = string.Empty;
@@ -50,14 +50,14 @@ public class Icon : StatelessElement, IPropertyDeserializable
         return textBlock;
         return textBlock;
     }
     }
 
 
-    public IEnumerable<object> GetProperties()
+    protected override IEnumerable<object> GetControlProperties()
     {
     {
         yield return IconName;
         yield return IconName;
         yield return Size;
         yield return Size;
         yield return Color;
         yield return Color;
     }
     }
 
 
-    public void DeserializeProperties(ImmutableList<object> values)
+    protected override void DeserializeControlProperties(List<object> values)
     {
     {
         IconName = (string)values[0];
         IconName = (string)values[0];
         Size = (double)values[1];
         Size = (double)values[1];

+ 3 - 3
src/PixiEditor.Extensions/FlyUI/Elements/Image.cs

@@ -12,7 +12,7 @@ using PixiEditor.Extensions.UI;
 
 
 namespace PixiEditor.Extensions.FlyUI.Elements;
 namespace PixiEditor.Extensions.FlyUI.Elements;
 
 
-public class Image : StatelessElement, IPropertyDeserializable
+public class Image : LayoutElement
 {
 {
     private string _source = null!;
     private string _source = null!;
     private double _width = -1;
     private double _width = -1;
@@ -78,7 +78,7 @@ public class Image : StatelessElement, IPropertyDeserializable
     }
     }
 
 
 
 
-    public IEnumerable<object> GetProperties()
+    protected override IEnumerable<object> GetControlProperties()
     {
     {
         yield return Source;
         yield return Source;
         
         
@@ -89,7 +89,7 @@ public class Image : StatelessElement, IPropertyDeserializable
         yield return FilterQuality;
         yield return FilterQuality;
     }
     }
 
 
-    public void DeserializeProperties(ImmutableList<object> values)
+    protected override void DeserializeControlProperties(List<object> values)
     {
     {
         var valuesList = values.ToList();
         var valuesList = values.ToList();
         Source = (string)valuesList.ElementAtOrDefault(0);
         Source = (string)valuesList.ElementAtOrDefault(0);

+ 5 - 1
src/PixiEditor.Extensions/FlyUI/Elements/LayoutBuilder.cs

@@ -93,6 +93,10 @@ public class LayoutBuilder
                 
                 
                 properties.Add(prop);
                 properties.Add(prop);
             }
             }
+            else if (type == null)
+            {
+                properties.Add(null);
+            }
             else
             else
             {
             {
                 var property = SpanUtility.Read(type, layoutSpan, ref offset);
                 var property = SpanUtility.Read(type, layoutSpan, ref offset);
@@ -127,7 +131,7 @@ public class LayoutBuilder
 
 
         if (element is IPropertyDeserializable deserializableProperties)
         if (element is IPropertyDeserializable deserializableProperties)
         {
         {
-            deserializableProperties.DeserializeProperties(properties.ToImmutableList());
+            deserializableProperties.DeserializeProperties(properties);
         }
         }
 
 
         if (element is IChildHost customChildrenDeserializable)
         if (element is IChildHost customChildrenDeserializable)

+ 48 - 4
src/PixiEditor.Extensions/FlyUI/Elements/LayoutElement.cs

@@ -1,12 +1,18 @@
-using System.ComponentModel;
+using System.Collections.Immutable;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using Avalonia.Controls;
 using Avalonia.Controls;
+using Avalonia.Data;
+using Avalonia.Input;
 using PixiEditor.Extensions.CommonApi.FlyUI;
 using PixiEditor.Extensions.CommonApi.FlyUI;
 using PixiEditor.Extensions.CommonApi.FlyUI.Events;
 using PixiEditor.Extensions.CommonApi.FlyUI.Events;
+using PixiEditor.Extensions.FlyUI.Converters;
+using Cursor = PixiEditor.Extensions.CommonApi.FlyUI.Cursor;
 
 
 namespace PixiEditor.Extensions.FlyUI.Elements;
 namespace PixiEditor.Extensions.FlyUI.Elements;
 
 
-public abstract class LayoutElement : ILayoutElement<Control>, INotifyPropertyChanged
+public abstract class LayoutElement : ILayoutElement<Control>, INotifyPropertyChanged, IPropertyDeserializable
 {
 {
     public int UniqueId { get; set; }
     public int UniqueId { get; set; }
     public event ElementEventHandler PointerEnter
     public event ElementEventHandler PointerEnter
@@ -33,17 +39,32 @@ public abstract class LayoutElement : ILayoutElement<Control>, INotifyPropertyCh
         remove => RemoveEvent(nameof(PointerReleased), value);
         remove => RemoveEvent(nameof(PointerReleased), value);
     }
     }
 
 
+    public Cursor? Cursor { get; set; }
+
     private Dictionary<string, List<ElementEventHandler>>? _events;
     private Dictionary<string, List<ElementEventHandler>>? _events;
 
 
     public virtual Control BuildNative()
     public virtual Control BuildNative()
     {
     {
         Control control = CreateNativeControl();
         Control control = CreateNativeControl();
 
 
-        SubscribeBasicEvents(control);
+        BuildCore(control);
         return control;
         return control;
     }
     }
 
 
-    protected void SubscribeBasicEvents(Control control)
+    protected void BuildCore(Control control)
+    {
+        Binding cursorBinding = new Binding()
+        {
+            Source = this,
+            Path = "Cursor",
+            Converter = new CursorToAvaloniaCursorConverter()
+        };
+
+        control.Bind(InputElement.CursorProperty, cursorBinding);
+        SubscribeBasicEvents(control);
+    }
+
+    private void SubscribeBasicEvents(Control control)
     {
     {
         control.PointerEntered += (sender, args) => RaiseEvent(nameof(PointerEnter), new ElementEventArgs() { Sender = this });
         control.PointerEntered += (sender, args) => RaiseEvent(nameof(PointerEnter), new ElementEventArgs() { Sender = this });
         control.PointerExited += (sender, args) => RaiseEvent(nameof(PointerLeave), new ElementEventArgs() { Sender = this });
         control.PointerExited += (sender, args) => RaiseEvent(nameof(PointerLeave), new ElementEventArgs() { Sender = this });
@@ -145,4 +166,27 @@ public abstract class LayoutElement : ILayoutElement<Control>, INotifyPropertyCh
         OnPropertyChanged(propertyName);
         OnPropertyChanged(propertyName);
         return true;
         return true;
     }
     }
+
+    public IEnumerable<object> GetProperties()
+    {
+        yield return Cursor;
+        foreach (var property in GetControlProperties())
+        {
+            yield return property;
+        }
+    }
+
+    protected virtual IEnumerable<object> GetControlProperties()
+    {
+        yield break;
+    }
+
+    public void DeserializeProperties(List<object> values)
+    {
+        Cursor = (Cursor?)values.ElementAtOrDefault(0);
+        var subValues = values[1..];
+        DeserializeControlProperties(subValues);
+    }
+
+    protected virtual void DeserializeControlProperties(List<object> values){}
 }
 }

+ 2 - 2
src/PixiEditor.Extensions/FlyUI/Elements/Padding.cs

@@ -45,12 +45,12 @@ public class Padding : SingleChildLayoutElement, IPropertyDeserializable
         _decorator.Child = null;
         _decorator.Child = null;
     }
     }
 
 
-    public IEnumerable<object> GetProperties()
+    protected override IEnumerable<object> GetControlProperties()
     {
     {
         yield return Edges;
         yield return Edges;
     }
     }
 
 
-    public void DeserializeProperties(ImmutableList<object> values)
+    protected override void DeserializeControlProperties(List<object> values)
     {
     {
         Edges = (Edges)values.ElementAtOrDefault(0, default(Edges));
         Edges = (Edges)values.ElementAtOrDefault(0, default(Edges));
     }
     }

+ 2 - 2
src/PixiEditor.Extensions/FlyUI/Elements/Row.cs

@@ -73,13 +73,13 @@ public class Row : MultiChildLayoutElement, IPropertyDeserializable
         return panel;
         return panel;
     }
     }
 
 
-    public IEnumerable<object> GetProperties()
+    protected override IEnumerable<object> GetControlProperties()
     {
     {
         yield return MainAxisAlignment;
         yield return MainAxisAlignment;
         yield return CrossAxisAlignment;
         yield return CrossAxisAlignment;
     }
     }
 
 
-    public void DeserializeProperties(ImmutableList<object> values)
+    protected override void DeserializeControlProperties(List<object> values)
     {
     {
         if (values.Count < 2)
         if (values.Count < 2)
             return;
             return;

+ 3 - 4
src/PixiEditor.Extensions/FlyUI/Elements/StatefulElement.cs

@@ -6,7 +6,7 @@ using PixiEditor.Extensions.CommonApi.FlyUI.State;
 
 
 namespace PixiEditor.Extensions.FlyUI.Elements;
 namespace PixiEditor.Extensions.FlyUI.Elements;
 
 
-public abstract class StatefulElement<TState> : LayoutElement, /*IPropertyDeserializable,*/
+public abstract class StatefulElement<TState> : LayoutElement,
     IStatefulElement<Control, TState> where TState : IState<Control>
     IStatefulElement<Control, TState> where TState : IState<Control>
 {
 {
     private TState? _state;
     private TState? _state;
@@ -46,8 +46,7 @@ public abstract class StatefulElement<TState> : LayoutElement, /*IPropertyDeseri
         _content = State.Build();
         _content = State.Build();
         Control control = _content.BuildNative();
         Control control = _content.BuildNative();
 
 
-        SubscribeBasicEvents(control);
-
+        BuildCore(control);
         _presenter.Content = control;
         _presenter.Content = control;
 
 
         return control;
         return control;
@@ -122,7 +121,7 @@ public abstract class StatefulElement<TState> : LayoutElement, /*IPropertyDeseri
         {
         {
             // TODO: Find a way to only apply changed properties, current solution shouldn't be a problem for most cases, but this
             // TODO: Find a way to only apply changed properties, current solution shouldn't be a problem for most cases, but this
             // might cause unnecessary redraws, binding fires and other stuff that might be expensive if we have a lot of elements
             // might cause unnecessary redraws, binding fires and other stuff that might be expensive if we have a lot of elements
-            propertyDeserializable.DeserializeProperties(fromProps.GetProperties().ToImmutableList());
+            propertyDeserializable.DeserializeProperties(fromProps.GetProperties().ToList());
         }
         }
     }
     }
 
 

+ 5 - 0
src/PixiEditor.Extensions/FlyUI/Elements/StatelessElement.cs

@@ -6,6 +6,11 @@ namespace PixiEditor.Extensions.FlyUI.Elements;
 
 
 public abstract class StatelessElement : LayoutElement, IStatelessElement<Control>
 public abstract class StatelessElement : LayoutElement, IStatelessElement<Control>
 {
 {
+    public override Control BuildNative()
+    {
+        return CreateNativeControl();
+    }
+
     protected override Control CreateNativeControl()
     protected override Control CreateNativeControl()
     {
     {
         return Build().BuildNative();
         return Build().BuildNative();

+ 3 - 3
src/PixiEditor.Extensions/FlyUI/Elements/Text.cs

@@ -11,7 +11,7 @@ using FontWeight = PixiEditor.Extensions.CommonApi.FlyUI.Properties.FontWeight;
 
 
 namespace PixiEditor.Extensions.FlyUI.Elements;
 namespace PixiEditor.Extensions.FlyUI.Elements;
 
 
-public class Text : StatelessElement, IPropertyDeserializable
+public class Text : LayoutElement
 {
 {
     private TextWrap _textWrap = TextWrap.None;
     private TextWrap _textWrap = TextWrap.None;
     private string _value = null!;
     private string _value = null!;
@@ -111,14 +111,14 @@ public class Text : StatelessElement, IPropertyDeserializable
         return textBlock;
         return textBlock;
     }
     }
 
 
-    public virtual IEnumerable<object> GetProperties()
+    protected override IEnumerable<object> GetControlProperties()
     {
     {
         yield return Value;
         yield return Value;
         yield return TextWrap;
         yield return TextWrap;
         yield return TextStyle;
         yield return TextStyle;
     }
     }
 
 
-    public virtual void DeserializeProperties(ImmutableList<object> values)
+    protected override void DeserializeControlProperties(List<object> values)
     {
     {
         Value = (string)values.ElementAtOrDefault(0);
         Value = (string)values.ElementAtOrDefault(0);
         TextWrap = (TextWrap)values.ElementAtOrDefault(1);
         TextWrap = (TextWrap)values.ElementAtOrDefault(1);

+ 1 - 1
src/PixiEditor.Extensions/FlyUI/IPropertyDeserializable.cs

@@ -5,5 +5,5 @@ namespace PixiEditor.Extensions.FlyUI;
 public interface IPropertyDeserializable
 public interface IPropertyDeserializable
 {
 {
     public IEnumerable<object> GetProperties();
     public IEnumerable<object> GetProperties();
-    public void DeserializeProperties(ImmutableList<object> values);
+    public void DeserializeProperties(List<object> values);
 }
 }

+ 0 - 1
src/PixiEditor/Helpers/Behaviours/ClearFocusOnClickBehavior.cs

@@ -17,7 +17,6 @@ internal class ClearFocusOnClickBehavior : Behavior<Control>
 
 
     private void AssociatedObject_LostFocus(object? sender, RoutedEventArgs? e)
     private void AssociatedObject_LostFocus(object? sender, RoutedEventArgs? e)
     {
     {
-
     }
     }
 
 
     protected override void OnDetaching()
     protected override void OnDetaching()

+ 36 - 11
tests/PixiEditor.Extensions.Sdk.Tests/NativeControlSerializationTest.cs

@@ -1,5 +1,6 @@
 using System.Text;
 using System.Text;
 using PixiEditor.Extensions.CommonApi.FlyUI;
 using PixiEditor.Extensions.CommonApi.FlyUI;
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
 using PixiEditor.Extensions.Sdk.Api.FlyUI;
 using PixiEditor.Extensions.Sdk.Api.FlyUI;
 
 
 namespace PixiEditor.Extensions.Sdk.Tests;
 namespace PixiEditor.Extensions.Sdk.Tests;
@@ -9,7 +10,7 @@ public class NativeControlSerializationTest
     [Fact]
     [Fact]
     public void TestThatNoChildLayoutSerializesCorrectBytes()
     public void TestThatNoChildLayoutSerializesCorrectBytes()
     {
     {
-        CompiledControl layout = new CompiledControl(0, "Layout");
+        ControlDefinition layout = new ControlDefinition(0, "Layout");
         layout.AddProperty("Title");
         layout.AddProperty("Title");
 
 
         int uniqueId = 0;
         int uniqueId = 0;
@@ -45,8 +46,8 @@ public class NativeControlSerializationTest
     [Fact]
     [Fact]
     public void TestThatChildLayoutSerializesCorrectBytes()
     public void TestThatChildLayoutSerializesCorrectBytes()
     {
     {
-        CompiledControl layout = new CompiledControl(0, "Layout");
-        layout.AddChild(new CompiledControl(1, "Center"));
+        ControlDefinition layout = new ControlDefinition(0, "Layout");
+        layout.AddChild(new ControlDefinition(1, "Center"));
 
 
         int uniqueId = 0;
         int uniqueId = 0;
         byte[] uniqueIdBytes = BitConverter.GetBytes(uniqueId);
         byte[] uniqueIdBytes = BitConverter.GetBytes(uniqueId);
@@ -87,12 +88,36 @@ public class NativeControlSerializationTest
         Assert.Equal(expectedBytes.ToArray(), layout.Serialize().ToArray());
         Assert.Equal(expectedBytes.ToArray(), layout.Serialize().ToArray());
     }
     }
 
 
+    [Fact]
+    public void TestThatBuildNativeBuildsPropertyBytesCorrectly()
+    {
+        Layout layout = new Layout();
+        var definition = layout.BuildNative();
+
+        Assert.Single(definition.Properties); // Cursor
+
+        byte[] serialized = definition.SerializeBytes();
+
+        Assert.Equal(23, serialized.Length);
+    }
+
+    [Fact]
+    public void TestThatStatelessElementSerializesBytesProperly()
+    {
+        WindowContentElement layout = new WindowContentElement();
+
+        var definition = layout.BuildNative();
+        var serialized = definition.SerializeBytes();
+
+        Assert.Equal(23, serialized.Length);
+    }
+
     [Fact]
     [Fact]
     public void TestThatChildNestedLayoutSerializesCorrectBytes()
     public void TestThatChildNestedLayoutSerializesCorrectBytes()
     {
     {
-        CompiledControl layout = new CompiledControl(0, "Layout");
-        CompiledControl center = new CompiledControl(1, "Center");
-        CompiledControl text = new CompiledControl(2, "Text");
+        ControlDefinition layout = new ControlDefinition(0, "Layout");
+        ControlDefinition center = new ControlDefinition(1, "Center");
+        ControlDefinition text = new ControlDefinition(2, "Text");
         text.AddProperty("Hello world");
         text.AddProperty("Hello world");
         center.AddChild(text);
         center.AddChild(text);
         layout.AddChild(center);
         layout.AddChild(center);
@@ -171,18 +196,18 @@ public class NativeControlSerializationTest
             new Center(
             new Center(
                 child: new Text("hello sexy.")));
                 child: new Text("hello sexy.")));
 
 
-        CompiledControl compiledControl = layout.BuildNative();
+        ControlDefinition compiledControl = layout.BuildNative();
 
 
         Assert.Equal("Layout", compiledControl.ControlTypeId);
         Assert.Equal("Layout", compiledControl.ControlTypeId);
-        Assert.Empty(compiledControl.Properties);
+        Assert.Single(compiledControl.Properties);
         Assert.Single(compiledControl.Children);
         Assert.Single(compiledControl.Children);
 
 
         Assert.Equal("Center", compiledControl.Children[0].ControlTypeId);
         Assert.Equal("Center", compiledControl.Children[0].ControlTypeId);
-        Assert.Empty(compiledControl.Children[0].Properties);
+        Assert.Single(compiledControl.Children[0].Properties);
 
 
         Assert.Equal("Text", compiledControl.Children[0].Children[0].ControlTypeId);
         Assert.Equal("Text", compiledControl.Children[0].Children[0].ControlTypeId);
         Assert.True(compiledControl.Children[0].Children[0].Properties.Count > 0);
         Assert.True(compiledControl.Children[0].Children[0].Properties.Count > 0);
-        Assert.Equal("hello sexy.", compiledControl.Children[0].Children[0].Properties[0].value);
+        Assert.Equal("hello sexy.", compiledControl.Children[0].Children[0].Properties[1].value);
     }
     }
 
 
     [Fact]
     [Fact]
@@ -196,4 +221,4 @@ public class NativeControlSerializationTest
 
 
         Assert.Contains(button.BuildQueuedEvents, x => x == "Click");
         Assert.Contains(button.BuildQueuedEvents, x => x == "Click");
     }
     }
-}
+}

+ 17 - 0
tests/PixiEditor.Extensions.Sdk.Tests/WindowContentElement.cs

@@ -0,0 +1,17 @@
+using System.Diagnostics.CodeAnalysis;
+using PixiEditor.Extensions.CommonApi.FlyUI;
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
+using PixiEditor.Extensions.Sdk.Api.FlyUI;
+using PixiEditor.Extensions.Sdk.Api.Window;
+
+namespace PixiEditor.Extensions.Sdk.Tests;
+
+[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:Parameter should not span multiple lines", Justification = "FlyUI style")]
+public class WindowContentElement : StatelessElement
+{
+    public override ILayoutElement<ControlDefinition> Build()
+    {
+        Layout layout = new Layout();
+        return layout;
+    }
+}