From 7f836e7bc0bf5795449332a59d70dfcb218028c6 Mon Sep 17 00:00:00 2001 From: Olaf Wintermann Date: Thu, 12 Jun 2025 21:01:55 +0200 Subject: [PATCH] implement button eventhandler --- .../java/de/unixwork/ui/ButtonBuilder.java | 23 +++---- .../src/main/java/de/unixwork/ui/Event.java | 14 ++++ .../java/de/unixwork/ui/EventWrapper.java | 64 +++++++++++++++++++ .../src/main/java/de/unixwork/ui/Toolkit.java | 17 ++++- .../java/de/unixwork/ui/ToolkitFuncs.java | 28 ++++++++ .../main/java/de/unixwork/ui/UiObject.java | 28 +++++++- .../test/java/de/unixwork/ui/demo/Main.java | 4 +- 7 files changed, 162 insertions(+), 16 deletions(-) create mode 100644 ui-java/src/main/java/de/unixwork/ui/EventWrapper.java create mode 100644 ui-java/src/main/java/de/unixwork/ui/ToolkitFuncs.java diff --git a/ui-java/src/main/java/de/unixwork/ui/ButtonBuilder.java b/ui-java/src/main/java/de/unixwork/ui/ButtonBuilder.java index 4e2d6e6..d5cfc46 100644 --- a/ui-java/src/main/java/de/unixwork/ui/ButtonBuilder.java +++ b/ui-java/src/main/java/de/unixwork/ui/ButtonBuilder.java @@ -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; - } } diff --git a/ui-java/src/main/java/de/unixwork/ui/Event.java b/ui-java/src/main/java/de/unixwork/ui/Event.java index 229f786..6846423 100644 --- a/ui-java/src/main/java/de/unixwork/ui/Event.java +++ b/ui-java/src/main/java/de/unixwork/ui/Event.java @@ -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 index 0000000..3d0bfc0 --- /dev/null +++ b/ui-java/src/main/java/de/unixwork/ui/EventWrapper.java @@ -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); + } +} diff --git a/ui-java/src/main/java/de/unixwork/ui/Toolkit.java b/ui-java/src/main/java/de/unixwork/ui/Toolkit.java index 4e8b05f..b73cb1c 100644 --- a/ui-java/src/main/java/de/unixwork/ui/Toolkit.java +++ b/ui-java/src/main/java/de/unixwork/ui/Toolkit.java @@ -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 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 index 0000000..13935b9 --- /dev/null +++ b/ui-java/src/main/java/de/unixwork/ui/ToolkitFuncs.java @@ -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; + } +} diff --git a/ui-java/src/main/java/de/unixwork/ui/UiObject.java b/ui-java/src/main/java/de/unixwork/ui/UiObject.java index 0bdc644..6938f5c 100644 --- a/ui-java/src/main/java/de/unixwork/ui/UiObject.java +++ b/ui-java/src/main/java/de/unixwork/ui/UiObject.java @@ -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 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; + } } diff --git a/ui-java/src/test/java/de/unixwork/ui/demo/Main.java b/ui-java/src/test/java/de/unixwork/ui/demo/Main.java index 02087e9..60e37e2 100644 --- a/ui-java/src/test/java/de/unixwork/ui/demo/Main.java +++ b/ui-java/src/test/java/de/unixwork/ui/demo/Main.java @@ -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(); -- 2.47.3