4

... or, How can I subclass a gtk::Widget?

I have the following dependencies in my Cargo.toml:

[dependencies]
num = "*"
gtk = "*"
cairo-rs = "*"
gdk = "*"
time = "*"

I want to create my own type of widget (for rendering fractals). I have:

extern crate cairo;
extern crate gtk;

use cairo::Context;
use gtk::signal::Inhibit;
use gtk::signal::WidgetSignals;
use gtk::traits::ContainerTrait;
use gtk::traits::WidgetTrait;

struct MyWidget {
    widget: gtk::DrawingArea,
    foo: u32,
}

impl MyWidget {
    fn new() -> MyWidget {
        let result = MyWidget {
            widget: gtk::DrawingArea::new().unwrap(),
            foo: 17
        };
        result.widget.connect_draw(move |_w, c| {
            // Cannot do: result.redraw(c)
            Inhibit(true)
        });
        result
    }
    fn modify(&mut self, x: u32) {
        self.foo += x;
        self.widget.queue_draw();
    }
    fn redraw(&self, _ : Context) -> Inhibit {
        println!("Should redraw for {}", self.foo);
        Inhibit(true)
    }
}

fn main() {
    gtk::init().ok();
    let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();

    let area = MyWidget::new();
    window.add(&area.widget);
    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(true)
    });
    window.connect_key_release_event(move |_w, _event| {
        // Cannot do: area.modify(3);
        Inhibit(true)
    });
    window.connect_button_release_event(move |_w, _event| {
        // Cannot do: area.modify(17);
        Inhibit(true)
    });

    window.show_all();
    gtk::main();
}

But when redraw gets called, w is of course the gtk::DrawingArea and not my FractalWidget. I have laborated with calling connect_draw with a closure, but not managed to use result in it (I have tried Boxing the result and moveing it into the lambda, but I'm new to this, so there is probably some way I haven't tried).

So, my actual question: Is there a way to either send more data into a rust-gnome redraw method (and other similar callbacks), or is there a way to extend a widget struct to contain my own data?

Rasmus Kaj
  • 4,224
  • 1
  • 20
  • 23
  • 1
    Ok, I tried to get it a bit more MCVE:ish ... Now the redraw function tries to use a member of the FractalWidget as if it were a member of w, which obviously don't work. – Rasmus Kaj Jul 26 '15 at 14:58
  • 1
    Actually, I've found a way to do it, so I will add an answer as well. – Rasmus Kaj Jul 26 '15 at 14:59
  • Haha, that's often one of the benefits of creating the MCVE, I've found. It forces you to look at the problem from a new perspective, and really focus on the error. ^_^ – Shepmaster Jul 26 '15 at 15:01
  • Note that your updated code still has errors (unterminated string, missing semicolon, missing imports, undefined variable `area`, ...). It's really only useful if we can paste it into a brand new file ^_^ and have it work. – Shepmaster Jul 26 '15 at 15:07
  • Actually, I found my semi-solution before trying to make it MCVE:ish, but I agree in principle ... :-) – Rasmus Kaj Jul 26 '15 at 15:35
  • Anyway, the code in the question as of an hour ago actually compiles. The interresting parts, that does not compile, are commented out. – Rasmus Kaj Jul 26 '15 at 19:12

1 Answers1

3

Ok, here's some code that actually works, mainly by using Arc<Mutex<MyWidget>> insead of plain MyWidget. This still feels rather clumsy, as I need an explicit clone before moving into a closure and locking the mutex inside it, but this is probably good stuff to do (even if there is only one thread for gtk events). Maybe the verbosity can be fixed by a macro ...

extern crate cairo;
extern crate gtk;

use cairo::Context;
use gtk::signal::Inhibit;
use gtk::signal::WidgetSignals;
use gtk::traits::ContainerTrait;
use gtk::traits::WidgetTrait;
use std::sync::{Arc,Mutex};

struct MyWidget {
    widget: gtk::DrawingArea,
    foo: u32,
}

impl MyWidget {
    fn new() -> Arc<Mutex<MyWidget>> {
        let result = Arc::new(Mutex::new(MyWidget {
            widget: gtk::DrawingArea::new().unwrap(),
            foo: 17
        }));
        let r2 = result.clone();
        result.lock().unwrap().widget.connect_draw(move |_w, c| {
            r2.lock().unwrap().redraw(c)
        });
        result
    }
    fn modify(&mut self, x: u32) {
        self.foo += x;
        self.widget.queue_draw();
    }
    fn redraw(&self, _ : Context) -> Inhibit {
        println!("Should redraw for {}", self.foo);
        Inhibit(true)
    }
}

fn main() {
    gtk::init().ok();
    let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();

    let area = MyWidget::new();
    window.add(&area.lock().unwrap().widget);
    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(true)
    });
    let a1 = area.clone();
    window.connect_key_release_event(move |_w, _event| {
        a1.lock().unwrap().modify(3);
        Inhibit(true)
    });
    let a2 = area.clone();
    window.connect_button_release_event(move |_w, _event| {
        a2.lock().unwrap().modify(17);
        Inhibit(true)
    });

    window.show_all();
    gtk::main();
}

I'll wait at least a few days with marking this answer as correct, in case anyone has a better one.

Rasmus Kaj
  • 4,224
  • 1
  • 20
  • 23
  • Did you understand how to draw a pixel inside the `DrawArea` in gtk-rs ? – Moebius Mar 16 '16 at 15:25
  • @Moebius : Well, kind of. Since I draw lots of pixels, I put them in a Pixbuf, and then use set_source_pixbuf(...), rectangle(...) and fill() to copy the Pixbuf to the Context. Probably not the most efficient method. – Rasmus Kaj May 16 '16 at 20:42