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}