plotters_gtk4/
paintable.rs

1use std::convert::Infallible;
2
3use gtk::{gdk, glib, graphene::Rect, gsk, pango, prelude::*, subclass::prelude::*};
4use plotters_backend::{
5    BackendColor, BackendCoord, BackendStyle, BackendTextStyle, DrawingBackend, DrawingErrorKind,
6};
7
8use crate::common;
9
10mod imp {
11    use std::{
12        cell::{OnceCell, RefCell},
13        sync::OnceLock,
14    };
15
16    use super::*;
17
18    #[derive(Debug, Default)]
19    pub struct Paintable {
20        pub(super) width: OnceCell<u32>,
21        pub(super) height: OnceCell<u32>,
22        pub(super) node: RefCell<Option<gsk::RenderNode>>,
23    }
24
25    #[glib::object_subclass]
26    impl ObjectSubclass for Paintable {
27        const NAME: &'static str = "PlottersGtk4Paintable";
28        type Type = super::Paintable;
29        type Interfaces = (gdk::Paintable,);
30    }
31
32    impl ObjectImpl for Paintable {
33        fn properties() -> &'static [glib::ParamSpec] {
34            static PROPERTIES: OnceLock<Vec<glib::ParamSpec>> = OnceLock::new();
35
36            PROPERTIES.get_or_init(|| {
37                vec![
38                    glib::ParamSpecUInt::builder("width")
39                        .maximum(i32::MAX as u32)
40                        .construct_only()
41                        .build(),
42                    glib::ParamSpecUInt::builder("height")
43                        .maximum(i32::MAX as u32)
44                        .construct_only()
45                        .build(),
46                ]
47            })
48        }
49
50        fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
51            match pspec.name() {
52                "width" => {
53                    let width = value.get().unwrap();
54                    self.width.set(width).unwrap();
55                }
56                "height" => {
57                    let height = value.get().unwrap();
58                    self.height.set(height).unwrap();
59                }
60                _ => unimplemented!(),
61            }
62        }
63
64        fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
65            match pspec.name() {
66                "width" => self.obj().width().into(),
67                "height" => self.obj().height().into(),
68                _ => unimplemented!(),
69            }
70        }
71    }
72
73    impl PaintableImpl for Paintable {
74        fn snapshot(&self, snapshot: &gdk::Snapshot, width: f64, height: f64) {
75            let node = self.node.borrow();
76
77            let Some(node) = node.as_ref() else {
78                return;
79            };
80
81            snapshot.save();
82
83            let (this_width, this_height) = self.obj().size();
84            snapshot.scale(
85                width as f32 / this_width as f32,
86                height as f32 / this_height as f32,
87            );
88
89            snapshot.push_clip(&Rect::new(0.0, 0.0, this_width as f32, this_height as f32));
90
91            snapshot.append_node(node);
92
93            snapshot.pop();
94
95            snapshot.restore();
96        }
97
98        fn flags(&self) -> gdk::PaintableFlags {
99            gdk::PaintableFlags::SIZE
100        }
101
102        fn intrinsic_width(&self) -> i32 {
103            self.obj().width() as i32
104        }
105
106        fn intrinsic_height(&self) -> i32 {
107            self.obj().height() as i32
108        }
109    }
110}
111
112glib::wrapper! {
113    /// A paintable to draw on in [`PaintableBackend`].
114    ///
115    /// This can be used on GTK UI files using its type name `PlottersGtk4Paintable`.
116    pub struct Paintable(ObjectSubclass<imp::Paintable>)
117        @implements gdk::Paintable;
118}
119
120impl Paintable {
121    /// Creates a new paintable with the given width and height.
122    pub fn new((w, h): (u32, u32)) -> Self {
123        glib::Object::builder()
124            .property("width", w)
125            .property("height", h)
126            .build()
127    }
128
129    /// Returns the width of the paintable.
130    pub fn width(&self) -> u32 {
131        *self.imp().width.get().unwrap()
132    }
133
134    /// Returns the height of the paintable.
135    pub fn height(&self) -> u32 {
136        *self.imp().height.get().unwrap()
137    }
138
139    /// Returns the width and height of the paintable.
140    pub fn size(&self) -> (u32, u32) {
141        (self.width(), self.height())
142    }
143
144    /// Clears the contents of the paintable.
145    pub fn clear(&self) {
146        self.set_node(None);
147    }
148
149    fn set_node(&self, node: Option<gsk::RenderNode>) {
150        self.imp().node.replace(node);
151        self.invalidate_contents();
152    }
153}
154
155/// Backend that draws to an object that implements [`gdk::Paintable`].
156#[derive(Debug)]
157pub struct PaintableBackend<'a> {
158    snapshot: Option<gtk::Snapshot>,
159    paintable: &'a Paintable,
160    layout: pango::Layout,
161    size: (u32, u32),
162}
163
164impl<'a> PaintableBackend<'a> {
165    /// Creates a new drawing backend backed with an object that implements
166    /// [`gdk::Paintable`].
167    pub fn new(paintable: &'a Paintable) -> Self {
168        let font_map = pangocairo::FontMap::default();
169        let context = font_map.create_context();
170        let layout = pango::Layout::new(&context);
171        Self {
172            snapshot: None,
173            paintable,
174            layout,
175            size: paintable.size(),
176        }
177    }
178
179    #[inline]
180    fn snapshot(&self) -> &gtk::Snapshot {
181        self.snapshot.as_ref().expect("backend was not prepared")
182    }
183}
184
185impl Drop for PaintableBackend<'_> {
186    fn drop(&mut self) {
187        if let Some(snapshot) = self.snapshot.take() {
188            self.paintable.set_node(snapshot.to_node());
189        }
190    }
191}
192
193impl DrawingBackend for PaintableBackend<'_> {
194    type ErrorType = Infallible;
195
196    #[inline]
197    fn get_size(&self) -> (u32, u32) {
198        self.size
199    }
200
201    fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
202        if self.snapshot.is_none() {
203            self.snapshot.replace(gtk::Snapshot::new());
204        }
205        Ok(())
206    }
207
208    fn present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
209        if let Some(snapshot) = self.snapshot.take() {
210            self.paintable.set_node(snapshot.to_node());
211        }
212        Ok(())
213    }
214
215    #[inline]
216    fn draw_pixel(
217        &mut self,
218        point: BackendCoord,
219        color: BackendColor,
220    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
221        common::draw_pixel(self.snapshot(), point, color)
222    }
223
224    #[inline]
225    fn draw_line<S: BackendStyle>(
226        &mut self,
227        from: BackendCoord,
228        to: BackendCoord,
229        style: &S,
230    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
231        common::draw_line(self.snapshot(), from, to, style)
232    }
233
234    #[inline]
235    fn draw_rect<S: BackendStyle>(
236        &mut self,
237        upper_left: BackendCoord,
238        bottom_right: BackendCoord,
239        style: &S,
240        fill: bool,
241    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
242        common::draw_rect(self.snapshot(), upper_left, bottom_right, style, fill)
243    }
244
245    #[inline]
246    fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
247        &mut self,
248        raw_path: I,
249        style: &S,
250    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
251        common::draw_path(self.snapshot(), raw_path, style)
252    }
253
254    #[inline]
255    fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
256        &mut self,
257        vert: I,
258        style: &S,
259    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
260        common::fill_polygon(self.snapshot(), vert, style)
261    }
262
263    #[inline]
264    fn draw_circle<S: BackendStyle>(
265        &mut self,
266        center: BackendCoord,
267        radius: u32,
268        style: &S,
269        fill: bool,
270    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
271        common::draw_circle(self.snapshot(), center, radius, style, fill)
272    }
273
274    #[inline]
275    fn estimate_text_size<TStyle: BackendTextStyle>(
276        &self,
277        text: &str,
278        style: &TStyle,
279    ) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> {
280        common::estimate_text_size(&self.layout, text, style)
281    }
282
283    #[inline]
284    fn draw_text<TStyle: BackendTextStyle>(
285        &mut self,
286        text: &str,
287        style: &TStyle,
288        pos: BackendCoord,
289    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
290        common::draw_text(self.snapshot(), &self.layout, text, style, pos)
291    }
292}