diff --git a/examples/plotter/main.rs b/examples/plotter/main.rs index 63a92398737..8dba1a80c03 100644 --- a/examples/plotter/main.rs +++ b/examples/plotter/main.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: MIT use plotters::prelude::*; -use slint::SharedPixelBuffer; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -14,57 +13,61 @@ slint::slint! { export { MainWindow } from "plotter.slint"; } -fn pdf(x: f64, y: f64, a: f64) -> f64 { - const SDX: f64 = 0.1; - const SDY: f64 = 0.1; - let x = x as f64 / 10.0; - let y = y as f64 / 10.0; - a * (-x * x / 2.0 / SDX / SDX - y * y / 2.0 / SDY / SDY).exp() +#[derive(Debug, Clone, Copy, PartialEq)] +struct PlotVariables { + pitch: f32, + yaw: f32, + amplitude: f32, + width: u32, + height: u32, } -fn render_plot(pitch: f32, yaw: f32, amplitude: f32) -> slint::Image { - let mut pixel_buffer = SharedPixelBuffer::new(640, 480); - let size = (pixel_buffer.width(), pixel_buffer.height()); - - let backend = BitMapBackend::with_buffer(pixel_buffer.make_mut_bytes(), size); - - // Plotters requires TrueType fonts from the file system to draw axis text - we skip that for - // WASM for now. - #[cfg(target_arch = "wasm32")] - let backend = wasm_backend::BackendWithoutText { backend }; - - let root = backend.into_drawing_area(); - - root.fill(&WHITE).expect("error filling drawing area"); - - let mut chart = ChartBuilder::on(&root) - .build_cartesian_3d(-3.0..3.0, 0.0..6.0, -3.0..3.0) - .expect("error building coordinate system"); - chart.with_projection(|mut p| { - p.pitch = pitch as f64; - p.yaw = yaw as f64; - p.scale = 0.7; - p.into_matrix() // build the projection matrix - }); - - chart.configure_axes().draw().expect("error drawing"); - - chart - .draw_series( - SurfaceSeries::xoz( - (-15..=15).map(|x| x as f64 / 5.0), - (-15..=15).map(|x| x as f64 / 5.0), - |x, y| pdf(x, y, amplitude as f64), +fn render_plot(vars: &PlotVariables) -> slint::Image { + fn probability_density_function(x: f64, y: f64, a: f64) -> f64 { + const SDX: f64 = 0.1; + const SDY: f64 = 0.1; + let x = x / 10.0; + let y = y / 10.0; + a * (-x * x / 2.0 / SDX / SDX - y * y / 2.0 / SDY / SDY).exp() + } + + let mut pixel_buffer = slint::SharedPixelBuffer::new(vars.width, vars.height); + { + let size = (pixel_buffer.width(), pixel_buffer.height()); + let backend = BitMapBackend::with_buffer(pixel_buffer.make_mut_bytes(), size); + + // Plotters requires TrueType fonts from the file system to draw axis text - we skip that for + // WASM for now. + #[cfg(target_arch = "wasm32")] + let backend = wasm_backend::BackendWithoutText { backend }; + + let root = backend.into_drawing_area(); + root.fill(&WHITE).expect("error filling drawing area"); + + let mut chart = ChartBuilder::on(&root) + .build_cartesian_3d(-3.0..3.0, 0.0..6.0, -3.0..3.0) + .expect("error building chart"); + chart.with_projection(|mut p| { + p.pitch = vars.pitch as f64; + p.yaw = vars.yaw as f64; + p.scale = 0.62; + p.into_matrix() + }); + chart.configure_axes().draw().expect("error drawing"); + chart + .draw_series( + SurfaceSeries::xoz( + (-30..=30).map(|x| x as f64 / 10.0), + (-30..=30).map(|x| x as f64 / 10.0), + |x, y| probability_density_function(x, y, (vars.amplitude as f64 / 1.0) * 6.0), + ) + .style_func(&|&v| { + (&HSLColor(240.0 / 360.0 - 240.0 / 360.0 * v / 5.0, 1.0, 0.7)).into() + }), ) - .style_func(&|&v| { - (&HSLColor(240.0 / 360.0 - 240.0 / 360.0 * v / 5.0, 1.0, 0.7)).into() - }), - ) - .expect("error drawing series"); - - root.present().expect("error presenting"); - drop(chart); - drop(root); + .expect("error drawing series"); + root.present().expect("error presenting"); + } slint::Image::from_rgb8(pixel_buffer) } @@ -76,9 +79,34 @@ pub fn main() { #[cfg(all(debug_assertions, target_arch = "wasm32"))] console_error_panic_hook::set_once(); - let main_window = MainWindow::new().unwrap(); - - main_window.on_render_plot(render_plot); - - main_window.run().unwrap(); + let main_window = MainWindow::new().expect("Cannot create main window"); + let main_window_weak = main_window.as_weak(); + + let mut plot_variables = + PlotVariables { pitch: -1.0, yaw: -1.0, amplitude: -1.0, width: 0, height: 0 }; + + main_window + .window() + .set_rendering_notifier(move |state, _| { + if let slint::RenderingState::BeforeRendering = state { + if let Some(main_window) = main_window_weak.upgrade() { + let current_plot_variables = PlotVariables { + pitch: main_window.get_pitch(), + yaw: main_window.get_yaw(), + amplitude: main_window.get_amplitude(), + width: main_window.get_texture_width() as u32, + height: main_window.get_texture_height() as u32, + }; + + if current_plot_variables != plot_variables { + plot_variables = current_plot_variables; + main_window.set_texture(render_plot(¤t_plot_variables)); + } + main_window.window().request_redraw(); + } + } + }) + .expect("Unable to set rendering notifier"); + + main_window.run().expect("Failed to run main window"); } diff --git a/examples/plotter/plotter.slint b/examples/plotter/plotter.slint index 30bbf9644e2..b22e2f09846 100644 --- a/examples/plotter/plotter.slint +++ b/examples/plotter/plotter.slint @@ -4,55 +4,57 @@ import { Slider, GroupBox, HorizontalBox, VerticalBox } from "std-widgets.slint"; export component MainWindow inherits Window { - in-out property pitch: 0.15; - in-out property yaw: 0.5; - - pure callback render_plot(/* pitch */ float, /* yaw */ float, /* amplitude */ float) -> image; - + in property texture <=> image.source; + out property texture-width: image.width / 1phx; + out property texture-height: image.height / 1phx; + out property amplitude <=> amplitude.value; + out property pitch: 0.15; + out property yaw: 0.5; title: "Slint Plotter Integration Example"; - preferred-width: 800px; - preferred-height: 600px; - + preferred-width: 999px; + preferred-height: 720px; VerticalBox { Text { font-size: 20px; - text: "2D Gaussian PDF"; + text: "2D Gaussian Probability Density Function"; horizontal-alignment: center; } - Image { - source: root.render_plot(root.pitch, root.yaw, amplitude-slider.value / 10); - touch := TouchArea { - property pressed-pitch; - property pressed-yaw; - - pointer-event(event) => { - if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) { - self.pressed-pitch = root.pitch; - self.pressed-yaw = root.yaw; + HorizontalBox { + image := Image { + touch := TouchArea { + property starting-drag-pitch; + property starting-drag-yaw; + pointer-event(event) => { + if (event.kind == PointerEventKind.down && event.button == PointerEventButton.left) { + self.starting-drag-pitch = root.pitch; + self.starting-drag-yaw = root.yaw; + } } - } - moved => { - if (self.enabled && self.pressed) { - root.pitch = self.pressed-pitch + (touch.mouse-y - touch.pressed-y) / self.height * 3.14; - root.yaw = self.pressed-yaw - (touch.mouse-x - touch.pressed-x) / self.width * 3.14; + moved => { + if (self.enabled && self.pressed) { + root.pitch = self.starting-drag-pitch + (touch.mouse-y - touch.pressed-y) / self.height * 3.14; + root.yaw = self.starting-drag-yaw - (touch.mouse-x - touch.pressed-x) / self.width * 3.14; + } } + mouse-cursor: self.pressed ? MouseCursor.grabbing : MouseCursor.grab; } - mouse-cursor: self.pressed ? MouseCursor.grabbing : MouseCursor.grab; } - } - HorizontalBox { - Text { - text: "Amplitude:"; - font-weight: 600; - vertical-alignment: center; - } + VerticalBox { + amplitude := Slider { + orientation: Orientation.vertical; + transform-rotation: -180deg; + minimum: 0; + maximum: 1; + value: 0.5; + } - amplitude-slider := Slider { - minimum: 0; - maximum: 100; - value: 50; + Text { + text: "Amplitude"; + font-weight: 600; + vertical-alignment: center; + } } } } diff --git a/rustfmt.toml b/rustfmt.toml index eb21ec39df3..61bf9fb3ff3 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -3,3 +3,4 @@ edition = "2021" use_small_heuristics = "Max" +max_width = 100