relm4/abstractions/
drawing.rs

1//! Utility to help drawing on a [`gtk::DrawingArea`] in a Relm4 application.
2//! Create a [`DrawHandler`], initialize it, and get its context when handling a message (that could be
3//! sent from the draw signal).
4
5use std::cell::RefCell;
6use std::ops::Deref;
7use std::rc::Rc;
8
9use gtk::cairo::{Context, Format, ImageSurface};
10use gtk::prelude::{DrawingAreaExtManual, WidgetExt};
11
12#[derive(Clone, Debug)]
13struct Surface {
14    surface: Rc<RefCell<ImageSurface>>,
15}
16
17impl Surface {
18    fn new(surface: ImageSurface) -> Self {
19        Self {
20            surface: Rc::new(RefCell::new(surface)),
21        }
22    }
23
24    fn get(&self) -> ImageSurface {
25        self.surface.borrow().clone()
26    }
27
28    fn set(&self, surface: &ImageSurface) {
29        *self.surface.borrow_mut() = surface.clone();
30    }
31}
32
33#[derive(Debug)]
34/// Context returned by [`DrawHandler`] that stores a [`Context`] along
35/// with additional data required for drawing.
36pub struct DrawContext {
37    context: Context,
38    draw_surface: Surface,
39    edit_surface: ImageSurface,
40    drawing_area: gtk::DrawingArea,
41}
42
43impl DrawContext {
44    fn new(
45        draw_surface: &Surface,
46        edit_surface: &ImageSurface,
47        drawing_area: &gtk::DrawingArea,
48    ) -> Self {
49        Self {
50            context: Context::new(edit_surface).unwrap(),
51            draw_surface: draw_surface.clone(),
52            edit_surface: edit_surface.clone(),
53            drawing_area: drawing_area.clone(),
54        }
55    }
56}
57
58impl Deref for DrawContext {
59    type Target = Context;
60
61    fn deref(&self) -> &Self::Target {
62        &self.context
63    }
64}
65
66impl Drop for DrawContext {
67    fn drop(&mut self) {
68        self.draw_surface.set(&self.edit_surface);
69        self.drawing_area.queue_draw();
70    }
71}
72
73/// Manager for drawing operations.
74#[derive(Debug)]
75#[must_use]
76pub struct DrawHandler {
77    draw_surface: Surface,
78    edit_surface: ImageSurface,
79    drawing_area: gtk::DrawingArea,
80}
81
82impl Default for DrawHandler {
83    fn default() -> Self {
84        Self::new()
85    }
86}
87
88impl DrawHandler {
89    /// Create a new [`DrawHandler`].
90    pub fn new() -> Self {
91        Self::new_with_drawing_area(gtk::DrawingArea::default())
92    }
93
94    /// Create a new [`DrawHandler`] with an existing [`gtk::DrawingArea`].
95    pub fn new_with_drawing_area(drawing_area: gtk::DrawingArea) -> Self {
96        let draw_surface = Surface::new(ImageSurface::create(Format::ARgb32, 100, 100).unwrap());
97        let edit_surface = ImageSurface::create(Format::ARgb32, 100, 100).unwrap();
98
99        use gtk::glib;
100        drawing_area.set_draw_func(glib::clone!(
101            #[strong]
102            draw_surface,
103            move |_, context, _, _| {
104                // TODO: only copy the area that was exposed?
105                if let Err(error) = context.set_source_surface(draw_surface.get(), 0.0, 0.0) {
106                    tracing::error!("Cannot set source surface: {:?}", error);
107                }
108
109                if let Err(error) = context.paint() {
110                    tracing::error!("Cannot paint: {:?}", error);
111                }
112            }
113        ));
114
115        Self {
116            draw_surface,
117            edit_surface,
118            drawing_area,
119        }
120    }
121
122    /// Get the drawing context to draw on a [`gtk::DrawingArea`].
123    /// If the size of the [`gtk::DrawingArea`] changed, the contents of the
124    /// surface will be replaced by a new, empty surface.
125    #[allow(deprecated)]
126    pub fn get_context(&mut self) -> DrawContext {
127        let allocation = self.drawing_area.allocation();
128        let scale = self.drawing_area.scale_factor();
129        let width = allocation.width() * scale;
130        let height = allocation.height() * scale;
131
132        if (width, height) != (self.edit_surface.width(), self.edit_surface.height()) {
133            match ImageSurface::create(Format::ARgb32, width, height) {
134                Ok(surface) => {
135                    surface.set_device_scale(f64::from(scale), f64::from(scale));
136                    self.edit_surface = surface;
137                }
138                Err(error) => tracing::error!("Cannot resize image surface: {:?}", error),
139            }
140        }
141        DrawContext::new(&self.draw_surface, &self.edit_surface, &self.drawing_area)
142    }
143
144    /// Get the width and height of the [`DrawHandler`] in pixels.
145    #[must_use]
146    pub fn size(&self) -> (i32, i32) {
147        let scale = self.drawing_area.scale_factor();
148        (
149            self.edit_surface.width() / scale,
150            self.edit_surface.height() / scale,
151        )
152    }
153
154    /// Get the height of the [`DrawHandler`] in pixels.
155    #[must_use]
156    pub fn height(&self) -> i32 {
157        let scale = self.drawing_area.scale_factor();
158        self.edit_surface.height() / scale
159    }
160
161    /// Get the width of the [`DrawHandler`] in pixels.
162    #[must_use]
163    pub fn width(&self) -> i32 {
164        let scale = self.drawing_area.scale_factor();
165        self.edit_surface.width() / scale
166    }
167
168    /// Get the height of the inner [`ImageSurface`].
169    ///
170    /// **NOTE:** Depending on the monitor scaling this is not necessarily
171    /// the height in pixels.
172    #[must_use]
173    pub fn surface_height(&self) -> i32 {
174        self.edit_surface.height()
175    }
176
177    /// Get the width of the inner [`ImageSurface`].
178    ///
179    /// **NOTE:** Depending on the monitor scaling this is not necessarily
180    /// the width in pixels.
181    #[must_use]
182    pub fn surface_width(&self) -> i32 {
183        self.edit_surface.width()
184    }
185
186    /// Get the [`gtk::DrawingArea`] of the [`DrawHandler`].
187    #[must_use]
188    pub fn drawing_area(&self) -> &gtk::DrawingArea {
189        &self.drawing_area
190    }
191}