Skip to content

Commit d2b6c99

Browse files
committed
Add more control over event loop
1 parent a17a4c7 commit d2b6c99

File tree

7 files changed

+162
-47
lines changed

7 files changed

+162
-47
lines changed

iui/examples/inputs.rs

Lines changed: 30 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ fn main() {
5050
(input_group, slider, spinner, entry, multi)
5151
};
5252

53-
// Set up the outputs for the application.
53+
// Set up the outputs for the application. Organization is very similar to the
54+
// previous setup.
5455
let (output_group, add_label, sub_label, text_label, bigtext_label) = {
5556
let output_group = Group::new(&ui, "Outputs");
5657
let output_vbox = VerticalBox::new(&ui);
@@ -76,66 +77,52 @@ fn main() {
7677
window.set_child(&ui, hbox);
7778
window.show(&ui);
7879

79-
// This update_view function is defined inline so that it can capture the environment, rather than
80-
// needing to be passed ui, the labels, and the state each time it is invoked.
81-
// It is wrapped in a refcounted pointer so it can be shared with several other closures.
82-
let update_view = Rc::new({
83-
let ui = ui.clone();
84-
let add_label = add_label.clone();
85-
let sub_label = sub_label.clone();
86-
let state = state.clone();
87-
move || {
88-
let state = state.borrow();
89-
add_label.set_text(&ui, &format!("Added: {}", state.slider_val + state.spinner_val));
90-
sub_label.set_text(&ui, &format!("Subtracted: {}", state.slider_val - state.spinner_val));
91-
text_label.set_text(&ui, &format!("Text: {}", state.entry_val));
92-
bigtext_label.set_text(&ui, &format!("Multiline Text: {}", state.multi_val));
93-
}
94-
});
95-
9680
// These on_changed functions allow updating the application state when a
9781
// control changes its value.
98-
// Note the calls to update_view after each change to the state!
9982

10083
slider.on_changed(&ui, {
101-
let update_view = update_view.clone();
10284
let state = state.clone();
103-
move |val| {
104-
state.borrow_mut().slider_val = val;
105-
update_view();
106-
}
85+
move |val| { state.borrow_mut().slider_val = val; }
10786
});
10887

10988
spinner.on_changed(&ui, {
110-
let update_view = update_view.clone();
11189
let state = state.clone();
112-
move |val| {
113-
state.borrow_mut().spinner_val = val;
114-
update_view();
115-
}
90+
move |val| { state.borrow_mut().spinner_val = val; }
11691
});
11792

11893
entry.on_changed(&ui, {
119-
let update_view = update_view.clone();
12094
let state = state.clone();
121-
move |val| {
122-
state.borrow_mut().entry_val = val;
123-
update_view();
124-
}
95+
move |val| { state.borrow_mut().entry_val = val; }
12596
});
12697

12798
multi.on_changed(&ui, {
128-
let update_view = update_view.clone();
12999
let state = state.clone();
130-
move |val| {
131-
state.borrow_mut().multi_val = val;
132-
update_view();
133-
}
100+
move |val| { state.borrow_mut().multi_val = val; }
134101
});
135102

136-
// The initial call to update_view sets up the initial GUI state.
137-
update_view();
138103

139-
// Start the application.
140-
ui.main();
104+
// This update_view function is defined inline so that it can capture the environment, rather than
105+
// needing to be passed ui, the labels, and the state each time it is invoked.
106+
// It is defined last so that it can operate with the minimum number of `.clone()` calls (since
107+
// the code won't need to access the controls after this).
108+
// It is wrapped in a refcounted pointer so it can be shared with several other closures.
109+
let update_view = Rc::new({
110+
let ui = ui.clone();
111+
move || {
112+
let state = state.borrow();
113+
114+
// Update all the labels
115+
add_label.set_text(&ui, &format!("Added: {}", state.slider_val + state.spinner_val));
116+
sub_label.set_text(&ui, &format!("Subtracted: {}", state.slider_val - state.spinner_val));
117+
text_label.set_text(&ui, &format!("Text: {}", state.entry_val));
118+
bigtext_label.set_text(&ui, &format!("Multiline Text: {}", state.multi_val));
119+
}
120+
});
121+
122+
// Rather than just invoking ui.run(), using EventLoop gives a lot more control
123+
// over the user interface event loop.
124+
// Here, the on_tick() callback is used to update the view against the state.
125+
let mut event_loop = ui.event_loop();
126+
event_loop.on_tick(&ui, move || { update_view(); });
127+
event_loop.run(&ui);
141128
}

iui/src/controls/entry.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ impl TextEntry for Entry {
137137
fn value(&self, _ctx: &UI) -> String {
138138
unsafe { CStr::from_ptr(ui_sys::uiEntryText(self.uiEntry)).to_string_lossy().into_owned() }
139139
}
140-
fn set_value(&self, ctx: &UI, value: &str) {
140+
fn set_value(&self, _ctx: &UI, value: &str) {
141141
let cstring = CString::new(value.as_bytes().to_vec()).unwrap();
142142
unsafe { ui_sys::uiEntrySetText(self.uiEntry, cstring.as_ptr()) }
143143
}

iui/src/ffi_tools.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Utilities to manage the state of the interface to the libUI bindings.
22
use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT};
3+
use std::mem;
4+
use libc::c_void;
35

46
static INITIALIZED: AtomicBool = ATOMIC_BOOL_INIT;
57

@@ -30,3 +32,8 @@ pub unsafe fn unset_initialized() {
3032
pub fn is_initialized() -> bool {
3133
INITIALIZED.load(Ordering::SeqCst)
3234
}
35+
36+
// Transmute a void-void callback into a Box<Box<FnMut()>>> and call it
37+
pub extern "C" fn void_void_callback(data: *mut c_void) {
38+
unsafe { mem::transmute::<*mut c_void, Box<Box<FnMut()>>>(data)() }
39+
}

iui/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
//! underlying library.
1212
//!
1313
//! After initialization, all the functionality used for creating actual UIs is in the [`controls`](controls/index.html) module.
14+
//!
15+
//! Fine-grained control of the event loop is avilable via the [`EventLoop`](struct.EventLoop.html) struct.
1416
1517
#[macro_use]
1618
extern crate bitflags;
@@ -24,7 +26,7 @@ mod error;
2426
mod ffi_tools;
2527
pub mod controls;
2628

27-
pub use ui::UI;
29+
pub use ui::{UI, EventLoop};
2830
pub use error::UIError;
2931

3032
/// Common imports are packaged into this module. It's meant to be glob-imported: `use iui::prelude::*`.

iui/src/ui.rs

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
use ui_sys;
22
use error::UIError;
33
use ffi_tools;
4+
use libc::{c_void, c_int};
45

56
use std::rc::Rc;
67
use std::marker::PhantomData;
78
use std::ffi::CStr;
89
use std::mem;
10+
use std::thread::sleep;
11+
use std::time::Duration;
912

1013
use controls::Window;
1114

@@ -89,13 +92,125 @@ impl UI {
8992
}
9093
}
9194

92-
/// Hands control of this thread to the UI toolkit, allowing it to display the UI and respond to events. Does not return until the UI [quit](struct.UI.html#method.quit)s.
95+
/// Hands control of this thread to the UI toolkit, allowing it to display the UI and respond to events.
96+
/// Does not return until the UI [quit](struct.UI.html#method.quit)s.
97+
///
98+
/// For more control, use the `EventLoop` struct.
9399
pub fn main(&self) {
94-
unsafe { ui_sys::uiMain() }
100+
self.event_loop().run(self);
101+
}
102+
103+
/// Returns an `EventLoop`, a struct that allows you to step over iterations or events in the UI.
104+
pub fn event_loop(&self) -> EventLoop {
105+
unsafe { ui_sys::uiMainSteps() };
106+
return EventLoop { _pd: PhantomData, callback: None }
95107
}
96108

97109
/// Running this function causes the UI to quit, exiting from [main](struct.UI.html#method.main) and no longer showing any widgets.
110+
///
111+
/// Run in every window's default `on_closing` callback.
98112
pub fn quit(&self) {
99113
unsafe { ui_sys::uiQuit() }
100114
}
115+
116+
/// Add a callback to the UI queue. These callbacks are run when the UI main() method is called,
117+
/// in the order in which they were queued.
118+
///
119+
/// # Example
120+
///
121+
/// ```
122+
/// use iui::prelude::*;
123+
///
124+
/// let ui = UI::init().unwrap();
125+
///
126+
/// // Let the UI exit immediately
127+
/// ui.quit();
128+
///
129+
/// ui.queue_main(|| { println!("Runs first") } );
130+
/// ui.queue_main(|| { println!("Runs second") } );
131+
/// ```
132+
pub fn queue_main<F: FnMut()>(&self, callback: F) {
133+
unsafe {
134+
let mut data: Box<Box<FnMut()>> = Box::new(Box::new((callback)));
135+
ui_sys::uiQueueMain(
136+
ffi_tools::void_void_callback,
137+
&mut *data as *mut Box<FnMut()> as *mut c_void,
138+
);
139+
mem::forget(data);
140+
}
141+
}
142+
143+
/// Set a callback to be run when the application quits.
144+
pub fn on_should_quit<F: FnMut()>(&self, callback: F) {
145+
unsafe {
146+
let mut data: Box<Box<FnMut()>> = Box::new(Box::new(callback));
147+
ui_sys::uiOnShouldQuit(
148+
ffi_tools::void_void_callback,
149+
&mut *data as *mut Box<FnMut()> as *mut c_void,
150+
);
151+
mem::forget(data);
152+
}
153+
}
154+
}
155+
156+
/// Provides fine-grained control over the user interface event loop, exposing the `on_tick` event
157+
/// which allows integration with other event loops, custom logic on event ticks, etc.
158+
pub struct EventLoop {
159+
// This PhantomData prevents UIToken from being Send and Sync
160+
_pd: PhantomData<*mut ()>,
161+
// This callback gets run during "run_delay" loops.
162+
callback: Option<Box<FnMut()>>
163+
}
164+
165+
impl EventLoop {
166+
/// Set the given callback to run when the event loop is executed.
167+
/// Note that if integrating other event loops you should consider
168+
/// the potential benefits and drawbacks of the various run modes.
169+
pub fn on_tick<F: FnMut() + 'static>(&mut self, _ctx: &UI, callback: F) {
170+
self.callback = Some(Box::new(callback));
171+
}
172+
173+
/// Executes a tick in the event loop, returning immediately.
174+
/// The `on_tick` callback is executed after the UI step.
175+
///
176+
/// Returns `true` if the application should continue running, and `false`
177+
/// if it should quit.
178+
pub fn next_tick(&mut self, _ctx: &UI) -> bool {
179+
let result = unsafe { ui_sys::uiMainStep(false as c_int) == 1 };
180+
if let Some(ref mut c) = self.callback {
181+
c();
182+
}
183+
result
184+
}
185+
186+
/// Hands control to the event loop until the next UI event occurs.
187+
/// The `on_tick` callback is executed after the UI step.
188+
///
189+
/// Returns `true` if the application should continue running, and `false`
190+
/// if it should quit.
191+
pub fn next_event_tick(&mut self, _ctx: &UI) -> bool {
192+
let result = unsafe { ui_sys::uiMainStep(true as c_int) == 1 };
193+
if let Some(ref mut c) = self.callback {
194+
c();
195+
}
196+
result
197+
}
198+
199+
/// Hands control to the event loop until [`UI::quit()`](struct.UI.html#method.quit) is called,
200+
/// running the callback given with `on_tick` after each UI event.
201+
pub fn run(&mut self, ctx: &UI) {
202+
loop {
203+
if !self.next_event_tick(ctx) { break; }
204+
}
205+
}
206+
207+
/// Hands control to the event loop until [`UI::quit()`](struct.UI.html#method.quit) is called,
208+
/// running the callback given with `on_tick` approximately every
209+
/// `delay` milliseconds.
210+
pub fn run_delay(&mut self, ctx: &UI, delay_ms: u32) {
211+
loop {
212+
if !self.next_tick(ctx) { break; }
213+
}
214+
sleep(Duration::new(0, delay_ms * 1000000))
215+
}
101216
}

ui-sys/src/ffi.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ extern {
1717
pub fn uiFreeInitError(err: *const c_char);
1818

1919
pub fn uiMain();
20+
pub fn uiMainStep(wait: c_int) -> c_int;
21+
pub fn uiMainSteps();
2022
pub fn uiQuit();
2123

2224
pub fn uiQueueMain(f: extern "C" fn(data: *mut c_void), data: *mut c_void);

ui-sys/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ extern {
2525
pub fn uiFreeInitError(err: *const c_char);
2626

2727
pub fn uiMain();
28+
pub fn uiMainStep(wait: c_int) -> c_int;
29+
pub fn uiMainSteps();
2830
pub fn uiQuit();
2931

3032
pub fn uiQueueMain(f: extern "C" fn(data: *mut c_void), data: *mut c_void);

0 commit comments

Comments
 (0)