]> uap-core.de Git - rssreader.git/commitdiff
implement button eventhandler
authorOlaf Wintermann <olaf.wintermann@gmail.com>
Thu, 12 Jun 2025 19:01:55 +0000 (21:01 +0200)
committerOlaf Wintermann <olaf.wintermann@gmail.com>
Thu, 12 Jun 2025 19:01:55 +0000 (21:01 +0200)
ui-java/src/main/java/de/unixwork/ui/ButtonBuilder.java
ui-java/src/main/java/de/unixwork/ui/Event.java
ui-java/src/main/java/de/unixwork/ui/EventWrapper.java [new file with mode: 0644]
ui-java/src/main/java/de/unixwork/ui/Toolkit.java
ui-java/src/main/java/de/unixwork/ui/ToolkitFuncs.java [new file with mode: 0644]
ui-java/src/main/java/de/unixwork/ui/UiObject.java
ui-java/src/test/java/de/unixwork/ui/demo/Main.java

index 4e2d6e657b2f27f61485f377bbfc72c887b72e22..d5cfc464f127a1264bd90132588a0535d8ddb7cb 100644 (file)
@@ -1,8 +1,12 @@
 package de.unixwork.ui;
 
 import java.lang.foreign.Arena;
+import java.lang.foreign.FunctionDescriptor;
 import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
 import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
 
 public class ButtonBuilder extends AbstractWidgetBuilder {
     private boolean fill;
@@ -112,7 +116,7 @@ public class ButtonBuilder extends AbstractWidgetBuilder {
 
         MemorySegment args = (MemorySegment)ui.button_args_new.invoke();
         if(fill) {
-            // TODO
+            // TODO: implement after toolkit fill refactoring
         }
         if(hexpand) {
             ui.button_args_set_hexpand.invoke(args, hexpand);
@@ -158,20 +162,13 @@ public class ButtonBuilder extends AbstractWidgetBuilder {
         ui.button_args_set_labeltype.invoke(args, labelType);
 
         if(onClick != null) {
-            // TODO
+            EventWrapper event = new EventWrapper(obj, onClick);
+
+            // set toolkit args
+            ui.button_args_set_onclick.invoke(args, event.getCallback());
+            ui.button_args_set_onclickdata.invoke(args, event.getUserData());
         }
 
         return args;
     }
-
-    public UiWidget create() {
-        UiWidget widget = null;
-        try (Arena arena = Arena.ofConfined()) {
-            MemorySegment buttonArgs = createArgs(arena);
-            widget = new UiWidget((MemorySegment) widgetConstructor.invoke(obj.ptr, buttonArgs));
-        } catch (Throwable e) {
-            throw new RuntimeException(e);
-        }
-        return widget;
-    }
 }
index 229f7867ea50baf7cad4bc7f7b100355e3b9c707..68464236a3fff876c737ad7f4fada63ac566a039 100644 (file)
@@ -1,5 +1,7 @@
 package de.unixwork.ui;
 
+import java.lang.foreign.MemorySegment;
+
 public class Event {
     private UiObject object;
     private Object documemt;
@@ -11,6 +13,18 @@ public class Event {
 
     }
 
+    public Event(MemorySegment eventPtr) {
+        Toolkit toolkit = Toolkit.getInstance();
+        ToolkitFuncs ui = ToolkitFuncs.getInstance();
+
+        try {
+            long objAddress = ((MemorySegment) ui.event_get_obj.invoke(eventPtr)).address();
+            object = toolkit.getToplevelObject(objAddress);
+        } catch (Throwable e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     public UiObject getObject() {
         return object;
     }
diff --git a/ui-java/src/main/java/de/unixwork/ui/EventWrapper.java b/ui-java/src/main/java/de/unixwork/ui/EventWrapper.java
new file mode 100644 (file)
index 0000000..3d0bfc0
--- /dev/null
@@ -0,0 +1,64 @@
+package de.unixwork.ui;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+public class EventWrapper {
+    private MemorySegment callback;
+    private MemorySegment userdata;
+
+    public EventWrapper(UiObject obj, EventHandler handler) {
+        // We need to create an upcall stub for the static ButtonBuilder.eventHandler method
+        // Also, the event handler must be registered in the object and the returned index
+        // is used as callback userdata. This way we can map the userdata to the java
+        // EventHandler object
+
+        Toolkit toolkit = Toolkit.getInstance();
+        // void callback(UiEvent *event, void *userdata)
+        FunctionDescriptor handlerSig = FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS);
+
+        MethodHandle callbackMethod = null;
+        try {
+            callbackMethod = MethodHandles.lookup().findStatic(
+                    EventWrapper.class,
+                    "eventHandler",
+                    MethodType.methodType(void.class, MemorySegment.class, MemorySegment.class));
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+        Arena objArena = obj.getArena();
+        callback = toolkit.getLinker().upcallStub(
+                callbackMethod,
+                handlerSig,
+                obj.getArena()); // very important to use the obj arena
+
+        long index = obj.addEventHandler(handler);
+        // use index as callback userdata, like casting it to intptr_t/void*
+        userdata = MemorySegment.ofAddress(index);
+    }
+
+    public MemorySegment getCallback() {
+        return callback;
+    }
+
+    public MemorySegment getUserData() {
+        return userdata;
+    }
+
+    public static void eventHandler(MemorySegment event, MemorySegment userdata) {
+        System.out.println("event handler");
+        int eventHandlerIndex = (int)userdata.address();
+
+        Event e = new Event(event);
+        UiObject obj = e.getObject();
+        EventHandler handler = obj.getEventHandler(eventHandlerIndex);
+        handler.callback(e);
+    }
+}
index 4e8b05f6eb68d5a3ef0287076d637f75461a7850..b73cb1ceb3090138083d30b1983276dda9a9aaeb 100644 (file)
@@ -4,12 +4,15 @@ import java.lang.foreign.*;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
+import java.util.HashMap;
 
 public class Toolkit {
     private static Toolkit instance;
 
     private Application app;
 
+    private HashMap<Long, UiObject> toplevelObjects = new HashMap<>();
+
     // used for all strings and other memory, that is expected to be const
     // and the UI toolkit does not create copies
     private final Arena staticArena = Arena.ofShared();
@@ -51,7 +54,7 @@ public class Toolkit {
         mainFunc = linker.downcallHandle(ui_main_addr, sigv_v);
     }
 
-    static Toolkit getInstance() {
+    public static Toolkit getInstance() {
         if(Toolkit.instance == null) {
             Toolkit.init("app1");
         }
@@ -164,4 +167,16 @@ public class Toolkit {
             throw new RuntimeException(e);
         }
     }
+
+    public void registerToplevelObject(UiObject obj) {
+        toplevelObjects.put(obj.ptr.address(), obj);
+    }
+
+    public void removeToplevelObject(UiObject obj) {
+        toplevelObjects.remove(obj.ptr.address());
+    }
+
+    public UiObject getToplevelObject(long address) {
+        return toplevelObjects.get(address);
+    }
 }
diff --git a/ui-java/src/main/java/de/unixwork/ui/ToolkitFuncs.java b/ui-java/src/main/java/de/unixwork/ui/ToolkitFuncs.java
new file mode 100644 (file)
index 0000000..13935b9
--- /dev/null
@@ -0,0 +1,28 @@
+package de.unixwork.ui;
+
+import java.lang.foreign.*;
+import java.lang.invoke.MethodHandle;
+
+public class ToolkitFuncs {
+    static ToolkitFuncs instance;
+
+    public MethodHandle event_get_obj;
+
+    private ToolkitFuncs(Linker linker, SymbolLookup lib) {
+        // void* func(void*)
+        FunctionDescriptor sigm_m = FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS);
+        FunctionDescriptor sigi_m = FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS);
+
+        MemorySegment event_get_obj_addr = lib.find("ui_event_get_obj").orElseThrow();
+
+        event_get_obj = linker.downcallHandle(event_get_obj_addr, sigm_m);
+    }
+
+    static ToolkitFuncs getInstance() {
+        if (instance == null) {
+            Toolkit toolkit = Toolkit.getInstance();
+            instance = new ToolkitFuncs(toolkit.getLinker(), toolkit.getSymbolLookup());
+        }
+        return instance;
+    }
+}
index 0bdc6448c1e7cc08de64eaa0cd13b71a7c171875..6938f5cc928124abe66b54c24d4a69c5a4b5b2fd 100644 (file)
@@ -1,15 +1,25 @@
 package de.unixwork.ui;
 
+import java.lang.foreign.Arena;
+import java.lang.foreign.FunctionDescriptor;
 import java.lang.foreign.MemorySegment;
+import java.lang.invoke.MethodHandle;
 import java.util.ArrayList;
+import java.util.function.Function;
 
 public class UiObject {
     MemorySegment ptr;
 
     private ArrayList<EventHandler> eventHandlers = new ArrayList<>();
 
-    UiObject(MemorySegment ptr) {
+    private Arena arena;
+
+    public UiObject(MemorySegment ptr) {
         this.ptr = ptr;
+        // TODO: maybe the C toolkit code should also handle toplevel object creation and call a callback
+        //       because there is some dynamic UiObject creation
+        Toolkit.getInstance().registerToplevelObject(this);
+        // TODO: handle UiObject destroy event
     }
 
     public static UiObject createWindow(String title) {
@@ -19,4 +29,20 @@ public class UiObject {
     public void show() {
         UiObjectFuncs.instance.show(this);
     }
+
+    public long addEventHandler(EventHandler handler) {
+        eventHandlers.add(handler);
+        return eventHandlers.size() - 1;
+    }
+
+    public EventHandler getEventHandler(int index) {
+        return eventHandlers.get(index);
+    }
+
+    public Arena getArena() {
+        if (arena == null) {
+            arena = Arena.ofShared();
+        }
+        return arena;
+    }
 }
index 02087e9a1dbe23a44a03cf2880e05536c84da68d..60e37e2bfb3965063babcd854ab2ca0f57400730 100644 (file)
@@ -5,7 +5,9 @@ import de.unixwork.ui.*;
 public class Main implements Application{
     public void startup() {
         UiObject window = UiObject.createWindow("Test Window");
-        Button.button(window).label("Click Me").create();
+        Button.button(window).label("Click Me").onClick(event -> {
+            System.out.println("Clicked");
+        }).create();
         Button.checkbox(window).label("Checkbox").create();
         Button.radioButton(window).label("Radio Button 1").varname("radiobutton").create();
         Button.radioButton(window).label("Radio Button 2").varname("radiobutton").create();