native_windows_derive2/
lib.rs

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