Rust GTK:在计时器上触发 ApplicationWindow/DrawingArea 重绘?
Rust GTK: Trigger ApplicationWindow/DrawingArea redraw on a timer?
我确定有一种简单的方法可以做到这一点,但我不知道它是什么。我有一个非常基本的 gtk::{Application, ApplicationWindow, DrawingArea};
设置。我希望 DrawingArea::connect_draw
闭包在计时器上重复触发,因此它会根据一些变化的状态进行更新。 (要是能被其他线程主动触发就好了,不过有个定时器就好了。)
到目前为止,我发现所有可以在计时器上运行的东西都失败了,因为这意味着将 ApplicationWindow
移动到另一个线程。 (因 NonNull<GObject> cannot be shared between threads safely
而失败)我目前拥有的触发重绘一般事件,因此如果我在 window 上单击鼠标,它会重绘,但不会自动重绘。
该代码在下面,但请告诉我如何使它工作?
//BOILER PLATE SCROLL DOWN
extern crate cairo;
extern crate rand;
extern crate gtk;
extern crate gdk;
extern crate glib;
use std::{thread, time};
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, DrawingArea};
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
fn main(){
let app = Application::builder()
.application_id("org.example.HelloWorld")
.build();
let (tx, rx ) : (Sender<f64>, Receiver<f64>)= mpsc::channel();
gtk::init().expect("GTK init failed");
let draw_area = DrawingArea::new();
let _id = draw_area.connect_draw(move |_unused, f| {
let red = rx.recv().unwrap();
f.set_source_rgb(red,0.5, 0.5);
f.paint().expect("Painting failed");
Inhibit(false)
});
app.connect_activate(move |app| {
let win = ApplicationWindow::builder()
.application(app)
.default_width(320)
.default_height(200)
.title("Hello, World!")
.build();
win.add(&draw_area);
win.show_all();
//IMPORTANT STUFF STARTS HERE
win.connect_event(|w, _g|{ //HORRIBLE HACK HELP FIX
w.queue_draw();
Inhibit(false)
});
glib::timeout_add_seconds(1, ||{
println!("I wish I could redraw instead of printing this line");
Continue(true)
});
//fails with "`NonNull<GObject>` cannot be shared between threads safely" :
// glib::timeout_add_seconds(1, ||{
// win.queue_draw();
// Continue(true)
// });
//IMPORTANT STUFF ENDS HERE
});
thread::spawn(move || {
loop {
thread::sleep(time::Duration::from_millis(100));
tx.send(rand::random::<f64>()).unwrap();
}
});
app.run();
}
编辑:我尝试了一个互斥锁版本,可能实施有误。下面的代码报同样的错误(NonNull<GObject> cannot be shared between threads safely
)
let mut_win = Mutex::new(win);
let arc_win = Arc::new(mut_win);
glib::timeout_add_seconds(1, move ||{
let mut w = arc_win.lock().unwrap();
(*w).queue_draw();
Continue(true)
});
好吧,在偶然发现 之后,我最终成功了。关键是将 window 存储在 thread-local 全局中(TBH 我不太明白那是什么意思,但它有效),然后通过静态函数访问它。
由于我的频道和 window 之间的范围不一致,我不得不稍微修改链接的答案。最后我决定分开处理。
我强烈怀疑这不是正确的方法,但至少它可以运行。
extern crate cairo;
extern crate rand;
extern crate gtk;
extern crate gdk;
extern crate glib;
use std::sync::{Arc, Mutex};
use std::{thread, time, u32};
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, DrawingArea};
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
use std::cell::RefCell;
const SIZE : usize = 400;
type Message = (usize, usize);
type Grid = [[bool; SIZE]; SIZE];
thread_local!(
static GLOBAL: RefCell<Option<ApplicationWindow>> = RefCell::new(None);
);
fn check_update_display(){
GLOBAL.with(|global|{
if let Some(win) = &*global.borrow() {
win.queue_draw();
}
})
}
fn main(){
let app = Application::builder()
.application_id("org.example.HelloWorld")
.build();
let (tx, rx ) : (Sender<Message>, Receiver<Message>) = mpsc::channel();
gtk::init().expect("GTK init failed");
let draw_area = DrawingArea::new();
let grid_mut = Arc::new(Mutex::new([[false; SIZE]; SIZE]));
let draw_grid_mut = Arc::clone(&grid_mut);
let _id = draw_area.connect_draw(move |_unused, f| {
let grid = *(draw_grid_mut.lock().unwrap());
f.set_source_rgb(0.0,0.0, 0.0);
f.paint().expect("Painting failed");
f.set_source_rgb(1.0,1.0, 1.0);
let mut count = 0;
for i in 0 .. SIZE{
for j in 0 .. SIZE {
if grid[i][j] {
count = count + 1;
f.move_to(i as f64, j as f64);
f.rectangle(i as f64 * 3.0, j as f64 * 3.0 , 1.0, 1.0);
}
}
}
f.stroke().unwrap();
Inhibit(false)
});
let reader_grid = Arc::clone(&grid_mut);
thread::spawn(move ||{
loop{
let mut g = reader_grid.lock().unwrap();
let (x, y) = rx.recv().unwrap();
g[x][y] = true;
drop(g);
thread::sleep(time::Duration::from_millis(10));
}
});
app.connect_activate(move |app| {
let win =ApplicationWindow::builder()
.application(app)
.default_width(320)
.default_height(200)
.title("steveburg")
.build();
win.add(&draw_area);
win.show_all();
GLOBAL.with(|global|{
*global.borrow_mut() = Some(win);
});
glib::timeout_add_seconds(1, move ||{
check_update_display();
Continue(true)
});
});
thread::spawn(move || {
steveburg(tx);
});
app.run();
}
fn random_pair() -> (i32, i32) {
let (x, y) = ((rand::random::<u32>() % 3) as i32 - 1, (rand::random::<u32>() % 3) as i32 - 1);
(x, y)
}
fn steveburg(tx : Sender<Message>){
let mut grid : Grid = [[false; SIZE]; SIZE];
loop{
let (mut x, mut y) = (SIZE/2, SIZE/2);
'drift: loop {
if x == 0 || x == SIZE - 1 || y == 0 || y == SIZE - 1 {
break 'drift;
}
for nx in 0 .. 3 {
for ny in 0 .. 3 {
if grid[x + nx -1][y + ny -1] {break 'drift}
}
}
let (xa, ya) = random_pair();
(x, y) = ((x as i32+ xa) as usize, (y as i32 + ya) as usize);
}
grid[x][y] = true;
tx.send((x, y)).unwrap();
thread::sleep(time::Duration::from_millis(10));
}
}
如果您在同一线程上执行所有操作,请使用 glib::timeout_add_seconds_local() 而不是 non-local 版本。
通用版本需要一个 Send
可用的闭包,并且可以随时从任何线程调用,从主线程调用闭包。本地版本只能从主线程调用,否则会崩溃。
由于不需要 Send
闭包,您可以将对小部件的引用移动到闭包中。
我确定有一种简单的方法可以做到这一点,但我不知道它是什么。我有一个非常基本的 gtk::{Application, ApplicationWindow, DrawingArea};
设置。我希望 DrawingArea::connect_draw
闭包在计时器上重复触发,因此它会根据一些变化的状态进行更新。 (要是能被其他线程主动触发就好了,不过有个定时器就好了。)
到目前为止,我发现所有可以在计时器上运行的东西都失败了,因为这意味着将 ApplicationWindow
移动到另一个线程。 (因 NonNull<GObject> cannot be shared between threads safely
而失败)我目前拥有的触发重绘一般事件,因此如果我在 window 上单击鼠标,它会重绘,但不会自动重绘。
该代码在下面,但请告诉我如何使它工作?
//BOILER PLATE SCROLL DOWN
extern crate cairo;
extern crate rand;
extern crate gtk;
extern crate gdk;
extern crate glib;
use std::{thread, time};
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, DrawingArea};
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
fn main(){
let app = Application::builder()
.application_id("org.example.HelloWorld")
.build();
let (tx, rx ) : (Sender<f64>, Receiver<f64>)= mpsc::channel();
gtk::init().expect("GTK init failed");
let draw_area = DrawingArea::new();
let _id = draw_area.connect_draw(move |_unused, f| {
let red = rx.recv().unwrap();
f.set_source_rgb(red,0.5, 0.5);
f.paint().expect("Painting failed");
Inhibit(false)
});
app.connect_activate(move |app| {
let win = ApplicationWindow::builder()
.application(app)
.default_width(320)
.default_height(200)
.title("Hello, World!")
.build();
win.add(&draw_area);
win.show_all();
//IMPORTANT STUFF STARTS HERE
win.connect_event(|w, _g|{ //HORRIBLE HACK HELP FIX
w.queue_draw();
Inhibit(false)
});
glib::timeout_add_seconds(1, ||{
println!("I wish I could redraw instead of printing this line");
Continue(true)
});
//fails with "`NonNull<GObject>` cannot be shared between threads safely" :
// glib::timeout_add_seconds(1, ||{
// win.queue_draw();
// Continue(true)
// });
//IMPORTANT STUFF ENDS HERE
});
thread::spawn(move || {
loop {
thread::sleep(time::Duration::from_millis(100));
tx.send(rand::random::<f64>()).unwrap();
}
});
app.run();
}
编辑:我尝试了一个互斥锁版本,可能实施有误。下面的代码报同样的错误(NonNull<GObject> cannot be shared between threads safely
)
let mut_win = Mutex::new(win);
let arc_win = Arc::new(mut_win);
glib::timeout_add_seconds(1, move ||{
let mut w = arc_win.lock().unwrap();
(*w).queue_draw();
Continue(true)
});
好吧,在偶然发现
由于我的频道和 window 之间的范围不一致,我不得不稍微修改链接的答案。最后我决定分开处理。
我强烈怀疑这不是正确的方法,但至少它可以运行。
extern crate cairo;
extern crate rand;
extern crate gtk;
extern crate gdk;
extern crate glib;
use std::sync::{Arc, Mutex};
use std::{thread, time, u32};
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, DrawingArea};
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
use std::cell::RefCell;
const SIZE : usize = 400;
type Message = (usize, usize);
type Grid = [[bool; SIZE]; SIZE];
thread_local!(
static GLOBAL: RefCell<Option<ApplicationWindow>> = RefCell::new(None);
);
fn check_update_display(){
GLOBAL.with(|global|{
if let Some(win) = &*global.borrow() {
win.queue_draw();
}
})
}
fn main(){
let app = Application::builder()
.application_id("org.example.HelloWorld")
.build();
let (tx, rx ) : (Sender<Message>, Receiver<Message>) = mpsc::channel();
gtk::init().expect("GTK init failed");
let draw_area = DrawingArea::new();
let grid_mut = Arc::new(Mutex::new([[false; SIZE]; SIZE]));
let draw_grid_mut = Arc::clone(&grid_mut);
let _id = draw_area.connect_draw(move |_unused, f| {
let grid = *(draw_grid_mut.lock().unwrap());
f.set_source_rgb(0.0,0.0, 0.0);
f.paint().expect("Painting failed");
f.set_source_rgb(1.0,1.0, 1.0);
let mut count = 0;
for i in 0 .. SIZE{
for j in 0 .. SIZE {
if grid[i][j] {
count = count + 1;
f.move_to(i as f64, j as f64);
f.rectangle(i as f64 * 3.0, j as f64 * 3.0 , 1.0, 1.0);
}
}
}
f.stroke().unwrap();
Inhibit(false)
});
let reader_grid = Arc::clone(&grid_mut);
thread::spawn(move ||{
loop{
let mut g = reader_grid.lock().unwrap();
let (x, y) = rx.recv().unwrap();
g[x][y] = true;
drop(g);
thread::sleep(time::Duration::from_millis(10));
}
});
app.connect_activate(move |app| {
let win =ApplicationWindow::builder()
.application(app)
.default_width(320)
.default_height(200)
.title("steveburg")
.build();
win.add(&draw_area);
win.show_all();
GLOBAL.with(|global|{
*global.borrow_mut() = Some(win);
});
glib::timeout_add_seconds(1, move ||{
check_update_display();
Continue(true)
});
});
thread::spawn(move || {
steveburg(tx);
});
app.run();
}
fn random_pair() -> (i32, i32) {
let (x, y) = ((rand::random::<u32>() % 3) as i32 - 1, (rand::random::<u32>() % 3) as i32 - 1);
(x, y)
}
fn steveburg(tx : Sender<Message>){
let mut grid : Grid = [[false; SIZE]; SIZE];
loop{
let (mut x, mut y) = (SIZE/2, SIZE/2);
'drift: loop {
if x == 0 || x == SIZE - 1 || y == 0 || y == SIZE - 1 {
break 'drift;
}
for nx in 0 .. 3 {
for ny in 0 .. 3 {
if grid[x + nx -1][y + ny -1] {break 'drift}
}
}
let (xa, ya) = random_pair();
(x, y) = ((x as i32+ xa) as usize, (y as i32 + ya) as usize);
}
grid[x][y] = true;
tx.send((x, y)).unwrap();
thread::sleep(time::Duration::from_millis(10));
}
}
如果您在同一线程上执行所有操作,请使用 glib::timeout_add_seconds_local() 而不是 non-local 版本。
通用版本需要一个 Send
可用的闭包,并且可以随时从任何线程调用,从主线程调用闭包。本地版本只能从主线程调用,否则会崩溃。
由于不需要 Send
闭包,您可以将对小部件的引用移动到闭包中。