From 88f538101282751edf0e2cfd963893a31ddef4b0 Mon Sep 17 00:00:00 2001 From: Olaf Wintermann Date: Sun, 7 Jun 2026 20:00:07 +0200 Subject: [PATCH] add possibility to implement actions globally for the application --- application/src/main.rs | 12 +++++-- ui-rs-derive/src/lib.rs | 65 ++++++++++++++++++++++++++++++++++++- ui-rs/src/ui/application.rs | 54 ++++++++++++++++-------------- ui-rs/src/ui/toolkit.rs | 5 +++ ui/common/action.c | 63 ++++++++++++++++++++++++++++++++--- ui/ui/toolkit.h | 2 ++ 6 files changed, 169 insertions(+), 32 deletions(-) diff --git a/application/src/main.rs b/application/src/main.rs index ef714dd..f0d79e9 100644 --- a/application/src/main.rs +++ b/application/src/main.rs @@ -34,7 +34,7 @@ mod note; use std::env; use entity::collection::{create_notebook_hierarchy, Node}; -use ui_rs::{ui}; +use ui_rs::{action, application, ui}; use ui_rs::ui::*; use crate::backend::{Backend, BackendHandle}; use crate::window::*; @@ -65,7 +65,7 @@ fn main() { backend: backend_handle, notebooks: notebooks, }; - ui::app_run::(&mut app); + ui::app_run(&mut app); drop(app); let _ = join_handle.join(); @@ -124,6 +124,14 @@ impl Application for App { } } +#[application(MainWindow)] +impl App { + #[action] + pub fn message(&mut self, _action: &ActionEvent) { + println!("app message handler"); + } +} + fn create_toolbar(app: &AppContext) { app.toolbar_item("new_notebook").icon(UiIconSet::NewFolder.as_str()).action("new_notebook").create(); app.toolbar_item("go_back").icon(UiIconSet::GoBack.as_str()).action("go_back").create(); diff --git a/ui-rs-derive/src/lib.rs b/ui-rs-derive/src/lib.rs index 5c6da1f..fd4d011 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, Type}; +use syn::{parse_macro_input, Attribute, Data, DeriveInput, Expr, ExprLit, GenericArgument, ImplItem, ItemImpl, Lit, Meta, PathArguments, Type}; #[proc_macro_derive(UiModel, attributes(bind))] pub fn derive_ui_model(input: TokenStream) -> TokenStream { @@ -208,6 +208,69 @@ pub fn ui_actions(_attr: TokenStream, item: TokenStream) -> TokenStream { TokenStream::from(expanded) } +/// Generates an implementation of [`Application`] for an `impl` block containing +/// methods marked with the `#[action]` attribute. +#[proc_macro_attribute] +pub fn application(attr: TokenStream, item: TokenStream) -> TokenStream { + let window_ty = parse_macro_input!(attr as syn::Type); + let impl_block = parse_macro_input!(item as ItemImpl); + 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 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_for_target( + app, + 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 AppActions<#window_ty> for #self_ty { + fn init_actions(&mut self, ctx: &UiContext) { + let app: *mut Self = self; + unsafe { + #( + ctx.add_action_for_target( + app, + stringify!(#handlers), + |target: &mut Self, event| { + target.#handlers(event); + } + ); + )*; + #(#typed_actions)* + } + } + } + }; + + TokenStream::from(expanded) +} + + fn has_handler_attr(attrs: &[Attribute]) -> Option> { for attr in attrs { if !attr.path().is_ident("action") { diff --git a/ui-rs/src/ui/application.rs b/ui-rs/src/ui/application.rs index 8da78c9..adbec18 100644 --- a/ui-rs/src/ui/application.rs +++ b/ui-rs/src/ui/application.rs @@ -34,7 +34,11 @@ use ui_rs_derive::UiModel; use crate::ui::ffi::{UiCallback, UiEvent}; use crate::ui::{call_mainthread, dialog, toolkit, ui_call_mainthread, ui_global_context, UiActions, UiContext, UiModel}; -pub trait Application { +pub trait AppActions { + fn init_actions(&mut self, _ctx: &UiContext) {} +} + +pub trait Application: AppActions { fn on_startup(&mut self, app: &AppContext); fn on_new_window(&mut self, app: &AppContext) {} @@ -68,53 +72,58 @@ extern "C" { fn ui_newwindow(); } -extern "C" fn app_startup(_event: *const UiEvent, data: *mut c_void) { +extern "C" fn app_startup>(_event: *const UiEvent, data: *mut c_void) { unsafe { - let app_ptr = data as *mut AppWrapper; - let app_ref: &mut AppWrapper = &mut *app_ptr; + let app_ptr = data as *mut A; + let app_ref: &mut A = &mut *app_ptr; let global_ctx = toolkit::UiContext::from_ptr(ui_global_context()); let ctx = AppContext:: { ctx: global_ctx, _marker: PhantomData, }; - app_ref.app.on_startup(&ctx); + app_ref.on_startup(&ctx); } } -extern "C" fn app_new_window(_event: *const UiEvent, data: *mut c_void) { +extern "C" fn app_new_window>(_event: *const UiEvent, data: *mut c_void) { unsafe { - let app_ptr = data as *mut AppWrapper; - let app_ref: &mut AppWrapper = &mut *app_ptr; + let app_ptr = data as *mut A; + let app_ref: &mut A = &mut *app_ptr; let global_ctx = toolkit::UiContext::from_ptr(ui_global_context()); let ctx = AppContext:: { ctx: global_ctx, _marker: PhantomData, }; - app_ref.app.on_new_window(&ctx); + app_ref.on_new_window(&ctx); } } -extern "C" fn app_exit(_event: *const UiEvent, data: *mut c_void) { +extern "C" fn app_exit>(_event: *const UiEvent, data: *mut c_void) { unsafe { - let app_ptr = data as *mut AppWrapper; - let app_ref: &mut AppWrapper = &mut *app_ptr; + let app_ptr = data as *mut A; + let app_ref: &mut A = &mut *app_ptr; let global_ctx = toolkit::UiContext::from_ptr(ui_global_context()); let ctx = AppContext:: { ctx: global_ctx, _marker: PhantomData, }; - app_ref.app.on_exit(&ctx); + app_ref.on_exit(&ctx); } } -pub fn app_run(app: &mut dyn Application) { - let mut wrapper = AppWrapper { app: app }; +pub fn app_run(app: &mut A) +where + T: UiModel + UiActions, + A: Application +{ unsafe { - let ptr: *mut AppWrapper = &mut wrapper; + let ctx = UiContext::from_ptr(ui_global_context()); + app.init_actions(&ctx); + let ptr: *mut A = app; let c_ptr: *mut c_void = ptr as *mut c_void; - ui_onstartup(Some(app_startup::), c_ptr); - ui_onnewwindow(Some(app_new_window::), c_ptr); - ui_onexit(Some(app_exit::), c_ptr); + ui_onstartup(Some(app_startup::), c_ptr); + ui_onnewwindow(Some(app_new_window::), c_ptr); + ui_onexit(Some(app_exit::), c_ptr); ui_main(); } } @@ -145,12 +154,9 @@ impl<'a> Application for ErrApp<'a> { } } +impl AppActions for ErrApp<'_> {} + pub fn app_run_startup_error(title: &str, message: &str) { let mut app = ErrApp { title, message }; app_run(&mut app); } - -#[repr(C)] -struct AppWrapper<'a, T> { - app: &'a mut dyn Application, -} \ No newline at end of file diff --git a/ui-rs/src/ui/toolkit.rs b/ui-rs/src/ui/toolkit.rs index 3e781ab..eec09d3 100644 --- a/ui-rs/src/ui/toolkit.rs +++ b/ui-rs/src/ui/toolkit.rs @@ -107,6 +107,11 @@ impl UiContext { assert!(!obj_wdata_ptr.is_null()); obj_wdata_ptr as *mut T }; + self.add_action_for_target(target_ptr, name, f); + } + + pub unsafe fn add_action_for_target(&self, target_ptr: *mut T, name: &str, f: F) + where F: FnMut(&mut T, &event::ActionEvent) + 'static { let wrapper = Box::new(ActionEventWrapper { callback: Box::new(f), target: target_ptr }); let ptr = self.reg_box(wrapper); let cstr = CString::new(name).unwrap(); diff --git a/ui/common/action.c b/ui/common/action.c index 5624c34..fea6407 100644 --- a/ui/common/action.c +++ b/ui/common/action.c @@ -180,21 +180,57 @@ int ui_call_action3(UiContext *ctx, const char *action_name, void *ptr, uint64_t return ret; } -void ui_broadcast_action(const char *action_name) { - ui_broadcast_action2(action_name, NULL, 0); +static void call_action_recursive(const char *action_name, UiContext *ctx, void *eventdata, UiEventType eventdatatype, int intval, UiBool call_children) { + UiEvent event; + memset(&event, 0, sizeof(UiEvent)); + event.obj = ctx->obj; + event.window = event.obj ? event.obj->window : NULL; + event.document = ctx->self_doc ? ctx->self_doc : ctx->document; + if(eventdata) { + event.eventdata = eventdata; + event.eventdatatype = eventdatatype; + } + event.intval = intval; + + UiAction *a = NULL; + if(ctx->actions) { + a = cxMapGet(ctx->actions, action_name); + } + if(a && a->callback) { + a->callback(&event, a->userdata); + } + + if(call_children) { + CxIterator i = cxListIterator(ctx->documents); + cx_foreach(void *, doc, i) { + UiContext *doc_ctx = ui_document_context(doc); + call_action_recursive(action_name, doc_ctx, eventdata, eventdatatype, intval, TRUE); + } + } } -void ui_broadcast_action2(const char *action_name, void *eventdata, int intval) { +static void broadcast_action(const char *action_name, void *eventdata, UiEventType eventdatatype, int intval) { CxList *objects = uic_object_list(); CxIterator i = cxListIterator(objects); cx_foreach(UiObject*, obj, i) { - ui_call_action2(obj->ctx, action_name, eventdata, intval); + call_action_recursive(action_name, obj->ctx, eventdata, eventdatatype, intval, TRUE); } + + call_action_recursive(action_name, ui_global_context(), eventdata, eventdatatype, intval, FALSE); +} + +void ui_broadcast_action(const char *action_name) { + broadcast_action(action_name, NULL, UI_EVENT_DATA_NULL, 0); +} + +void ui_broadcast_action2(const char *action_name, void *eventdata, int intval) { + broadcast_action(action_name, eventdata, UI_EVENT_DATA_POINTER, intval); } typedef struct UiActionBroadcast { char *action; void *eventdata; + UiEventType eventdatataype; int intval; } UiActionBroadcast; @@ -204,7 +240,10 @@ void ui_mainthread_broadcast(const char *action_name) { static int mainthread_action_broadcast(void *data) { UiActionBroadcast *broadcast = data; - ui_broadcast_action2(broadcast->action, broadcast->eventdata, broadcast->intval); + broadcast_action(broadcast->action, broadcast->eventdata, broadcast->eventdatataype, broadcast->intval); + if(broadcast->eventdatataype == UI_EVENT_DATA_TYPED_OBJECT) { + free(broadcast->eventdata); + } free(broadcast->action); free(broadcast); return 0; @@ -214,6 +253,20 @@ void ui_mainthread_broadcast2(const char *action_name, void *eventdata, int intv UiActionBroadcast *broadcast = malloc(sizeof(UiActionBroadcast)); broadcast->action = strdup(action_name); broadcast->eventdata = eventdata; + broadcast->eventdatataype = UI_EVENT_DATA_POINTER; broadcast->intval = intval; ui_call_mainthread(mainthread_action_broadcast, broadcast); } + +void ui_mainthread_broadcast3(const char *action_name, void *ptr, uint64_t type_id) { + UiTypedObj *obj = malloc(sizeof(UiTypedObj)); + obj->ptr = ptr; + obj->type = type_id; + + UiActionBroadcast *broadcast = malloc(sizeof(UiActionBroadcast)); + broadcast->action = strdup(action_name); + broadcast->eventdata = obj; + broadcast->eventdatataype = UI_EVENT_DATA_TYPED_OBJECT; + broadcast->intval = 0; + ui_call_mainthread(mainthread_action_broadcast, broadcast); +} diff --git a/ui/ui/toolkit.h b/ui/ui/toolkit.h index 0dd606b..cb06ae8 100644 --- a/ui/ui/toolkit.h +++ b/ui/ui/toolkit.h @@ -604,8 +604,10 @@ UIEXPORT int ui_call_action2(UiContext *ctx, const char *action_name, void *even 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_broadcast_action3(const char *action_name, void *ptr, uint64_t type_id); UIEXPORT void ui_mainthread_broadcast(const char *action_name); UIEXPORT void ui_mainthread_broadcast2(const char *action_name, void *eventdata, int intval); +UIEXPORT void ui_mainthread_broadcast3(const char *action_name, void *ptr, uint64_t type_id); UIEXPORT void ui_set_state(UiContext *ctx, int state); UIEXPORT void ui_unset_state(UiContext *ctx, int state); -- 2.52.0