painter
This commit is contained in:
commit
038a25948f
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
1613
Cargo.lock
generated
Normal file
1613
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "cinrad_g"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cairo-rs = { version = "0.17.0", features = ["xlib"] }
|
||||||
|
glib = "0.17.9"
|
||||||
|
gtk = { version = "0.6.6", package = "gtk4", features = ["v4_8"] }
|
||||||
|
# gtk = "0.15.5"
|
||||||
|
|
||||||
|
plotters = "0.3.4"
|
||||||
|
plotters-backend = "0.3.4"
|
||||||
340
src/backend.rs
Normal file
340
src/backend.rs
Normal file
@ -0,0 +1,340 @@
|
|||||||
|
use cairo::{Context as CairoContext, FontSlant, FontWeight};
|
||||||
|
|
||||||
|
use plotters_backend::text_anchor::{HPos, VPos};
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use plotters_backend::{
|
||||||
|
BackendColor, BackendCoord, BackendStyle, BackendTextStyle, DrawingBackend, DrawingErrorKind,
|
||||||
|
FontStyle, FontTransform,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The drawing backend that is backed with a Cairo context
|
||||||
|
pub struct CairoBackend<'a> {
|
||||||
|
context: &'a CairoContext,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
init_flag: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CairoError;
|
||||||
|
|
||||||
|
impl std::fmt::Display for CairoError {
|
||||||
|
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(fmt, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for CairoError {}
|
||||||
|
|
||||||
|
impl<'a> CairoBackend<'a> {
|
||||||
|
fn set_color(&self, color: &BackendColor) {
|
||||||
|
self.context.set_source_rgba(
|
||||||
|
f64::from(color.rgb.0) / 255.0,
|
||||||
|
f64::from(color.rgb.1) / 255.0,
|
||||||
|
f64::from(color.rgb.2) / 255.0,
|
||||||
|
color.alpha,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_stroke_width(&self, width: u32) {
|
||||||
|
self.context.set_line_width(f64::from(width));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_font<S: BackendTextStyle>(&self, font: &S) {
|
||||||
|
match font.style() {
|
||||||
|
FontStyle::Normal => self.context.select_font_face(
|
||||||
|
font.family().as_str(),
|
||||||
|
FontSlant::Normal,
|
||||||
|
FontWeight::Normal,
|
||||||
|
),
|
||||||
|
FontStyle::Bold => self.context.select_font_face(
|
||||||
|
font.family().as_str(),
|
||||||
|
FontSlant::Normal,
|
||||||
|
FontWeight::Bold,
|
||||||
|
),
|
||||||
|
FontStyle::Oblique => self.context.select_font_face(
|
||||||
|
font.family().as_str(),
|
||||||
|
FontSlant::Oblique,
|
||||||
|
FontWeight::Normal,
|
||||||
|
),
|
||||||
|
FontStyle::Italic => self.context.select_font_face(
|
||||||
|
font.family().as_str(),
|
||||||
|
FontSlant::Italic,
|
||||||
|
FontWeight::Normal,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
self.context.set_font_size(font.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(context: &'a CairoContext, (w, h): (u32, u32)) -> Result<Self, CairoError> {
|
||||||
|
Ok(Self {
|
||||||
|
context,
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
init_flag: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DrawingBackend for CairoBackend<'a> {
|
||||||
|
type ErrorType = cairo::Error;
|
||||||
|
|
||||||
|
fn get_size(&self) -> (u32, u32) {
|
||||||
|
(self.width, self.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
if !self.init_flag {
|
||||||
|
let (x0, y0, x1, y1) = self
|
||||||
|
.context
|
||||||
|
.clip_extents()
|
||||||
|
.map_err(DrawingErrorKind::DrawingError)?;
|
||||||
|
|
||||||
|
self.context.scale(
|
||||||
|
(x1 - x0) / f64::from(self.width),
|
||||||
|
(y1 - y0) / f64::from(self.height),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.init_flag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_pixel(
|
||||||
|
&mut self,
|
||||||
|
point: BackendCoord,
|
||||||
|
color: BackendColor,
|
||||||
|
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
self.context
|
||||||
|
.rectangle(f64::from(point.0), f64::from(point.1), 1.0, 1.0);
|
||||||
|
self.context.set_source_rgba(
|
||||||
|
f64::from(color.rgb.0) / 255.0,
|
||||||
|
f64::from(color.rgb.1) / 255.0,
|
||||||
|
f64::from(color.rgb.2) / 255.0,
|
||||||
|
color.alpha,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.context
|
||||||
|
.fill()
|
||||||
|
.map_err(DrawingErrorKind::DrawingError)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_line<S: BackendStyle>(
|
||||||
|
&mut self,
|
||||||
|
from: BackendCoord,
|
||||||
|
to: BackendCoord,
|
||||||
|
style: &S,
|
||||||
|
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
self.set_color(&style.color());
|
||||||
|
self.set_stroke_width(style.stroke_width());
|
||||||
|
|
||||||
|
self.context.move_to(f64::from(from.0), f64::from(from.1));
|
||||||
|
self.context.line_to(f64::from(to.0), f64::from(to.1));
|
||||||
|
|
||||||
|
self.context
|
||||||
|
.stroke()
|
||||||
|
.map_err(DrawingErrorKind::DrawingError)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_rect<S: BackendStyle>(
|
||||||
|
&mut self,
|
||||||
|
upper_left: BackendCoord,
|
||||||
|
bottom_right: BackendCoord,
|
||||||
|
style: &S,
|
||||||
|
fill: bool,
|
||||||
|
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
self.set_color(&style.color());
|
||||||
|
self.set_stroke_width(style.stroke_width());
|
||||||
|
|
||||||
|
self.context.rectangle(
|
||||||
|
f64::from(upper_left.0),
|
||||||
|
f64::from(upper_left.1),
|
||||||
|
f64::from(bottom_right.0 - upper_left.0),
|
||||||
|
f64::from(bottom_right.1 - upper_left.1),
|
||||||
|
);
|
||||||
|
|
||||||
|
if fill {
|
||||||
|
self.context
|
||||||
|
.fill()
|
||||||
|
.map_err(DrawingErrorKind::DrawingError)?;
|
||||||
|
} else {
|
||||||
|
self.context
|
||||||
|
.stroke()
|
||||||
|
.map_err(DrawingErrorKind::DrawingError)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
|
||||||
|
&mut self,
|
||||||
|
path: I,
|
||||||
|
style: &S,
|
||||||
|
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
self.set_color(&style.color());
|
||||||
|
self.set_stroke_width(style.stroke_width());
|
||||||
|
|
||||||
|
let mut path = path.into_iter();
|
||||||
|
if let Some((x, y)) = path.next() {
|
||||||
|
self.context.move_to(f64::from(x), f64::from(y));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (x, y) in path {
|
||||||
|
self.context.line_to(f64::from(x), f64::from(y));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.context
|
||||||
|
.stroke()
|
||||||
|
.map_err(DrawingErrorKind::DrawingError)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
|
||||||
|
&mut self,
|
||||||
|
path: I,
|
||||||
|
style: &S,
|
||||||
|
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
self.set_color(&style.color());
|
||||||
|
self.set_stroke_width(style.stroke_width());
|
||||||
|
|
||||||
|
let mut path = path.into_iter();
|
||||||
|
|
||||||
|
if let Some((x, y)) = path.next() {
|
||||||
|
self.context.move_to(f64::from(x), f64::from(y));
|
||||||
|
|
||||||
|
for (x, y) in path {
|
||||||
|
self.context.line_to(f64::from(x), f64::from(y));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.context.close_path();
|
||||||
|
self.context
|
||||||
|
.fill()
|
||||||
|
.map_err(DrawingErrorKind::DrawingError)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_circle<S: BackendStyle>(
|
||||||
|
&mut self,
|
||||||
|
center: BackendCoord,
|
||||||
|
radius: u32,
|
||||||
|
style: &S,
|
||||||
|
fill: bool,
|
||||||
|
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
self.set_color(&style.color());
|
||||||
|
self.set_stroke_width(style.stroke_width());
|
||||||
|
|
||||||
|
self.context.new_sub_path();
|
||||||
|
self.context.arc(
|
||||||
|
f64::from(center.0),
|
||||||
|
f64::from(center.1),
|
||||||
|
f64::from(radius),
|
||||||
|
0.0,
|
||||||
|
std::f64::consts::PI * 2.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
if fill {
|
||||||
|
self.context
|
||||||
|
.fill()
|
||||||
|
.map_err(DrawingErrorKind::DrawingError)?;
|
||||||
|
} else {
|
||||||
|
self.context
|
||||||
|
.stroke()
|
||||||
|
.map_err(DrawingErrorKind::DrawingError)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn estimate_text_size<S: BackendTextStyle>(
|
||||||
|
&self,
|
||||||
|
text: &str,
|
||||||
|
font: &S,
|
||||||
|
) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
self.set_font(font);
|
||||||
|
|
||||||
|
let extents = self
|
||||||
|
.context
|
||||||
|
.text_extents(text)
|
||||||
|
.map_err(DrawingErrorKind::DrawingError)?;
|
||||||
|
|
||||||
|
Ok((extents.width() as u32, extents.height() as u32))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_text<S: BackendTextStyle>(
|
||||||
|
&mut self,
|
||||||
|
text: &str,
|
||||||
|
style: &S,
|
||||||
|
pos: BackendCoord,
|
||||||
|
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
|
||||||
|
let color = style.color();
|
||||||
|
let (mut x, mut y) = (pos.0, pos.1);
|
||||||
|
|
||||||
|
let degree = match style.transform() {
|
||||||
|
FontTransform::None => 0.0,
|
||||||
|
FontTransform::Rotate90 => 90.0,
|
||||||
|
FontTransform::Rotate180 => 180.0,
|
||||||
|
FontTransform::Rotate270 => 270.0,
|
||||||
|
//FontTransform::RotateAngle(angle) => angle as f64,
|
||||||
|
} / 180.0
|
||||||
|
* std::f64::consts::PI;
|
||||||
|
|
||||||
|
if degree != 0.0 {
|
||||||
|
self.context
|
||||||
|
.save()
|
||||||
|
.map_err(DrawingErrorKind::DrawingError)?;
|
||||||
|
self.context.translate(f64::from(x), f64::from(y));
|
||||||
|
self.context.rotate(degree);
|
||||||
|
|
||||||
|
x = 0;
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.set_font(style);
|
||||||
|
self.set_color(&color);
|
||||||
|
|
||||||
|
let extents = self
|
||||||
|
.context
|
||||||
|
.text_extents(text)
|
||||||
|
.map_err(DrawingErrorKind::DrawingError)?;
|
||||||
|
|
||||||
|
let dx = match style.anchor().h_pos {
|
||||||
|
HPos::Left => 0.0,
|
||||||
|
HPos::Right => -extents.width(),
|
||||||
|
HPos::Center => -extents.width() / 2.0,
|
||||||
|
};
|
||||||
|
let dy = match style.anchor().v_pos {
|
||||||
|
VPos::Top => extents.height(),
|
||||||
|
VPos::Center => extents.height() / 2.0,
|
||||||
|
VPos::Bottom => 0.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.context.move_to(
|
||||||
|
f64::from(x) + dx - extents.x_bearing(),
|
||||||
|
f64::from(y) + dy - extents.y_bearing() - extents.height(),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.context
|
||||||
|
.show_text(text)
|
||||||
|
.map_err(DrawingErrorKind::DrawingError)?;
|
||||||
|
|
||||||
|
if degree != 0.0 {
|
||||||
|
self.context
|
||||||
|
.restore()
|
||||||
|
.map_err(DrawingErrorKind::DrawingError)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
72
src/main.rs
Normal file
72
src/main.rs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
use crate::backend::CairoBackend;
|
||||||
|
use gtk::gdk::CairoContext;
|
||||||
|
use gtk::{glib, Application, ApplicationWindow};
|
||||||
|
use gtk::{prelude::*, DrawingArea};
|
||||||
|
use plotters::prelude::*;
|
||||||
|
|
||||||
|
use cairo::Context;
|
||||||
|
|
||||||
|
pub mod backend;
|
||||||
|
|
||||||
|
const APP_ID: &str = "org.gtk_rs.HelloWorld2";
|
||||||
|
|
||||||
|
fn main() -> glib::ExitCode {
|
||||||
|
// Create a new application
|
||||||
|
let app = Application::builder().application_id(APP_ID).build();
|
||||||
|
|
||||||
|
// Connect to "activate" signal of `app`
|
||||||
|
app.connect_activate(build_ui);
|
||||||
|
|
||||||
|
// Run the application
|
||||||
|
app.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_ui(app: &Application) {
|
||||||
|
// Create a window and set the title
|
||||||
|
let window = ApplicationWindow::builder()
|
||||||
|
.application(app)
|
||||||
|
.title("My GTK App")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let draw_area = DrawingArea::new();
|
||||||
|
|
||||||
|
draw_area.set_draw_func(|a, cr, width, height| {
|
||||||
|
draw(cr, 2000, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.set_child(Some(&draw_area));
|
||||||
|
// Present window
|
||||||
|
window.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(ctx: &Context, width: u32, height: u32) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// let mut backend = root.fill(&WHITE)?;
|
||||||
|
let mut root = CairoBackend::new(ctx, (width, height))?.into_drawing_area();
|
||||||
|
|
||||||
|
let mut chart = ChartBuilder::on(&root)
|
||||||
|
.caption("y=x^2", ("sans-serif", 50).into_font())
|
||||||
|
.margin(5)
|
||||||
|
.x_label_area_size(30)
|
||||||
|
.y_label_area_size(30)
|
||||||
|
.build_cartesian_2d(-1f32..1f32, -0.1f32..1f32)?;
|
||||||
|
|
||||||
|
chart.configure_mesh().draw()?;
|
||||||
|
|
||||||
|
chart
|
||||||
|
.draw_series(LineSeries::new(
|
||||||
|
(-50..=50).map(|x| x as f32 / 50.0).map(|x| (x, x * x)),
|
||||||
|
&RED,
|
||||||
|
))?
|
||||||
|
.label("y = x^2")
|
||||||
|
.legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &RED));
|
||||||
|
|
||||||
|
chart
|
||||||
|
.configure_series_labels()
|
||||||
|
.background_style(&WHITE.mix(0.8))
|
||||||
|
.border_style(&BLACK)
|
||||||
|
.draw()?;
|
||||||
|
|
||||||
|
root.present()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user