]> uap-core.de Git - note.git/commitdiff
implement UiModel derive main
authorOlaf Wintermann <olaf.wintermann@gmail.com>
Wed, 15 Apr 2026 15:36:49 +0000 (17:36 +0200)
committerOlaf Wintermann <olaf.wintermann@gmail.com>
Wed, 15 Apr 2026 15:36:49 +0000 (17:36 +0200)
application/src/main.rs
ui-rs-derive/src/lib.rs
ui-rs/src/ui/list.rs
ui-rs/src/ui/toolkit.rs
ui-rs/src/ui/window.rs

index a62fe81200f77ea7eff96f7a4b80f79414821077..2a12395e5a744576464521cddb06d156382ad477 100644 (file)
@@ -1,5 +1,5 @@
-use ui_rs::ui;
-use ui_rs::ui::{ListValue, UiList};
+use ui_rs::{ui, UiModel};
+use ui_rs::ui::{ListValue, UiList, UiModel};
 
 fn main() {
     ui::app_init("note");
@@ -10,32 +10,30 @@ fn main() {
 
 struct App;
 
+#[derive(UiModel, Default)]
 struct TestData {
     i: i32,
-    list: Option<UiList<i32>>
+    #[bind("list")]
+    list: UiList<i32>
 }
 
+
 fn create_window() {
-    let testdata = TestData { i: 0, list: None } ;
+    let testdata = TestData::default();
 
     let window = ui::window("note", testdata, |obj, data| {
-        let mut list = obj.ctx.list::<i32>();
-        let v = list.data();
-        v.push(10);
-        v.push(11);
-        v.push(12);
-
-
+        let list = data.list.data();
+        list.push(10);
+        list.push(11);
+        list.push(12);
 
         obj.button_builder().label("Hello").onclick(|e| {
             println!("Button clicked: {}", e.data.i);
             e.data.i += 1;
         }).create();
-        obj.listview_builder::<i32>().fill(true).value(&list).getvalue(|elm, _row, _col| {
+        obj.listview_builder::<i32>().fill(true).varname("list").getvalue(|elm, _row, _col| {
             ListValue::String(elm.to_string())
         }).create();
-
-        data.list = Some(list);
     });
 
     window.show();
index e0ab62ceefa8b70ed058f0007e2a4abf3da000ed..9f427d214dfc56c5af5901d38e3a8fe1038a08f9 100644 (file)
@@ -1,20 +1,69 @@
 use proc_macro::TokenStream;
 use quote::quote;
-use syn::{parse_macro_input, DeriveInput};
+use syn::{parse_macro_input, Data, DeriveInput, Expr, ExprLit, Lit, Meta};
 
-#[proc_macro_derive(ViewModel)]
-pub fn derive_view_model(input: TokenStream) -> TokenStream {
+#[proc_macro_derive(UiModel, attributes(bind))]
+pub fn derive_ui_model(input: TokenStream) -> TokenStream {
     let input = parse_macro_input!(input as DeriveInput);
 
     let name = input.ident;
 
+    let fields = match &input.data {
+        Data::Struct(data) => &data.fields,
+        _ => panic!("UiModel can only be derived for structs"),
+    };
+
+    let mut inits = Vec::new();
+
+    // Add initialization code for all fields that have the [bind] attribute
+    for field in fields {
+        let field_name = match &field.ident {
+            Some(ident) => ident,
+            None => panic!("UiModel fields must be named") // tupels are not supported
+        };
+
+        // find bind attribute
+        for attr in &field.attrs {
+            if !attr.path().is_ident("bind") {
+                continue;
+            }
+
+            match &attr.meta {
+                Meta::Path(_) => {
+                    // unnamed bind
+                    inits.push(quote! {
+                        self.#field_name.init(_ctx, None);
+                    });
+                },
+                Meta::List(list) => {
+                    // named bind
+                    let expr: Expr = list.parse_args().unwrap();
+                    match &expr {
+                        Expr::Lit(ExprLit { lit: Lit::Str(lit_str), .. }) => {
+                            let name = lit_str.value();
+                            inits.push(quote! {
+                                self.#field_name.init(_ctx, Some(#name));
+                            });
+                        },
+                        _ => {
+                            panic!("UiModel bind attribute must be a string literal")
+                        }
+                    }
+                }
+                _ => panic!("Unexpected Attribute"),
+            }
+        }
+    }
+
     let expanded = quote! {
-        impl ViewModel for #name {
+        impl UiModel for #name {
             fn init(&mut self, _ctx: &ui_rs::ui::UiContext) {
-                // empty for now
+                #(#inits)*
             }
         }
     };
 
+    //eprintln!("{}", expanded);
+
     TokenStream::from(expanded)
 }
\ No newline at end of file
index 045fef952c0d5b4363f8e4184d2e13bdad09f5f4..21ec4955390d1eda5af3ff53a4d80c3571c84908 100644 (file)
@@ -172,6 +172,13 @@ impl<'a, T, E> ListBuilder<'a, T, E> {
         self
     }
 
+    pub fn varname(&mut self, varname: &str) -> &mut Self {
+        let cstr = CString::new(varname).unwrap();
+        unsafe {
+            ui_list_args_set_varname(self.args, cstr.as_ptr());
+        }
+        self
+    }
     pub fn value<C>(&mut self, value: &toolkit::UiList<C>) -> &mut Self {
         unsafe {
             ui_list_args_set_value(self.args, value.ptr);
index d6b2fcee182813c31ff95127488bf71cc9cf4565..1e7abb4bcee493ca4dbbbf51a79bd3819fd39417 100644 (file)
@@ -24,6 +24,15 @@ impl Clone for UiContext {
     }
 }
 
+impl Default for UiContext {
+    fn default() -> Self {
+        UiContext {
+            ptr: std::ptr::null_mut(),
+            doc: std::ptr::null_mut()
+        }
+    }
+}
+
 impl Drop for UiContext {
     fn drop(&mut self) {
         if !self.doc.is_null() {
@@ -42,23 +51,16 @@ impl UiContext {
             doc: std::ptr::null_mut()
         }
     }
-    
-    pub fn list<T>(&self) -> UiList<T> {
-        let v: Vec<T> = Vec::new();
-        let b = Box::new(v);
-        let data = Box::into_raw(b);
 
-        unsafe {
-            UiList {
-                ptr: ui_list_new2(self.ptr, std::ptr::null_mut(), list_init::<T>, data as *mut c_void),
-                data: Box::from_raw(data as *mut Vec<T>)
-            }
-        }
+    pub fn list<T>(&self) -> UiList<T> {
+        let mut ls = UiList::<T>::default();
+        ls.init(self, None);
+        ls
     }
 }
 
-pub trait WindowModel {
-    fn init(&mut self, ctx: *const ffi::UiContext);
+pub trait UiModel {
+    fn init(&mut self, ctx: &UiContext);
 }
 
 pub struct UiObject<T> {
@@ -93,12 +95,49 @@ impl<T> UiList<T> {
     pub fn data(&mut self) -> &mut Vec<T> {
         self.data.as_mut()
     }
+
+    pub fn init(&mut self, ctx: &UiContext, name: Option<&str>) {
+        unsafe {
+            let c_string = name.map(|n| CString::new(n).unwrap());
+            let c_str = c_string.as_ref().map_or(std::ptr::null(), |s| s.as_ptr());
+            let data: *mut Vec<T> = &mut *self.data;
+            self.ptr = ui_list_new2(ctx.ptr, c_str, list_init::<T>, data as *mut c_void);
+        }
+    }
+}
+
+
+/* -------------------------------- Default implementation -------------------------------- */
+
+macro_rules! value_default_impl {
+    ($type_name:ident) => {
+        impl Default for $type_name {
+            fn default() -> Self {
+                Self { ptr: std::ptr::null_mut() }
+            }
+        }
+    };
+}
+
+value_default_impl!(UiInteger);
+value_default_impl!(UiDouble);
+value_default_impl!(UiString);
+value_default_impl!(UiText);
+
+impl<T> Default for UiList<T> {
+    fn default() -> Self {
+        Self {
+            ptr: std::ptr::null_mut(),
+            data: Box::new(Vec::new())
+        }
+    }
 }
 
+/* -------------------------------- C functions -------------------------------- */
 
 extern "C" {
     fn ui_init(appname: *const c_char, argc: c_int, argv: *const *const c_char);
-    
+
     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);
index a19569ee69d29db646e623c9297680a80f1daef6..a046e290e57d27831df90d70091ec96d9971d367 100644 (file)
@@ -4,7 +4,7 @@
 use std::ffi::{c_char, c_int, c_void};
 use std::ffi::CString;
 use std::marker::PhantomData;
-use crate::ui::{toolkit};
+use crate::ui::{toolkit, UiModel};
 use crate::ui::ffi::{UiContext, UiDestructor, UiObject};
 
 extern "C" {
@@ -56,7 +56,7 @@ enum WindowType {
     Simple
 }
 
-fn window_create<T, F>(title: &str, kind: WindowType, mut data: T, create_ui: F) -> toolkit::UiObject<T>
+fn window_create<T: UiModel, F>(title: &str, kind: WindowType, mut data: T, create_ui: F) -> toolkit::UiObject<T>
 where F: FnOnce(&mut toolkit::UiObject<T>, &mut T) {
     // create the window
     let objptr = unsafe {
@@ -80,6 +80,9 @@ where F: FnOnce(&mut toolkit::UiObject<T>, &mut T) {
         _data: PhantomData
     };
 
+    // init view model
+    data.init(&obj.ctx);
+
     // call ui building closure
     create_ui(&mut obj, &mut data);
 
@@ -92,25 +95,25 @@ where F: FnOnce(&mut toolkit::UiObject<T>, &mut T) {
     obj
 }
 
-pub fn window<T, F>(title: &str, data: T, create_ui: F) -> toolkit::UiObject<T>
+pub fn window<T: UiModel, F>(title: &str, data: T, create_ui: F) -> toolkit::UiObject<T>
 where F: FnOnce(&mut toolkit::UiObject<T>, &mut T)
 {
     window_create(title, WindowType::Standard, data, create_ui)
 }
 
-pub fn sidebar_window<T, F>(title: &str, data: T, create_ui: F) -> toolkit::UiObject<T>
+pub fn sidebar_window<T: UiModel, F>(title: &str, data: T, create_ui: F) -> toolkit::UiObject<T>
 where F: FnOnce(&mut toolkit::UiObject<T>, &mut T)
 {
     window_create(title, WindowType::Sidebar, data, create_ui)
 }
 
-pub fn splitview_window<T, F>(title: &str, sidebar: bool, data: T, create_ui: F) -> toolkit::UiObject<T>
+pub fn splitview_window<T: UiModel, F>(title: &str, sidebar: bool, data: T, create_ui: F) -> toolkit::UiObject<T>
 where F: FnOnce(&mut toolkit::UiObject<T>, &mut T)
 {
     window_create(title, WindowType::SplitView(sidebar), data, create_ui)
 }
 
-pub fn simple_window<T, F>(title: &str, data: T, create_ui: F) -> toolkit::UiObject<T>
+pub fn simple_window<T: UiModel, F>(title: &str, data: T, create_ui: F) -> toolkit::UiObject<T>
 where F: FnOnce(&mut toolkit::UiObject<T>, &mut T)
 {
     window_create(title, WindowType::Simple, data, create_ui)