Mateholiker
Mateholiker

Reputation: 369

How to use gtk::DrawingArea?

I am trying to build a GUI in Rust using GTK, Cairo and Glade. I want to draw a playing field using gtk::DrawingArea but I do not know how to use it. I have this:

extern crate cairo;
extern crate gtk;

use gtk::*;

fn main() {
    gtk::init().unwrap(); //init gtk before using it

    let glade_src = include_str!("Glade_gui.glade"); //build the glade gui
    let builder = gtk::Builder::new_from_string(glade_src);

    //get widgets from the gui
    let draw_area: gtk::DrawingArea = builder.get_object("zeichenbrett").unwrap();
    let window: gtk::Window = builder.get_object("fenster").unwrap();

    let size = (600, 600); //define the size for the image

    let style_context: gtk::StyleContext = draw_area.get_style_context().unwrap(); //get the style context from the drawing area
    let surface: cairo::ImageSurface =
        cairo::ImageSurface::create(cairo::Format::ARgb32, size.0, size.1).unwrap(); //build a new ImageSurface to draw on
    let context: cairo::Context = cairo::Context::new(&surface); //build a new cairo context from that ImageSurface to draw on

    //just a blue area
    context.set_source_rgb(0.0, 0.0, 1.0);
    context.paint();
    context.stroke();

    gtk::functions::render_background(
        &style_context,
        &context,
        0.0,
        0.0,
        size.0 as f64,
        size.1 as f64,
    ); //here I thought that I drew the context                        cairo::Context to the drawingArea but it seems to do nothing.

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

It compiles and runs, but no playing field is shown on the window.

I think I am not using the render_background function correctly, but I do not know how to do it right.

Here is the Glade_gui.glade file:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkWindow" id="fenster">
    <property name="can_focus">False</property>
    <property name="title" translatable="yes">Reise nach Jerusalem</property>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkBox">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="spacing">5</property>
            <property name="homogeneous">True</property>
            <child>
              <object class="GtkButton" id="links">
                <property name="label" translatable="yes">Left</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkButton" id="rechts">
                <property name="label" translatable="yes">Right</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">1</property>
              </packing>
            </child>
            <child>
              <object class="GtkButton" id="quit">
                <property name="label" translatable="yes">Quit</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">2</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkDrawingArea" id="zeichenbrett">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>

Upvotes: 5

Views: 2070

Answers (2)

Nicolas
Nicolas

Reputation: 7081

I had to spend several hours to find a solution with gtk-rs and GTK4 as there are very few examples. In GTK4, instead of connecting a draw signal, the draw functions must be registered with gtk_drawing_area_set_draw_func. With the Rust bindings, we need to use DrawingAreaExtManual::set_draw_func to set the draw function. In my case, I was subclassing DrawingArea, which gave something like this:

use std::cell::Cell;
use std::f64::consts::PI;

use glib::Object;
use gtk::gdk::RGBA;
use gtk::glib;
use gtk::glib::{clone, ParamSpec, Properties, Value};
use gtk::prelude::*;
use gtk::subclass::prelude::*;

mod imp {
    use super::*;

    // Object holding the state
    #[derive(Properties)]
    #[properties(wrapper_type = super::CustomDrawingArea)]
    pub struct CustomDrawingArea {
        #[property(name = "color", get, set)]
        pub(super) color: Cell<RGBA>,
    }

    impl Default for CustomDrawingArea {
        fn default() -> Self {
            Self {
                color: Cell::new(RGBA::BLACK),
            }
        }
    }

    // The central trait for subclassing a GObject
    #[glib::object_subclass]
    impl ObjectSubclass for CustomDrawingArea {
        const NAME: &'static str = "CustomDrawingArea";
        type Type = super::CustomDrawingArea;
        type ParentType = gtk::DrawingArea;
    }

    impl ObjectImpl for CustomDrawingArea {
        fn constructed(&self) {
            self.parent_constructed();

            // Set the drawing function. This draws a circle filled with the color and a black outline.
            DrawingAreaExtManual::set_draw_func(self.obj().as_ref(), clone!(@weak self as widget => move |_, cr, w, h| {
                let r = w as f64 / 2.0 - 1.0;

                cr.arc(w as f64 / 2.0, h as f64 / 2.0, r, 0.0, 2.0 * PI);
                GdkCairoContextExt::set_source_rgba(cr, &widget.color.get());
                cr.fill().unwrap();

                cr.set_line_width(1.0);
                cr.arc(w as f64 / 2.0, h as f64 / 2.0, r, 0.0, 2.0 * PI);
                GdkCairoContextExt::set_source_rgba(cr, &RGBA::BLACK);
                cr.stroke().unwrap();
            }));

            // Make sure to redraw when the property is updated. 
            self.obj().connect_color_notify(|widget| widget.queue_draw());
        }

        // This is needed for the #[property] macro.
        fn properties() -> &'static [ParamSpec] {
            Self::derived_properties()
        }

        fn set_property(&self, id: usize, value: &Value, pspec: &ParamSpec) {
            self.derived_set_property(id, value, pspec)
        }

        fn property(&self, id: usize, pspec: &ParamSpec) -> Value {
            self.derived_property(id, pspec)
        }
    }

    impl WidgetImpl for CustomDrawingArea {}
    impl DrawingAreaImpl for CustomDrawingArea {}
}

glib::wrapper! {
    pub struct CustomDrawingArea(ObjectSubclass<imp::CustomDrawingArea>)
        @extends gtk::DrawingArea, gtk::Widget,
        @implements gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget;
}

impl CustomDrawingArea {
    pub fn new() -> Self {
        Object::builder().build()
    }
}

Upvotes: 0

oldtechaa
oldtechaa

Reputation: 1524

As a note, I have no experience in Rust, so I'll use mainly C examples.

You are attempting to render your GUI before starting gtk::main. The proper way to implement a GTK+ GUI is to add your widgets, connect their draw signal (and any other necessary signals) to your own draw callback function, then run gtk::main.

As an example, take the documentation's simple GtkDrawingArea example found here. In this example, g_signal_connect is used to connect the draw signal to the draw_callback callback function. This way, when the widget is actually created by gtk::main, it will draw your desired image on it.

Your draw_callback function will get a Cairo context as a parameter. You will do all your drawing on that context, so there is no need to create your own. This is also demonstrated in the docs by the use of the pointer cr in the parameters of draw_callback.

All your drawing needs to be done in the draw callback function. It will get called anytime an update is necessary (including creation of the GtkDrawingArea widget), or you can force an update by emitting the queue_draw signal.

The C docs can be a great help even when using a different language, especially if your chosen language doesn't have comprehensive documentation.

Also, the recommended modern way to create a GTK+ application is to use GtkApplication. You may want to look into that.

Upvotes: 1

Related Questions