native_windows_derive/
lib.rs

1extern crate proc_macro as pm;
2extern crate proc_macro2 as pm2;
3
4#[macro_use]
5extern crate syn;
6use syn::{DeriveInput, GenericParam, TypeParam, LifetimeDef};
7use syn::punctuated::Punctuated;
8
9#[macro_use]
10extern crate quote;
11
12use proc_macro_crate::crate_name;
13
14mod controls;
15mod events;
16mod layouts;
17mod shared;
18
19mod ui;
20use ui::NwgUi;
21
22
23struct BaseNames {
24    n_module: syn::Ident,
25    n_partial_module: syn::Ident,
26    n_struct: syn::Ident,
27    n_struct_ui: syn::Ident,
28}
29
30fn to_snake_case(s: &str) -> String {
31    let mut snake = String::with_capacity(s.len());
32
33    for (i, c) in s.char_indices() {
34        if c.is_ascii_uppercase() {
35            if i != 0 {
36                snake.push('_');
37            }
38            snake.push_str(c.to_lowercase().to_string().as_ref());
39        } else {
40            snake.push(c);
41        }
42    }
43
44    snake
45}
46
47fn parse_base_names(d: &DeriveInput) -> BaseNames {
48    let base_name = d.ident.to_string();
49    let module_name = format!("{}_ui", to_snake_case(&base_name));
50    let partial_module = format!("partial_{}_ui", to_snake_case(&base_name));
51    let struct_name = format!("{}Ui", &base_name);
52
53    BaseNames {
54        n_module: syn::Ident::new(&module_name, pm2::Span::call_site()),
55        n_partial_module: syn::Ident::new(&partial_module, pm2::Span::call_site()),
56        n_struct: syn::Ident::new(&base_name, pm2::Span::call_site()),
57        n_struct_ui: syn::Ident::new(&struct_name, pm2::Span::call_site()),
58    }
59}
60
61fn parse_ui_data(d: &DeriveInput) -> Option<&syn::DataStruct> {
62    match &d.data {
63        syn::Data::Struct(ds) => Some(ds),
64        _ => None
65    }
66}
67
68/// Extract generic names from definition.
69/// It is useful to erase definition and generate `impl<T: Trait1> Struct<T> {...}` tokens.
70///
71/// For example `<'a: 'b, T: Trait1, const C: usize = 10>` becomes `<'a, T, C>`
72fn extract_generic_names(generics: &Punctuated<GenericParam, Token![,]>) -> Punctuated<GenericParam, Token![,]> {
73    let mut generic_names: Punctuated<GenericParam, Token![,]> = Punctuated::new();
74    for generic_param in generics {
75        let ident = match generic_param {
76            GenericParam::Type(t) => GenericParam::Type(TypeParam::from(t.ident.clone())),
77            GenericParam::Lifetime(l) => GenericParam::Lifetime(LifetimeDef::new(l.lifetime.clone())),
78            GenericParam::Const(c) => GenericParam::Type(TypeParam::from(c.ident.clone())), // a little hack
79        };
80        generic_names.push(ident);
81    }
82    generic_names
83}
84
85/**
86
87The `NwgUi` macro implements the native-windows-gui `NativeUi` trait on the selected struct
88
89For a detailed documentation of this macro see the documentation "native-windows-docs/nwd_basics.html"
90
91
92# Usage
93
94```rust
95use native_windows_gui as nwg;
96
97#[derive(NwgUi, Default)]
98pub struct BasicApp {
99    #[nwg_control(title: "Window")]
100    #[nwg_events( OnWindowClose: [nwg::stop_thread_dispatch()] )]
101    window: nwg::Window,
102
103    #[nwg_resource(family: "Arial")]
104    font: nwg::Font,
105
106    #[nwg_layout(parent: window)]
107    my_layout: nwg::GridLayout,
108
109    #[nwg_control(text: "Button")]
110    #[nwg_layout_item(layout: my_layout, col: 0, row: 0)]
111    button: nwg::Button,
112}
113
114// ...
115
116let my_ui = BasicAppUi::build_ui(Default::default()).unwrap();
117```
118
119The macro creates a new struct named `[StructName]Ui` in a submodule named `[struct_name]_ui`.
120
121The trait `NativeUi` is implemented on this struct and the boilerplate code is generated for every field tagged by attributes.
122Fields without attributes, even `nwg` types, are left untouched.
123
124Finally, the derive macro also creates a default event handler that will live through the ui struct lifetime. 
125
126
127# Attributes usage
128
129Actual UI creation works by tagging the struct fields with the some attributes
130
131## Controls
132
133Use the `nwg_control` attribute to instance a control from a struct field:
134
135```
136nwg_control(builder_field: builder_value,*)
137```
138
139This syntax is basically a compressed version of the nwg control builders. The control attribute
140also has built-in helpers: auto parent detection and compressed flags syntax (see the docs for more info on these features).
141
142```
143#[nwg_control(text: "Heisenberg", size: (280, 25), position: (10, 10))]
144name_edit: nwg::TextInput,
145
146// is the same as 
147
148nwg::TextInput::builder()
149    .text("Heisenberg")
150    .size((280, 25))
151    .position((10, 10))
152    .build(&mut data.text_edit);
153```
154
155## Resources
156
157Use the `nwg_resource` to generate a resource from a struct field. It works the exact same way as `nwg_controls`. 
158Resources are always instanced before the controls.
159
160## Events
161
162Use the `nwg_events` attribute to add events to the default event handler. Events can only be applied to a field that
163was tagged with `nwg_control`.
164
165```
166nwg_events( EVENT_TYPE: [CALLBACK(ARGS),*] )
167```
168
169where:
170 - **EVENT_TYPE** is any value of the Event enum.
171 - **CALLBACK** is the function that will be called when the event is triggered.
172 - **ARGS** specifies the parameters of the callback (optional).
173
174## Events arguments
175
176By default, native windows derive assumes the callback is a method of the Ui structure. So for example, 
177`TestApp::callback1` assumes the method has the following signature `callback1(&self)`.
178
179That's very limiting. For example, if the same callback is used by two different controls, there's no way to differenciate them. In order to fix this, NWD lets you define the callbacks parameters using those identifiers:
180
181 - **SELF**: Sends the ui struct `&UiStruct`. If there are no parameters, this is the default.
182 - **RC_SELF**: Sends the rc ui struct `&Rc<UiStruct>`. Useful for binding dynamic events
183 - **CTRL**: Sends the control that triggered the event. Ex: `&Button`
184 - **HANDLE**: Sends the handle of the control. `&ControlHandle`
185 - **EVT**: Sends the event that was triggered. `&Event`
186 - **EVT_DATA**: Sends the data of the event that was triggered. `&EventData`
187
188It's also possible to not use any parameters, ex: `TestApp::callback1()`. 
189
190Different event types:
191
192```
193struct TestApp {
194    #[nwg_control]
195    #[nwg_events(
196        OnButtonClick: [TestApp::callback1, TestApp::callback2],
197        OnMouseMove: [TestApp::callback3(SELF, CTRL)],
198        OnButtonDoubleClick: [callback, another_callback()]
199    )]
200    button: nwg::Button
201}
202
203fn callback(me: &TestApp) {}
204fn another_callback() {}
205
206impl TestApp {
207    fn callback1(&self) { }
208    fn callback2(&self) { }
209    fn callback3(&self, ctrl: &nwg::Button) { }
210}
211```
212
213## Layouts
214
215Use the `nwg_layout` attribute to instance a layout from a struct field and `nwg_layout_item` to associate a control to a layout.
216
217Under the hood, both these attribute work the same way as `nwg_control`. `nwg_layout` uses the builder attribute for a the layout struct and
218`nwg_layout_item` uses the parameters of the item type of the parent (ex: `GridLayoutItem` for `GridLayout`).
219
220NWD cannot guess the parent of layout items.
221
222## Partials
223
224Use the `nwg_partial` attribute to instance a partial from a struct field:
225
226If parts of your UI is another struct that implements the `PartialUi` trait, it can be easily included in your base UI using `nwg_partial`.
227The attribute accepts an optional parameter "parent" to pass a parent control to the partial initializer. Unlike the parent in `nwg_controls`,
228it must be explicitly defined.
229
230nwg_partial works by calling `PartialUi::build_partial` after initializing the controls of the base UI, calling `PartialUi::process_event` in the default event handler,
231and binds the default handler to the handles returned by `PartialUi::handles`
232
233Also see `NwgPartial` for the macro to generate a nwg partial.
234
235```
236struct Ui {
237    window: nwg::Window,
238
239    #[nwg_partial(parent: window)]
240    partial: MyPartial
241}
242```
243
244*/
245#[proc_macro_derive(NwgUi, attributes(nwg_control, nwg_resource, nwg_events, nwg_layout, nwg_layout_item, nwg_partial))]
246pub fn derive_ui(input: pm::TokenStream) -> pm::TokenStream {
247    let base = parse_macro_input!(input as DeriveInput);
248    let names = parse_base_names(&base);
249    let ui_data = parse_ui_data(&base).expect("NWG derive can only be implemented on structs");
250
251    let module_name = &names.n_module;
252    let struct_name = &names.n_struct;
253    let ui_struct_name = &names.n_struct_ui;
254
255    let lt = &base.generics.lt_token;
256    let generic_params = &base.generics.params;
257    let generic_names = extract_generic_names(generic_params);
258    let gt = &base.generics.gt_token;
259    let where_clause = &base.generics.where_clause;
260
261    let generics = quote! { #lt #generic_params #gt }; // <'a: 'b, T: Trait1, const C>
262    let generic_names = quote! { #lt #generic_names #gt }; // <'a, T, C>
263
264    let ui = NwgUi::build(&ui_data, false);
265    let controls = ui.controls();
266    let resources = ui.resources();
267    let partials = ui.partials();
268    let layouts = ui.layouts();
269    let events = ui.events();
270
271    let nwg_name = crate_name("native-windows-gui");
272
273    // Returns an error in the examples, so we try a default value
274    let nwg = match nwg_name {
275        Ok(name) => syn::Ident::new(&name, proc_macro2::Span::call_site()),
276        Err(_) => syn::Ident::new("native_windows_gui", proc_macro2::Span::call_site()),   
277    };
278
279    let derive_ui = quote! {
280        mod #module_name {
281            extern crate #nwg as nwg;
282            use nwg::*;
283            use super::*;
284            use std::ops::Deref;
285            use std::cell::RefCell;
286            use std::rc::Rc;
287            use std::fmt;
288
289            pub struct #ui_struct_name #generics #where_clause {
290                inner: Rc<#struct_name #generic_names>,
291                default_handlers: RefCell<Vec<EventHandler>>
292            }
293
294            impl #generics NativeUi<#ui_struct_name #generic_names> for #struct_name #generic_names #where_clause {
295                fn build_ui(mut data: Self) -> Result<#ui_struct_name #generic_names, NwgError> {
296                    #resources
297                    #controls
298                    #partials
299
300                    let inner = Rc::new(data);
301                    let ui = #ui_struct_name { inner: inner.clone(), default_handlers: Default::default() };
302
303                    #events
304                    #layouts
305                    
306                    Ok(ui)
307                }
308            }
309
310            impl #generics Drop for #ui_struct_name #generic_names #where_clause {
311                /// To make sure that everything is freed without issues, the default handler must be unbound.
312                fn drop(&mut self) {
313                    let mut handlers = self.default_handlers.borrow_mut();
314                    for handler in handlers.drain(0..) {
315                        nwg::unbind_event_handler(&handler);
316                    }
317                }
318            }
319
320            impl #generics Deref for #ui_struct_name #generic_names #where_clause {
321                type Target = #struct_name #generic_names;
322
323                fn deref(&self) -> &Self::Target {
324                    &self.inner
325                }
326            }
327
328            impl #generics fmt::Debug for #ui_struct_name #generic_names #where_clause {
329                fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330                    write!(f, "[#ui_struct_name Ui]")
331                }
332            }
333        }
334    };
335
336    pm::TokenStream::from(derive_ui)
337}
338
339
340/**
341The `NwgPartial` macro implements the native-windows-gui `PartialUi` trait on the selected struct
342
343`NwgPartial` accepts the same attributes as `NwgUi`. See the docs of the `NwgUi` trait for detailed usage. There are some particularities though:
344
345 - Partials cannot be used by independently. They must be included in a UI that implements `NwgUi`. 
346 - Partials do not require a top level window. If no window is defined, the partial will require a parent value passed from the `nwg_partial` attribute
347 - It's possible to derive both `NwgUi` and `NwgPartial` from the same struct as long as the partial do not need a parent.
348 - Partials can contains other partials
349
350```
351#[derive(Default, NwgPartial)]
352pub struct MyPartial {
353  partial_data: u32,
354
355  #[nwg_control]
356  button: nwg::Button
357}
358
359#[derive(Default, NwgUi)]
360pub struct MyApp {
361   app_data: u32,
362
363   #[nwg_control]
364   #[nwg_events( OnInit: [hello], OnWindowClose: [nwg::stop_thread_dispatch()] )]
365   window: nwg::Window,
366
367   #[nwg_partial(parent: window)]
368   partial: MyPartial
369}
370```
371
372*/
373#[proc_macro_derive(NwgPartial, attributes(nwg_control, nwg_resource, nwg_events, nwg_layout, nwg_layout_item, nwg_partial))]
374pub fn derive_partial(input: pm::TokenStream) -> pm::TokenStream {
375    let base = parse_macro_input!(input as DeriveInput);
376
377    let names = parse_base_names(&base);
378
379    let partial_name = &names.n_partial_module;
380    let struct_name = &names.n_struct;
381
382    let lt = &base.generics.lt_token;
383    let generic_params = &base.generics.params;
384    let generic_names = extract_generic_names(generic_params);
385    let gt = &base.generics.gt_token;
386    let where_clause = &base.generics.where_clause;
387
388    let generics = quote! { #lt #generic_params #gt }; // <'a: 'b, T: Trait1, const C>
389    let generic_names = quote! { #lt #generic_names #gt }; // <'a, T, C>
390
391    let ui_data = parse_ui_data(&base).expect("NWG derive can only be implemented on structs");
392    let ui = NwgUi::build(&ui_data, true);
393    let controls = ui.controls();
394    let resources = ui.resources();
395    let partials = ui.partials();
396    let layouts = ui.layouts();
397    let events = ui.events();
398
399    let nwg_name = crate_name("native-windows-gui");
400    
401    // Returns an error in the examples, so we try a default value
402    let nwg = match nwg_name {
403        Ok(name) => syn::Ident::new(&name, proc_macro2::Span::call_site()),
404        Err(_) => syn::Ident::new("native_windows_gui", proc_macro2::Span::call_site()),   
405    };
406
407    let partial_ui = quote! {
408        mod #partial_name {
409            extern crate #nwg as nwg;
410            use nwg::*;
411            use super::*;
412        
413            impl #generics PartialUi for #struct_name #generic_names #where_clause {
414
415                #[allow(unused)]
416                fn build_partial<W: Into<ControlHandle>>(data: &mut Self, _parent: Option<W>) -> Result<(), NwgError> {
417                    let parent = _parent.map(|p| p.into());
418                    let parent_ref = parent.as_ref();
419                    
420                    #resources
421                    #controls
422                    #partials
423
424                    let ui = data;
425                    #layouts
426                    Ok(())
427                }
428
429                fn process_event<'a>(&self, _evt: Event, _evt_data: &EventData, _handle: ControlHandle) {
430                    #events
431                }
432
433                fn handles(&self) -> Vec<&ControlHandle> {
434                    Vec::new()
435                }
436            }
437        }
438    };
439
440    pm::TokenStream::from(partial_ui)
441}