From: Olaf Wintermann Date: Fri, 5 Jun 2026 18:10:27 +0000 (+0200) Subject: add support for actions with typed arguments X-Git-Url: https://uap-core.de/gitweb/?a=commitdiff_plain;h=234ed8674da9b6901480146e4e1ea0adef48ecd3;p=note.git add support for actions with typed arguments --- diff --git a/application/src/notebook.rs b/application/src/notebook.rs index d8030c9..a8465ad 100644 --- a/application/src/notebook.rs +++ b/application/src/notebook.rs @@ -1,3 +1,4 @@ +use std::any::Any; use std::rc::Rc; use ui_rs::{action, ui_actions, UiModel}; use ui_rs::ui::*; @@ -96,6 +97,9 @@ impl Notebook { if let Some(mut nb) = self.doc_ref.get_doc() { nb.ctx.attach(&doc); self.selected_note = Some(doc); + + let param: Box = Box::new("hello world".to_string()); + nb.ctx.call_action_with_parameter("navigation", param); } } } diff --git a/application/src/window.rs b/application/src/window.rs index 69d6c84..ddf5ca6 100644 --- a/application/src/window.rs +++ b/application/src/window.rs @@ -137,6 +137,11 @@ impl MainWindow { } + #[action(String)] + pub fn navigation(&mut self, _event: &ActionEvent, arg: &String) { + println!("navigation: {}", arg); + } + #[action] pub fn message(&mut self, _event: &ActionEvent) { while let Ok(msg) = self.broadcast_rx.try_recv() { diff --git a/ui-rs-derive/src/lib.rs b/ui-rs-derive/src/lib.rs index 03fc8f2..5c6da1f 100644 --- a/ui-rs-derive/src/lib.rs +++ b/ui-rs-derive/src/lib.rs @@ -1,6 +1,6 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, Attribute, Data, DeriveInput, Expr, ExprLit, ImplItem, ItemImpl, Lit, Meta}; +use syn::{parse_macro_input, Attribute, Data, DeriveInput, Expr, ExprLit, ImplItem, ItemImpl, Lit, Meta, Type}; #[proc_macro_derive(UiModel, attributes(bind))] pub fn derive_ui_model(input: TokenStream) -> TokenStream { @@ -158,15 +158,34 @@ pub fn ui_actions(_attr: TokenStream, item: TokenStream) -> TokenStream { let self_ty = &impl_block.self_ty; let mut handlers = vec![]; + let mut param_handlers = vec![]; for item in &impl_block.items { if let ImplItem::Fn(method) = item { - if has_handler_attr(&method.attrs) { - handlers.push(method.sig.ident.clone()); + if let Some(attr) = has_handler_attr(&method.attrs) { + match attr { + Some(param) => param_handlers.push((param, method.sig.ident.clone())), + None => handlers.push(method.sig.ident.clone()), + } } } } + let typed_actions = param_handlers.into_iter().map(|(ty, name)| { + quote! { + ctx.add_action( + stringify!(#name), + |target: &mut Self, event| { + if let EventType::TypedObject(obj) = &event.event_type { + if let Some(arg) = obj.downcast_ref::<#ty>() { + target.#name(event, arg); + } + } + } + ); + } + }); + let expanded = quote! { #impl_block impl UiActions for #self_ty { @@ -179,7 +198,8 @@ pub fn ui_actions(_attr: TokenStream, item: TokenStream) -> TokenStream { target.#handlers(event); } ); - )* + )*; + #(#typed_actions)* } } } @@ -188,8 +208,21 @@ pub fn ui_actions(_attr: TokenStream, item: TokenStream) -> TokenStream { TokenStream::from(expanded) } -fn has_handler_attr(attrs: &[Attribute]) -> bool { - attrs.iter().any(|attr| attr.path().is_ident("action")) +fn has_handler_attr(attrs: &[Attribute]) -> Option> { + for attr in attrs { + if !attr.path().is_ident("action") { + continue; + } + match &attr.meta { + Meta::Path(_) => return Some(None), + Meta::List(list) => { + let ty: Type = syn::parse2(list.tokens.clone()).ok()?; + return Some(Some(ty)); + }, + _ => panic!("Unexpected Attribute") + } + } + None } #[proc_macro_attribute] diff --git a/ui-rs/src/ui/event.rs b/ui-rs/src/ui/event.rs index 984a8fa..87d84bc 100644 --- a/ui-rs/src/ui/event.rs +++ b/ui-rs/src/ui/event.rs @@ -28,12 +28,15 @@ #![allow(dead_code)] +use std::any::Any; use std::ffi::{c_char, c_void, CStr, CString}; use std::slice; use crate::ui::{event, ffi, UiDouble, UiInteger, UiString, UiText, UiRange, ListSelection, UiObject, NoAppData, UiSourceList}; -use crate::ui::ffi::{UiEvent, UiSubListEventData, UiTextChangeEventData}; +use crate::ui::ffi::{UiEvent, UiSubListEventData, UiTextChangeEventData, UiTypedObj}; use crate::ui::text::{TextInsert, TextDelete}; +pub const RUST_TYPED_OBJECT_BOX_ANY: u64 = 0x4416222c10162501; + pub struct Event<'a, T> { pub obj: &'a mut UiObject, pub data: &'a mut T, @@ -64,7 +67,8 @@ enum UiEventType { ListElm, DND, SubList, - FileList + FileList, + TypedObject } pub enum EventType<'a> { @@ -82,7 +86,8 @@ pub enum EventType<'a> { ListElement, Dnd, SubList(&'a mut SubListEvent), - FileList + FileList, + TypedObject(&'a mut dyn Any) } enum EventTypeData { @@ -100,7 +105,8 @@ enum EventTypeData { ListElement, Dnd, SubList(SubListEvent), - FileList + FileList, + TypedObject(*mut Box) } fn get_event_data(e: *const ffi::UiEvent) -> EventTypeData { @@ -179,7 +185,18 @@ fn get_event_data(e: *const ffi::UiEvent) -> EventTypeData { UiEventType::FileList => { // filelist EventTypeData::Null - } + }, + UiEventType::TypedObject => { + unsafe { + if ui_typed_obj_get_type(ptr.cast()) == RUST_TYPED_OBJECT_BOX_ANY && !ptr.is_null() { + let box_ptr: *mut Box = ui_typed_obj_get_ptr(ptr.cast()).cast(); + EventTypeData::TypedObject(box_ptr) + } else { + EventTypeData::Null + + } + } + }, _ => EventTypeData::Null } } @@ -201,6 +218,11 @@ fn get_event_type<'a>(data: &'a mut EventTypeData) -> EventType<'a> { EventTypeData::Dnd => EventType::Dnd, EventTypeData::SubList(s) => EventType::SubList( s ), EventTypeData::FileList => EventType::FileList, + EventTypeData::TypedObject(ptr) => unsafe { + let boxed: &mut Box = ptr.as_mut().unwrap(); + let any: &mut dyn Any = boxed.as_mut(); + EventType::TypedObject(any) + } } } @@ -333,6 +355,9 @@ extern "C" { fn ui_event_get_int(event: *const UiEvent) -> i32; fn ui_event_get_set(event: *const UiEvent) -> i32; + fn ui_typed_obj_get_ptr(obj: *const UiTypedObj) -> *mut c_void; + fn ui_typed_obj_get_type(obj: *const UiTypedObj) -> u64; + fn ui_text_change_event_get_type(data: *const UiTextChangeEventData) -> i32; fn ui_text_change_event_get_begin(data: *const UiTextChangeEventData) -> i32; fn ui_text_change_event_get_end(data: *const UiTextChangeEventData) -> i32; diff --git a/ui-rs/src/ui/ffi.rs b/ui-rs/src/ui/ffi.rs index 1908c50..21daf48 100644 --- a/ui-rs/src/ui/ffi.rs +++ b/ui-rs/src/ui/ffi.rs @@ -51,6 +51,11 @@ pub struct UiSubListEventData { _private: [u8; 0], } +#[repr(C)] +pub struct UiTypedObj { + _private: [u8; 0], +} + #[repr(C)] pub struct UiTextChangeEventData { _private: [u8; 0], diff --git a/ui-rs/src/ui/toolkit.rs b/ui-rs/src/ui/toolkit.rs index 394626b..d0666a8 100644 --- a/ui-rs/src/ui/toolkit.rs +++ b/ui-rs/src/ui/toolkit.rs @@ -28,8 +28,9 @@ #![allow(dead_code)] +use std::any::Any; use std::ffi::{c_char, c_int, c_void, CStr, CString}; -use crate::ui::{action_event_wrapper, event, ffi, ui_object_get_context, ui_object_get_windowdata, ui_reg_destructor, ui_remove_destructor, ActionEventWrapper, Event, EventWrapper, SubList}; +use crate::ui::{action_event_wrapper, event, ffi, ui_object_get_context, ui_object_get_windowdata, ui_reg_destructor, ui_remove_destructor, ActionEventWrapper, Event, EventWrapper, SubList, RUST_TYPED_OBJECT_BOX_ANY}; use std::marker::PhantomData; use std::mem; @@ -117,6 +118,14 @@ impl UiContext { ui_call_action(self.ptr, cstr.as_ptr()); } } + + pub fn call_action_with_parameter(&self, action: &str, mut param: Box) { + let cstr = CString::new(action).unwrap(); + unsafe { + let param_ptr = &mut param as *mut Box; + ui_call_action3(self.ptr, cstr.as_ptr(), param_ptr.cast(), RUST_TYPED_OBJECT_BOX_ANY); + } + } } pub extern "C" fn destroy_boxed(data: *mut c_void) { @@ -1275,6 +1284,7 @@ extern "C" { fn ui_text_length(text: *const ffi::UiText) -> c_int; fn ui_text_remove(text: *mut ffi::UiText, begin: c_int, end: c_int); - fn ui_call_action(ctx: *mut ffi::UiContext, action: *const c_char); + fn ui_call_action(ctx: *mut ffi::UiContext, action: *const c_char) -> c_int; + fn ui_call_action3(ctx: *mut ffi::UiContext, action: *const c_char, data: *mut c_void, typeid: u64) -> c_int; fn ui_mainthread_broadcast(action: *const c_char); } diff --git a/ui/common/action.c b/ui/common/action.c index 7c99361..5624c34 100644 --- a/ui/common/action.c +++ b/ui/common/action.c @@ -143,11 +143,7 @@ void uic_action_callback(UiEvent *event, const char *action_name) { } } -void ui_call_action(UiContext *ctx, const char *action_name) { - ui_call_action2(ctx, action_name, NULL, 0); -} - -void ui_call_action2(UiContext *ctx, const char *action_name, void *eventdata, int intval) { +static int call_action(UiContext *ctx, const char *action_name, void *eventdata, UiEventType eventdatatype, int intval) { UiAction *action = uic_resolve_action(ctx, action_name); if(action && action->callback) { UiEvent event; @@ -157,13 +153,33 @@ void ui_call_action2(UiContext *ctx, const char *action_name, void *eventdata, i event.document = ctx->self_doc ? ctx->self_doc : ctx->document; if(eventdata) { event.eventdata = eventdata; - event.eventdatatype = UI_EVENT_DATA_POINTER; + event.eventdatatype = eventdatatype; } event.intval = intval; action->callback(&event, action->userdata); + return 1; + } else { + return 0; } } +int ui_call_action(UiContext *ctx, const char *action_name) { + return ui_call_action2(ctx, action_name, NULL, 0); +} + +int ui_call_action2(UiContext *ctx, const char *action_name, void *eventdata, int intval) { + return call_action(ctx, action_name, eventdata, UI_EVENT_DATA_POINTER, intval); +} + +int ui_call_action3(UiContext *ctx, const char *action_name, void *ptr, uint64_t type_id) { + UiTypedObj *obj = malloc(sizeof(UiTypedObj)); + obj->ptr = ptr; + obj->type = type_id; + int ret = call_action(ctx, action_name, obj, UI_EVENT_DATA_TYPED_OBJECT, 0); + free(obj); + return ret; +} + void ui_broadcast_action(const char *action_name) { ui_broadcast_action2(action_name, NULL, 0); } diff --git a/ui/common/wrapper.c b/ui/common/wrapper.c index 56194e8..29af77e 100644 --- a/ui/common/wrapper.c +++ b/ui/common/wrapper.c @@ -250,6 +250,17 @@ int ui_event_get_set(UiEvent *event) { } +/* ---------------------------- UiTypedObj ---------------------------- */ + +void* ui_typed_obj_get_ptr(UiTypedObj *obj) { + return obj->ptr; +} + +uint64_t ui_typed_obj_get_type(UiTypedObj *obj) { + return obj->type; +} + + /* ------------------------- SubListItem (public) ------------------------- */ void ui_sublist_item_set_icon(UiSubListItem *item, const char *icon) { diff --git a/ui/common/wrapper.h b/ui/common/wrapper.h index 6f7507c..2883e56 100644 --- a/ui/common/wrapper.h +++ b/ui/common/wrapper.h @@ -84,6 +84,9 @@ UIEXPORT int ui_event_get_eventdatatype(UiEvent *event); UIEXPORT int ui_event_get_int(UiEvent *event); UIEXPORT int ui_event_get_set(UiEvent *event); +UIEXPORT void* ui_typed_obj_get_ptr(UiTypedObj *obj); +UIEXPORT uint64_t ui_typed_obj_get_type(UiTypedObj *obj); + UIEXPORT int ui_list_selection_get_count(UiListSelection *sel); UIEXPORT int* ui_list_selection_get_rows(UiListSelection *sel); UIEXPORT void ui_list_set_selected_indices(UiList *list, int *indices, int num); diff --git a/ui/ui/toolkit.h b/ui/ui/toolkit.h index 0e89071..901d303 100644 --- a/ui/ui/toolkit.h +++ b/ui/ui/toolkit.h @@ -213,6 +213,8 @@ typedef struct UiListSelection UiListSelection; typedef struct UiTextStyle UiTextStyle; typedef struct UiColor UiColor; +typedef enum UiEventType UiEventType; + /* begin opaque types */ typedef struct UiContext UiContext; typedef struct UiContainer UiContainer; @@ -317,14 +319,32 @@ struct UiObject { unsigned int ref; }; +enum UiEventType { + UI_EVENT_DATA_NULL = 0, + UI_EVENT_DATA_POINTER, + UI_EVENT_DATA_STRING, + UI_EVENT_DATA_INTEGER_VALUE, + UI_EVENT_DATA_STRING_VALUE, + UI_EVENT_DATA_TEXT_VALUE, + UI_EVENT_DATA_DOUBLE_VALUE, + UI_EVENT_DATA_RANGE_VALUE, + UI_EVENT_DATA_TEXT_CHANGED, + UI_EVENT_DATA_LIST_SELECTION, + UI_EVENT_DATA_LIST_ELM, + UI_EVENT_DATA_DND, + UI_EVENT_DATA_SUBLIST, + UI_EVENT_DATA_FILE_LIST, + UI_EVENT_DATA_TYPED_OBJECT +}; + struct UiEvent { - UiObject *obj; - void *document; - void *window; - void *eventdata; - int eventdatatype; - int intval; - int set; + UiObject *obj; + void *document; + void *window; + void *eventdata; + UiEventType eventdatatype; + int intval; + int set; }; struct UiMouseEvent { @@ -486,22 +506,11 @@ typedef struct UiCondVar { int intdata; } UiCondVar; -enum UiEventType { - UI_EVENT_DATA_NULL = 0, - UI_EVENT_DATA_POINTER, - UI_EVENT_DATA_STRING, - UI_EVENT_DATA_INTEGER_VALUE, - UI_EVENT_DATA_STRING_VALUE, - UI_EVENT_DATA_TEXT_VALUE, - UI_EVENT_DATA_DOUBLE_VALUE, - UI_EVENT_DATA_RANGE_VALUE, - UI_EVENT_DATA_TEXT_CHANGED, - UI_EVENT_DATA_LIST_SELECTION, - UI_EVENT_DATA_LIST_ELM, - UI_EVENT_DATA_DND, - UI_EVENT_DATA_SUBLIST, - UI_EVENT_DATA_FILE_LIST -}; +// eventdata in case eventdatatype is UI_EVENT_DATA_TYPED_OBJECT +typedef struct UiTypedObj { + void *ptr; + uint64_t type; +} UiTypedObj; #define UI_COLOR(r, g, b) (UiColor){r, g, b} struct UiColor { @@ -587,8 +596,9 @@ UIEXPORT void ui_add_action_with_accelerator( const char *accelerator, const char *accelerator_text); UIEXPORT void ui_update_action_bindings(UiContext *ctx); -UIEXPORT void ui_call_action(UiContext *ctx, const char *action_name); -UIEXPORT void ui_call_action2(UiContext *ctx, const char *action_name, void *eventdata, int intval); +UIEXPORT int ui_call_action(UiContext *ctx, const char *action_name); +UIEXPORT int ui_call_action2(UiContext *ctx, const char *action_name, void *eventdata, int intval); +UIEXPORT int ui_call_action3(UiContext *ctx, const char *action_name, void *ptr, uint64_t type_id); UIEXPORT void ui_broadcast_action(const char *action_name); UIEXPORT void ui_broadcast_action2(const char *action_name, void *eventdata, int intval); UIEXPORT void ui_mainthread_broadcast(const char *action_name);