2
0
Эх сурвалжийг харах

Border, padding and sturct serialization

flabbet 1 жил өмнө
parent
commit
aff12fcd3d
23 өөрчлөгдсөн 368 нэмэгдсэн , 38 устгасан
  1. 1 1
      src/PixiEditor.AvaloniaUI/Models/ExtensionServices/WindowProvider.cs
  2. 8 1
      src/PixiEditor.Extensions.CommonApi/FlyUI/ByteMap.cs
  3. 14 1
      src/PixiEditor.Extensions.CommonApi/FlyUI/Properties/Color.cs
  4. 21 1
      src/PixiEditor.Extensions.CommonApi/FlyUI/Properties/Edges.cs
  5. 7 0
      src/PixiEditor.Extensions.CommonApi/FlyUI/Properties/IStructProperty.cs
  6. 2 2
      src/PixiEditor.Extensions.CommonApi/Windowing/IWindowProvider.cs
  7. 27 0
      src/PixiEditor.Extensions.Wasm/Api/FlyUI/Border.cs
  8. 42 5
      src/PixiEditor.Extensions.Wasm/Api/FlyUI/CompiledControl.cs
  9. 3 9
      src/PixiEditor.Extensions.Wasm/Api/FlyUI/Container.cs
  10. 3 3
      src/PixiEditor.Extensions.Wasm/Api/FlyUI/Image.cs
  11. 24 0
      src/PixiEditor.Extensions.Wasm/Api/FlyUI/Padding.cs
  12. 5 0
      src/PixiEditor.Extensions.Wasm/Api/FlyUI/Row.cs
  13. 3 3
      src/PixiEditor.Extensions.Wasm/Api/FlyUI/Text.cs
  14. 5 3
      src/PixiEditor.Extensions.Wasm/Api/Window/WindowProvider.cs
  15. 6 0
      src/PixiEditor.Extensions.Wasm/Bridge/Native.Windowing.cs
  16. 16 0
      src/PixiEditor.Extensions.WasmRuntime/Api/WindowingApi.cs
  17. 24 0
      src/PixiEditor.Extensions/FlyUI/Converters/ColorToAvaloniaBrushConverter.cs
  18. 11 2
      src/PixiEditor.Extensions/FlyUI/ElementMap.cs
  19. 66 0
      src/PixiEditor.Extensions/FlyUI/Elements/Border.cs
  20. 4 4
      src/PixiEditor.Extensions/FlyUI/Elements/Container.cs
  21. 29 2
      src/PixiEditor.Extensions/FlyUI/Elements/LayoutBuilder.cs
  22. 46 0
      src/PixiEditor.Extensions/FlyUI/Elements/Padding.cs
  23. 1 1
      src/PixiEditor/Models/AppExtensions/Services/WindowProvider.cs

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/ExtensionServices/WindowProvider.cs

@@ -44,7 +44,7 @@ public class WindowProvider : IWindowProvider
         return new PopupWindow(new PixiEditorPopup { Title = new LocalizedString(title), Content = body });
     }
 
-    public IPopupWindow GetWindow(WindowType type)
+    public IPopupWindow GetWindow(BuiltInWindowType type)
     {
         string id = type.GetDescription();
         return GetWindow($"PixiEditor.{id}");

+ 8 - 1
src/PixiEditor.Extensions.CommonApi/FlyUI/ByteMap.cs

@@ -1,4 +1,6 @@
-namespace PixiEditor.Extensions.CommonApi.FlyUI;
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
+
+namespace PixiEditor.Extensions.CommonApi.FlyUI;
 
 public static class ByteMap
 {
@@ -40,6 +42,10 @@ public static class ByteMap
         {
             return 8;
         }
+        if(type == typeof(byte[]))
+        {
+            return 9;
+        }
 
         throw new Exception($"Unknown unmanaged type: {type}");
     }
@@ -57,6 +63,7 @@ public static class ByteMap
             6 => typeof(byte),
             7 => typeof(char),
             8 => typeof(string),
+            9 => typeof(byte[]),
             _ => throw new Exception($"Unknown unmanaged type id: {id}")
         };
     }

+ 14 - 1
src/PixiEditor.Extensions.CommonApi/FlyUI/Properties/Color.cs

@@ -1,6 +1,6 @@
 namespace PixiEditor.Extensions.CommonApi.FlyUI.Properties;
 
-public struct Color
+public struct Color : IStructProperty
 {
     public byte R { get; set; }
     public byte G { get; set; }
@@ -19,4 +19,17 @@ public struct Color
     {
         return new Color(r, g, b, a);
     }
+
+    byte[] IStructProperty.Serialize()
+    {
+        return new byte[] { R, G, B, A };
+    }
+
+    void IStructProperty.Deserialize(byte[] data)
+    {
+        R = data[0];
+        G = data[1];
+        B = data[2];
+        A = data[3];
+    }
 }

+ 21 - 1
src/PixiEditor.Extensions.CommonApi/FlyUI/Properties/Edges.cs

@@ -2,7 +2,7 @@
 
 namespace PixiEditor.Extensions.CommonApi.FlyUI.Properties;
 
-public struct Edges
+public struct Edges : IStructProperty
 {
     public double Left { get; set; }
     public double Top { get; set; }
@@ -46,4 +46,24 @@ public struct Edges
     {
         return new Edges(a.Left / b, a.Top / b, a.Right / b, a.Bottom / b);
     }
+
+    byte[] IStructProperty.Serialize()
+    {
+        byte[] data = new byte[32];
+        
+        BitConverter.GetBytes(Left).CopyTo(data, 0);
+        BitConverter.GetBytes(Top).CopyTo(data, 8);
+        BitConverter.GetBytes(Right).CopyTo(data, 16);
+        BitConverter.GetBytes(Bottom).CopyTo(data, 24);
+        
+        return data;
+    }
+
+    void IStructProperty.Deserialize(byte[] data)
+    {
+        Left = BitConverter.ToDouble(data, 0);
+        Top = BitConverter.ToDouble(data, 8);
+        Right = BitConverter.ToDouble(data, 16);
+        Bottom = BitConverter.ToDouble(data, 24);
+    }
 }

+ 7 - 0
src/PixiEditor.Extensions.CommonApi/FlyUI/Properties/IStructProperty.cs

@@ -0,0 +1,7 @@
+namespace PixiEditor.Extensions.CommonApi.FlyUI.Properties;
+
+public interface IStructProperty
+{
+    public byte[] Serialize();
+    public void Deserialize(byte[] data);
+}

+ 2 - 2
src/PixiEditor.Extensions.CommonApi/Windowing/IWindowProvider.cs

@@ -5,11 +5,11 @@ namespace PixiEditor.Extensions.CommonApi.Windowing;
 public interface IWindowProvider
 {
     public IPopupWindow CreatePopupWindow(string title, object body);
-    public IPopupWindow GetWindow(WindowType type);
+    public IPopupWindow GetWindow(BuiltInWindowType type);
     public IPopupWindow GetWindow(string windowId);
 }
 
-public enum WindowType
+public enum BuiltInWindowType
 {
     [Description("PalettesBrowser")]
     PalettesBrowser

+ 27 - 0
src/PixiEditor.Extensions.Wasm/Api/FlyUI/Border.cs

@@ -0,0 +1,27 @@
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
+
+namespace PixiEditor.Extensions.Wasm.Api.FlyUI;
+
+public class Border : SingleChildLayoutElement
+{
+    public Color Color { get; set; }
+    public Edges Edges { get; set; }
+
+    public Border(LayoutElement child = null, Color color = default, Edges edges = default)
+    {
+        Child = child;
+        Color = color;
+        Edges = edges;
+    }
+
+    public override CompiledControl BuildNative()
+    {
+        CompiledControl control = new(UniqueId, "Border");
+        control.Children.Add(Child.BuildNative());
+
+        control.AddProperty(Color);
+        control.AddProperty(Edges);
+
+        return control;
+    }
+}

+ 42 - 5
src/PixiEditor.Extensions.Wasm/Api/FlyUI/CompiledControl.cs

@@ -2,6 +2,7 @@
 using System.Runtime.InteropServices;
 using System.Text;
 using PixiEditor.Extensions.CommonApi.FlyUI;
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
 
 namespace PixiEditor.Extensions.Wasm.Api.FlyUI;
 
@@ -14,18 +15,40 @@ public class CompiledControl
     internal List<string> QueuedEvents => _buildQueuedEvents;
 
     private List<string> _buildQueuedEvents = new List<string>();
+
     public CompiledControl(int uniqueId, string controlTypeId)
     {
         ControlTypeId = controlTypeId;
         UniqueId = uniqueId;
     }
 
-    public void AddProperty<T>(T value) where T : unmanaged
+    public void AddProperty<T>(T value)
+    {
+        InternalAddProperty(value);
+    }
+
+    private void InternalAddProperty(object value)
     {
-        Properties.Add((value, typeof(T)));
+        if (value is string s)
+        {
+            AddStringProperty(s);
+        }
+        else if (value is Enum enumProp)
+        {
+            var enumValue = Convert.ChangeType(value, enumProp.GetTypeCode());
+            Properties.Add((enumValue, enumValue.GetType()));
+        }
+        else if (value is IStructProperty structProperty)
+        {
+            Properties.Add((value, typeof(byte[])));
+        }
+        else
+        {
+            Properties.Add((value, value.GetType()));
+        }
     }
 
-    public void AddStringProperty(string value)
+    private void AddStringProperty(string value)
     {
         Properties.Add((value, typeof(string)));
     }
@@ -49,7 +72,7 @@ public class CompiledControl
     private List<byte> Serialize(List<byte> bytes)
     {
         // TODO: Make it more efficient
-        
+
         byte[] uniqueIdBytes = BitConverter.GetBytes(UniqueId);
         bytes.AddRange(uniqueIdBytes);
         byte[] idLengthBytes = BitConverter.GetBytes(ControlTypeId.Length);
@@ -60,7 +83,7 @@ public class CompiledControl
         bytes.AddRange(SerializeProperties());
         bytes.AddRange(BitConverter.GetBytes(Children.Count));
         SerializeChildren(bytes);
-        
+
         return bytes;
     }
 
@@ -94,6 +117,7 @@ public class CompiledControl
                 byte b => new byte[] { b },
                 char c => BitConverter.GetBytes(c),
                 string s => Encoding.UTF8.GetBytes(s),
+                IStructProperty structProperty => GetWellKnownStructBytes(structProperty),
                 null => [],
                 _ => throw new Exception($"Unknown unmanaged type: {property.value.GetType()}")
             });
@@ -102,6 +126,19 @@ public class CompiledControl
         return result;
     }
 
+    private static List<byte> GetWellKnownStructBytes(IStructProperty structProperty)
+    {
+        List<byte> bytes = new List<byte>(BitConverter.GetBytes(structProperty.GetType().Name.Length));
+        bytes.AddRange(Encoding.UTF8.GetBytes(structProperty.GetType().Name));
+
+        byte[] structBytes = structProperty.Serialize();
+        
+        bytes.AddRange(BitConverter.GetBytes(structBytes.Length));
+        bytes.AddRange(structBytes);
+        
+        return bytes;
+    }
+
     internal void AddEvent(string eventName)
     {
         _buildQueuedEvents.Add(eventName);

+ 3 - 9
src/PixiEditor.Extensions.Wasm/Api/FlyUI/Container.cs

@@ -22,15 +22,9 @@ public class Container : SingleChildLayoutElement
     public override CompiledControl BuildNative()
     {
         CompiledControl container = new CompiledControl(UniqueId, "Container");
-        container.AddProperty(Margin.Left);
-        container.AddProperty(Margin.Top);
-        container.AddProperty(Margin.Right);
-        container.AddProperty(Margin.Bottom);
-        
-        container.AddProperty(BackgroundColor.R);
-        container.AddProperty(BackgroundColor.G);
-        container.AddProperty(BackgroundColor.B);
-        container.AddProperty(BackgroundColor.A);
+        container.AddProperty(Margin);
+
+        container.AddProperty(BackgroundColor);
         
         container.AddProperty(Width);
         container.AddProperty(Height);

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

@@ -41,11 +41,11 @@ public class Image : StatelessElement
     {
         CompiledControl image = new CompiledControl(UniqueId, "Image");
         
-        image.AddStringProperty(Source);
+        image.AddProperty(Source);
         image.AddProperty(Width);
         image.AddProperty(Height);
-        image.AddProperty((int)FillMode);
-        image.AddProperty((byte)FilterQuality);
+        image.AddProperty(FillMode);
+        image.AddProperty(FilterQuality);
         
         BuildPendingEvents(image);
         return image;

+ 24 - 0
src/PixiEditor.Extensions.Wasm/Api/FlyUI/Padding.cs

@@ -0,0 +1,24 @@
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
+
+namespace PixiEditor.Extensions.Wasm.Api.FlyUI;
+
+public class Padding : SingleChildLayoutElement
+{
+    public Edges Edges { get; set; } = Edges.All(0);
+    
+    public Padding(LayoutElement child = null, Edges edges = default)
+    {
+        Edges = edges;
+        Child = child;
+    }
+    
+    public override CompiledControl BuildNative()
+    {
+        CompiledControl control = new CompiledControl(UniqueId, "Padding");
+        control.Children.Add(Child.BuildNative());
+        
+        control.AddProperty(Edges);
+
+        return control;
+    }
+}

+ 5 - 0
src/PixiEditor.Extensions.Wasm/Api/FlyUI/Row.cs

@@ -2,6 +2,11 @@
 
 public class Row : MultiChildLayoutElement
 {
+    public Row(params LayoutElement[] children)
+    {
+        Children = new List<LayoutElement>(children);
+    }
+    
     public override CompiledControl BuildNative()
     {
         CompiledControl control = new CompiledControl(UniqueId, "Row");

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

@@ -23,9 +23,9 @@ public class Text : StatelessElement
     public override CompiledControl BuildNative()
     {
         CompiledControl text = new CompiledControl(UniqueId, "Text");
-        text.AddStringProperty(Value);
-        text.AddProperty((int)TextWrap);
-        text.AddProperty((int)FontStyle);
+        text.AddProperty(Value);
+        text.AddProperty(TextWrap);
+        text.AddProperty(FontStyle);
         text.AddProperty(FontSize);
 
         BuildPendingEvents(text);

+ 5 - 3
src/PixiEditor.Extensions.Wasm/Api/Window/WindowProvider.cs

@@ -52,13 +52,15 @@ public class WindowProvider : IWindowProvider
         return CreatePopupWindow(title, element);
     }
 
-    public IPopupWindow GetWindow(WindowType type)
+    public IPopupWindow GetWindow(BuiltInWindowType type)
     {
-        throw new NotImplementedException();
+        int handle = Native.get_built_in_window((int)type);
+        return new PopupWindow(handle);
     }
 
     public IPopupWindow GetWindow(string windowId)
     {
-        throw new NotImplementedException();
+        int handle = Native.get_window(windowId);
+        return new PopupWindow(handle);
     }
 }

+ 6 - 0
src/PixiEditor.Extensions.Wasm/Bridge/Native.Windowing.cs

@@ -46,4 +46,10 @@ internal static partial class Native
 
     [MethodImpl(MethodImplOptions.InternalCall)]
     internal static extern int show_window_async(int windowHandle);
+
+    [MethodImpl(MethodImplOptions.InternalCall)]
+    public static extern int get_built_in_window(int type);
+
+    [MethodImpl(MethodImplOptions.InternalCall)]
+    public static extern int get_window(string windowId);
 }

+ 16 - 0
src/PixiEditor.Extensions.WasmRuntime/Api/WindowingApi.cs

@@ -1,5 +1,6 @@
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Extensions.CommonApi.Async;
+using PixiEditor.Extensions.CommonApi.Windowing;
 using PixiEditor.Extensions.FlyUI.Elements;
 using PixiEditor.Extensions.WasmRuntime.Utilities;
 using PixiEditor.Extensions.Windowing;
@@ -17,6 +18,21 @@ internal class WindowingApi : ApiGroupHandler
         int handle = NativeObjectManager.AddObject(popupWindow);
         return handle;
     }
+    
+    [ApiFunction("get_built_in_window")]
+    public int OpenBuiltInWindow(int type)
+    {
+        var windowType = (BuiltInWindowType)type;
+        var window = Api.Windowing.GetWindow(windowType);
+        return NativeObjectManager.AddObject(window);
+    }
+    
+    [ApiFunction("get_window")]
+    public int OpenWindow(string windowId)
+    {
+        var window = Api.Windowing.GetWindow(windowId);
+        return NativeObjectManager.AddObject(window);
+    }
 
     [ApiFunction("set_window_title")]
     public void SetWindowTitle(int handle, string title)

+ 24 - 0
src/PixiEditor.Extensions/FlyUI/Converters/ColorToAvaloniaBrushConverter.cs

@@ -0,0 +1,24 @@
+using System.Globalization;
+using Avalonia.Data.Converters;
+using Avalonia.Media;
+using Color = PixiEditor.Extensions.CommonApi.FlyUI.Properties.Color;
+
+namespace PixiEditor.Extensions.FlyUI.Converters;
+
+public class ColorToAvaloniaBrushConverter : IValueConverter
+{
+    public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+    {
+        if (value is Color color)
+        {
+            return new SolidColorBrush(new Avalonia.Media.Color(color.A, color.R, color.G, color.B));
+        }
+
+        return null;
+    }
+
+    public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+    {
+        throw new NotImplementedException();
+    }
+}

+ 11 - 2
src/PixiEditor.Extensions/FlyUI/ElementMap.cs

@@ -2,15 +2,18 @@
 using System.Text;
 using Avalonia.Controls;
 using PixiEditor.Extensions.CommonApi.FlyUI;
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
 
 namespace PixiEditor.Extensions.FlyUI;
 
 public class ElementMap
 {
     public IReadOnlyDictionary<string, Type> ControlMap => controlMap;
+    public IReadOnlyDictionary<string, Type> WellKnownStructs => wellKnownStructs;
 
     // TODO: Code generation
     private Dictionary<string, Type> controlMap = new Dictionary<string, Type>();
+    private Dictionary<string, Type> wellKnownStructs = new Dictionary<string, Type>();
 
     public ElementMap()
     {
@@ -19,11 +22,17 @@ public class ElementMap
 
     public void AddElementsFromAssembly(Assembly assembly)
     {
-        var types = assembly.GetTypes().Where(x => x.GetInterfaces().Contains(typeof(ILayoutElement<Control>)));
-        foreach (var type in types)
+        var layoutElementTypes = assembly.GetTypes().Where(x => x.GetInterfaces().Contains(typeof(ILayoutElement<Control>)));
+        foreach (var type in layoutElementTypes)
         {
             controlMap.Add(type.Name, type); // TODO: Extension unique name prefix?
         }
+        
+        var structTypes = assembly.GetTypes().Where(x => x.GetInterfaces().Contains(typeof(IStructProperty)));
+        foreach (var type in structTypes)
+        {
+            wellKnownStructs.Add(type.Name, type);
+        }
     }
 
     public byte[] Serialize()

+ 66 - 0
src/PixiEditor.Extensions/FlyUI/Elements/Border.cs

@@ -0,0 +1,66 @@
+using System.Collections.Immutable;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Data;
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
+using PixiEditor.Extensions.Extensions;
+using PixiEditor.Extensions.FlyUI.Converters;
+
+namespace PixiEditor.Extensions.FlyUI.Elements;
+
+public class Border : SingleChildLayoutElement, IPropertyDeserializable
+{
+    private Edges _edges;
+    private Color _color;
+    
+    public Color Color { get => _color; set => SetField(ref _color, value); }
+    public Edges Edges { get => _edges; set => SetField(ref _edges, value); }
+    
+    public Border(LayoutElement child = null, Color color = default, Edges edges = default)
+    {
+        Child = child;
+        _color = color;
+        _edges = edges;
+    }
+    
+    public override Control BuildNative()
+    {
+        Avalonia.Controls.Border border = new Avalonia.Controls.Border();
+        
+        if (Child != null)
+        {
+            border.Child = Child.BuildNative();
+        }
+        
+        Binding colorBinding = new Binding()
+        {
+            Source = this,
+            Path = nameof(Color),
+            Converter = new ColorToAvaloniaBrushConverter()
+        };
+        
+        Binding edgesBinding = new Binding()
+        {
+            Source = this,
+            Path = nameof(Edges),
+            Converter = new EdgesToThicknessConverter()
+        };
+        
+        border.Bind(Avalonia.Controls.Border.BorderBrushProperty, colorBinding);
+        border.Bind(Avalonia.Controls.Border.BorderThicknessProperty, edgesBinding);
+        
+        return border;
+    }
+
+    public IEnumerable<object> GetProperties()
+    {
+        yield return Color;
+        yield return Edges;
+    }
+
+    public void DeserializeProperties(ImmutableList<object> values)
+    {
+        Color = (Color)values.ElementAtOrDefault(0, default(Color));
+        Edges = (Edges)values.ElementAtOrDefault(1, default(Edges));
+    }
+}

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

@@ -83,10 +83,10 @@ public class Container : SingleChildLayoutElement, IPropertyDeserializable
 
     public void DeserializeProperties(ImmutableList<object> values)
     {
-        Margin = new Edges((double)values.ElementAtOrDefault(0), (double)values.ElementAtOrDefault(1), (double)values.ElementAtOrDefault(2), (double)values.ElementAtOrDefault(3));
-        BackgroundColor = new Color((byte)values.ElementAtOrDefault(4), (byte)values.ElementAtOrDefault(5), (byte)values.ElementAtOrDefault(6), (byte)values.ElementAtOrDefault(7));
-        Width = (double)values.ElementAtOrDefault(8, double.NaN);
-        Height = (double)values.ElementAtOrDefault(9, double.NaN);
+        Margin = (Edges)values.ElementAtOrDefault(0, default(Edges));
+        BackgroundColor = (Color)values.ElementAtOrDefault(1, default(Color));
+        Width = (double)values.ElementAtOrDefault(2, double.NaN);
+        Height = (double)values.ElementAtOrDefault(3, double.NaN);
         
         Width = Width < 0 ? double.NaN : Width;
         Height = Height < 0 ? double.NaN : Height;

+ 29 - 2
src/PixiEditor.Extensions/FlyUI/Elements/LayoutBuilder.cs

@@ -2,6 +2,7 @@
 using System.Text;
 using Avalonia.Controls;
 using PixiEditor.Extensions.CommonApi.FlyUI;
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
 using PixiEditor.Extensions.FlyUI.Exceptions;
 using PixiEditor.Extensions.Helpers;
 
@@ -38,7 +39,7 @@ public class LayoutBuilder
         int propertiesCount = BitConverter.ToInt32(layoutSpan[offset..(offset + int32Size)]);
         offset += int32Size;
 
-        List<object> properties = DeserializeProperties(layoutSpan, propertiesCount, ref offset);
+        List<object> properties = DeserializeProperties(layoutSpan, propertiesCount, ref offset, elementMap);
 
         int childrenCount = BitConverter.ToInt32(layoutSpan[offset..(offset + int32Size)]);
         offset += int32Size;
@@ -48,7 +49,7 @@ public class LayoutBuilder
         return BuildLayoutElement(uniqueId, controlTypeId, properties, children, duplicatedIdTactic);
     }
 
-    private static List<object> DeserializeProperties(Span<byte> layoutSpan, int propertiesCount, ref int offset)
+    private static List<object> DeserializeProperties(Span<byte> layoutSpan, int propertiesCount, ref int offset, ElementMap map)
     {
         var properties = new List<object>();
         for (int i = 0; i < propertiesCount; i++)
@@ -65,6 +66,32 @@ public class LayoutBuilder
                 offset += stringLength;
                 properties.Add(value);
             }
+            else if (type == typeof(byte[]))
+            {
+                int wellKnownStructNameLength = BitConverter.ToInt32(layoutSpan[offset..(offset + int32Size)]);
+                offset += int32Size;
+                
+                string wellKnownStructName = Encoding.UTF8.GetString(layoutSpan[offset..(offset + wellKnownStructNameLength)]);
+                offset += wellKnownStructNameLength;
+                
+                int structSize = BitConverter.ToInt32(layoutSpan[offset..(offset + int32Size)]);
+                offset += int32Size;
+                
+                byte[] value = layoutSpan[offset..(offset + structSize)].ToArray();
+                
+                offset += structSize;
+                
+                map.WellKnownStructs.TryGetValue(wellKnownStructName, out Type? structType);
+                if (structType == null)
+                {
+                    throw new Exception($"Struct type {wellKnownStructName} not found in map");
+                }
+                
+                IStructProperty prop = (IStructProperty)Activator.CreateInstance(structType);
+                prop.Deserialize(value);
+                
+                properties.Add(prop);
+            }
             else
             {
                 var property = SpanUtility.Read(type, layoutSpan, ref offset);

+ 46 - 0
src/PixiEditor.Extensions/FlyUI/Elements/Padding.cs

@@ -0,0 +1,46 @@
+using System.Collections.Immutable;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Data;
+using PixiEditor.Extensions.CommonApi.FlyUI.Properties;
+using PixiEditor.Extensions.Extensions;
+using PixiEditor.Extensions.FlyUI.Converters;
+
+namespace PixiEditor.Extensions.FlyUI.Elements;
+
+public class Padding : SingleChildLayoutElement, IPropertyDeserializable
+{
+    private Edges _edges = Edges.All(0);
+    
+    public Edges Edges { get => _edges; set => SetField(ref _edges, value); }
+    public override Control BuildNative()
+    {
+        Decorator decorator = new();
+        
+        if(Child != null)
+        {
+            decorator.Child = Child.BuildNative();
+        }
+        
+        Binding edgesBinding = new()
+        {
+            Source = this,
+            Path = nameof(Edges),
+            Converter = new EdgesToThicknessConverter(),
+        };
+        
+        decorator.Bind(Decorator.PaddingProperty, edgesBinding);
+        
+        return decorator;
+    }
+
+    public IEnumerable<object> GetProperties()
+    {
+        yield return Edges;
+    }
+
+    public void DeserializeProperties(ImmutableList<object> values)
+    {
+        Edges = (Edges)values.ElementAtOrDefault(0, default(Edges));
+    }
+}

+ 1 - 1
src/PixiEditor/Models/AppExtensions/Services/WindowProvider.cs

@@ -27,7 +27,7 @@ public class WindowProvider : IWindowProvider
         return new PopupWindow(new BasicPopup { Title = title, Body = body });
     }
 
-    public PopupWindow GetWindow(WindowType type)
+    public PopupWindow GetWindow(BuiltInWindowType type)
     {
         return GetWindow($"PixiEditor.{type}");
     }