I want to update a cairo drawing inside a DrawingArea. I tried to achieve this by calling DrawingArea::connect_draw(...) with a new function as the parameter. My issue is that it does not replace the original drawing function, but calls both when showing the window.
Here is an example
extern crate cairo;
extern crate gio;
extern crate gtk;
use gio::prelude::*;
use gtk::prelude::*;
fn main() {
let application = gtk::Application::new(Some("com.example"), Default::default())
.expect("Initialization failed...");
application.connect_activate(|app| {
build_ui(app);
});
application.run(&vec![]);
}
fn build_ui(application: >k::Application) {
let window = get_window(application);
let drawing_area = Box::new(gtk::DrawingArea::new)();
// Set drawing function
drawing_area.connect_draw(|_, ctx| draw(ctx, 0.5, 2.0));
// Change drawing function
drawing_area.connect_draw(|_, ctx| draw(ctx, 0.9, 1.0)); // <-- Why is this not working as expected?
window.add(&drawing_area);
window.show_all();
}
fn get_window(application: >k::Application) -> gtk::ApplicationWindow {
let window = gtk::ApplicationWindow::new(application);
window.set_default_size(500i32, 500i32);
// Set transparency
set_visual(&window, None);
window.connect_screen_changed(set_visual);
window.set_app_paintable(true);
window
}
fn draw(ctx: &cairo::Context, param1: f64, param2: f64) -> gtk::Inhibit {
ctx.scale(500f64, 500f64);
ctx.set_source_rgba(1.0, 0.2, 0.2, param1);
ctx.arc(0.5, 0.5, 0.2, 0.0, 3.1414 * param2);
ctx.fill();
Inhibit(false)
}
fn set_visual(window: >k::ApplicationWindow, _screen: Option<&gdk::Screen>) {
if let Some(screen) = window.get_screen() {
if let Some(ref visual) = screen.get_rgba_visual() {
window.set_visual(Some(visual));
}
}
}
I expect half a circle to be shown. Yet the old, full circle is still there, even though I tried to replace connect_draw. How can I properly replace this?
Using the connect_draw function attaches another signal handler, but will not replace existing handlers. However, the function returns a SignalHandlerId which you should then be able to use to disonnect your original signal handler using the signal_handler_disconnect function (see https://gtk-rs.org/docs/glib/signal/fn.signal_handler_disconnect.html).
Related
I'm sure there's an easy way to do this but I don't know what it is. I have a very basic gtk::{Application, ApplicationWindow, DrawingArea}; setup. I want the DrawingArea::connect_draw closure to be triggered repeatedly on a timer, so it updates according to some changing state. (It would also be cool if it could be actively triggered by other threads, but a timer is fine.)
So far everything I've found that would work on a timer fails because it would mean moving the ApplicationWindow to another thread. (fails with NonNull<GObject> cannot be shared between threads safely) What I have currently triggers redraw on generic events, so if I click my mouse on the window it will redraw, but not do so automatically.
That code is below, but please show me how to make this work?
//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();
}
EDIT: I tried a mutex version, maybe have implemented it wrong. The following code gives the same error (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)
});
Use glib::timeout_add_seconds_local() instead of the non-local version if you're doing everything on the same thread.
The generic version requires a Send-able closure and can be called from any thread at any time, calling the closure from your main thread. The local version can only be called from the main thread and panics otherwise.
By not requiring a Send-able closure, you can move a reference to your widgets into the closure.
Okay, I eventually made it work, after stumbling onto gtk-rs: how to update view from another thread . The key is to stash window in a thread-local global (TBH I don't really understand what that means but it works), and then access it through a static function.
I had to modify the linked answer a bit because of scope disagreements between my channel and my window. Eventually I just decided to deal with them separately.
I strongly suspect this is not the right way to do this, but at least it runs.
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));
}
}
I used FLTK to create a window and two buttons inside, the btn_A has a callback and should change the btn_B label, but I dont see any non-monstrous approach do to this, ples halp? =''[
fn main() {
showMainWindow();
}
pub fn showMainWindow() {
//WINDOW
let application=app::App::default();
let mut win = window::Window::default().with_size(500,300);
//BTN_A
let mut btn_A:Listener<_> = button::Button::new(100,100,100,50,"btn_A").into();
//BTN_B
let mut btn_B:Listener<_> = button::Button::new(300,100,100,50,"btn_B").into();
//BTN_A_CALLBACK
btn_A.handle(|elem,evt| match evt {
enums::Event::Push => { btn_A(elem); true }
_ => { false }
});
win.end();
win.show();
application.run().unwrap();
}
pub fn btn_A(elem:&mut button::Button) {
elem.deactivate(); //deactivate itself
//but how do I access btn_B here?
}
In principle all that is needed is to pass a mutable reference to btn_B to your handler function:
pub fn btn_A(elem:&mut button::Button, btn_B: &mut button::Button) {
...
}
However there is one slight problem with your code: You named the function the same as the variable that holds your button.
Apart from that in the most recent version of the fltk crate (v.1.2.23, that I used because you did not specify which version you used in your question) there does not seem to be a Listener<_> type.
Here is an example based on the snippet you posted for changing the label of btn_B:
use fltk::{prelude::{WidgetExt, GroupExt, WidgetBase}, window, app, button, enums};
fn main() {
showMainWindow();
}
pub fn showMainWindow() {
//WINDOW
let application = app::App::default();
let mut win = window::Window::default().with_size(500, 300);
//BTN_A
let mut btn_A = button::Button::new(100, 100, 100, 50, "btn_A");
//BTN_B
let mut btn_B = button::Button::new(300, 100, 100, 50, "btn_B");
//BTN_A_CALLBACK
btn_A.handle(move |elem, evt| match evt {
enums::Event::Push => {
btn_A_click(elem, &mut btn_B);
true
}
_ => false,
});
win.end();
win.show();
application.run().unwrap();
}
pub fn btn_A_click(elem: &mut button::Button, btn_B: &mut button::Button) {
elem.deactivate(); //deactivate itself
//but how do I access btn_B here?
btn_B.set_label("New title.")
}
Also note, that the handle closure now takes ownership of btn_B because of the move keyword.
I wanted to try to make a game in Rust using Piston. This is the first time I have used this library. I took this code from the official doc to test it. However, when my mouse touches the application window it closes immediately and I don’t understand why.
extern crate glutin_window;
extern crate graphics;
extern crate opengl_graphics;
extern crate piston;
use glutin_window::GlutinWindow as Window;
use opengl_graphics::{GlGraphics, OpenGL};
use piston::event_loop::{EventSettings, Events};
use piston::input::{RenderArgs, RenderEvent, UpdateArgs, UpdateEvent};
use piston::window::WindowSettings;
pub struct App {
gl: GlGraphics, // OpenGL drawing backend.
rotation: f64, // Rotation for the square.
}
impl App {
fn render(&mut self, args: &RenderArgs) {
use graphics::*;
const GREEN: [f32; 4] = [0.0, 1.0, 0.0, 1.0];
const RED: [f32; 4] = [1.0, 0.0, 0.0, 1.0];
let square = rectangle::square(0.0, 0.0, 50.0);
let rotation = self.rotation;
let (x, y) = (args.window_size[0] / 2.0, args.window_size[1] / 2.0);
self.gl.draw(args.viewport(), |c, gl| {
// Clear the screen.
clear(GREEN, gl);
let transform = c
.transform
.trans(x, y)
.rot_rad(rotation)
.trans(-25.0, -25.0);
// Draw a box rotating around the middle of the screen.
rectangle(RED, square, transform, gl);
});
}
fn update(&mut self, args: &UpdateArgs) {
// Rotate 2 radians per second.
self.rotation += 2.0 * args.dt;
}
}
fn main() {
// Change this to OpenGL::V2_1 if not working.
let opengl = OpenGL::V3_2;
// Create an Glutin window.
let mut window: Window = WindowSettings::new("spinning-square", [200, 200])
.graphics_api(opengl)
.exit_on_esc(true)
.build()
.unwrap();
// Create a new game and run it.
let mut app = App {
gl: GlGraphics::new(opengl),
rotation: 0.0,
};
let mut events = Events::new(EventSettings::new());
while let Some(e) = events.next(&mut window) {
if let Some(args) = e.render_args() {
app.render(&args);
}
if let Some(args) = e.update_args() {
app.update(&args);
}
}
}
The error:
thread 'main' panicked at 'attempted to leave type `platform::platform::x11::util::input::PointerState` uninitialized, which is invalid', /home/zenmoa/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/mem/mod.rs:658:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
This is apparently a bug in an old version of winit, which is fixed in the latest winit release. However, various crates e.g. amethyst, piston, ggez, etc. still use winit 0.19. In Rust 1.48.0 the issue has apparently manifested itself as a panic.
On the amethyst issue tracker a comment mentions, that for now a possible workaround is to revert back to Rust 1.47.0. If you're using rustup, then you can do that by executing the following command:
rustup default 1.47.0
My Gtk-rs application crashes whenever I try calling the show_all() function.
In a simple application window, I have added a headerbar and a label. If I compile without adding the headerbar, the window works and shows the label as intended. However if I add the headerbar the window crashes.
use gio::prelude::*;
use gtk::{
prelude::*,
HeaderBarExt,
GtkWindowExt
};
fn gen_header_bar(subtitle: Option<String>) -> gtk::HeaderBar {
let header_bar = gtk::HeaderBar::new();
header_bar.set_title(Some(crate::consts::APP_NAME));
header_bar.set_show_close_button(true);
match subtitle {
Some(subtitle) => {
header_bar.set_subtitle(Some(&subtitle));
},
_ => {
}
}
header_bar
}
pub fn build_application_window() -> Result<(), Box<dyn std::error::Error>> {
let application = gtk::Application::new(
Some(crate::consts::APP_ID),
gio::ApplicationFlags::FLAGS_NONE,
)?;
application.connect_activate(move |app| {
let window = gtk::ApplicationWindow::new(app);
window.set_title(crate::consts::APP_NAME);
window.set_default_size(32 * 10, 200); // golden ratio
window.set_position(gtk::WindowPosition::Center);
let header_bar = gen_header_bar(None);
window.set_titlebar(Some(&header_bar));
window.add(&{
let label = gtk::Label::new(Some("Welcome!"));
label
});
window.show_all(); // crashes here
});
application.run(&std::env::args().collect::<Vec<_>>());
Ok(())
}
What is causing this?
I'm struggling with a SpinButton in Rust using Relm. (Disclaimer, I'm a noob with Rust and GTK)
#![feature(proc_macro)]
extern crate gtk;
use chrono::{NaiveTime, Duration};
use gtk::prelude::*;
use gtk::{
WidgetExt,
ContainerExt,
EntryExt,
Adjustment
};
use relm::Widget;
use relm_attributes::widget;
#[derive(Msg, Debug)]
pub enum Msg {
Changed,
Display,
}
#[widget]
impl Widget for DurationSpin {
fn model(m: Duration) -> Duration {
m
}
fn update(&mut self, event: Msg) {
match event {
Msg::Display => self.format_display(),
Msg::Changed => {
println!("update, self.spin_btn.get_value_as_int() = {:?}", self.spin_btn.get_value_as_int());
self.model = Duration::minutes(self.spin_btn.get_value_as_int() as i64)
},
};
}
fn format_display(&mut self) {
println!("format_display, self.spin_btn.get_value_as_int() = {:?}", self.spin_btn.get_value_as_int());
let minus = self.model.num_hours() * 60;
self.spin_btn.set_text(&format!("{:02}:{:02}",
self.model.num_hours(), self.model.num_minutes() - minus));
}
view! {
#[name="spin_btn"]
gtk::SpinButton {
max_width_chars: 5,
width_chars: 5,
max_length: 5,
numeric: false,
output => (Msg::Display, Inhibit(false)),
value_changed => Msg::Changed,
adjustment: &Adjustment::new(self.model.num_minutes() as f64, 0.0, 720.0, 1.0, 60.0, 0.0),
}
}
}
(whole project here: https://github.com/Geobert/rusty_flexi)
My issue is that clicking on "+" makes get_value_as_int always returns '1'.
It seems that my output signal is causing this as deactivating the connection solves the bug but I can't see what's wrong with it.
It seems the output signal handler must not be asynchronous. That means you should not use relm message passing in this case.
You should do something like:
fn init_view(&mut self) {
let hours = self.model.num_hours();
let minutes = self.model.num_minutes();
self.spin_btn.connect_output(move |spin_btn| {
let minus = hours * 60;
spin_btn.set_text(&format!("{:02}:{:02}",
hours, minutes - minus));
Inhibit(false)
});
}
and remove output => (Msg::Display, Inhibit(false)),