Skip to content

Commit e93d015

Browse files
committed
Port to composite templates
1 parent f2619a9 commit e93d015

File tree

8 files changed

+257
-173
lines changed

8 files changed

+257
-173
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ edition = "2018"
88

99
[dependencies]
1010
gtk = { package = "gtk4", version = "0.5" }
11+
once_cell = "1"
1112

1213
[dependencies.plotters]
1314
git = "https://github.com/plotters-rs/plotters"

src/gaussian_plot/imp.rs

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
use gtk::glib;
2+
use gtk::prelude::*;
3+
use gtk::subclass::prelude::*;
4+
5+
use std::cell::Cell;
6+
use std::error::Error;
7+
use std::f64;
8+
9+
use plotters::prelude::*;
10+
use plotters_cairo::CairoBackend;
11+
12+
use once_cell::sync::Lazy;
13+
14+
#[derive(Debug, Default)]
15+
pub struct GaussianPlot {
16+
pitch: Cell<f64>,
17+
yaw: Cell<f64>,
18+
mean_x: Cell<f64>,
19+
mean_y: Cell<f64>,
20+
std_x: Cell<f64>,
21+
std_y: Cell<f64>,
22+
}
23+
24+
#[glib::object_subclass]
25+
impl ObjectSubclass for GaussianPlot {
26+
const NAME: &'static str = "GaussianPlot";
27+
type Type = super::GaussianPlot;
28+
type ParentType = gtk::Widget;
29+
}
30+
31+
impl ObjectImpl for GaussianPlot {
32+
fn properties() -> &'static [glib::ParamSpec] {
33+
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
34+
vec![
35+
glib::ParamSpecDouble::builder("pitch")
36+
.minimum(-f64::consts::PI)
37+
.maximum(f64::consts::PI)
38+
.build(),
39+
glib::ParamSpecDouble::builder("yaw")
40+
.minimum(0.0)
41+
.maximum(f64::consts::PI)
42+
.build(),
43+
glib::ParamSpecDouble::builder("mean-x")
44+
.minimum(-10.0)
45+
.maximum(10.0)
46+
.build(),
47+
glib::ParamSpecDouble::builder("mean-y")
48+
.minimum(-10.0)
49+
.maximum(10.0)
50+
.build(),
51+
glib::ParamSpecDouble::builder("std-x")
52+
.minimum(0.0)
53+
.maximum(10.0)
54+
.build(),
55+
glib::ParamSpecDouble::builder("std-y")
56+
.minimum(0.0)
57+
.maximum(10.0)
58+
.build(),
59+
]
60+
});
61+
PROPERTIES.as_ref()
62+
}
63+
64+
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
65+
match pspec.name() {
66+
"pitch" => {
67+
self.pitch.set(value.get().unwrap());
68+
}
69+
"yaw" => {
70+
self.yaw.set(value.get().unwrap());
71+
}
72+
"mean-x" => {
73+
self.mean_x.set(value.get().unwrap());
74+
}
75+
"mean-y" => {
76+
self.mean_y.set(value.get().unwrap());
77+
}
78+
"std-x" => {
79+
self.std_x.set(value.get().unwrap());
80+
}
81+
"std-y" => {
82+
self.std_y.set(value.get().unwrap());
83+
}
84+
_ => unimplemented!(),
85+
}
86+
self.obj().queue_draw();
87+
}
88+
89+
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
90+
match pspec.name() {
91+
"pitch" => self.pitch.get().to_value(),
92+
"yaw" => self.yaw.get().to_value(),
93+
"mean-x" => self.mean_x.get().to_value(),
94+
"mean-y" => self.mean_y.get().to_value(),
95+
"std-x" => self.std_x.get().to_value(),
96+
"std-y" => self.std_y.get().to_value(),
97+
_ => unimplemented!(),
98+
}
99+
}
100+
}
101+
102+
impl WidgetImpl for GaussianPlot {
103+
fn snapshot(&self, snapshot: &gtk::Snapshot) {
104+
let width = self.obj().width() as u32;
105+
let height = self.obj().height() as u32;
106+
if width == 0 || height == 0 {
107+
return;
108+
}
109+
110+
let bounds = gtk::graphene::Rect::new(0.0, 0.0, width as f32, height as f32);
111+
let cr = snapshot.append_cairo(&bounds);
112+
let backend = CairoBackend::new(&cr, (width, height)).unwrap();
113+
self.plot_pdf(backend).unwrap();
114+
}
115+
}
116+
117+
impl GaussianPlot {
118+
fn gaussian_pdf(&self, x: f64, y: f64) -> f64 {
119+
let x_diff = (x - self.mean_x.get()) / self.std_x.get();
120+
let y_diff = (y - self.mean_y.get()) / self.std_y.get();
121+
let exponent = -(x_diff * x_diff + y_diff * y_diff) / 2.0;
122+
let denom = (2.0 * std::f64::consts::PI / self.std_x.get() / self.std_y.get()).sqrt();
123+
let gaussian_pdf = 1.0 / denom;
124+
gaussian_pdf * exponent.exp()
125+
}
126+
127+
fn plot_pdf<'a, DB: DrawingBackend + 'a>(
128+
&self,
129+
backend: DB,
130+
) -> Result<(), Box<dyn Error + 'a>> {
131+
let root = backend.into_drawing_area();
132+
133+
root.fill(&WHITE)?;
134+
135+
let mut chart = ChartBuilder::on(&root).build_cartesian_3d(
136+
-10.0f64..10.0,
137+
0.0f64..1.2,
138+
-10.0f64..10.0,
139+
)?;
140+
141+
chart.with_projection(|mut p| {
142+
p.pitch = self.pitch.get();
143+
p.yaw = self.yaw.get();
144+
p.scale = 0.7;
145+
p.into_matrix() // build the projection matrix
146+
});
147+
148+
chart
149+
.configure_axes()
150+
.light_grid_style(BLACK.mix(0.15))
151+
.max_light_lines(3)
152+
.draw()?;
153+
chart.draw_series(
154+
SurfaceSeries::xoz(
155+
(-50..=50).map(|x| x as f64 / 5.0),
156+
(-50..=50).map(|x| x as f64 / 5.0),
157+
|x, y| self.gaussian_pdf(x, y),
158+
)
159+
.style_func(&|&v| (&HSLColor(240.0 / 360.0 - 240.0 / 360.0 * v, 1.0, 0.7)).into()),
160+
)?;
161+
162+
root.present()?;
163+
Ok(())
164+
}
165+
}

src/gaussian_plot/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use gtk::glib;
2+
3+
mod imp;
4+
5+
glib::wrapper! {
6+
pub struct GaussianPlot(ObjectSubclass<imp::GaussianPlot>) @extends gtk::Widget;
7+
}

src/main.rs

Lines changed: 4 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,7 @@
1-
use std::cell::RefCell;
2-
use std::error::Error;
3-
use std::rc::Rc;
4-
51
use gtk::prelude::*;
6-
use plotters::prelude::*;
7-
use plotters_cairo::CairoBackend;
8-
9-
const UI_SOURCE: &str = include_str!("ui.xml");
10-
11-
#[derive(Clone, Copy)]
12-
struct PlottingState {
13-
mean_x: f64,
14-
mean_y: f64,
15-
std_x: f64,
16-
std_y: f64,
17-
pitch: f64,
18-
roll: f64,
19-
}
20-
21-
impl PlottingState {
22-
fn guassian_pdf(&self, x: f64, y: f64) -> f64 {
23-
let x_diff = (x - self.mean_x) / self.std_x;
24-
let y_diff = (y - self.mean_y) / self.std_y;
25-
let exponent = -(x_diff * x_diff + y_diff * y_diff) / 2.0;
26-
let denom = (2.0 * std::f64::consts::PI / self.std_x / self.std_y).sqrt();
27-
let gaussian_pdf = 1.0 / denom;
28-
gaussian_pdf * exponent.exp()
29-
}
30-
31-
fn plot_pdf<'a, DB: DrawingBackend + 'a>(
32-
&self,
33-
backend: DB,
34-
) -> Result<(), Box<dyn Error + 'a>> {
35-
let root = backend.into_drawing_area();
362

37-
root.fill(&WHITE)?;
38-
39-
let mut chart = ChartBuilder::on(&root).build_cartesian_3d(
40-
-10.0f64..10.0,
41-
0.0f64..1.2,
42-
-10.0f64..10.0,
43-
)?;
44-
45-
chart.with_projection(|mut p| {
46-
p.pitch = self.pitch;
47-
p.yaw = self.roll;
48-
p.scale = 0.7;
49-
p.into_matrix() // build the projection matrix
50-
});
51-
52-
chart
53-
.configure_axes()
54-
.light_grid_style(BLACK.mix(0.15))
55-
.max_light_lines(3)
56-
.draw()?;
57-
chart.draw_series(
58-
SurfaceSeries::xoz(
59-
(-50..=50).map(|x| x as f64 / 5.0),
60-
(-50..=50).map(|x| x as f64 / 5.0),
61-
|x, y| self.guassian_pdf(x, y),
62-
)
63-
.style_func(&|&v| (&HSLColor(240.0 / 360.0 - 240.0 / 360.0 * v, 1.0, 0.7)).into()),
64-
)?;
65-
66-
root.present()?;
67-
Ok(())
68-
}
69-
}
70-
71-
fn build_ui(app: &gtk::Application) {
72-
let builder = gtk::Builder::from_string(UI_SOURCE);
73-
let window = builder
74-
.object::<gtk::ApplicationWindow>("MainWindow")
75-
.unwrap();
76-
window.set_application(Some(app));
77-
78-
let drawing_area = builder
79-
.object::<gtk::DrawingArea>("MainDrawingArea")
80-
.unwrap();
81-
let pitch_scale = builder.object::<gtk::Scale>("PitchScale").unwrap();
82-
let yaw_scale = builder.object::<gtk::Scale>("YawScale").unwrap();
83-
let mean_x_scale = builder.object::<gtk::Scale>("MeanXScale").unwrap();
84-
let mean_y_scale = builder.object::<gtk::Scale>("MeanYScale").unwrap();
85-
let std_x_scale = builder.object::<gtk::Scale>("SDXScale").unwrap();
86-
let std_y_scale = builder.object::<gtk::Scale>("SDYScale").unwrap();
87-
88-
let app_state = Rc::new(RefCell::new(PlottingState {
89-
mean_x: mean_x_scale.value(),
90-
mean_y: mean_y_scale.value(),
91-
std_x: std_x_scale.value(),
92-
std_y: std_y_scale.value(),
93-
pitch: pitch_scale.value(),
94-
roll: yaw_scale.value(),
95-
}));
96-
97-
window.set_application(Some(app));
98-
99-
let state_cloned = app_state.clone();
100-
drawing_area.set_draw_func(move |_widget, cr, w, h| {
101-
let state = state_cloned.borrow();
102-
let backend = CairoBackend::new(cr, (w as u32, h as u32)).unwrap();
103-
state.plot_pdf(backend).unwrap();
104-
});
105-
106-
let handle_change =
107-
|what: &gtk::Scale, how: Box<dyn Fn(&mut PlottingState) -> &mut f64 + 'static>| {
108-
let app_state = app_state.clone();
109-
let drawing_area = drawing_area.clone();
110-
what.connect_value_changed(move |target| {
111-
let mut state = app_state.borrow_mut();
112-
*how(&mut state) = target.value();
113-
drawing_area.queue_draw();
114-
});
115-
};
116-
117-
handle_change(&pitch_scale, Box::new(|s| &mut s.pitch));
118-
handle_change(&yaw_scale, Box::new(|s| &mut s.roll));
119-
handle_change(&mean_x_scale, Box::new(|s| &mut s.mean_x));
120-
handle_change(&mean_y_scale, Box::new(|s| &mut s.mean_y));
121-
handle_change(&std_x_scale, Box::new(|s| &mut s.std_x));
122-
handle_change(&std_y_scale, Box::new(|s| &mut s.std_y));
123-
124-
window.show();
125-
}
3+
mod gaussian_plot;
4+
mod window;
1265

1276
fn main() {
1287
let application = gtk::Application::new(
@@ -131,7 +10,8 @@ fn main() {
13110
);
13211

13312
application.connect_activate(|app| {
134-
build_ui(app);
13+
let win = window::Window::new(app);
14+
win.show();
13515
});
13616

13717
application.run();

src/window/imp.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use gtk::glib;
2+
use gtk::prelude::*;
3+
use gtk::subclass::prelude::*;
4+
5+
#[derive(Debug, Default, gtk::CompositeTemplate)]
6+
#[template(file = "ui.xml")]
7+
pub struct Window;
8+
9+
#[glib::object_subclass]
10+
impl ObjectSubclass for Window {
11+
const NAME: &'static str = "Window";
12+
type Type = super::Window;
13+
type ParentType = gtk::ApplicationWindow;
14+
15+
fn class_init(klass: &mut Self::Class) {
16+
crate::gaussian_plot::GaussianPlot::ensure_type();
17+
klass.bind_template();
18+
}
19+
20+
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
21+
obj.init_template();
22+
}
23+
}
24+
25+
impl ObjectImpl for Window {}
26+
impl WidgetImpl for Window {}
27+
impl WindowImpl for Window {}
28+
impl ApplicationWindowImpl for Window {}

src/window/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
mod imp;
2+
3+
use gtk::glib;
4+
5+
glib::wrapper! {
6+
pub struct Window(ObjectSubclass<imp::Window>)
7+
@extends gtk::ApplicationWindow, gtk::Window, gtk::Widget;
8+
}
9+
10+
impl Window {
11+
pub fn new<P: glib::IsA<gtk::Application>>(app: &P) -> Self {
12+
glib::Object::builder().property("application", app).build()
13+
}
14+
}

0 commit comments

Comments
 (0)