]> uap-core.de Git - note.git/commitdiff
add possibility to implement actions globally for the application
authorOlaf Wintermann <olaf.wintermann@gmail.com>
Sun, 7 Jun 2026 18:00:07 +0000 (20:00 +0200)
committerOlaf Wintermann <olaf.wintermann@gmail.com>
Sun, 7 Jun 2026 18:00:07 +0000 (20:00 +0200)
application/src/main.rs
ui-rs-derive/src/lib.rs
ui-rs/src/ui/application.rs
ui-rs/src/ui/toolkit.rs
ui/common/action.c
ui/ui/toolkit.h

index ef714dd864385451d14e0469315860f7a6b82130..f0d79e97f73b0ed5ecfd4427cb6233f7a73dff9f 100644 (file)
@@ -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::<MainWindow>(&mut app);
+    ui::app_run(&mut app);
 
     drop(app);
     let _ = join_handle.join();
@@ -124,6 +124,14 @@ impl Application<MainWindow> for App {
     }
 }
 
+#[application(MainWindow)]
+impl App {
+    #[action]
+    pub fn message(&mut self, _action: &ActionEvent) {
+        println!("app message handler");
+    }
+}
+
 fn create_toolbar(app: &AppContext<MainWindow>) {
     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();
index 5c6da1f5629fbb6cac386a4db323abdba7a1e6bc..fd4d011abc75e673dd5cb2741dbec052ba39ac5c 100644 (file)
@@ -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<Option<Type>> {
     for attr in attrs {
         if !attr.path().is_ident("action") {
index 8da78c9d4c12285a3bba674ccf73f95878fb753a..adbec18c5c40e6cb1dbb9a1cf39ff6230fbf8b7c 100644 (file)
@@ -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<T: UiModel + UiActions> {
+pub trait AppActions<T: UiModel + UiActions> {
+    fn init_actions(&mut self, _ctx: &UiContext) {}
+}
+
+pub trait Application<T: UiModel + UiActions>: AppActions<T> {
     fn on_startup(&mut self, app: &AppContext<T>);
 
     fn on_new_window(&mut self, app: &AppContext<T>) {}
@@ -68,53 +72,58 @@ extern "C" {
     fn ui_newwindow();
 }
 
-extern "C" fn app_startup<T: UiModel + UiActions>(_event: *const UiEvent, data: *mut c_void) {
+extern "C" fn app_startup<T: UiModel + UiActions, A: Application<T>>(_event: *const UiEvent, data: *mut c_void) {
     unsafe {
-        let app_ptr = data as *mut AppWrapper<T>;
-        let app_ref: &mut AppWrapper<T> = &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::<T> {
             ctx: global_ctx,
             _marker: PhantomData,
         };
-        app_ref.app.on_startup(&ctx);
+        app_ref.on_startup(&ctx);
     }
 }
 
-extern "C" fn app_new_window<T: UiModel + UiActions>(_event: *const UiEvent, data: *mut c_void) {
+extern "C" fn app_new_window<T: UiModel + UiActions, A: Application<T>>(_event: *const UiEvent, data: *mut c_void) {
     unsafe {
-        let app_ptr = data as *mut AppWrapper<T>;
-        let app_ref: &mut AppWrapper<T> = &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::<T> {
             ctx: global_ctx,
             _marker: PhantomData,
         };
-        app_ref.app.on_new_window(&ctx);
+        app_ref.on_new_window(&ctx);
     }
 }
 
-extern "C" fn app_exit<T: UiModel + UiActions>(_event: *const UiEvent, data: *mut c_void) {
+extern "C" fn app_exit<T: UiModel + UiActions, A: Application<T>>(_event: *const UiEvent, data: *mut c_void) {
     unsafe {
-        let app_ptr = data as *mut AppWrapper<T>;
-        let app_ref: &mut AppWrapper<T> = &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::<T> {
             ctx: global_ctx,
             _marker: PhantomData,
         };
-        app_ref.app.on_exit(&ctx);
+        app_ref.on_exit(&ctx);
     }
 }
 
-pub fn app_run<T: UiModel + UiActions>(app: &mut dyn Application<T>) {
-    let mut wrapper = AppWrapper { app: app };
+pub fn app_run<T, A>(app: &mut A)
+where
+    T: UiModel + UiActions,
+    A: Application<T>
+{
     unsafe {
-        let ptr: *mut AppWrapper<T> = &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::<T>), c_ptr);
-        ui_onnewwindow(Some(app_new_window::<T>), c_ptr);
-        ui_onexit(Some(app_exit::<T>), c_ptr);
+        ui_onstartup(Some(app_startup::<T, A>), c_ptr);
+        ui_onnewwindow(Some(app_new_window::<T, A>), c_ptr);
+        ui_onexit(Some(app_exit::<T, A>), c_ptr);
         ui_main();
     }
 }
@@ -145,12 +154,9 @@ impl<'a> Application<NoAppData> for ErrApp<'a> {
     }
 }
 
+impl AppActions<NoAppData> 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<T>,
-}
\ No newline at end of file
index 3e781ab20743af3655e1a0e2e45488c54593e89f..eec09d33a3f10557041669564a795d858fa5b8d2 100644 (file)
@@ -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<T, F>(&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();
index 5624c34384cc84328c9accaf1ee8d04f050d9d9c..fea640710cee12f605c17fcdefcd6f06a471447d 100644 (file)
@@ -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);
+}
index 0dd606badf1dccfadefbdc9a7c6ce6fc081e2887..cb06ae8ff8e7eeb23ad835b40abb7030ff02ed9a 100644 (file)
@@ -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);