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