Native Windows GUI: Partials

With small applications, controls, resources and layouts can usually fit within a single UI struct. That won't always be the case. Native windows GUI partials are a way to extend GUI beyond a single structure. Partials are only a way to organize data; they do not use any kind of special methods.

Partials are self-contained. That means they shouldn't have access to their parent structure data.

Partials are fully supported by native-windows-derive. See the derive section to learn how to use partial derive.

PartialUi

Just like NativeUi, PartialUi is a trait that must be implemented on a UI struct. Here's the trait definition:

pub trait PartialUi<D> {
    fn build_partial<W: Into<ControlHandle>>(d: &mut D, parent: Option<W>) -> Result<(), NwgError>;
    fn process_event(&self, _evt: Event, _evt_data: &EventData, _handle: ControlHandle) {}
    fn handles<'a>(&'a self) -> Vec<&'a ControlHandle> { vec![] }
}

build_partial

build_partial is the meat of the partial. It initialize the resources, the controls, and the layouts. Optionally you can send a parent control to this method. For partial without top level window, this parent control will be the root.

process_event

process_event role is to process the NWG events. The method will be called inside a nwg callback and dispatch the callback the same way the event handlers works.

handles

handles is the easiest method to implement. It's role is to return the handle to the top level windows of the partial. If the partial does not have any window, it should return an empty array.

Implementing partials into NativeUi

First, lets look at a basic application structure:
extern crate native_windows_gui as nwg;

use nwg::{NativeUi, PartialUi};

#[derive(Default)]
pub struct MainUi {
    window: nwg::Window,
    form: SubmitForm,
}

#[derive(Default)]
pub struct SubmitForm {
    form_data: String,
    layout: nwg::GridLayout,
    value: nwg::TextInput,
    sumbit_button: nwg::Button
}

This example describes a basic application with a form. Forms are probably the best use case for UI partials. With this defined, it is time to implement the NWG UI traits.

First, lets implement PartialUi. Just because is simpler than the next step.
impl PartialUi for SubmitForm {

    fn build_partial<W: Into<nwg::ControlHandle>>(data: &mut SubmitForm, parent: Option<W>) -> Result<(), nwg::NwgError> {
        let parent = parent.unwrap().into();

        nwg::TextInput::builder()
            .text(&data.form_data)
            .parent(&parent)
            .build(&mut data.value)?;

        nwg::Button::builder()
            .text("Save")
            .parent(&parent)
            .build(&mut data.sumbit_button)?;

        nwg::GridLayout::builder()
            .child(0, 0, &data.value)
            .child(0, 1, &data.sumbit_button)
            .parent(&parent)
            .build(&data.layout)?;

        Ok(())
    }

    fn process_event<'a>(&self, evt: nwg::Event, _evt_data: &nwg::EventData, handle: nwg::ControlHandle) {
        use nwg::Event as E;

        match evt {
            E::OnButtonClick => 
                if &handle == &self.sumbit_button {
                    println!("PARTIAL EVENT!");
                },
            _ => {}
        }
    }

    fn handles(&self) -> Vec<&nwg::ControlHandle> {
        // No top level window in this partial
        Vec::new()
    }
}


There's nothing new there. In fact it is simpler than implementing NativeUi because there's no event binding.

Next, it is time to implement NwgUi on MainUi. If you've already gone through small application layout, this shouldn't be too hard:

// ... Imports & NwgUi definition ...

pub struct MainUiWrapper {
    inner: Rc<MainUi>,
    default_handler: RefCell<Vec<nwg::EventHandler>>
}

impl nwg::NativeUi<MainUiWrapper> for MainUi {
    fn build_ui(mut data: MainUi) -> Result<MainUiWrapper, nwg::NwgError> {
        nwg::Window::builder()
            .size((500, 500))
            .position((500, 300))
            .title("My Form")
            .build(&mut data.window)?;

        // !!! Partials controls setup !!!
        SubmitForm::build_partial(&mut data.form, Some(&data.window))?;

        let ui = MainUiWrapper {
            inner: Rc::new(data),
            default_handler: Default::default(),
        };

        // !!! Partials Event Binding !!!
        let mut window_handles = vec![&ui.window.handle];
        window_handles.append(&mut ui.form.handles());

        for handle in window_handles.iter() {
            let evt_ui = Rc::downgrade(&ui.inner);
            let handle_events = move |evt, evt_data, handle| {
                use nwg::Event as E;

                if let Some(ui) = evt_ui.upgrade() {

                    // !!! Partials Event Dispatch !!!
                    ui.form.process_event(evt, &evt_data, handle);

                    match evt {
                        E::OnButtonClick => 
                            if &handle == &ui.form.sumbit_button {
                                println!("SAVING!");
                            },
                        E::OnWindowClose => 
                            if &handle == &ui.window {
                                nwg::stop_thread_dispatch();
                            },
                        _ => {}
                    }
                }
            };

            ui.default_handler.borrow_mut().push(
                nwg::full_bind_event_handler(handle, handle_events)
            );
        }

        return Ok(ui);
    }
}


// .. Drop & Deref impl omitted 

I've tagged the important bits where the new partial API is called.

build_partial is called just after the base control creation. This way the parent (if any) is sure to have been initialized.

handles is probably the most complex one. Because we must bind an event handler for each top level window, we begin by storing the handles of the main UI in a Vec, and then, for each partials, the handles method is called and the result is added to the vector. Finally, we iterator over the handles and bind the event handler on each one. The EventHandler are stored in the wrapper struct.

process_event is called in the event handler. The events parameters are forwarded to the function.
Note that we can also reference the partial control in the base handler using this code: &handle == &ui.form.sumbit_button.

Examples