]> uap-core.de Git - note.git/commitdiff
add support for multiple app windows
authorOlaf Wintermann <olaf.wintermann@gmail.com>
Sun, 7 Jun 2026 11:53:07 +0000 (13:53 +0200)
committerOlaf Wintermann <olaf.wintermann@gmail.com>
Sun, 7 Jun 2026 11:53:07 +0000 (13:53 +0200)
application/src/main.rs
ui-rs/src/ui/application.rs
ui-rs/src/ui/toolbar.rs
ui/cocoa/list.m
ui/common/app.c
ui/common/app.h
ui/gtk/list.c
ui/gtk/list.h
ui/ui/toolkit.h

index 97aa3dda54f59a1edc425ebf5aaaf32db75f45c4..ef714dd864385451d14e0469315860f7a6b82130 100644 (file)
@@ -116,7 +116,10 @@ impl Application<MainWindow> for App {
         //});
 
         create_toolbar(app);
+        self.on_new_window(app);
+    }
 
+    fn on_new_window(&mut self, app: &AppContext<MainWindow>) {
         create_window(self, app);
     }
 }
@@ -127,10 +130,14 @@ fn create_toolbar(app: &AppContext<MainWindow>) {
     app.toolbar_item("go_forward").icon(UiIconSet::GoForward.as_str()).action("go_forward").create();
     app.toolbar_item("new_note").icon(UiIconSet::Add.as_str()).action("new_note").create();
 
-    app.toolbar_add_default("new_notebook", ToolbarItemPosition::SidebarRight);
+    app.toolbar_add_default("new_notebook", ToolbarItemPosition::SidebarLeft);
     app.toolbar_add_default("go_back", ToolbarItemPosition::Left);
     app.toolbar_add_default("go_forward", ToolbarItemPosition::Left);
     app.toolbar_add_default("new_note", ToolbarItemPosition::Left);
+
+    app.toolbar_appmenu(|menu|{
+        menu.item("New Window").onclick(|f| new_app_window() ).create();
+    });
 }
 
 
index df3aee089dcd55eb471eb4e1550b15f5e1c9cc4e..8da78c9d4c12285a3bba674ccf73f95878fb753a 100644 (file)
@@ -32,11 +32,13 @@ use std::ffi::{c_char, c_int, c_void, CString};
 use std::marker::PhantomData;
 use ui_rs_derive::UiModel;
 use crate::ui::ffi::{UiCallback, UiEvent};
-use crate::ui::{dialog, toolkit, ui_global_context, UiActions, UiContext, UiModel};
+use crate::ui::{call_mainthread, dialog, toolkit, ui_call_mainthread, ui_global_context, UiActions, UiContext, UiModel};
 
 pub trait Application<T: UiModel + UiActions> {
     fn on_startup(&mut self, app: &AppContext<T>);
 
+    fn on_new_window(&mut self, app: &AppContext<T>) {}
+
     fn on_exit(&mut self, _app: &AppContext<T>) { }
 }
 
@@ -60,7 +62,10 @@ extern "C" {
     fn ui_main();
 
     fn ui_onstartup(callback: UiCallback, userdata: *mut c_void);
+    fn ui_onnewwindow(callback: UiCallback, userdata: *mut c_void);
     fn ui_onexit(callback: UiCallback, userdata: *mut c_void);
+
+    fn ui_newwindow();
 }
 
 extern "C" fn app_startup<T: UiModel + UiActions>(_event: *const UiEvent, data: *mut c_void) {
@@ -76,6 +81,19 @@ extern "C" fn app_startup<T: UiModel + UiActions>(_event: *const UiEvent, data:
     }
 }
 
+extern "C" fn app_new_window<T: UiModel + UiActions>(_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 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);
+    }
+}
+
 extern "C" fn app_exit<T: UiModel + UiActions>(_event: *const UiEvent, data: *mut c_void) {
     unsafe {
         let app_ptr = data as *mut AppWrapper<T>;
@@ -95,12 +113,21 @@ pub fn app_run<T: UiModel + UiActions>(app: &mut dyn Application<T>) {
         let ptr: *mut AppWrapper<T> = &mut wrapper;
         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_main();
     }
 }
 
 
+pub fn new_app_window() {
+    unsafe {
+        call_mainthread(||{
+            ui_newwindow();
+        });
+    }
+}
+
 
 
 struct ErrApp<'a> {
index defdbab688d7e06183909d94d7c100061539211d..57e8f97d008758884b8c50db5076448889b71cf1 100644 (file)
@@ -68,7 +68,7 @@ impl<T: UiModel + UiActions> AppContext<T> {
         }
     }
 
-    pub fn toolbar_menuitem(&self, name: &str) -> ToolbarMenuItemBuilder<T> {
+    pub fn toolbar_menu(&self, name: &str) -> ToolbarMenuItemBuilder<T> {
         unsafe {
             ToolbarMenuItemBuilder {
                 args: ui_toolbar_menu_args_new(),
@@ -85,6 +85,22 @@ impl<T: UiModel + UiActions> AppContext<T> {
             ui_toolbar_add_default(cstr.as_ptr(), pos as c_int);
         }
     }
+
+    pub fn toolbar_appmenu<F>(&self, f: F)
+    where F: FnOnce(&mut Menu<T>) {
+        unsafe {
+            let args = ui_toolbar_menu_args_new();
+            ui_toolbar_menu_create(std::ptr::null(), args);
+            ui_toolbar_menu_args_free(args);
+
+            let mut menu = Menu::<T> {
+                ctx: toolkit::ui_global_context(),
+                _marker: PhantomData
+            };
+            f(&mut menu);
+            ui_menu_end();
+        }
+    }
 }
 
 pub struct ToolbarItemBuilder<T: UiModel + UiActions> {
@@ -243,9 +259,16 @@ impl<T: UiModel + UiActions> Drop for ToolbarMenuItemBuilder<T> {
 }
 
 impl<T: UiModel + UiActions> ToolbarMenuItemBuilder<T> {
-    pub fn create(&mut self) {
+    pub fn create<F>(&mut self, f: F)
+    where F: FnOnce(&mut Menu<T>) {
         unsafe {
             ui_toolbar_menu_create(self.name.as_ptr(), self.args);
+            let mut menu = Menu::<T> {
+                ctx: toolkit::ui_global_context(),
+                _marker: PhantomData
+            };
+            f(&mut menu);
+            ui_menu_end();
         }
     }
     pub fn label(&mut self, label: &str) -> &mut Self {
index bf6debac049efc415a7178af0ee02e6906cfbb69..132d84aa0244fce6f0e22baea0a6caa1ab2168a2 100644 (file)
@@ -215,6 +215,7 @@ UIWIDGET ui_table_create(UiObject* obj, UiListArgs *args) {
 void ui_tableview_update(UiList *list, int i) {
     NSTableView *tableview = (__bridge NSTableView*)list->obj;
     if(i < 0) {
+        [tableview deselectAll: tableview];
         [tableview reloadData];
     } else {
         [tableview reloadData]; // TODO: optimize
index 9674599fdd794e92a54da6e8405ef0a70425d078..e3bfd94037d4935355f18738df6ca465e32ab6b0 100644 (file)
@@ -30,6 +30,8 @@
 
 static ui_callback   startup_func;
 static void          *startup_data;
+static ui_callback   newwindow_func;
+static void          *newwindow_data;
 static ui_callback   open_func;
 void                 *open_data;
 static ui_callback   exit_func;
@@ -41,6 +43,11 @@ void ui_onstartup(ui_callback f, void *userdata) {
     startup_data = userdata;
 }
 
+void ui_onnewwindow(ui_callback f, void *userdata) {
+    newwindow_func = f;
+    newwindow_data = userdata;
+}
+
 void ui_onopen(ui_callback f, void *userdata) {
     open_func = f;
     open_data = userdata;
@@ -58,6 +65,12 @@ void uic_application_startup(UiEvent *event) {
     }
 }
 
+void uic_application_newwindow(UiEvent *event) {
+    if(newwindow_func) {
+        newwindow_func(event, newwindow_data);
+    }
+}
+
 void uic_application_open(UiEvent *event) {
     if(open_func) {
         open_func(event, open_data);
@@ -69,3 +82,7 @@ void uic_application_exit(UiEvent *event) {
         exit_func(event, exit_data);
     }
 }
+
+void ui_newwindow(void) {
+    uic_application_newwindow(NULL);
+}
index 02356a916e5fcc3e96d85b51e59bff838a32555b..9176e85f8f99e2466e9c8a068835cf41accea6d8 100644 (file)
@@ -36,6 +36,7 @@ extern "C" {
 #endif
 
 void uic_application_startup(UiEvent *event);
+void uic_application_newwindow(UiEvent *event);
 void uic_application_open(UiEvent *event);
 void uic_application_exit(UiEvent *event);
 
index 72146873a2f450e2dfb1c2a4a5626ec2a7693389..70ecc3b42b6a38698c6a845b733294538a414607 100644 (file)
@@ -1259,6 +1259,11 @@ static GtkListStore* create_list_store(UiListView *listview, UiList *list) {
     return store;
 }
 
+static void ui_destroy_tree_eventdata(GtkWidget *object, UiTreeEventData *data) {
+    free(data->activate_action);
+    free(data->selection_action);
+    free(data);
+}
 
 UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) {
     // create treeview
@@ -1316,22 +1321,23 @@ UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) {
     event->obj = obj;
     event->activate = args->onactivate;
     event->activatedata = args->onactivatedata;
+    event->activate_action = args->onactivate_action ? strdup(args->onactivate_action) : NULL;
     event->selection = args->onselection;
     event->selectiondata = args->onselectiondata;
+    event->selection_action = args->onselection_action ? strdup(args->onselection_action) : NULL;
     g_signal_connect(
             view,
             "destroy",
-            G_CALLBACK(ui_destroy_userdata),
+            G_CALLBACK(ui_destroy_tree_eventdata),
             event);
-    
-    if(args->onactivate) {
+    if(args->onactivate || args->onactivate_action) {
         g_signal_connect(
                 view,
                 "row-activated",
                 G_CALLBACK(ui_listview_activate_event),
                 event);
     }
-    if(args->onselection) {
+    if(args->onselection || args->onselection_action) {
         GtkTreeSelection *selection = gtk_tree_view_get_selection(
                 GTK_TREE_VIEW(view));
         g_signal_connect(
@@ -1543,20 +1549,27 @@ UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) {
     list->obj = tableview;
     
     // add callback
-    UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData));
+    UiTreeEventData *event = malloc(sizeof(UiTreeEventData));
     event->obj = obj;
     event->activate = args->onactivate;
-    event->selection = args->onselection;
     event->activatedata = args->onactivatedata;
+    event->activate_action = args->onactivate_action ? strdup(args->onactivate_action) : NULL;
+    event->selection = args->onselection;
     event->selectiondata = args->onselectiondata;
-    if(args->onactivate) {
+    event->selection_action = args->onselection_action ? strdup(args->onselection_action) : NULL;
+    g_signal_connect(
+            view,
+            "destroy",
+            G_CALLBACK(ui_destroy_tree_eventdata),
+            event);
+    if(args->onactivate || args->onactivate_action) {
         g_signal_connect(
                 view,
                 "row-activated",
                 G_CALLBACK(ui_listview_activate_event),
                 event);
     }
-    if(args->onselection) {
+    if(args->onselection || args->onselection_action) {
         GtkTreeSelection *selection = gtk_tree_view_get_selection(
                 GTK_TREE_VIEW(view));
         g_signal_connect(
@@ -1565,8 +1578,6 @@ UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) {
                 G_CALLBACK(ui_listview_selection_event),
                 event);
     }
-    // TODO: destroy callback
-    
     
     if(args->ondragstart) {
         ui_listview_add_dnd(tableview, args);
@@ -1798,13 +1809,18 @@ void ui_listview_activate_event(
     e.window = event->obj->window;
     e.document = event->obj->ctx->document;
     e.eventdata = &selection;
+    e.eventdatatype = UI_EVENT_DATA_LIST_SELECTION;
     e.intval = selection.count > 0 ? selection.rows[0] : -1;
     e.set = ui_get_setop();
-    event->activate(&e, event->activatedata);
     
-    if(selection.count > 0) {
-        free(selection.rows);
+    if(event->activate) {
+        event->activate(&e, event->activatedata);
+    }
+    if(event->activate_action) {
+        uic_action_callback(&e, event->activate_action);
     }
+    
+    free(selection.rows);
 }
 
 void ui_listview_selection_event(
@@ -1822,13 +1838,17 @@ void ui_listview_selection_event(
     e.window = event->obj->window;
     e.document = event->obj->ctx->document;
     e.eventdata = &selection;
+    e.eventdatatype = UI_EVENT_DATA_LIST_SELECTION;
     e.intval = selection.count > 0 ? selection.rows[0] : -1;
     e.set = ui_get_setop();
-    event->selection(&e, event->selectiondata);
-    
-    if(selection.count > 0) {
-        free(selection.rows);
+    if(event->selection) {
+        event->selection(&e, event->selectiondata);
     }
+    if(event->selection_action) {
+        uic_action_callback(&e, event->selection_action);
+    }
+    
+    free(selection.rows);
 }
 
 UiListSelection ui_listview_get_selection(
index d73ae560b96b7bc49ff02f2f5931b639f3c7740b..69f30e4d8f2c2d379e09dcc73cf25a22f754f42d 100644 (file)
@@ -106,6 +106,8 @@ typedef struct UiTreeEventData {
     ui_callback selection;
     void        *activatedata;
     void        *selectiondata;
+    char        *activate_action;
+    char        *selection_action;
 } UiTreeEventData;
 
 typedef struct UiListBox UiListBox;
index 901d30305f807b55ad34694558c1262ec5fadd2b..0dd606badf1dccfadefbdc9a7c6ce6fc081e2887 100644 (file)
@@ -545,9 +545,12 @@ UIEXPORT void ui_object_ref(UiObject *obj);
 UIEXPORT int ui_object_unref(UiObject *obj);
 
 UIEXPORT void ui_onstartup(ui_callback f, void *userdata);
+UIEXPORT void ui_onnewwindow(ui_callback f, void *userdata);
 UIEXPORT void ui_onopen(ui_callback f, void *userdata);
 UIEXPORT void ui_onexit(ui_callback f, void *userdata);
 
+UIEXPORT void ui_newwindow(void);
+
 UIEXPORT int ui_app_save_settings(void);
 UIEXPORT void ui_app_exit_on_shutdown(UiBool exitapp);