Browse Source

Stateful elements are working

Krzysztof Krysiński 1 year ago
parent
commit
3ca9a8c6f3
22 changed files with 228 additions and 27 deletions
  1. 12 0
      src/PixiEditor.AvaloniaUI/Models/AppExtensions/ExtensionLoader.cs
  2. 2 1
      src/PixiEditor.Extensions.CommonApi/LayoutBuilding/ByteMap.cs
  3. 22 0
      src/PixiEditor.Extensions.Wasm/Api/LayoutBuilding/State.cs
  4. 38 0
      src/PixiEditor.Extensions.Wasm/Api/LayoutBuilding/StatefulElement.cs
  5. 1 0
      src/PixiEditor.Extensions.Wasm/Api/Window/IWindowProvider.cs
  6. 11 0
      src/PixiEditor.Extensions.Wasm/Api/Window/WindowProvider.cs
  7. 3 0
      src/PixiEditor.Extensions.Wasm/Interop.cs
  8. 4 0
      src/PixiEditor.Extensions.Wasm/native/layout_builder_api.c
  9. 19 1
      src/PixiEditor.Extensions.WasmRuntime/WasmExtensionInstance.cs
  10. 14 0
      src/PixiEditor.Extensions/LayoutBuilding/Elements/ContainerState.cs
  11. 8 0
      src/PixiEditor.Extensions/LayoutBuilding/Elements/Exceptions/DuplicateIdElementException.cs
  12. 9 0
      src/PixiEditor.Extensions/LayoutBuilding/Elements/IChildrenDeserializable.cs
  13. 25 11
      src/PixiEditor.Extensions/LayoutBuilding/Elements/LayoutBuilder.cs
  14. 6 1
      src/PixiEditor.Extensions/LayoutBuilding/Elements/SingleChildLayoutElement.cs
  15. 0 1
      src/PixiEditor.Extensions/LayoutBuilding/Elements/State.cs
  16. 17 0
      src/PixiEditor.Extensions/LayoutBuilding/Elements/StatefulContainer.cs
  17. 0 1
      src/PixiEditor.Extensions/LayoutBuilding/Elements/StatefulElement.cs
  18. 2 1
      src/PixiEditor.Extensions/LayoutBuilding/GlobalControlFactory.cs
  19. 11 0
      src/WasmSampleExtension/ButtonTextElement.cs
  20. 21 0
      src/WasmSampleExtension/ButtonTextElementState.cs
  21. 1 3
      src/WasmSampleExtension/Program.cs
  22. 2 7
      src/WasmSampleExtension/SampleExtension.cs

+ 12 - 0
src/PixiEditor.AvaloniaUI/Models/AppExtensions/ExtensionLoader.cs

@@ -71,6 +71,9 @@ internal class ExtensionLoader
         }
         }
         catch (Exception ex)
         catch (Exception ex)
         {
         {
+#if DEBUG
+            throw;
+#endif
             // TODO: Log exception
             // TODO: Log exception
             // Maybe it's not a good idea to send webhook exceptions in the extension loader
             // Maybe it's not a good idea to send webhook exceptions in the extension loader
             //CrashHelper.SendExceptionInfoToWebhook(ex);
             //CrashHelper.SendExceptionInfoToWebhook(ex);
@@ -99,14 +102,23 @@ internal class ExtensionLoader
         }
         }
         catch (JsonException)
         catch (JsonException)
         {
         {
+#if DEBUG
+            throw;
+#endif
             //MessageBox.Show(new LocalizedString("ERROR_INVALID_PACKAGE", packageJsonPath), "ERROR");
             //MessageBox.Show(new LocalizedString("ERROR_INVALID_PACKAGE", packageJsonPath), "ERROR");
         }
         }
         catch (ExtensionException ex)
         catch (ExtensionException ex)
         {
         {
+#if DEBUG
+            throw;
+#endif
             //MessageBox.Show(ex.DisplayMessage, "ERROR");
             //MessageBox.Show(ex.DisplayMessage, "ERROR");
         }
         }
         catch (Exception ex)
         catch (Exception ex)
         {
         {
+#if DEBUG
+            throw;
+#endif
             //MessageBox.Show(new LocalizedString("ERROR_LOADING_PACKAGE", packageJsonPath), "ERROR");
             //MessageBox.Show(new LocalizedString("ERROR_LOADING_PACKAGE", packageJsonPath), "ERROR");
             CrashHelper.SendExceptionInfoToWebhook(ex);
             CrashHelper.SendExceptionInfoToWebhook(ex);
         }
         }

+ 2 - 1
src/PixiEditor.Extensions.CommonApi/LayoutBuilding/ByteMap.cs

@@ -10,7 +10,8 @@ public static class ByteMap
         { "Layout", 0 },
         { "Layout", 0 },
         { "Center", 1 },
         { "Center", 1 },
         { "Text", 2 },
         { "Text", 2 },
-        { "Button", 3}
+        { "Button", 3 },
+        { "StatefulContainer", 4 }
     };
     };
 
 
     public static byte GetTypeByteId(Type type)
     public static byte GetTypeByteId(Type type)

+ 22 - 0
src/PixiEditor.Extensions.Wasm/Api/LayoutBuilding/State.cs

@@ -0,0 +1,22 @@
+using PixiEditor.Extensions.CommonApi.LayoutBuilding;
+using PixiEditor.Extensions.CommonApi.LayoutBuilding.State;
+
+namespace PixiEditor.Extensions.Wasm.Api.LayoutBuilding;
+
+public abstract class State : IState<CompiledControl>
+{
+    ILayoutElement<CompiledControl> IState<CompiledControl>.Build()
+    {
+        return BuildElement();
+    }
+
+    public abstract LayoutElement BuildElement();
+
+    public void SetState(Action setAction)
+    {
+        setAction();
+        StateChanged?.Invoke();
+    }
+
+    public event Action StateChanged;
+}

+ 38 - 0
src/PixiEditor.Extensions.Wasm/Api/LayoutBuilding/StatefulElement.cs

@@ -0,0 +1,38 @@
+using PixiEditor.Extensions.CommonApi.LayoutBuilding.State;
+
+namespace PixiEditor.Extensions.Wasm.Api.LayoutBuilding;
+
+public abstract class StatefulElement<TState> : LayoutElement, IStatefulElement<CompiledControl, TState> where TState : IState<CompiledControl>
+{
+    private TState state;
+
+    IState<CompiledControl> IStatefulElement<CompiledControl>.State
+    {
+        get
+        {
+            if (state == null)
+            {
+                state = CreateState();
+                state.StateChanged += () =>
+                {
+                    CompiledControl newLayout = BuildNative();
+                    WasmExtension.Api.WindowProvider.LayoutStateChanged(UniqueId, newLayout);
+                };
+            }
+
+            return state;
+        }
+    }
+
+    public TState State => (TState)((IStatefulElement<CompiledControl>)this).State;
+
+    public override CompiledControl BuildNative()
+    {
+        CompiledControl control = State.Build().BuildNative();
+        CompiledControl statefulContainer = new CompiledControl(UniqueId, "StatefulContainer");
+        statefulContainer.Children.Add(control);
+        return statefulContainer;
+    }
+
+    public abstract TState CreateState();
+}

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

@@ -6,4 +6,5 @@ namespace PixiEditor.Extensions.Wasm.Api.Window;
 public interface IWindowProvider
 public interface IWindowProvider
 {
 {
     public void CreatePopupWindow(string title, LayoutElement body);
     public void CreatePopupWindow(string title, LayoutElement body);
+    internal void LayoutStateChanged(int uniqueId, CompiledControl newLayout);
 }
 }

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

@@ -19,6 +19,17 @@ public class WindowProvider : IWindowProvider
         SubscribeToEvents(compiledControl);
         SubscribeToEvents(compiledControl);
     }
     }
 
 
+    void IWindowProvider.LayoutStateChanged(int uniqueId, CompiledControl newLayout)
+    {
+        byte[] bytes = newLayout.Serialize().ToArray();
+        IntPtr ptr = Marshal.AllocHGlobal(bytes.Length);
+        Marshal.Copy(bytes, 0, ptr, bytes.Length);
+        Interop.StateChanged(uniqueId, ptr, bytes.Length);
+        Marshal.FreeHGlobal(ptr);
+
+        SubscribeToEvents(newLayout);
+    }
+
     private void SubscribeToEvents(CompiledControl body)
     private void SubscribeToEvents(CompiledControl body)
     {
     {
         foreach (CompiledControl child in body.Children)
         foreach (CompiledControl child in body.Children)

+ 3 - 0
src/PixiEditor.Extensions.Wasm/Interop.cs

@@ -19,6 +19,9 @@ internal class Interop
     [MethodImpl(MethodImplOptions.InternalCall)]
     [MethodImpl(MethodImplOptions.InternalCall)]
     internal static extern void SubscribeToEvent(int internalControlId, string eventName);
     internal static extern void SubscribeToEvent(int internalControlId, string eventName);
 
 
+    [MethodImpl(MethodImplOptions.InternalCall)]
+    internal static extern void StateChanged(int uniqueId, IntPtr data, int length);
+
     internal static void Load()
     internal static void Load()
     {
     {
         Type extensionType = Assembly.GetEntryAssembly().ExportedTypes
         Type extensionType = Assembly.GetEntryAssembly().ExportedTypes

+ 4 - 0
src/PixiEditor.Extensions.Wasm/native/layout_builder_api.c

@@ -7,6 +7,9 @@
 __attribute((import_name("subscribe_to_event")))
 __attribute((import_name("subscribe_to_event")))
 void subscribe_to_event(int32_t elementId, char* eventName, int32_t length);
 void subscribe_to_event(int32_t elementId, char* eventName, int32_t length);
 
 
+__attribute__((import_name("state_changed")))
+void state_changed(uint32_t elementId, uint8_t* data, int32_t length);
+
 void internal_subscribe_to_event(int32_t elementId, MonoString* eventName)
 void internal_subscribe_to_event(int32_t elementId, MonoString* eventName)
 {
 {
     char* eventNameString = mono_wasm_string_get_utf8(eventName);
     char* eventNameString = mono_wasm_string_get_utf8(eventName);
@@ -26,4 +29,5 @@ void raise_element_event(int elementId, const char* eventName)
 void attach_layout_builder_calls()
 void attach_layout_builder_calls()
 {
 {
     mono_add_internal_call("PixiEditor.Extensions.Wasm.Interop::SubscribeToEvent", internal_subscribe_to_event);
     mono_add_internal_call("PixiEditor.Extensions.Wasm.Interop::SubscribeToEvent", internal_subscribe_to_event);
+    mono_add_internal_call("PixiEditor.Extensions.Wasm.Interop::StateChanged", state_changed);
 }
 }

+ 19 - 1
src/PixiEditor.Extensions.WasmRuntime/WasmExtensionInstance.cs

@@ -3,6 +3,7 @@ using System.Text;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Threading;
 using Avalonia.Threading;
 using PixiEditor.Extensions.CommonApi.LayoutBuilding;
 using PixiEditor.Extensions.CommonApi.LayoutBuilding;
+using PixiEditor.Extensions.CommonApi.LayoutBuilding.State;
 using PixiEditor.Extensions.LayoutBuilding.Elements;
 using PixiEditor.Extensions.LayoutBuilding.Elements;
 using Wasmtime;
 using Wasmtime;
 
 
@@ -69,7 +70,7 @@ public class WasmExtensionInstance : Extension
             string title = WasmMemoryUtility.GetString(titleOffset, titleLength);
             string title = WasmMemoryUtility.GetString(titleOffset, titleLength);
             Span<byte> arr = memory.GetSpan<byte>(bodyOffset, bodyLength);
             Span<byte> arr = memory.GetSpan<byte>(bodyOffset, bodyLength);
 
 
-            var body = LayoutBuilder.Deserialize(arr);
+            var body = LayoutBuilder.Deserialize(arr, DuplicateResolutionTactic.ThrowException);
 
 
             Api.WindowProvider.CreatePopupWindow(title, body.BuildNative()).Show();
             Api.WindowProvider.CreatePopupWindow(title, body.BuildNative()).Show();
         });
         });
@@ -87,5 +88,22 @@ public class WasmExtensionInstance : Extension
                 //WasmMemoryUtility.Free(nameOffset);
                 //WasmMemoryUtility.Free(nameOffset);
             });
             });
         });
         });
+
+        Linker.DefineFunction("env", "state_changed", (int controlId, int bodyOffset, int bodyLength) =>
+        {
+            Span<byte> arr = memory.GetSpan<byte>(bodyOffset, bodyLength);
+
+            var element = managedElements[controlId];
+            var body = LayoutBuilder.Deserialize(arr, DuplicateResolutionTactic.Replace);
+
+            Dispatcher.UIThread.InvokeAsync(() =>
+            {
+                managedElements[controlId] = element;
+                if (element is StatefulContainer statefulElement && body is StatefulContainer statefulBodyElement)
+                {
+                    statefulElement.State.SetState(() => statefulElement.State.Content = statefulBodyElement.State.Content);
+                }
+            });
+        });
     }
     }
 }
 }

+ 14 - 0
src/PixiEditor.Extensions/LayoutBuilding/Elements/ContainerState.cs

@@ -0,0 +1,14 @@
+using Avalonia.Controls;
+using PixiEditor.Extensions.CommonApi.LayoutBuilding;
+
+namespace PixiEditor.Extensions.LayoutBuilding.Elements;
+
+public class ContainerState : State
+{
+    public ILayoutElement<Control> Content { get; set; }
+
+    public override ILayoutElement<Control> Build()
+    {
+        return Content;
+    }
+}

+ 8 - 0
src/PixiEditor.Extensions/LayoutBuilding/Elements/Exceptions/DuplicateIdElementException.cs

@@ -0,0 +1,8 @@
+namespace PixiEditor.Extensions.LayoutBuilding.Elements.Exceptions;
+
+public class DuplicateIdElementException : Exception
+{
+    public DuplicateIdElementException(int id) : base($"Element with id {id} already exists")
+    {
+    }
+}

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

@@ -0,0 +1,9 @@
+using Avalonia.Controls;
+using PixiEditor.Extensions.CommonApi.LayoutBuilding;
+
+namespace PixiEditor.Extensions.LayoutBuilding.Elements;
+
+public interface IChildrenDeserializable
+{
+    public void DeserializeChildren(List<ILayoutElement<Control>> children);
+}

+ 25 - 11
src/PixiEditor.Extensions/LayoutBuilding/Elements/LayoutBuilder.cs

@@ -3,6 +3,7 @@ using System.Text;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using PixiEditor.Extensions.CommonApi.LayoutBuilding;
 using PixiEditor.Extensions.CommonApi.LayoutBuilding;
 using PixiEditor.Extensions.Helpers;
 using PixiEditor.Extensions.Helpers;
+using PixiEditor.Extensions.LayoutBuilding.Elements.Exceptions;
 
 
 namespace PixiEditor.Extensions.LayoutBuilding.Elements;
 namespace PixiEditor.Extensions.LayoutBuilding.Elements;
 
 
@@ -16,7 +17,7 @@ public class LayoutBuilder
         this.managedElements = managedElements;
         this.managedElements = managedElements;
     }
     }
 
 
-    public ILayoutElement<Control> Deserialize(Span<byte> layoutSpan)
+    public ILayoutElement<Control> Deserialize(Span<byte> layoutSpan, DuplicateResolutionTactic duplicatedIdTactic)
     {
     {
         int offset = 0;
         int offset = 0;
 
 
@@ -34,9 +35,9 @@ public class LayoutBuilder
         int childrenCount = BitConverter.ToInt32(layoutSpan[offset..(offset + int32Size)]);
         int childrenCount = BitConverter.ToInt32(layoutSpan[offset..(offset + int32Size)]);
         offset += int32Size;
         offset += int32Size;
 
 
-        List<ILayoutElement<Control>> children = DeserializeChildren(layoutSpan, childrenCount, ref offset);
+        List<ILayoutElement<Control>> children = DeserializeChildren(layoutSpan, childrenCount, ref offset, duplicatedIdTactic);
 
 
-        return BuildLayoutElement(uniqueId, controlTypeId, properties, children);
+        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)
@@ -66,19 +67,19 @@ public class LayoutBuilder
         return properties;
         return properties;
     }
     }
 
 
-    private List<ILayoutElement<Control>> DeserializeChildren(Span<byte> layoutSpan, int childrenCount, ref int offset)
+    private List<ILayoutElement<Control>> DeserializeChildren(Span<byte> layoutSpan, int childrenCount, ref int offset, DuplicateResolutionTactic duplicatedIdTactic)
     {
     {
         var children = new List<ILayoutElement<Control>>();
         var children = new List<ILayoutElement<Control>>();
         for (int i = 0; i < childrenCount; i++)
         for (int i = 0; i < childrenCount; i++)
         {
         {
-            children.Add(Deserialize(layoutSpan[offset..]));
+            children.Add(Deserialize(layoutSpan[offset..], duplicatedIdTactic));
         }
         }
 
 
         return children;
         return children;
     }
     }
 
 
     private ILayoutElement<Control> BuildLayoutElement(int uniqueId, int controlId, List<object> properties,
     private ILayoutElement<Control> BuildLayoutElement(int uniqueId, int controlId, List<object> properties,
-        List<ILayoutElement<Control>> children)
+        List<ILayoutElement<Control>> children, DuplicateResolutionTactic duplicatedIdTactic)
     {
     {
         Func<ILayoutElement<Control>> factory = GlobalControlFactory.Map[controlId];
         Func<ILayoutElement<Control>> factory = GlobalControlFactory.Map[controlId];
         var element = factory();
         var element = factory();
@@ -93,16 +94,29 @@ public class LayoutBuilder
             deserializableProperties.DeserializeProperties(properties);
             deserializableProperties.DeserializeProperties(properties);
         }
         }
 
 
-        if (element is ISingleChildLayoutElement<Control> singleChildLayoutElement)
+        if (element is IChildrenDeserializable customChildrenDeserializable)
         {
         {
-            singleChildLayoutElement.Child = children[0];
+            customChildrenDeserializable.DeserializeChildren(children);
         }
         }
-        else if (element is IMultiChildLayoutElement<Control> multiChildLayoutElement)
+
+        if (!managedElements.TryAdd(uniqueId, layoutElement))
         {
         {
-            multiChildLayoutElement.Children = children;
+            if (duplicatedIdTactic == DuplicateResolutionTactic.ThrowException)
+            {
+                throw new DuplicateIdElementException(uniqueId);
+            }
+            else if (duplicatedIdTactic == DuplicateResolutionTactic.Replace)
+            {
+                managedElements[uniqueId] = layoutElement;
+            }
         }
         }
 
 
-        managedElements.Add(uniqueId, layoutElement);
         return layoutElement;
         return layoutElement;
     }
     }
 }
 }
+
+public enum DuplicateResolutionTactic
+{
+    ThrowException,
+    Replace,
+}

+ 6 - 1
src/PixiEditor.Extensions/LayoutBuilding/Elements/SingleChildLayoutElement.cs

@@ -3,8 +3,13 @@ using PixiEditor.Extensions.CommonApi.LayoutBuilding;
 
 
 namespace PixiEditor.Extensions.LayoutBuilding.Elements;
 namespace PixiEditor.Extensions.LayoutBuilding.Elements;
 
 
-public abstract class SingleChildLayoutElement : LayoutElement, ISingleChildLayoutElement<Control>
+public abstract class SingleChildLayoutElement : LayoutElement, ISingleChildLayoutElement<Control>, IChildrenDeserializable
 {
 {
     public ILayoutElement<Control>? Child { get; set; }
     public ILayoutElement<Control>? Child { get; set; }
     public abstract override Control BuildNative();
     public abstract override Control BuildNative();
+
+    void IChildrenDeserializable.DeserializeChildren(List<ILayoutElement<Control>> children)
+    {
+        Child = children.FirstOrDefault();
+    }
 }
 }

+ 0 - 1
src/PixiEditor.Extensions/LayoutBuilding/Elements/State.cs

@@ -6,7 +6,6 @@ namespace PixiEditor.Extensions.LayoutBuilding.Elements;
 
 
 public abstract class State : IState<Control>
 public abstract class State : IState<Control>
 {
 {
-
     public abstract ILayoutElement<Control> Build();
     public abstract ILayoutElement<Control> Build();
 
 
     public void SetState(Action setAction)
     public void SetState(Action setAction)

+ 17 - 0
src/PixiEditor.Extensions/LayoutBuilding/Elements/StatefulContainer.cs

@@ -0,0 +1,17 @@
+using Avalonia.Controls;
+using PixiEditor.Extensions.CommonApi.LayoutBuilding;
+
+namespace PixiEditor.Extensions.LayoutBuilding.Elements;
+
+public class StatefulContainer : StatefulElement<ContainerState>, IChildrenDeserializable
+{
+    public override ContainerState CreateState()
+    {
+        return new();
+    }
+
+    void IChildrenDeserializable.DeserializeChildren(List<ILayoutElement<Control>> children)
+    {
+        State.Content = children.FirstOrDefault();
+    }
+}

+ 0 - 1
src/PixiEditor.Extensions/LayoutBuilding/Elements/StatefulElement.cs

@@ -1,6 +1,5 @@
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Presenters;
-using Avalonia.VisualTree;
 using PixiEditor.Extensions.CommonApi.LayoutBuilding.State;
 using PixiEditor.Extensions.CommonApi.LayoutBuilding.State;
 
 
 namespace PixiEditor.Extensions.LayoutBuilding.Elements;
 namespace PixiEditor.Extensions.LayoutBuilding.Elements;

+ 2 - 1
src/PixiEditor.Extensions/LayoutBuilding/GlobalControlFactory.cs

@@ -14,6 +14,7 @@ public static class GlobalControlFactory
         { 0, () => new Layout() },
         { 0, () => new Layout() },
         { 1,  () => new Center() },
         { 1,  () => new Center() },
         { 2, () => new Text() },
         { 2, () => new Text() },
-        { 3, () => new Button() }
+        { 3, () => new Button() },
+        { 4, () => new StatefulContainer() }
     };
     };
 }
 }

+ 11 - 0
src/WasmSampleExtension/ButtonTextElement.cs

@@ -0,0 +1,11 @@
+using PixiEditor.Extensions.Wasm.Api.LayoutBuilding;
+
+namespace WasmSampleExtension;
+
+public class ButtonTextElement : StatefulElement<ButtonTextElementState>
+{
+    public override ButtonTextElementState CreateState()
+    {
+        return new();
+    }
+}

+ 21 - 0
src/WasmSampleExtension/ButtonTextElementState.cs

@@ -0,0 +1,21 @@
+using PixiEditor.Extensions.CommonApi.LayoutBuilding.Events;
+using PixiEditor.Extensions.Wasm.Api.LayoutBuilding;
+
+namespace WasmSampleExtension;
+
+public class ButtonTextElementState : State
+{
+    public int ClickedTimes { get; private set; } = 0;
+
+    public override LayoutElement BuildElement()
+    {
+        return new Button(
+            onClick: OnClick,
+            child: new Text($"Clicked: {ClickedTimes}"));
+    }
+
+    private void OnClick(ElementEventArgs args)
+    {
+        SetState(() => ClickedTimes++);
+    }
+}

+ 1 - 3
src/WasmSampleExtension/Program.cs

@@ -1,6 +1,4 @@
-using PixiEditor.Extensions.Wasm;
-
-namespace SampleExtension.WASM;
+namespace WasmSampleExtension;
 
 
 public static class Program
 public static class Program
 {
 {

+ 2 - 7
src/WasmSampleExtension/SampleExtension.cs

@@ -1,7 +1,7 @@
 using PixiEditor.Extensions.Wasm;
 using PixiEditor.Extensions.Wasm;
 using PixiEditor.Extensions.Wasm.Api.LayoutBuilding;
 using PixiEditor.Extensions.Wasm.Api.LayoutBuilding;
 
 
-namespace SampleExtension.WASM;
+namespace WasmSampleExtension;
 
 
 public class SampleExtension : WasmExtension
 public class SampleExtension : WasmExtension
 {
 {
@@ -16,12 +16,7 @@ public class SampleExtension : WasmExtension
 
 
         Layout layout = new Layout(
         Layout layout = new Layout(
             new Center(
             new Center(
-                child: new Button(
-                    child: new Text("hello sexy."),
-                    onClick: _ =>
-                    {
-                        Api.Logger.Log("button clicked!");
-                    })
+                child: new ButtonTextElement()
                 )
                 )
             );
             );