From f19a958f7f1a20d9bff74c27d394b9f9fd6f3113 Mon Sep 17 00:00:00 2001 From: Olaf Wintermann Date: Tue, 18 Aug 2026 21:18:10 +0200 Subject: [PATCH] add menubuilder --- ui-rs/src/ui/menu.rs | 87 ++++++++++++++++++++++++++++++++++++++++- ui-rs/src/ui/toolkit.rs | 8 ++-- ui/cocoa/container.m | 25 +++++++++--- ui/cocoa/label.m | 5 +++ ui/cocoa/objs.mk | 1 + ui/cocoa/text.m | 8 ++++ ui/cocoa/webview.m | 37 ++++++++++++++---- ui/cocoa/window.m | 4 +- ui/common/menu.c | 11 ++++++ ui/common/menu.h | 2 + ui/ui/menu.h | 3 ++ 11 files changed, 170 insertions(+), 21 deletions(-) diff --git a/ui-rs/src/ui/menu.rs b/ui-rs/src/ui/menu.rs index 69e9fa6..8926d33 100644 --- a/ui-rs/src/ui/menu.rs +++ b/ui-rs/src/ui/menu.rs @@ -30,8 +30,9 @@ use std::ffi::{c_char, c_int, c_void, CString}; use std::marker::PhantomData; -use crate::ui::{event, ffi, toolkit, AppContext, EventWrapper, GetValueFunc, ToggleButtonCreate, UiActions, UiContext, UiModel}; -use crate::ui::ffi::{UiCallback, UiMenuItemArgs, UiMenuItemListArgs, UiMenuToggleItemArgs, UiObject, UiToggleArgs}; +use std::ops::Deref; +use crate::ui::{event, ffi, toolkit, ui_document_context, ui_document_new, ui_document_unref, AppContext, EventWrapper, GetValueFunc, ToggleButtonCreate, UiActions, UiContext, UiModel}; +use crate::ui::ffi::{UiCallback, UiDestructor, UiMenuBuilder, UiMenuItemArgs, UiMenuItemListArgs, UiMenuToggleItemArgs, UiObject, UiToggleArgs}; impl AppContext { pub fn menu(&self, title: &str, f: F) @@ -230,6 +231,83 @@ impl Drop for MenuToggleItemBuilder { } } + +pub struct MenuBuilder { + menu: Menu, + doc: *mut c_void, + ctx: *mut ffi::UiContext, + builder: *mut UiMenuBuilder, +} + +impl Deref for MenuBuilder { + type Target = Menu; + + fn deref(&self) -> &Self::Target { + &self.menu + } +} + +impl MenuBuilder { + pub fn ref_by_obj(&self, obj: &toolkit::UiObject) { + unsafe { + ui_menubuilder_ref_by_obj(self.builder, obj.ptr); + } + } +} + +impl Clone for MenuBuilder { + fn clone(&self) -> Self { + unsafe { + ui_menubuilder_ref(self.builder); + } + MenuBuilder { + menu: Menu { + ctx: self.ctx, + _marker: PhantomData + }, + doc: self.doc, + ctx: self.ctx, + builder: self.builder, + } + } +} + +impl Drop for MenuBuilder { + fn drop(&mut self) { + unsafe { + ui_menubuilder_unref(self.builder); + } + } +} + +pub fn menubuilder_new() -> MenuBuilder { + unsafe { + let doc = ui_document_new(8); + let ctx = ui_document_context(doc); + let mut builder: *mut UiMenuBuilder = std::ptr::null_mut(); + ui_contextmenu_builder(&mut builder); + ui_menubuilder_set_custom_data(builder, doc, menubuilder_doc_destroy); + let menu = Menu { + ctx, + _marker: PhantomData, + }; + + MenuBuilder { + menu, + doc, + ctx, + builder + } + } +} + + +extern "C" fn menubuilder_doc_destroy(doc: *mut c_void) { + unsafe { + ui_document_unref(doc); + } +} + unsafe extern "C" { fn ui_menu_create(label: *const c_char); fn ui_menuitem_create(args: *const UiMenuItemArgs); @@ -240,6 +318,11 @@ unsafe extern "C" { fn ui_menu_toggleitemlist_create(args: *const UiMenuItemListArgs); fn ui_menu_radioitemlist_create(args: *const UiMenuItemListArgs); pub fn ui_menu_end(); + fn ui_contextmenu_builder(out_builder: *mut *mut ffi::UiMenuBuilder); + fn ui_menubuilder_set_custom_data(builder: *mut UiMenuBuilder, data: *mut c_void, destr: UiDestructor); + fn ui_menubuilder_ref_by_obj(builder: *mut ffi::UiMenuBuilder, obj: *mut ffi::UiObject); + fn ui_menubuilder_ref(builder: *mut ffi::UiMenuBuilder); + fn ui_menubuilder_unref(builder: *mut ffi::UiMenuBuilder); fn ui_menuitem_args_new() -> *mut UiMenuItemArgs; fn ui_menuitem_args_set_label(args: *mut UiMenuItemArgs, label: *const c_char); diff --git a/ui-rs/src/ui/toolkit.rs b/ui-rs/src/ui/toolkit.rs index 0e57608..f575b18 100644 --- a/ui-rs/src/ui/toolkit.rs +++ b/ui-rs/src/ui/toolkit.rs @@ -1446,10 +1446,10 @@ unsafe extern "C" { fn ui_configfile(name: *const c_char) -> *mut c_char; pub fn ui_call_mainthread(func: UiThreadFunc, data: *mut c_void); - fn ui_document_new(size: usize) -> *mut c_void; - fn ui_document_ref(doc: *mut c_void); - fn ui_document_unref(doc: *mut c_void); - fn ui_document_context(doc: *mut c_void) -> *mut ffi::UiContext; + pub fn ui_document_new(size: usize) -> *mut c_void; + pub fn ui_document_ref(doc: *mut c_void); + pub fn ui_document_unref(doc: *mut c_void); + pub fn ui_document_context(doc: *mut c_void) -> *mut ffi::UiContext; fn ui_attach_document(ctx: *mut ffi::UiContext, doc: *mut c_void); fn ui_detach_document(ctx: *mut ffi::UiContext, doc: *mut c_void); fn ui_context_onattach(ctx: *mut ffi::UiContext, callback: UiCallback, data: *mut c_void); diff --git a/ui/cocoa/container.m b/ui/cocoa/container.m index 2bd54a2..80daf77 100644 --- a/ui/cocoa/container.m +++ b/ui/cocoa/container.m @@ -45,7 +45,10 @@ static UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs *args, NSUserInterf // add new box to the obj container chain uic_object_push_container(obj, container); - return (__bridge void*)box; + void *widget = (__bridge void*)box; + uic_widget_set_visibility_states(obj->ctx, widget, args->visibility_states); + + return widget; } UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs *args) { @@ -71,7 +74,10 @@ UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs *args) { // add new box to the obj container chain uic_object_push_container(obj, container); - return (__bridge void*)grid; + void *widget = (__bridge void*)grid; + uic_widget_set_visibility_states(obj->ctx, widget, args->visibility_states); + + return widget; } UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs *args) { @@ -116,7 +122,10 @@ UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs *args) { uic_object_push_container(obj, container); - return (__bridge void*)frame; + void *widget = (__bridge void*)frame; + uic_widget_set_visibility_states(obj->ctx, widget, args->visibility_states); + + return widget; } UIWIDGET ui_expander_create(UiObject *obj, UiFrameArgs *args) { @@ -140,7 +149,10 @@ UIWIDGET ui_scrolledwindow_create(UiObject *obj, UiFrameArgs *args) { UiContainer *container = ui_create_container(obj, scrollview); uic_object_push_container(obj, container); - return (__bridge void*)scrollview; + void *widget = (__bridge void*)scrollview; + uic_widget_set_visibility_states(obj->ctx, widget, args->visibility_states); + + return widget; } static int64_t tabview_get(UiInteger *i) { @@ -165,7 +177,10 @@ UIWIDGET ui_tabview_create(UiObject *obj, UiTabViewArgs *args) { UiContainer *container = ui_create_container(obj, tabview); uic_object_push_container(obj, container); - return (__bridge void*)tabview; + void *widget = (__bridge void*)tabview; + uic_widget_set_visibility_states(obj->ctx, widget, args->visibility_states); + + return widget; } void ui_tab_create(UiObject *obj, const char* title) { diff --git a/ui/cocoa/label.m b/ui/cocoa/label.m index 44e9045..36df5f7 100644 --- a/ui/cocoa/label.m +++ b/ui/cocoa/label.m @@ -64,6 +64,9 @@ static UIWIDGET create_label(UiObject *obj, UiLabelArgs *args) { if(s->value.ptr) { label.stringValue = [[NSString alloc]initWithUTF8String:s->value.ptr]; } + if(s->value.free) { + s->value.free(s->value.ptr); + } } return (__bridge void*)label; @@ -91,6 +94,7 @@ char* ui_label_get(UiString *s) { s->value.free(s->value.ptr); } s->value.ptr = strdup(label.stringValue.UTF8String); + s->value.free = free; return s->value.ptr; } @@ -100,5 +104,6 @@ void ui_label_set(UiString *s, const char *str) { s->value.free(s->value.ptr); } s->value.ptr = NULL; + s->value.free = NULL; label.stringValue = [[NSString alloc] initWithUTF8String:str]; } diff --git a/ui/cocoa/objs.mk b/ui/cocoa/objs.mk index a37ec4f..4582da4 100644 --- a/ui/cocoa/objs.mk +++ b/ui/cocoa/objs.mk @@ -52,6 +52,7 @@ COCOAOBJ += image.o COCOAOBJ += entry.o COCOAOBJ += TabView.o COCOAOBJ += action.o +COCOAOBJ += webview.o TOOLKITOBJS += $(COCOAOBJ:%=$(COCOA_OBJPRE)%) TOOLKITSOURCE += $(COCOAOBJ:%.o=cocoa/%.m) diff --git a/ui/cocoa/text.m b/ui/cocoa/text.m index acbffed..ffcc88c 100644 --- a/ui/cocoa/text.m +++ b/ui/cocoa/text.m @@ -564,3 +564,11 @@ int ui_textfield_get_position(UIWIDGET textfield) { } @end + + + +/* -------------------- Path TextField -------------------- */ + +UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs *args) { + return NULL; // TODO: implement +} diff --git a/ui/cocoa/webview.m b/ui/cocoa/webview.m index a4b4dae..8555599 100644 --- a/ui/cocoa/webview.m +++ b/ui/cocoa/webview.m @@ -29,6 +29,14 @@ #import "webview.h" #import "Container.h" +static UiWebViewData* create_webview_data(void) { + UiWebViewData *data = malloc(sizeof(UiWebViewData)); + memset(data, 0, sizeof(UiWebViewData)); + data->javascript = TRUE; + data->zoom = 1; + return data; +} + UIWIDGET ui_webview_create(UiObject *obj, UiWebviewArgs *args) { UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_GENERIC); @@ -47,11 +55,8 @@ UIWIDGET ui_webview_create(UiObject *obj, UiWebviewArgs *args) { if(value->value) { ui_webview_set(value, value->value, UI_WEBVIEW_OBJECT_TYPE); } else { - UiWebViewData *data = malloc(sizeof(UiWebViewData)); - memset(data, 0, sizeof(UiWebViewData)); + UiWebViewData *data = create_webview_data(); data->webview = (__bridge void*)webview; - data->javascript = TRUE; - data->zoom = 1; value->value = data; } } @@ -60,10 +65,7 @@ UIWIDGET ui_webview_create(UiObject *obj, UiWebviewArgs *args) { } UiWebViewData* ui_webview_data_clone(UiWebViewData *data) { - UiWebViewData *newdata = malloc(sizeof(UiWebViewData)); - memset(newdata, 0, sizeof(UiWebViewData)); - newdata->zoom = 1; - newdata->javascript = TRUE; + UiWebViewData *newdata = create_webview_data(); if(data) { newdata->uri = data->uri ? strdup(data->uri) : NULL; @@ -98,6 +100,12 @@ void* ui_webview_get(UiGeneric *g) { UiWebViewData *data = g->value; WKWebView *webview = (__bridge WKWebView*)g->obj; + if(data == NULL) { + data = create_webview_data(); + data->webview = g->obj; + g->value = data; + } + if(data->type == WEBVIEW_CONTENT_URL) { (void)ui_webview_get_uri(g); // this updates data->uri } @@ -157,6 +165,13 @@ void ui_webview_destroy(UiGeneric *g) { void ui_webview_load_url(UiGeneric *g, const char *url) { WKWebView *webview = (__bridge WKWebView*)g->obj; UiWebViewData *data = g->value; + + if(data == NULL) { + data = create_webview_data(); + data->webview = g->obj; + g->value = data; + } + data->type = WEBVIEW_CONTENT_URL; if(!url) { @@ -183,6 +198,12 @@ void ui_webview_load_content( UiWebViewData *data = g->value; WKWebView *webview = (__bridge WKWebView*)g->obj; + if(data == NULL) { + data = create_webview_data(); + data->webview = g->obj; + g->value = data; + } + data->type = WEBVIEW_CONTENT_CONTENT; free(data->uri); diff --git a/ui/cocoa/window.m b/ui/cocoa/window.m index c027867..86a97b7 100644 --- a/ui/cocoa/window.m +++ b/ui/cocoa/window.m @@ -400,7 +400,7 @@ UiObject *ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs *args) { EventData *event = [[EventData alloc] init:args->onclick userdata:args->onclickdata action:nil]; event.obj = obj; - event.value = 2; + event.value = 4; rbutton4.target = event; rbutton4.action = @selector(handleEvent:); objc_setAssociatedObject(rbutton4, "eventdata", event, OBJC_ASSOCIATION_RETAIN); @@ -420,7 +420,7 @@ UiObject *ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs *args) { EventData *event = [[EventData alloc] init:args->onclick userdata:args->onclickdata action:nil]; event.obj = obj; - event.value = 2; + event.value = 3; rbutton3.target = event; rbutton3.action = @selector(handleEvent:); objc_setAssociatedObject(rbutton3, "eventdata", event, OBJC_ASSOCIATION_RETAIN); diff --git a/ui/common/menu.c b/ui/common/menu.c index 83eb258..f245cf0 100644 --- a/ui/common/menu.c +++ b/ui/common/menu.c @@ -309,6 +309,8 @@ void ui_contextmenu_builder(UiMenuBuilder **out_builder) { builder->menus_begin = NULL; builder->menus_end = NULL; builder->current = cxLinkedListCreate(cxDefaultAllocator, CX_STORE_POINTERS); + builder->custom_data = NULL; + builder->custom_destr = NULL; builder->ref = 1; current_builder = builder; *out_builder = builder; @@ -316,6 +318,10 @@ void ui_contextmenu_builder(UiMenuBuilder **out_builder) { ui_menu_create(NULL); } +void ui_menubuilder_set_custom_data(UiMenuBuilder *builder, void *data, ui_destructor_func destr) { + builder->custom_destr = destr; + builder->custom_data = data; +} static void free_menuitem(UiMenuItemI *item) { @@ -389,3 +395,8 @@ void ui_menubuilder_unref(UiMenuBuilder *builder) { ui_menubuilder_free(builder); } } + +void ui_menubuilder_ref_by_obj(UiMenuBuilder *builder, UiObject *obj) { + builder->ref++; + ui_reg_destructor(obj->ctx, builder, (ui_destructor_func)ui_menubuilder_unref); +} diff --git a/ui/common/menu.h b/ui/common/menu.h index 510e504..f1a4aa2 100644 --- a/ui/common/menu.h +++ b/ui/common/menu.h @@ -122,6 +122,8 @@ struct UiMenuBuilder { UiMenu *menus_begin; UiMenu *menus_end; CxList *current; + ui_destructor_func custom_destr; + void *custom_data; int ref; }; diff --git a/ui/ui/menu.h b/ui/ui/menu.h index 5ad8776..b6850e7 100644 --- a/ui/ui/menu.h +++ b/ui/ui/menu.h @@ -105,6 +105,7 @@ UIEXPORT void ui_menu_end(void); // TODO: private UIEXPORT void ui_contextmenu_builder(UiMenuBuilder **out_builder); UIEXPORT void ui_menubuilder_ref(UiMenuBuilder *builder); UIEXPORT void ui_menubuilder_unref(UiMenuBuilder *builder); +UIEXPORT void ui_menubuilder_ref_by_obj(UiMenuBuilder *builder, UiObject *obj); UIEXPORT UIMENU ui_contextmenu_create(UiMenuBuilder *builder, UiObject *obj, UIWIDGET widget); UIEXPORT void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y); @@ -112,6 +113,8 @@ UIEXPORT void ui_contextmenu_popup(UIMENU menu, UIWIDGET widget, int x, int y); UIEXPORT void ui_menu_close(void); UIEXPORT int ui_menu_is_open(void); +UIEXPORT void ui_menubuilder_set_custom_data(UiMenuBuilder *builder, void *data, ui_destructor_func destr); + #ifdef __cplusplus } #endif -- 2.52.0