Crates.io | gtk-rs-state |
lib.rs | gtk-rs-state |
version | 0.4.1 |
source | src |
created_at | 2020-01-25 23:27:52.017769 |
updated_at | 2020-12-12 20:53:06.078912 |
description | Allows modifying gtk-rs widgets from other threads. |
homepage | |
repository | https://github.com/andrewdavidmackenzie/gtk-rs-state |
max_upload_size | |
id | 201986 |
size | 48,370 |
Runs on stable.
This is not the fastest implementation, but for almost all use cases this should be enough.
[dependencies]
gtk-rs-state = "0.3"
Using this boils down to
Don't call do_in_gtk_eventloop() in the main thread since this will block.
gtk_refs!(
pub mod widgets; // The macro emits a new module with this name
struct WidgetRefs; // The macro emits a struct with this name containing:
main_window : gtk::Window , // widget_name : Widgettype
button1 : gtk::Button // ..
);
fn main() {
if gtk::init().is_err() {
println!("Failed to initialize GTK.");
return;
}
let window = Window::new(WindowType::Toplevel);
window.set_title("gtk-rs-state Example Program");
window.set_default_size(350, 70);
let button = Button::new_with_label("Spawn another thread!");
window.add(&button);
window.show_all();
window.connect_delete_event(|_, _| {
gtk::main_quit();
Inhibit(false)
});
button.connect_clicked(|_| {
std::thread::spawn(some_workfunction);
println!("Clicked!");
});
// You need the following two statements to prepare the
// static storage needed for cross thread access.
// See the `from_glade.rs` example for a more elegant solution
let widget_references = widgets::WidgetRefs {
main_window: window.clone(),
button1: button.clone(),
};
widgets::init_storage(widget_references);
// End
// This type has a function for each of your widgets.
// These functions return a clone() of the widget.
window.show_all();
window.connect_delete_event(move |_, _| {
gtk::main_quit();
Inhibit(false)
});
// Start event loop
gtk::main();
}
fn compute() {
use std::thread::sleep;
use std::time::Duration;
sleep(Duration::from_secs(1));
}
fn some_workfunction() {
let mut i = 0;
loop {
compute();
i += 1;
let text = format!("Round {} in {:?}", i, std::thread::current().id());
widgets::do_in_gtk_eventloop(|refs| {
refs.button1().set_label(&text);
});
}
}
The macro generates the following code:
pub mod widgets {
pub struct WidgetRefs {
pub main_window : gtk::Window,
...
}
impl From<>k::Builder> for WidgetRefs { ... };
impl WidgetRefs {
fn main_window() -> gtk::Window { } // returns a .clone() of the widget
...
}
pub fn init_storage(WidgetRefs);
pub fn init_storage_from_builder(>k::Builder);
pub fn do_in_gtk_eventloop( FnOnce(Rc<WidgetRefs>) );
}
do_in_gtk_eventloop(closure)
is executed on the gtk event loop via glib::idle_add()
.do_in_gtk_eventloop()
does wait until the closure has run.do_in_gtk_eventloop()
will panic as well. You may not see the panic because the process usually exits too fast.Please also see the examples folder if you want to: - Use additional non-send fields in the struct (other stuff than widget references) - Use glade
+---------------------------------+ +----------------------------------+
|GTK event loop thread | |Global statics |
| | | |
| +----------------------------+ | | TX : Sender<(Fn, Cb)> |
| |Thread local statics | | | |
| | | | | |
| | DATA : Non-Send Refereces | | +----------------------------------+
| | RX : Receiver<(Fn, Cb)> | |
| | | | +----------------------------------+
| +----------------------------+ | |Some other thread |
| | | |
| | | do some stuff |
| +---------------------------+ | | |
| |event loop() | | | call do_in_gtk_eventloop(Fn) |
| | | | | This Fn has access to DATA |
| | +----------------------+ | | | |
| | |closure added with | | | | |
| | |idle_add() to execute | | | +----------------------------------+
| | |on the gtk thread { | | |
| | | <-----------------------------------------------+
| | | Pop (Fn,Cb) from RX | | | |
| | | Call Fn(DATA) | | | |
| | | Signal end of Fn | | | +----------------------------------+ |
| | | via Cb | | | |do_in_gtk_eventloop(Fn) | |
| | | | | | | | |
| | | | | | | Box closure Fn and transmute | |
| | +----------------------+ | | | livetime to 'static | |
| | | | | | |
| +---------------------------+ | | Create a signal Cb | |
| | | Push (boxed Fn, Cb) to TX | |
+---------------------------------+ | Add this closure via idle_add() +---+
| Wait for the signal Cb |
| return |
| |
| |
+----------------------------------+
init_storage()
initializes DATA, RX and TX.
unsafe
There is one usage of unsafe which is for convenience only. It allows the closure to reference the local stack instead of requiring 'static on the closure.
You can easily remove the unsafe, but then you are forced to move
everything into the with_ref
closure.