use std::convert::Infallible;
use gtk::{gdk, glib, graphene::Rect, gsk, pango, prelude::*, subclass::prelude::*};
use plotters_backend::{
BackendColor, BackendCoord, BackendStyle, BackendTextStyle, DrawingBackend, DrawingErrorKind,
};
use crate::common;
mod imp {
use std::{
cell::{OnceCell, RefCell},
sync::OnceLock,
};
use super::*;
#[derive(Debug, Default)]
pub struct Paintable {
pub(super) width: OnceCell<u32>,
pub(super) height: OnceCell<u32>,
pub(super) node: RefCell<Option<gsk::RenderNode>>,
}
#[glib::object_subclass]
impl ObjectSubclass for Paintable {
const NAME: &'static str = "PlottersGtk4Paintable";
type Type = super::Paintable;
type Interfaces = (gdk::Paintable,);
}
impl ObjectImpl for Paintable {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: OnceLock<Vec<glib::ParamSpec>> = OnceLock::new();
PROPERTIES.get_or_init(|| {
vec![
glib::ParamSpecUInt::builder("width")
.maximum(i32::MAX as u32)
.construct_only()
.build(),
glib::ParamSpecUInt::builder("height")
.maximum(i32::MAX as u32)
.construct_only()
.build(),
]
})
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"width" => {
let width = value.get().unwrap();
self.width.set(width).unwrap();
}
"height" => {
let height = value.get().unwrap();
self.height.set(height).unwrap();
}
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"width" => self.obj().width().into(),
"height" => self.obj().height().into(),
_ => unimplemented!(),
}
}
}
impl PaintableImpl for Paintable {
fn snapshot(&self, snapshot: &gdk::Snapshot, width: f64, height: f64) {
let node = self.node.borrow();
let Some(node) = node.as_ref() else {
return;
};
snapshot.save();
let (this_width, this_height) = self.obj().size();
snapshot.scale(
width as f32 / this_width as f32,
height as f32 / this_height as f32,
);
snapshot.push_clip(&Rect::new(0.0, 0.0, this_width as f32, this_height as f32));
snapshot.append_node(node);
snapshot.pop();
snapshot.restore();
}
fn flags(&self) -> gdk::PaintableFlags {
gdk::PaintableFlags::SIZE
}
fn intrinsic_width(&self) -> i32 {
self.obj().width() as i32
}
fn intrinsic_height(&self) -> i32 {
self.obj().height() as i32
}
}
}
glib::wrapper! {
pub struct Paintable(ObjectSubclass<imp::Paintable>)
@implements gdk::Paintable;
}
impl Paintable {
pub fn new((w, h): (u32, u32)) -> Self {
glib::Object::builder()
.property("width", w)
.property("height", h)
.build()
}
pub fn width(&self) -> u32 {
*self.imp().width.get().unwrap()
}
pub fn height(&self) -> u32 {
*self.imp().height.get().unwrap()
}
pub fn size(&self) -> (u32, u32) {
(self.width(), self.height())
}
pub fn clear(&self) {
self.set_node(None);
}
fn set_node(&self, node: Option<gsk::RenderNode>) {
self.imp().node.replace(node);
self.invalidate_contents();
}
}
#[derive(Debug)]
pub struct PaintableBackend<'a> {
snapshot: Option<gtk::Snapshot>,
paintable: &'a Paintable,
layout: pango::Layout,
size: (u32, u32),
}
impl<'a> PaintableBackend<'a> {
pub fn new(paintable: &'a Paintable) -> Self {
let font_map = pangocairo::FontMap::default();
let context = font_map.create_context();
let layout = pango::Layout::new(&context);
Self {
snapshot: None,
paintable,
layout,
size: paintable.size(),
}
}
#[inline]
fn snapshot(&self) -> >k::Snapshot {
self.snapshot.as_ref().expect("backend was not prepared")
}
}
impl Drop for PaintableBackend<'_> {
fn drop(&mut self) {
if let Some(snapshot) = self.snapshot.take() {
self.paintable.set_node(snapshot.to_node());
}
}
}
impl DrawingBackend for PaintableBackend<'_> {
type ErrorType = Infallible;
#[inline]
fn get_size(&self) -> (u32, u32) {
self.size
}
fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
if self.snapshot.is_none() {
self.snapshot.replace(gtk::Snapshot::new());
}
Ok(())
}
fn present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
if let Some(snapshot) = self.snapshot.take() {
self.paintable.set_node(snapshot.to_node());
}
Ok(())
}
#[inline]
fn draw_pixel(
&mut self,
point: BackendCoord,
color: BackendColor,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
common::draw_pixel(self.snapshot(), point, color)
}
#[inline]
fn draw_line<S: BackendStyle>(
&mut self,
from: BackendCoord,
to: BackendCoord,
style: &S,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
common::draw_line(self.snapshot(), from, to, style)
}
#[inline]
fn draw_rect<S: BackendStyle>(
&mut self,
upper_left: BackendCoord,
bottom_right: BackendCoord,
style: &S,
fill: bool,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
common::draw_rect(self.snapshot(), upper_left, bottom_right, style, fill)
}
#[inline]
fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
&mut self,
raw_path: I,
style: &S,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
common::draw_path(self.snapshot(), raw_path, style)
}
#[inline]
fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
&mut self,
vert: I,
style: &S,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
common::fill_polygon(self.snapshot(), vert, style)
}
#[inline]
fn draw_circle<S: BackendStyle>(
&mut self,
center: BackendCoord,
radius: u32,
style: &S,
fill: bool,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
common::draw_circle(self.snapshot(), center, radius, style, fill)
}
#[inline]
fn estimate_text_size<TStyle: BackendTextStyle>(
&self,
text: &str,
style: &TStyle,
) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> {
common::estimate_text_size(&self.layout, text, style)
}
#[inline]
fn draw_text<TStyle: BackendTextStyle>(
&mut self,
text: &str,
style: &TStyle,
pos: BackendCoord,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
common::draw_text(self.snapshot(), &self.layout, text, style, pos)
}
}