From: Olaf Wintermann Date: Wed, 15 Apr 2026 15:36:49 +0000 (+0200) Subject: implement UiModel derive X-Git-Url: https://uap-core.de/gitweb/?a=commitdiff_plain;h=refs%2Fheads%2Fmain;p=note.git implement UiModel derive --- diff --git a/application/src/main.rs b/application/src/main.rs index a62fe81..2a12395 100644 --- a/application/src/main.rs +++ b/application/src/main.rs @@ -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> + #[bind("list")] + list: UiList } + 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::(); - 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::().fill(true).value(&list).getvalue(|elm, _row, _col| { + obj.listview_builder::().fill(true).varname("list").getvalue(|elm, _row, _col| { ListValue::String(elm.to_string()) }).create(); - - data.list = Some(list); }); window.show(); diff --git a/ui-rs-derive/src/lib.rs b/ui-rs-derive/src/lib.rs index e0ab62c..9f427d2 100644 --- a/ui-rs-derive/src/lib.rs +++ b/ui-rs-derive/src/lib.rs @@ -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 diff --git a/ui-rs/src/ui/list.rs b/ui-rs/src/ui/list.rs index 045fef9..21ec495 100644 --- a/ui-rs/src/ui/list.rs +++ b/ui-rs/src/ui/list.rs @@ -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(&mut self, value: &toolkit::UiList) -> &mut Self { unsafe { ui_list_args_set_value(self.args, value.ptr); diff --git a/ui-rs/src/ui/toolkit.rs b/ui-rs/src/ui/toolkit.rs index d6b2fce..1e7abb4 100644 --- a/ui-rs/src/ui/toolkit.rs +++ b/ui-rs/src/ui/toolkit.rs @@ -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(&self) -> UiList { - let v: Vec = 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::, data as *mut c_void), - data: Box::from_raw(data as *mut Vec) - } - } + pub fn list(&self) -> UiList { + let mut ls = UiList::::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 { @@ -93,12 +95,49 @@ impl UiList { pub fn data(&mut self) -> &mut Vec { 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 = &mut *self.data; + self.ptr = ui_list_new2(ctx.ptr, c_str, list_init::, 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 Default for UiList { + 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); diff --git a/ui-rs/src/ui/window.rs b/ui-rs/src/ui/window.rs index a19569e..a046e29 100644 --- a/ui-rs/src/ui/window.rs +++ b/ui-rs/src/ui/window.rs @@ -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(title: &str, kind: WindowType, mut data: T, create_ui: F) -> toolkit::UiObject +fn window_create(title: &str, kind: WindowType, mut data: T, create_ui: F) -> toolkit::UiObject where F: FnOnce(&mut toolkit::UiObject, &mut T) { // create the window let objptr = unsafe { @@ -80,6 +80,9 @@ where F: FnOnce(&mut toolkit::UiObject, &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, &mut T) { obj } -pub fn window(title: &str, data: T, create_ui: F) -> toolkit::UiObject +pub fn window(title: &str, data: T, create_ui: F) -> toolkit::UiObject where F: FnOnce(&mut toolkit::UiObject, &mut T) { window_create(title, WindowType::Standard, data, create_ui) } -pub fn sidebar_window(title: &str, data: T, create_ui: F) -> toolkit::UiObject +pub fn sidebar_window(title: &str, data: T, create_ui: F) -> toolkit::UiObject where F: FnOnce(&mut toolkit::UiObject, &mut T) { window_create(title, WindowType::Sidebar, data, create_ui) } -pub fn splitview_window(title: &str, sidebar: bool, data: T, create_ui: F) -> toolkit::UiObject +pub fn splitview_window(title: &str, sidebar: bool, data: T, create_ui: F) -> toolkit::UiObject where F: FnOnce(&mut toolkit::UiObject, &mut T) { window_create(title, WindowType::SplitView(sidebar), data, create_ui) } -pub fn simple_window(title: &str, data: T, create_ui: F) -> toolkit::UiObject +pub fn simple_window(title: &str, data: T, create_ui: F) -> toolkit::UiObject where F: FnOnce(&mut toolkit::UiObject, &mut T) { window_create(title, WindowType::Simple, data, create_ui)