Browse Source

LayoutBuilder is working

Krzysztof Krysiński 1 year ago
parent
commit
34f90d5992
42 changed files with 814 additions and 52 deletions
  1. 73 0
      src/PixiEditor.Extensions.CommonApi/LayoutBuilding/ByteMap.cs
  2. 6 0
      src/PixiEditor.Extensions.CommonApi/LayoutBuilding/ILayoutElement.cs
  3. 6 0
      src/PixiEditor.Extensions.CommonApi/LayoutBuilding/IMultiChildLayoutElement.cs
  4. 6 0
      src/PixiEditor.Extensions.CommonApi/LayoutBuilding/ISingleChildLayoutElement.cs
  5. 6 0
      src/PixiEditor.Extensions.CommonApi/LayoutBuilding/ITextElement.cs
  6. 22 0
      src/PixiEditor.Extensions.CommonApi/LayoutBuilding/LayoutSerializationSpec.md
  7. 9 0
      src/PixiEditor.Extensions.CommonApi/PixiEditor.Extensions.CommonApi.csproj
  8. 35 0
      src/PixiEditor.Extensions.Tests/LayoutBuilderTests.cs
  9. 4 0
      src/PixiEditor.Extensions.Tests/PixiEditor.Extensions.Tests.csproj
  10. 0 9
      src/PixiEditor.Extensions.Tests/UnitTest1.cs
  11. 157 0
      src/PixiEditor.Extensions.Wasm.Tests/NativeControlSerializationTest.cs
  12. 4 0
      src/PixiEditor.Extensions.Wasm.Tests/PixiEditor.Extensions.Wasm.Tests.csproj
  13. 0 9
      src/PixiEditor.Extensions.Wasm.Tests/UnitTest1.cs
  14. 0 6
      src/PixiEditor.Extensions.Wasm/Api/IWindowProvider.cs
  15. 20 0
      src/PixiEditor.Extensions.Wasm/Api/LayoutBuilding/Center.cs
  16. 21 0
      src/PixiEditor.Extensions.Wasm/Api/LayoutBuilding/Layout.cs
  17. 87 0
      src/PixiEditor.Extensions.Wasm/Api/LayoutBuilding/NativeControl.cs
  18. 20 0
      src/PixiEditor.Extensions.Wasm/Api/LayoutBuilding/Text.cs
  19. 1 1
      src/PixiEditor.Extensions.Wasm/Api/Logging/ILogger.cs
  20. 1 3
      src/PixiEditor.Extensions.Wasm/Api/Logging/Logger.cs
  21. 8 0
      src/PixiEditor.Extensions.Wasm/Api/Window/IWindowProvider.cs
  22. 16 0
      src/PixiEditor.Extensions.Wasm/Api/Window/WindowProvider.cs
  23. 0 9
      src/PixiEditor.Extensions.Wasm/Api/WindowProvider.cs
  24. 1 1
      src/PixiEditor.Extensions.Wasm/Interop.cs
  25. 4 0
      src/PixiEditor.Extensions.Wasm/PixiEditor.Extensions.Wasm.csproj
  26. 2 0
      src/PixiEditor.Extensions.Wasm/PixiEditorApi.cs
  27. 13 0
      src/PixiEditor.Extensions.Wasm/Utilities/InteropUtility.cs
  28. 1 0
      src/PixiEditor.Extensions.Wasm/native/api.h
  29. 7 5
      src/PixiEditor.Extensions.Wasm/native/window_api.c
  30. 6 0
      src/PixiEditor.Extensions.WasmRuntime/MemoryUtility.cs
  31. 5 2
      src/PixiEditor.Extensions.WasmRuntime/PixiEditorApiLinkerExtensions.cs
  32. 19 0
      src/PixiEditor.Extensions/Helpers/SpanUtility.cs
  33. 32 0
      src/PixiEditor.Extensions/LayoutBuilding/Elements/Center.cs
  34. 9 0
      src/PixiEditor.Extensions/LayoutBuilding/Elements/IDeserializable.cs
  35. 24 0
      src/PixiEditor.Extensions/LayoutBuilding/Elements/Layout.cs
  36. 91 0
      src/PixiEditor.Extensions/LayoutBuilding/Elements/LayoutConverter.cs
  37. 24 0
      src/PixiEditor.Extensions/LayoutBuilding/Elements/Text.cs
  38. 17 0
      src/PixiEditor.Extensions/LayoutBuilding/GlobalControlFactory.cs
  39. 0 6
      src/PixiEditor.Extensions/LayoutBuilding/ILayoutBuilder.cs
  40. 4 0
      src/PixiEditor.Extensions/PixiEditor.Extensions.csproj
  41. 45 0
      src/PixiEditor.sln
  42. 8 1
      src/WasmSampleExtension/SampleExtension.cs

+ 73 - 0
src/PixiEditor.Extensions.CommonApi/LayoutBuilding/ByteMap.cs

@@ -0,0 +1,73 @@
+namespace PixiEditor.Extensions.CommonApi.LayoutBuilding;
+
+public static class ByteMap
+{
+    public static IReadOnlyDictionary<string, int> ControlMap => controlMap;
+
+    private static Dictionary<string, int> controlMap = new Dictionary<string, int>()
+    {
+        // If you do add a new control, make sure to add it to GlobalControlFactory inside PixiEditor project
+        { "Layout", 0 },
+        { "Center", 1 },
+        { "Text", 2 }
+    };
+
+    public static byte GetTypeByteId(Type type)
+    {
+        if (type == typeof(int))
+        {
+            return 0;
+        }
+        if (type == typeof(float))
+        {
+            return 1;
+        }
+        if (type == typeof(bool))
+        {
+            return 2;
+        }
+        if (type == typeof(double))
+        {
+            return 3;
+        }
+        if (type == typeof(long))
+        {
+            return 4;
+        }
+        if (type == typeof(short))
+        {
+            return 5;
+        }
+        if (type == typeof(byte))
+        {
+            return 6;
+        }
+        if (type == typeof(char))
+        {
+            return 7;
+        }
+        if (type == typeof(string))
+        {
+            return 8;
+        }
+
+        throw new Exception($"Unknown unmanaged type: {type}");
+    }
+
+    public static Type GetTypeFromByteId(byte id)
+    {
+        return id switch
+        {
+            0 => typeof(int),
+            1 => typeof(float),
+            2 => typeof(bool),
+            3 => typeof(double),
+            4 => typeof(long),
+            5 => typeof(short),
+            6 => typeof(byte),
+            7 => typeof(char),
+            8 => typeof(string),
+            _ => throw new Exception($"Unknown unmanaged type id: {id}")
+        };
+    }
+}

+ 6 - 0
src/PixiEditor.Extensions.CommonApi/LayoutBuilding/ILayoutElement.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.Extensions.CommonApi.LayoutBuilding;
+
+public interface ILayoutElement<out TBuildResult>
+{
+    public TBuildResult Build();
+}

+ 6 - 0
src/PixiEditor.Extensions.CommonApi/LayoutBuilding/IMultiChildLayoutElement.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.Extensions.CommonApi.LayoutBuilding;
+
+public interface IMultiChildLayoutElement<TBuildResult> : ILayoutElement<TBuildResult>
+{
+    public List<ILayoutElement<TBuildResult>> Children { get; set; }
+}

+ 6 - 0
src/PixiEditor.Extensions.CommonApi/LayoutBuilding/ISingleChildLayoutElement.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.Extensions.CommonApi.LayoutBuilding;
+
+public interface ISingleChildLayoutElement<TBuildResult> : ILayoutElement<TBuildResult>
+{
+    public ILayoutElement<TBuildResult> Child { get; set; }
+}

+ 6 - 0
src/PixiEditor.Extensions.CommonApi/LayoutBuilding/ITextElement.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.Extensions.CommonApi.LayoutBuilding;
+
+public interface ITextElement<out TBuildResult> : ILayoutElement<TBuildResult>
+{
+    public string Data { get; set; }
+}

+ 22 - 0
src/PixiEditor.Extensions.CommonApi/LayoutBuilding/LayoutSerializationSpec.md

@@ -0,0 +1,22 @@
+# LayoutBuilder interface
+
+LayoutBuilder is and abstract API used to build layouts inside PixiEditor.
+Layout data is passed as byte span, which is then deserialized into a layout object.
+
+This spec describes how to serialize and deserialize layout data.
+
+## Layout data
+
+Layout byte span is a recursive structure containing elements and properties data.
+
+Byte sequence:
+```
+    4 bytes - id of control,
+    4 bytes - length of properties data,
+    n bytes - properties data,
+        - 1 byte - property type,
+        - (if property type is string) 4 bytes - length of string)
+        - x bytes - property value, where x is determined by property type,
+    4 bytes - number of children,
+    n bytes - children data, where children get serialized recursively.
+```

+ 9 - 0
src/PixiEditor.Extensions.CommonApi/PixiEditor.Extensions.CommonApi.csproj

@@ -0,0 +1,9 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>net8.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+    </PropertyGroup>
+
+</Project>

+ 35 - 0
src/PixiEditor.Extensions.Tests/LayoutBuilderTests.cs

@@ -0,0 +1,35 @@
+using Avalonia.Controls;
+using PixiEditor.Extensions.LayoutBuilding;
+using PixiEditor.Extensions.LayoutBuilding.Elements;
+
+namespace PixiEditor.Extensions.Test;
+
+public class LayoutBuilderTests
+{
+    [Fact]
+    public void TestCenteredTextLayoutIsBuildCorrectly()
+    {
+        Layout layout = new Layout(
+            body: new Center(
+                child: new Text("Hello")));
+
+        object result = layout.Build();
+
+        Assert.IsType<Panel>(result);
+        Panel grid = (Panel)result;
+        Assert.Single(grid.Children);
+
+        Assert.IsType<Panel>(grid.Children[0]);
+        Panel childGrid = (Panel)grid.Children[0];
+
+        Assert.Equal(Avalonia.Layout.HorizontalAlignment.Center, childGrid.HorizontalAlignment);
+        Assert.Equal(Avalonia.Layout.VerticalAlignment.Center, childGrid.VerticalAlignment);
+
+        Assert.Single(childGrid.Children);
+
+        Assert.IsType<TextBlock>(childGrid.Children[0]);
+        TextBlock textBlock = (TextBlock)childGrid.Children[0];
+
+        Assert.Equal("Hello", textBlock.Text);
+    }
+}

+ 4 - 0
src/PixiEditor.Extensions.Tests/PixiEditor.Extensions.Tests.csproj

@@ -23,4 +23,8 @@
         </PackageReference>
         </PackageReference>
     </ItemGroup>
     </ItemGroup>
 
 
+    <ItemGroup>
+      <ProjectReference Include="..\PixiEditor.Extensions\PixiEditor.Extensions.csproj" />
+    </ItemGroup>
+
 </Project>
 </Project>

+ 0 - 9
src/PixiEditor.Extensions.Tests/UnitTest1.cs

@@ -1,9 +0,0 @@
-namespace PixiEditor.Extensions.Test;
-
-public class UnitTest1
-{
-    [Fact]
-    public void Test1()
-    {
-    }
-}

+ 157 - 0
src/PixiEditor.Extensions.Wasm.Tests/NativeControlSerializationTest.cs

@@ -0,0 +1,157 @@
+using System.Text;
+using PixiEditor.Extensions.CommonApi.LayoutBuilding;
+using PixiEditor.Extensions.Wasm.Api.LayoutBuilding;
+
+namespace PixiEditor.Extensions.Wasm.Tests;
+
+public class NativeControlSerializationTest
+{
+    [Fact]
+    public void TestThatNoChildLayoutSerializesCorrectBytes()
+    {
+        NativeControl layout = new NativeControl("Layout");
+        layout.AddProperty("Title");
+
+        int controlId = ByteMap.ControlMap["Layout"];
+        byte[] controlIdBytes = BitConverter.GetBytes(controlId);
+
+        int propertiesCount = 1;
+        byte[] propertiesCountBytes = BitConverter.GetBytes(propertiesCount);
+
+        int stringLen = "Title".Length;
+        byte[] stringLenBytes = BitConverter.GetBytes(stringLen);
+
+        byte[] titleBytes = Encoding.UTF8.GetBytes("Title");
+
+        int childCount = 0;
+        byte[] childCountBytes = BitConverter.GetBytes(childCount);
+
+        List<byte> expectedBytes = new();
+        expectedBytes.AddRange(controlIdBytes);
+        expectedBytes.AddRange(propertiesCountBytes);
+        expectedBytes.Add(ByteMap.GetTypeByteId(typeof(string)));
+        expectedBytes.AddRange(stringLenBytes);
+        expectedBytes.AddRange(titleBytes);
+        expectedBytes.AddRange(childCountBytes);
+
+        Assert.Equal(expectedBytes.ToArray(), layout.Serialize().ToArray());
+    }
+
+    [Fact]
+    public void TestThatChildLayoutSerializesCorrectBytes()
+    {
+        NativeControl layout = new NativeControl("Layout");
+        layout.AddChild(new NativeControl("Center"));
+
+        int controlId = ByteMap.ControlMap["Layout"];
+        byte[] controlIdBytes = BitConverter.GetBytes(controlId);
+
+        int propertiesCount = 0;
+        byte[] propertiesCountBytes = BitConverter.GetBytes(propertiesCount);
+
+        int childCount = 1;
+        byte[] childCountBytes = BitConverter.GetBytes(childCount);
+
+        int childControlId = ByteMap.ControlMap["Center"];
+        byte[] childControlIdBytes = BitConverter.GetBytes(childControlId);
+
+        int childPropertiesCount = 0;
+        byte[] childPropertiesCountBytes = BitConverter.GetBytes(childPropertiesCount);
+
+        int childChildCount = 0;
+        byte[] childChildCountBytes = BitConverter.GetBytes(childChildCount);
+
+        List<byte> expectedBytes = new();
+        expectedBytes.AddRange(controlIdBytes);
+        expectedBytes.AddRange(propertiesCountBytes);
+        expectedBytes.AddRange(childCountBytes);
+        expectedBytes.AddRange(childControlIdBytes);
+        expectedBytes.AddRange(childPropertiesCountBytes);
+        expectedBytes.AddRange(childChildCountBytes);
+
+        Assert.Equal(expectedBytes.ToArray(), layout.Serialize().ToArray());
+    }
+
+    [Fact]
+    public void TestThatChildNestedLayoutSerializesCorrectBytes()
+    {
+        NativeControl layout = new NativeControl("Layout");
+        NativeControl center = new NativeControl("Center");
+        NativeControl text = new NativeControl("Text");
+        text.AddProperty("Hello world");
+        center.AddChild(text);
+        layout.AddChild(center);
+
+        int controlId = ByteMap.ControlMap["Layout"];
+        byte[] controlIdBytes = BitConverter.GetBytes(controlId);
+
+        int propertiesCount = 0;
+        byte[] propertiesCountBytes = BitConverter.GetBytes(propertiesCount);
+
+        int childCount = 1;
+        byte[] childCountBytes = BitConverter.GetBytes(childCount);
+
+        int childControlId = ByteMap.ControlMap["Center"];
+        byte[] childControlIdBytes = BitConverter.GetBytes(childControlId);
+
+        int childPropertiesCount = 0;
+        byte[] childPropertiesCountBytes = BitConverter.GetBytes(childPropertiesCount);
+
+        int childChildCount = 1;
+        byte[] childChildCountBytes = BitConverter.GetBytes(childChildCount);
+
+        int textControlId = ByteMap.ControlMap["Text"];
+        byte[] textControlIdBytes = BitConverter.GetBytes(textControlId);
+
+        int textPropertiesCount = 1;
+        byte[] textPropertiesCountBytes = BitConverter.GetBytes(textPropertiesCount);
+
+        int textStringLen = "Hello world".Length;
+        byte[] textStringLenBytes = BitConverter.GetBytes(textStringLen);
+
+        byte[] textTitleBytes = Encoding.UTF8.GetBytes("Hello world");
+
+        int textChildCount = 0;
+        byte[] textChildCountBytes = BitConverter.GetBytes(textChildCount);
+
+
+        List<byte> expectedBytes = new();
+        expectedBytes.AddRange(controlIdBytes);
+        expectedBytes.AddRange(propertiesCountBytes);
+        expectedBytes.AddRange(childCountBytes);
+
+        expectedBytes.AddRange(childControlIdBytes);
+        expectedBytes.AddRange(childPropertiesCountBytes);
+        expectedBytes.AddRange(childChildCountBytes);
+
+        expectedBytes.AddRange(textControlIdBytes);
+        expectedBytes.AddRange(textPropertiesCountBytes);
+        expectedBytes.Add(ByteMap.GetTypeByteId(typeof(string)));
+        expectedBytes.AddRange(textStringLenBytes);
+        expectedBytes.AddRange(textTitleBytes);
+        expectedBytes.AddRange(textChildCountBytes);
+
+        Assert.Equal(expectedBytes.ToArray(), layout.Serialize().ToArray());
+    }
+
+    [Fact]
+    public void TestThatLayoutBuilderProperlyConvertsToNativeControls()
+    {
+        Layout layout = new Layout(
+            new Center(
+                child: new Text("hello sexy.")));
+
+        NativeControl nativeControl = layout.Build();
+
+        Assert.Equal("Layout", nativeControl.ControlId);
+        Assert.Empty(nativeControl.Properties);
+        Assert.Single(nativeControl.Children);
+
+        Assert.Equal("Center", nativeControl.Children[0].ControlId);
+        Assert.Empty(nativeControl.Children[0].Properties);
+
+        Assert.Equal("Text", nativeControl.Children[0].Children[0].ControlId);
+        Assert.Single(nativeControl.Children[0].Children[0].Properties);
+        Assert.Equal("hello sexy.", nativeControl.Children[0].Children[0].Properties[0]);
+    }
+}

+ 4 - 0
src/PixiEditor.Extensions.Wasm.Tests/PixiEditor.Extensions.Wasm.Tests.csproj

@@ -22,4 +22,8 @@
         </PackageReference>
         </PackageReference>
     </ItemGroup>
     </ItemGroup>
 
 
+    <ItemGroup>
+      <ProjectReference Include="..\PixiEditor.Extensions.Wasm\PixiEditor.Extensions.Wasm.csproj" />
+    </ItemGroup>
+
 </Project>
 </Project>

+ 0 - 9
src/PixiEditor.Extensions.Wasm.Tests/UnitTest1.cs

@@ -1,9 +0,0 @@
-namespace PixiEditor.Extensions.Wasm.Tests;
-
-public class UnitTest1
-{
-    [Fact]
-    public void Test1()
-    {
-    }
-}

+ 0 - 6
src/PixiEditor.Extensions.Wasm/Api/IWindowProvider.cs

@@ -1,6 +0,0 @@
-namespace PixiEditor.Extensions.Wasm.Api;
-
-public interface IWindowProvider
-{
-    public void CreatePopupWindow(string title, string body);
-}

+ 20 - 0
src/PixiEditor.Extensions.Wasm/Api/LayoutBuilding/Center.cs

@@ -0,0 +1,20 @@
+using PixiEditor.Extensions.CommonApi.LayoutBuilding;
+
+namespace PixiEditor.Extensions.Wasm.Api.LayoutBuilding;
+
+public class Center : ISingleChildLayoutElement<NativeControl>
+{
+    public ILayoutElement<NativeControl> Child { get; set; }
+
+    public Center(ILayoutElement<NativeControl> child)
+    {
+        Child = child;
+    }
+
+    NativeControl ILayoutElement<NativeControl>.Build()
+    {
+        NativeControl center = new NativeControl("Center");
+        center.AddChild(Child.Build());
+        return center;
+    }
+}

+ 21 - 0
src/PixiEditor.Extensions.Wasm/Api/LayoutBuilding/Layout.cs

@@ -0,0 +1,21 @@
+using PixiEditor.Extensions.CommonApi.LayoutBuilding;
+
+namespace PixiEditor.Extensions.Wasm.Api.LayoutBuilding;
+
+public sealed class Layout : ISingleChildLayoutElement<NativeControl>
+{
+    public ILayoutElement<NativeControl> Child { get; set; }
+
+    public Layout(ILayoutElement<NativeControl> body = null)
+    {
+        Child = body;
+    }
+
+    public NativeControl Build()
+    {
+        NativeControl layout = new NativeControl("Layout");
+        layout.AddChild(Child.Build());
+        return layout;
+    }
+
+}

+ 87 - 0
src/PixiEditor.Extensions.Wasm/Api/LayoutBuilding/NativeControl.cs

@@ -0,0 +1,87 @@
+using System.Text;
+using PixiEditor.Extensions.CommonApi.LayoutBuilding;
+
+namespace PixiEditor.Extensions.Wasm.Api.LayoutBuilding;
+
+public class NativeControl
+{
+    public string ControlId { get; set; }
+
+    public List<object> Properties { get; set; } = new();
+    public List<NativeControl> Children { get; set; } = new();
+
+    public NativeControl(string controlId)
+    {
+        ControlId = controlId;
+    }
+
+    public void AddProperty<T>(T value) where T : unmanaged
+    {
+        Properties.Add(value);
+    }
+
+    public void AddProperty(string value)
+    {
+        Properties.Add(value);
+    }
+
+    public void AddChild(NativeControl child)
+    {
+        Children.Add(child);
+    }
+
+    public Span<byte> Serialize()
+    {
+        return Serialize(new List<byte>()).ToArray();
+    }
+
+    private List<byte> Serialize(List<byte> bytes)
+    {
+        // TODO: Make it more efficient
+
+        byte[] idBytes = BitConverter.GetBytes(ByteMap.ControlMap[ControlId]);
+        bytes.AddRange(idBytes);
+        bytes.AddRange(BitConverter.GetBytes(Properties.Count));
+        bytes.AddRange(SerializeProperties());
+        bytes.AddRange(BitConverter.GetBytes(Children.Count));
+        SerializeChildren(bytes);
+        return bytes;
+    }
+
+    private void SerializeChildren(List<byte> bytes)
+    {
+        foreach (NativeControl child in Children)
+        {
+            child.Serialize(bytes);
+        }
+    }
+
+    private List<byte> SerializeProperties()
+    {
+        var result = new List<byte>();
+        foreach (var property in Properties)
+        {
+            result.Add(ByteMap.GetTypeByteId(property.GetType()));
+            if (property is string str)
+            {
+                result.AddRange(BitConverter.GetBytes(str.Length));
+            }
+
+            result.AddRange(property switch
+            {
+                int i => BitConverter.GetBytes(i),
+                float f => BitConverter.GetBytes(f),
+                bool b => BitConverter.GetBytes(b),
+                double d => BitConverter.GetBytes(d),
+                long l => BitConverter.GetBytes(l),
+                short s => BitConverter.GetBytes(s),
+                byte b => new byte[] { b },
+                char c => BitConverter.GetBytes(c),
+                string s => Encoding.UTF8.GetBytes(s),
+                _ => throw new Exception($"Unknown unmanaged type: {property.GetType()}")
+            });
+        }
+
+        return result;
+    }
+}

+ 20 - 0
src/PixiEditor.Extensions.Wasm/Api/LayoutBuilding/Text.cs

@@ -0,0 +1,20 @@
+using PixiEditor.Extensions.CommonApi.LayoutBuilding;
+
+namespace PixiEditor.Extensions.Wasm.Api.LayoutBuilding;
+
+public class Text : ITextElement<NativeControl>
+{
+    public string Data { get; set; }
+
+    public Text(string data)
+    {
+        Data = data;
+    }
+
+    NativeControl ILayoutElement<NativeControl>.Build()
+    {
+        NativeControl text = new NativeControl("Text");
+        text.AddProperty(Data);
+        return text;
+    }
+}

+ 1 - 1
src/PixiEditor.Extensions.Wasm/Api/ILogger.cs → src/PixiEditor.Extensions.Wasm/Api/Logging/ILogger.cs

@@ -1,4 +1,4 @@
-namespace PixiEditor.Extensions.Wasm.Api;
+namespace PixiEditor.Extensions.Wasm.Api.Logging;
 
 
 public interface ILogger
 public interface ILogger
 {
 {

+ 1 - 3
src/PixiEditor.Extensions.Wasm/Api/Logger.cs → src/PixiEditor.Extensions.Wasm/Api/Logging/Logger.cs

@@ -1,6 +1,4 @@
-using System.Runtime.InteropServices;
-
-namespace PixiEditor.Extensions.Wasm.Api;
+namespace PixiEditor.Extensions.Wasm.Api.Logging;
 
 
 public class Logger : ILogger
 public class Logger : ILogger
 {
 {

+ 8 - 0
src/PixiEditor.Extensions.Wasm/Api/Window/IWindowProvider.cs

@@ -0,0 +1,8 @@
+using PixiEditor.Extensions.Wasm.Api.LayoutBuilding;
+
+namespace PixiEditor.Extensions.Wasm.Api.Window;
+
+public interface IWindowProvider
+{
+    public void CreatePopupWindow(string title, NativeControl body);
+}

+ 16 - 0
src/PixiEditor.Extensions.Wasm/Api/Window/WindowProvider.cs

@@ -0,0 +1,16 @@
+using System.Runtime.InteropServices;
+using PixiEditor.Extensions.Wasm.Api.LayoutBuilding;
+using PixiEditor.Extensions.Wasm.Utilities;
+
+namespace PixiEditor.Extensions.Wasm.Api.Window;
+
+public class WindowProvider : IWindowProvider
+{
+    public void CreatePopupWindow(string title, NativeControl body)
+    {
+        byte[] bytes = body.Serialize().ToArray();
+        IntPtr ptr = Marshal.AllocHGlobal(bytes.Length);
+        Marshal.Copy(bytes, 0, ptr, bytes.Length);
+        Interop.CreatePopupWindow(title, ptr, bytes.Length);
+    }
+}

+ 0 - 9
src/PixiEditor.Extensions.Wasm/Api/WindowProvider.cs

@@ -1,9 +0,0 @@
-namespace PixiEditor.Extensions.Wasm.Api;
-
-public class WindowProvider : IWindowProvider
-{
-    public void CreatePopupWindow(string title, string body)
-    {
-        Interop.CreatePopupWindow(title, body);
-    }
-}

+ 1 - 1
src/PixiEditor.Extensions.Wasm/Interop.cs

@@ -11,7 +11,7 @@ internal class Interop
     internal static extern void LogMessage(string message);
     internal static extern void LogMessage(string message);
 
 
     [MethodImpl(MethodImplOptions.InternalCall)]
     [MethodImpl(MethodImplOptions.InternalCall)]
-    internal static extern void CreatePopupWindow(string title, string body);
+    internal static extern void CreatePopupWindow(string title, IntPtr data, int length);
 
 
     internal static void Load()
     internal static void Load()
     {
     {

+ 4 - 0
src/PixiEditor.Extensions.Wasm/PixiEditor.Extensions.Wasm.csproj

@@ -7,6 +7,10 @@
         <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
         <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     </PropertyGroup>
     </PropertyGroup>
 
 
+    <ItemGroup>
+      <ProjectReference Include="..\PixiEditor.Extensions.CommonApi\PixiEditor.Extensions.CommonApi.csproj" />
+    </ItemGroup>
+
   <Import Project="build\PixiEditor.Extensions.Wasm.targets"/>
   <Import Project="build\PixiEditor.Extensions.Wasm.targets"/>
   <Import Project="build\PixiEditor.Extensions.Wasm.props"/>
   <Import Project="build\PixiEditor.Extensions.Wasm.props"/>
 
 

+ 2 - 0
src/PixiEditor.Extensions.Wasm/PixiEditorApi.cs

@@ -1,4 +1,6 @@
 using PixiEditor.Extensions.Wasm.Api;
 using PixiEditor.Extensions.Wasm.Api;
+using PixiEditor.Extensions.Wasm.Api.Logging;
+using PixiEditor.Extensions.Wasm.Api.Window;
 
 
 namespace PixiEditor.Extensions.Wasm;
 namespace PixiEditor.Extensions.Wasm;
 
 

+ 13 - 0
src/PixiEditor.Extensions.Wasm/Utilities/InteropUtility.cs

@@ -0,0 +1,13 @@
+using System.Runtime.InteropServices;
+
+namespace PixiEditor.Extensions.Wasm.Utilities;
+
+public static class InteropUtility
+{
+    public static IntPtr ByteArrayToIntPtr(byte[] array)
+    {
+        var ptr = Marshal.AllocHGlobal(array.Length);
+        Marshal.Copy(array, 0, ptr, array.Length);
+        return ptr;
+    }
+}

+ 1 - 0
src/PixiEditor.Extensions.Wasm/native/api.h

@@ -1,2 +1,3 @@
 void attach_logger_calls();
 void attach_logger_calls();
 void attach_window_calls();
 void attach_window_calls();
+void logger_log_message(MonoString* message);

+ 7 - 5
src/PixiEditor.Extensions.Wasm/native/window_api.c

@@ -2,17 +2,19 @@
 #include <string.h>
 #include <string.h>
 #include <assert.h>
 #include <assert.h>
 
 
+#include "api.h"
+
 __attribute__((import_name("create_popup_window")))
 __attribute__((import_name("create_popup_window")))
-void create_popup_window(const char* title, int32_t titleLength, const char* content, int32_t contentLength);
+void create_popup_window(const char* title, int32_t titleLength, uint8_t* data, int32_t length);
 
 
-void logger_create_popup_window(MonoString* title, MonoString* content)
+// content is byte[] from C#
+void internal_create_popup_window(MonoString* title, uint8_t* data, int32_t length)
 {
 {
     char* title_utf8 = mono_wasm_string_get_utf8(title);
     char* title_utf8 = mono_wasm_string_get_utf8(title);
-    char* content_utf8 = mono_wasm_string_get_utf8(content);
-    create_popup_window(title_utf8, strlen(title_utf8), content_utf8, strlen(content_utf8));
+    create_popup_window(title_utf8, strlen(title_utf8), data, length);
 }
 }
 
 
 void attach_window_calls()
 void attach_window_calls()
 {
 {
-    mono_add_internal_call("PixiEditor.Extensions.Wasm.Interop::CreatePopupWindow", logger_create_popup_window);
+    mono_add_internal_call("PixiEditor.Extensions.Wasm.Interop::CreatePopupWindow", internal_create_popup_window);
 }
 }

+ 6 - 0
src/PixiEditor.Extensions.WasmRuntime/MemoryUtility.cs

@@ -11,4 +11,10 @@ public static class MemoryUtility
         
         
         return Encoding.UTF8.GetString(span);
         return Encoding.UTF8.GetString(span);
     }
     }
+
+    public static Span<T> GetSpanFromWasmMemory<T>(int bodyOffset, int bodyLength, Memory memory) where T : unmanaged
+    {
+        var span = memory.GetSpan<T>(bodyOffset, bodyLength);
+        return span;
+    }
 }
 }

+ 5 - 2
src/PixiEditor.Extensions.WasmRuntime/PixiEditorApiLinkerExtensions.cs

@@ -1,3 +1,4 @@
+using PixiEditor.Extensions.LayoutBuilding.Elements;
 using Wasmtime;
 using Wasmtime;
 
 
 namespace PixiEditor.Extensions.WasmRuntime;
 namespace PixiEditor.Extensions.WasmRuntime;
@@ -15,8 +16,10 @@ public static class PixiEditorApiLinkerExtensions
         linker.DefineFunction("env", "create_popup_window",(int titleOffset, int titleLength, int bodyOffset, int bodyLength) =>
         linker.DefineFunction("env", "create_popup_window",(int titleOffset, int titleLength, int bodyOffset, int bodyLength) =>
         {
         {
             string title = MemoryUtility.GetStringFromWasmMemory(titleOffset, titleLength, instance.Instance.GetMemory("memory"));
             string title = MemoryUtility.GetStringFromWasmMemory(titleOffset, titleLength, instance.Instance.GetMemory("memory"));
-            string body = MemoryUtility.GetStringFromWasmMemory(bodyOffset, bodyLength, instance.Instance.GetMemory("memory"));
-            instance.Api.WindowProvider.CreatePopupWindow(title, body).ShowDialog();
+            Span<byte> arr = MemoryUtility.GetSpanFromWasmMemory<byte>(bodyOffset, bodyLength, instance.Instance.GetMemory("memory"));
+
+            var body = LayoutConverter.Deserialize(arr);
+            instance.Api.WindowProvider.CreatePopupWindow(title, body.Build()).ShowDialog();
         });
         });
     }
     }
 }
 }

+ 19 - 0
src/PixiEditor.Extensions/Helpers/SpanUtility.cs

@@ -0,0 +1,19 @@
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace PixiEditor.Extensions.Helpers;
+
+public static class SpanUtility
+{
+    public static object? Read(Type type, Span<byte> span, ref int offset)
+    {
+        if (type == typeof(string))
+        {
+            int stringLength = BitConverter.ToInt32(span[offset..(offset + sizeof(int))]);
+            offset += sizeof(int);
+            return Encoding.UTF8.GetString(span[offset..(offset + stringLength)]);
+        }
+
+        return Marshal.PtrToStructure(span[offset..(offset + Marshal.SizeOf(type))].GetPinnableReference(), type);
+    }
+}

+ 32 - 0
src/PixiEditor.Extensions/LayoutBuilding/Elements/Center.cs

@@ -0,0 +1,32 @@
+using Avalonia.Controls;
+using PixiEditor.Extensions.CommonApi.LayoutBuilding;
+
+namespace PixiEditor.Extensions.LayoutBuilding.Elements;
+
+public class Center : ISingleChildLayoutElement<Control>, IDeserializable
+{
+    public ILayoutElement<Control> Child { get; set; }
+
+    public Center(ILayoutElement<Control> child = null)
+    {
+        Child = child;
+    }
+
+    Control ILayoutElement<Control>.Build()
+    {
+        return new Panel()
+        {
+            Children =
+            {
+                Child.Build()
+            },
+            HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
+            VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
+        };
+    }
+
+    void IDeserializable.DeserializeProperties(List<object> values)
+    {
+
+    }
+}

+ 9 - 0
src/PixiEditor.Extensions/LayoutBuilding/Elements/IDeserializable.cs

@@ -0,0 +1,9 @@
+using Avalonia.Controls;
+using PixiEditor.Extensions.CommonApi.LayoutBuilding;
+
+namespace PixiEditor.Extensions.LayoutBuilding.Elements;
+
+public interface IDeserializable
+{
+    public void DeserializeProperties(List<object> values);
+}

+ 24 - 0
src/PixiEditor.Extensions/LayoutBuilding/Elements/Layout.cs

@@ -0,0 +1,24 @@
+using Avalonia.Controls;
+using PixiEditor.Extensions.CommonApi.LayoutBuilding;
+
+namespace PixiEditor.Extensions.LayoutBuilding.Elements;
+
+public sealed class Layout : ISingleChildLayoutElement<Control>, IDeserializable
+{
+    public ILayoutElement<Control> Child { get; set; }
+
+    public Layout(ILayoutElement<Control> body = null)
+    {
+        Child = body;
+    }
+
+    public Control Build()
+    {
+        return new Panel { Children = { Child.Build() } };
+    }
+
+    void IDeserializable.DeserializeProperties(List<object> values)
+    {
+
+    }
+}

+ 91 - 0
src/PixiEditor.Extensions/LayoutBuilding/Elements/LayoutConverter.cs

@@ -0,0 +1,91 @@
+using System.Runtime.InteropServices;
+using System.Text;
+using Avalonia.Controls;
+using PixiEditor.Extensions.CommonApi.LayoutBuilding;
+using PixiEditor.Extensions.Helpers;
+
+namespace PixiEditor.Extensions.LayoutBuilding.Elements;
+
+public static class LayoutConverter
+{
+    private static int int32Size = sizeof(int);
+
+    public static ILayoutElement<Control> Deserialize(Span<byte> layoutSpan)
+    {
+        int offset = 0;
+        int controlId = BitConverter.ToInt32(layoutSpan[..int32Size]);
+        offset += int32Size;
+
+        int propertiesCount = BitConverter.ToInt32(layoutSpan[offset..(offset + int32Size)]);
+        offset += int32Size;
+
+        List<object> properties = DeserializeProperties(layoutSpan, propertiesCount, ref offset);
+
+        int childrenCount = BitConverter.ToInt32(layoutSpan[offset..(offset + int32Size)]);
+        offset += int32Size;
+
+        List<ILayoutElement<Control>> children = DeserializeChildren(layoutSpan, childrenCount, ref offset);
+
+        return BuildLayoutElement(controlId, properties, children);
+    }
+
+    private static List<object> DeserializeProperties(Span<byte> layoutSpan, int propertiesCount, ref int offset)
+    {
+        var properties = new List<object>();
+        for (int i = 0; i < propertiesCount; i++)
+        {
+            int propertyType = layoutSpan[offset];
+            offset++;
+
+            Type type = ByteMap.GetTypeFromByteId((byte)propertyType);
+            if (type == typeof(string))
+            {
+                int stringLength = BitConverter.ToInt32(layoutSpan[offset..(offset + int32Size)]);
+                offset += int32Size;
+                string value = Encoding.UTF8.GetString(layoutSpan[offset..(offset + stringLength)]);
+                offset += stringLength;
+                properties.Add(value);
+            }
+            else
+            {
+                var property = SpanUtility.Read(type, layoutSpan, ref offset);
+                properties.Add(property);
+            }
+        }
+
+        return properties;
+    }
+
+    private static List<ILayoutElement<Control>> DeserializeChildren(Span<byte> layoutSpan, int childrenCount, ref int offset)
+    {
+        var children = new List<ILayoutElement<Control>>();
+        for (int i = 0; i < childrenCount; i++)
+        {
+            children.Add(Deserialize(layoutSpan[offset..]));
+        }
+
+        return children;
+    }
+
+    private static ILayoutElement<Control> BuildLayoutElement(int controlId, List<object> properties, List<ILayoutElement<Control>> children)
+    {
+        Func<IDeserializable> factory = GlobalControlFactory.Map[controlId];
+        var element = factory();
+        
+        if(element is not ILayoutElement<Control> layoutElement)
+            throw new Exception("Element is not ILayoutElement<Control>");
+        
+        element.DeserializeProperties(properties);
+
+        if (element is ISingleChildLayoutElement<Control> singleChildLayoutElement)
+        {
+            singleChildLayoutElement.Child = children[0];
+        }
+        else if (element is IMultiChildLayoutElement<Control> multiChildLayoutElement)
+        {
+            multiChildLayoutElement.Children = children;
+        }
+        
+        return layoutElement;
+    }
+}

+ 24 - 0
src/PixiEditor.Extensions/LayoutBuilding/Elements/Text.cs

@@ -0,0 +1,24 @@
+using Avalonia.Controls;
+using PixiEditor.Extensions.CommonApi.LayoutBuilding;
+
+namespace PixiEditor.Extensions.LayoutBuilding.Elements;
+
+public class Text : ITextElement<Control>, IDeserializable
+{
+    public string Data { get; set; }
+
+    public Text(string data = "")
+    {
+        Data = data;
+    }
+
+    Control ILayoutElement<Control>.Build()
+    {
+        return new TextBlock { Text = Data };
+    }
+
+    void IDeserializable.DeserializeProperties(List<object> values)
+    {
+        Data = (string)values[0];
+    }
+}

+ 17 - 0
src/PixiEditor.Extensions/LayoutBuilding/GlobalControlFactory.cs

@@ -0,0 +1,17 @@
+using Avalonia.Controls;
+using PixiEditor.Extensions.CommonApi.LayoutBuilding;
+using PixiEditor.Extensions.LayoutBuilding.Elements;
+
+namespace PixiEditor.Extensions.LayoutBuilding;
+
+public static class GlobalControlFactory
+{
+    public static IReadOnlyDictionary<int, Func<IDeserializable>> Map => map;
+
+    private static Dictionary<int, Func<IDeserializable>> map = new()
+    {
+        { 0, () => new Layout() },
+        { 1,  () => new Center() },
+        { 2, () => new Text() }
+    };
+}

+ 0 - 6
src/PixiEditor.Extensions/LayoutBuilding/ILayoutBuilder.cs

@@ -1,6 +0,0 @@
-namespace PixiEditor.Extensions.LayoutBuilding;
-
-public interface ILayoutBuilder
-{
-    
-}

+ 4 - 0
src/PixiEditor.Extensions/PixiEditor.Extensions.csproj

@@ -19,4 +19,8 @@
       <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
       <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
       <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
       <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
     </ItemGroup>
     </ItemGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\PixiEditor.Extensions.CommonApi\PixiEditor.Extensions.CommonApi.csproj" />
+    </ItemGroup>
 </Project>
 </Project>

+ 45 - 0
src/PixiEditor.sln

@@ -92,6 +92,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditor.Extensions.Wasm.
 EndProject
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditor.Extensions.WasmRuntime.Tests", "PixiEditor.Extensions.WasmRuntime.Tests\PixiEditor.Extensions.WasmRuntime.Tests.csproj", "{C16EF6F1-4E40-4CC4-9320-99C3C97929D7}"
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditor.Extensions.WasmRuntime.Tests", "PixiEditor.Extensions.WasmRuntime.Tests\PixiEditor.Extensions.WasmRuntime.Tests.csproj", "{C16EF6F1-4E40-4CC4-9320-99C3C97929D7}"
 EndProject
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditor.Extensions.CommonApi", "PixiEditor.Extensions.CommonApi\PixiEditor.Extensions.CommonApi.csproj", "{43C03D0E-EF50-4225-A268-CB9B8E0E8622}"
+EndProject
 Global
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
 		Debug|Any CPU = Debug|Any CPU
@@ -1563,6 +1565,48 @@ Global
 		{C16EF6F1-4E40-4CC4-9320-99C3C97929D7}.Steam|x64.Build.0 = Debug|Any CPU
 		{C16EF6F1-4E40-4CC4-9320-99C3C97929D7}.Steam|x64.Build.0 = Debug|Any CPU
 		{C16EF6F1-4E40-4CC4-9320-99C3C97929D7}.Steam|x86.ActiveCfg = Debug|Any CPU
 		{C16EF6F1-4E40-4CC4-9320-99C3C97929D7}.Steam|x86.ActiveCfg = Debug|Any CPU
 		{C16EF6F1-4E40-4CC4-9320-99C3C97929D7}.Steam|x86.Build.0 = Debug|Any CPU
 		{C16EF6F1-4E40-4CC4-9320-99C3C97929D7}.Steam|x86.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Debug|x64.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Debug|x86.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.DevRelease|Any CPU.ActiveCfg = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.DevRelease|Any CPU.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.DevRelease|x64.ActiveCfg = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.DevRelease|x64.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.DevRelease|x86.ActiveCfg = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.DevRelease|x86.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.DevSteam|Any CPU.ActiveCfg = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.DevSteam|Any CPU.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.DevSteam|x64.ActiveCfg = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.DevSteam|x64.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.DevSteam|x86.ActiveCfg = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.DevSteam|x86.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.MSIX Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.MSIX Debug|Any CPU.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.MSIX Debug|x64.ActiveCfg = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.MSIX Debug|x64.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.MSIX Debug|x86.ActiveCfg = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.MSIX Debug|x86.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.MSIX|Any CPU.ActiveCfg = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.MSIX|Any CPU.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.MSIX|x64.ActiveCfg = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.MSIX|x64.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.MSIX|x86.ActiveCfg = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.MSIX|x86.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Release|Any CPU.Build.0 = Release|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Release|x64.ActiveCfg = Release|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Release|x64.Build.0 = Release|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Release|x86.ActiveCfg = Release|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Release|x86.Build.0 = Release|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Steam|Any CPU.ActiveCfg = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Steam|Any CPU.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Steam|x64.ActiveCfg = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Steam|x64.Build.0 = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Steam|x86.ActiveCfg = Debug|Any CPU
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622}.Steam|x86.Build.0 = Debug|Any CPU
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 		HideSolutionNode = FALSE
@@ -1603,6 +1647,7 @@ Global
 		{221E745C-D21F-4725-BBB8-DFB2DE5CF61D} = {13DD041C-EE2D-4AF8-B43E-D7BFC7415E4D}
 		{221E745C-D21F-4725-BBB8-DFB2DE5CF61D} = {13DD041C-EE2D-4AF8-B43E-D7BFC7415E4D}
 		{9C1A500D-7A3D-49E3-BD39-05867B1D37F1} = {13DD041C-EE2D-4AF8-B43E-D7BFC7415E4D}
 		{9C1A500D-7A3D-49E3-BD39-05867B1D37F1} = {13DD041C-EE2D-4AF8-B43E-D7BFC7415E4D}
 		{C16EF6F1-4E40-4CC4-9320-99C3C97929D7} = {13DD041C-EE2D-4AF8-B43E-D7BFC7415E4D}
 		{C16EF6F1-4E40-4CC4-9320-99C3C97929D7} = {13DD041C-EE2D-4AF8-B43E-D7BFC7415E4D}
+		{43C03D0E-EF50-4225-A268-CB9B8E0E8622} = {13DD041C-EE2D-4AF8-B43E-D7BFC7415E4D}
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {D04B4AB0-CA33-42FD-A909-79966F9255C5}
 		SolutionGuid = {D04B4AB0-CA33-42FD-A909-79966F9255C5}

+ 8 - 1
src/WasmSampleExtension/SampleExtension.cs

@@ -1,4 +1,5 @@
 using PixiEditor.Extensions.Wasm;
 using PixiEditor.Extensions.Wasm;
+using PixiEditor.Extensions.Wasm.Api.LayoutBuilding;
 
 
 namespace SampleExtension.WASM;
 namespace SampleExtension.WASM;
 
 
@@ -12,6 +13,12 @@ public class SampleExtension : WasmExtension
     public override void OnInitialized()
     public override void OnInitialized()
     {
     {
         Api.Logger.Log("WASM SampleExtension initialized!");
         Api.Logger.Log("WASM SampleExtension initialized!");
-        Api.WindowProvider.CreatePopupWindow("WASM SampleExtension", "Hello from WASM PixiEditor Extension");
+
+        Layout layout = new Layout(
+            new Center(
+                child: new Text("hello sexy.")));
+
+        Api.WindowProvider.CreatePopupWindow("WASM SampleExtension", layout.Build());
     }
     }
 }
 }
+