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 pub struct Paintable(ObjectSubclass<imp::Paintable>)
117 @implements gdk::Paintable;
118}
119
120impl Paintable {
121 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 pub fn width(&self) -> u32 {
131 *self.imp().width.get().unwrap()
132 }
133
134 pub fn height(&self) -> u32 {
136 *self.imp().height.get().unwrap()
137 }
138
139 pub fn size(&self) -> (u32, u32) {
141 (self.width(), self.height())
142 }
143
144 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#[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 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) -> >k::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}