Getting started

In this first chapter, you will install NWG and then look at a very simple application. This tutorial will use both native-windows-derive (the procedural macro to generate GUI's from rust structs) and the plain native-window-gui api. The next chapter "Basics" will go deeper into these.

Installing NWG

NWG should work with any recent version of rust (64 bits or 32 bits). Any channel can be used (stable, beta, nightly). NWG was mainly tested using the MSVC ABI builds. on both stable and nightly
To use NWG in your project add it to cargo.toml:
[dependencies]
native-windows-gui = "1.0.12"
native-windows-derive = "1.0.3"
And then, in main.rs or lib.rs :
extern crate native_windows_gui as nwg;
extern crate native_windows_derive as nwd;

Hello World!

Here is a minimalistic program that display a window with a text input and a button. When the button is clicked, a messagebox that says "Hello {your name}!" is created. This first example use native-windows-derive, scroll further down to see the plain API version.
/*!
    A very simple application that show your name in a message box.
    See `basic` for the version without the derive macro
*/


extern crate native_windows_gui as nwg;
extern crate native_windows_derive as nwd;

use nwd::NwgUi;
use nwg::NativeUi;


#[derive(Default, NwgUi)]
pub struct BasicApp {
    #[nwg_control(size: (300, 115), position: (300, 300), title: "Basic example", flags: "WINDOW|VISIBLE")]
    #[nwg_events( OnWindowClose: [BasicApp::say_goodbye] )]
    window: nwg::Window,

    #[nwg_control(text: "Heisenberg", size: (280, 25), position: (10, 10))]
    name_edit: nwg::TextInput,

    #[nwg_control(text: "Say my name", size: (280, 60), position: (10, 40))]
    #[nwg_events( OnButtonClick: [BasicApp::say_hello] )]
    hello_button: nwg::Button
}

impl BasicApp {

    fn say_hello(&self) {
        nwg::simple_message("Hello", &format!("Hello {}", self.name_edit.text()));
    }
    
    fn say_goodbye(&self) {
        nwg::simple_message("Goodbye", &format!("Goodbye {}", self.name_edit.text()));
        nwg::stop_thread_dispatch();
    }

}

fn main() {
    nwg::init().expect("Failed to init Native Windows GUI");

    let _app = BasicApp::build_ui(Default::default()).expect("Failed to build UI");

    nwg::dispatch_thread_events();
}

The example in details

Basic project structure

Native-windows-gui uses structures to represent a UI interface. Controls are represent as members. It's that simple.

Each control is a thin wrapper over a window handle (HWND). For more complex controls (such as listboxes), the control may contains extra data. All controls implements the Default trait. Default-built controls are considered uninitialized. Calling their methods will cause the program to panic.

Ui actions (displaying a message box in this case) should be implemented as structure methods. That is in no way a hard rule though. Any type of functions can be used. It just wraps nicely with the way the derive macro handles the events.

Native windows derive

By deriving NwgUi, Native-windows-derive can automatically generate the Ui code. The derive macro has no way to actually know how to actually create the controls, so more information must be provided over the fields.

nwg_control specifies the control parameters. The actual syntax is ident: expr. The parameters names maps directly to the control builder field (yes NWG uses builders to create controls, more on that later). Check a specific control documentation to see which parameters are available.

In this example, absolute positioning is used. Ex: position: (300, 300). This is ok for this example because the window cannot be resized, but NWG also supports layouts.

nwg_events specifies the events that the control responds to. It take an array of EVENT_NAME: [callbacks]. Note that this is only the default way to dispatch events. nwg_events can be further customized to match all your callback needs. Again, check a specific control documentation to see which events can be raised.

The derive macro has other functions that are out of scope for this introduction, see the native-windows-derive section for more information.

Initialization

Native windows GUI needs to initialize a few things before being usable. This includes styles and loading some control classes. The initialization done by calling nwg::init(). This function should not fail, but if it does, it's because the host system is really broken. In such case, the only thing left to do is to panic. Note that some helper functions (such as nwg::simple_message) can still be called.

The next thing to do is to initialize your application. As mentionned in the previous step, this means instantiating your struct. native-windows-derive implements the NativeUi trait, which gives you the build_ui function. The build_ui function takes a single argument: the initial state of your application (aka your UI struct default state). Because this application is very simple, there's no user state to initialize, so we just pass Default::default at let native-windows-derive do its magic.

Finally, the only thing left is to start listening for events on the thread. This is done by calling nwg::dispatch_thread_events(). This function will block the thread until nwg::stop_thread_dispatch() is called. NWG does not provide a default handler on window close, so you will have to manually bind the `OnWindowClose` event

Now lets looks a the inner workings of NWG in the next section...

Hello World! (no macro)

Here is the same example without the macro.
/*!
    A very simple application that show your name in a message box.
    See `basic_d` for the derive version
*/

extern crate native_windows_gui as nwg;
use nwg::NativeUi;


#[derive(Default)]
pub struct BasicApp {
    window: nwg::Window,
    name_edit: nwg::TextInput,
    hello_button: nwg::Button
}

impl BasicApp {

    fn say_hello(&self) {
        nwg::simple_message("Hello", &format!("Hello {}", self.name_edit.text()));
    }
    
    fn say_goodbye(&self) {
        nwg::simple_message("Goodbye", &format!("Goodbye {}", self.name_edit.text()));
        nwg::stop_thread_dispatch();
    }

}

//
// ALL of this stuff is handled by native-windows-derive
//
mod basic_app_ui {
    use native_windows_gui as nwg;
    use super::*;
    use std::rc::Rc;
    use std::cell::RefCell;
    use std::ops::Deref;

    pub struct BasicAppUi {
        inner: Rc<BasicApp>,
        default_handler: RefCell<Option<nwg::EventHandler>>
    }

    impl nwg::NativeUi<BasicAppUi> for BasicApp {
        fn build_ui(mut data: BasicApp) -> Result<BasicAppUi, nwg::NwgError> {
            use nwg::Event as E;
            
            // Controls
            nwg::Window::builder()
                .flags(nwg::WindowFlags::WINDOW | nwg::WindowFlags::VISIBLE)
                .size((300, 115))
                .position((300, 300))
                .title("Basic example")
                .build(&mut data.window)?;

            nwg::TextInput::builder()
                .size((280, 25))
                .position((10, 10))
                .text("Heisenberg")
                .parent(&data.window)
                .focus(true)
                .build(&mut data.name_edit)?;

            nwg::Button::builder()
                .size((280, 60))
                .position((10, 40))
                .text("Say my name")
                .parent(&data.window)
                .build(&mut data.hello_button)?;

            // Wrap-up
            let ui = BasicAppUi {
                inner: Rc::new(data),
                default_handler: Default::default(),
            };

            // Events
            let evt_ui = Rc::downgrade(&ui.inner);
            let handle_events = move |evt, _evt_data, handle| {
                if let Some(ui) = evt_ui.upgrade() {
                    match evt {
                        E::OnButtonClick => 
                            if &handle == &ui.hello_button {
                                BasicApp::say_hello(&ui);
                            },
                        E::OnWindowClose => 
                            if &handle == &ui.window {
                                BasicApp::say_goodbye(&ui);
                            },
                        _ => {}
                    }
                }
            };

           *ui.default_handler.borrow_mut() = Some(nwg::full_bind_event_handler(&ui.window.handle, handle_events));

            return Ok(ui);
        }
    }

    impl Drop for BasicAppUi {
        /// To make sure that everything is freed without issues, the default handler must be unbound.
        fn drop(&mut self) {
            let handler = self.default_handler.borrow();
            if handler.is_some() {
                nwg::unbind_event_handler(handler.as_ref().unwrap());
            }
        }
    }

    impl Deref for BasicAppUi {
        type Target = BasicApp;

        fn deref(&self) -> &BasicApp {
            &self.inner
        }
    }
}

fn main() {
    nwg::init().expect("Failed to init Native Windows GUI");
    nwg::Font::set_global_family("Segoe UI").expect("Failed to set default font");
    let _ui = BasicApp::build_ui(Default::default()).expect("Failed to build UI");
    nwg::dispatch_thread_events();
}

The implementation in details

Native UI

Implementing NativeUI is the recommended way to create native-windows-gui application. This example is very close to what native-windows-derive outputs. As mentioned in the previous section, all of the code logic is in build_ui.

For clarity, the method is separated in 3 steps: Controls, Wrap-up, and Events.

Controls

Native windows controls are built with builders. Each controls can instance a builder using the nwg::CONTROL::builder() method. Default values and builder parameters are available in the documentation.

The builder build function does not return the new control, instead you must pass a mutable reference to the function. This allows further customization of controls using DerefMut shenanigans.

The control build can fail. End the builder call with a ? to propagate the error to the caller.

Regarding controls, Native-windows-derive includes a few features that improves quality of life.

Wrap up

To keep things clean, a new structure called [Control]Ui wraps the inner application data.

Before going further, the application data needs to be Rc'ed. This is because the application data needs to be shared between the events callbacks.

This is the way of doing things is used because it is the safest and simplest way to handle callbacks.

The ui wrapper also contains an handle to the default event handler. More on that in the next section.

Events

Okay. You might look at the way NWG handles events and tell yourself "wtf is this". Don't stress out. It might be different from how other GUI platform handle events, but in the end, this way of doing things wraps very nicely with both Rust and the way events are dispatched in Windows.

full_bind_event_handler(parent_handle, callback)

In Winapi, events hooking can be done by "subclassing" a window. This is exactly what full_bind_event_handler do (with lots, lots of safety guards).

Every time an event is raised by the parent or it's children, the callback will be called. Behind the scene, NWG will carefully parse the raw winapi parameters and return three simple values: an event Event, it's data (if any) EventData, and the sender window handle ControlHandle.

You can see that there are no way to fetch our application data from these parameters. That's why the application data (now behind a RC) is cloned and passed to the closure. A WeakRef is used here so that Drop can be called.

Inside the callback, the developer needs to do three things:
Note that full_bind_event_handler applies to the parent and all its children. This means you only need to bind handlers on TOP LEVEL window. Lastly, the handler returned by full_bind_event_handler is saved in the wrapper struct under default_handler.

Freeing

Events handlers are not automatically unbound on drop. it must be done using nwg::unbind_event_handler(&handler)

Because we use a WeakRef in our event handler, the ui struct only has 1 strong refcount. This way we can unbind the event handler in the drop method.

And that is all for events handling. Thankfully, native-windows-derive handles all of this for us!

End note

Right now, you've learned about 50% of NWG. That's enough to build small applications, but there's two intermediate topics that you will want to learn: layouts, and resources handling (fonts, image). For larger applications you will want to look at more advanced topics such as UI partials, dynamic control creation, dynamic event binding (and unbinding), and multithreading.

Good luck with your UI projects. Hopefully, NWG will be a good fit.