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}