diff --git a/Cargo.toml b/Cargo.toml index eb2b46e..f922eb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,11 +7,20 @@ license = "MIT" name = "dataplotlib" version = "0.1.3" +[features] +default = ["use-sdl2"] +use-sdl2 = ["sdl2_mt"] +use-image = ["image"] + [dependencies] -[dependencies.sdl2] -features = ["gfx", "image"] -version = "0.27.2" +[dependencies.image] +version = "0.15.0" +optional = true + +[dependencies.sdl2_mt] +version = "0.1.0" +optional = true [lib] name = "dataplotlib" diff --git a/examples/coloredxy.rs b/examples/coloredxy.rs index 692f4cb..9022d52 100644 --- a/examples/coloredxy.rs +++ b/examples/coloredxy.rs @@ -2,6 +2,7 @@ extern crate dataplotlib; use dataplotlib::util::{linspace, zip2}; use dataplotlib::plotbuilder::PlotBuilder2D; use dataplotlib::plotter::Plotter; +use dataplotlib::draw_sdl::DrawSDL; fn main() { let x = linspace(0, 10, 100); @@ -18,7 +19,9 @@ fn main() { pb.add_color_xy(xy_sin, [1.0, 0.0, 0.0, 1.0]); pb.add_color_xy(xy_lin, [0.0, 0.0, 1.0, 1.0]); + let sdlh = dataplotlib::sdl2_init(); + let sdl2_window = DrawSDL::new(sdlh); + let mut plt = Plotter::new(); - plt.plot2d(pb); - plt.join(); + plt.plot2d(pb, sdl2_window); } diff --git a/examples/simplexy.rs b/examples/simplexy.rs index 18d2b70..12c134c 100644 --- a/examples/simplexy.rs +++ b/examples/simplexy.rs @@ -2,6 +2,7 @@ extern crate dataplotlib; use dataplotlib::util::{linspace, zip2}; use dataplotlib::plotbuilder::PlotBuilder2D; use dataplotlib::plotter::Plotter; +use dataplotlib::draw_sdl::DrawSDL; fn main() { let x = linspace(0, 10, 100); @@ -18,7 +19,9 @@ fn main() { pb.add_simple_xy(xy_sin); pb.add_simple_xy(xy_lin); + let sdlh = dataplotlib::sdl2_init(); + let sdl2_window = DrawSDL::new(sdlh); + let mut plt = Plotter::new(); - plt.plot2d(pb); - plt.join(); + plt.plot2d(pb, sdl2_window); } diff --git a/src/draw.rs b/src/draw.rs index 81fec32..73cff6b 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -1,30 +1,61 @@ #![allow(dead_code)] +#[derive(Copy, Clone, Debug)] pub enum MouseButton { Left, Middle, Right, } +#[derive(Copy, Clone, Debug)] pub enum Event { Quit, Resize(f64, f64), KeyDown(i32), KeyUp(i32), - MouseDown(Option, f64, f64), - MouseUp(Option, f64, f64), - MouseMove(Option, f64, f64), + MouseDown(MouseButton, f64, f64), + MouseUp(MouseButton, f64, f64), + MouseMove(MouseButton, f64, f64), + MouseScroll(i32, i32), } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub struct Range { pub min: f64, pub max: f64, } -pub trait Drawable { +impl Range { + pub fn size(&self) -> f64 { + self.max - self.min + } + + pub fn contains(&self, pt: f64) -> bool { + pt <= self.max && pt >= self.min + } +} + +#[derive(Copy, Clone, Debug)] +pub struct Range2d(pub Range, pub Range); + +impl Range2d { + pub fn contains(&self, pt: (f64, f64)) -> bool { + self.0.contains(pt.0) && self.1.contains(pt.1) + } +} + +impl From<(Range, Range)> for Range2d { + fn from((a, b): (Range, Range)) -> Self { + Range2d(a, b) + } +} + +pub trait Drawable: Send { /// Sets the visible range of worldspace - fn set_view(&mut self, x: Range, y: Range); + fn set_view(&mut self, view: Range2d); + + /// Gets the visible range of worldspace + fn get_view(&self) -> Range2d; /// Set color for various drawing actions fn set_color(&mut self, color: [u8; 4]); @@ -35,12 +66,27 @@ pub trait Drawable { /// Draws a line from (x, y) -> (x, y) in worldspace fn line(&mut self, a: (f64, f64), b: (f64, f64)); + /// Draws a line from (x, y) -> (x, y) in worldspace + fn thick_line(&mut self, a: (f64, f64), b: (f64, f64), thickness: u16); + /// Draws a rectangle bounded by two corners fn rectangle(&mut self, a: (f64, f64), b: (f64, f64)); - /// Returns the next pending event + /// Draws a rectangle bounded by two corners + fn unfilled_rectangle(&mut self, a: (f64, f64), b: (f64, f64)); + + /// Presents the previously drawn buffer + fn present(&mut self); + + /// Returns the next pending events fn get_events(&mut self) -> Vec; /// Asks that the Drawable stop any tasks and cleanup fn close(&mut self) {} // provide empty default impl } + +pub fn point2window(pt: f64, view: Range, window: Range, invert: bool) -> f64 { + let moved_pt = if invert { view.max - pt } else { pt - view.min }; + + (moved_pt / view.size()) * (window.size() + window.min) +} \ No newline at end of file diff --git a/src/draw_sdl.rs b/src/draw_sdl.rs index 19df3ed..14988ae 100644 --- a/src/draw_sdl.rs +++ b/src/draw_sdl.rs @@ -1,55 +1,53 @@ #![allow(dead_code)] #![allow(unused_variables)] -use sdl2::{self, Sdl, EventPump}; -use sdl2::render::Renderer; -use sdl2::event::Event as SdlEvent; -use sdl2::pixels; +use sdl2_mt; +use sdl2_mt::event::Event as SdlEvent; +use sdl2_mt::pixels; -use sdl2::gfx::primitives::DrawRenderer; +use sdl2_mt::Sdl2Mt; use draw::*; pub struct DrawSDL { - context: Sdl, - events: EventPump, - renderer: Renderer<'static>, - screenspace: (Range, Range), + sdlh: Sdl2Mt, + window_id: u32, + screenspace: Range2d, + realspace: Range2d, color: pixels::Color, } impl DrawSDL { - pub fn new() -> DrawSDL { - let sdl_context = sdl2::init().unwrap(); - let video_subsys = sdl_context.video().unwrap(); - let window = video_subsys.window("2D plot", 720, 720) - .position_centered() - .resizable() - .opengl() - .build() - .unwrap(); + pub fn new(sdlh: Sdl2Mt) -> Box { + let window_id = sdlh.create_simple_window("2D plot", 720, 720).unwrap(); - let events = sdl_context.event_pump().unwrap(); - let renderer = window.renderer().build().unwrap(); + let default_s = Range { min: 0.0, max: 0.0 }; - let default = Range { + let default_r = Range { min: 0.0, - max: 0.0, + max: 720.0, }; - DrawSDL { - context: sdl_context, - events: events, - renderer: renderer, - screenspace: (default, default), + Box::new(DrawSDL { + sdlh, + window_id, + screenspace: Range2d(default_s, default_s), + realspace: Range2d(default_r, default_r), color: pixels::Color::RGBA(0, 0, 0, 255), - } + }) } } impl Drawable for DrawSDL { /// Sets the visible range of worldspace - fn set_view(&mut self, x: Range, y: Range) {} + fn set_view(&mut self, view: Range2d) { + self.screenspace = view; + } + + /// Gets the visible range of worldspace + fn get_view(&self) -> Range2d { + self.screenspace + } /// Set color for various drawing actions fn set_color(&mut self, color: [u8; 4]) { @@ -58,49 +56,158 @@ impl Drawable for DrawSDL { /// Clears the output surface fn clear(&mut self) { - self.renderer.set_draw_color(self.color); - self.renderer.clear(); + let window_id = self.window_id; + let color = self.color; + self.sdlh + .run_on_ui_thread(Box::new(move |_sdl, windows| { + let canvas = windows.get_mut(&window_id).unwrap(); + canvas.set_draw_color(color); + canvas.clear(); + })) + .unwrap(); } /// Draws a line from (x, y) -> (x, y) in worldspace - fn line(&mut self, (x1, y1): (f64, f64), (x2, y2): (f64, f64)) { - - // need to use point2plot! + fn line(&mut self, p1: (f64, f64), p2: (f64, f64)) { + self.thick_line(p1, p2, 1); + } - self.renderer - .thick_line(x1 as i16, y1 as i16, x2 as i16, y2 as i16, 2, self.color) + /// Draws a line from (x, y) -> (x, y) in worldspace + fn thick_line(&mut self, (x1, y1): (f64, f64), (x2, y2): (f64, f64), thickness: u16) { + + let x1 = point2window(x1, self.screenspace.0, self.realspace.0, false); + let y1 = point2window(y1, self.screenspace.1, self.realspace.1, true); + + let x2 = point2window(x2, self.screenspace.0, self.realspace.0, false); + let y2 = point2window(y2, self.screenspace.1, self.realspace.1, true); + + let window_id = self.window_id; + let color = self.color; + self.sdlh + .run_on_ui_thread(Box::new(move |_sdl, windows| { + let canvas = windows.get_mut(&window_id).unwrap(); + canvas.set_draw_color(color); + canvas + .draw_line((x1 as i32, y1 as i32), (x2 as i32, y2 as i32)) + .unwrap(); + })) .unwrap(); } /// Draws a rectangle bounded by two corners fn rectangle(&mut self, (x1, y1): (f64, f64), (x2, y2): (f64, f64)) { - // need to use point2plot! + let x1 = point2window(x1, self.screenspace.0, self.realspace.0, false); + let y1 = point2window(y1, self.screenspace.1, self.realspace.1, false); + + let x2 = point2window(x2, self.screenspace.0, self.realspace.0, false); + let y2 = point2window(y2, self.screenspace.1, self.realspace.1, false); + + let x1 = x1 as i32; + let y1 = y1 as i32; + let w = (x2 as i32 - x1) as u32; + let h = (y2 as i32 - y1) as u32; + + use sdl2_mt::rect::Rect; + + let window_id = self.window_id; + let color = self.color; + self.sdlh + .run_on_ui_thread(Box::new(move |_sdl, windows| { + let canvas = windows.get_mut(&window_id).unwrap(); + canvas.set_draw_color(color); + canvas.fill_rect(Rect::new(x1, y1, w, h)).unwrap(); + })) + .unwrap(); + } + + /// Draws a rectangle bounded by two corners + fn unfilled_rectangle(&mut self, (x1, y1): (f64, f64), (x2, y2): (f64, f64)) { + + let x1 = point2window(x1, self.screenspace.0, self.realspace.0, false); + let y1 = point2window(y1, self.screenspace.1, self.realspace.1, false); + + let x2 = point2window(x2, self.screenspace.0, self.realspace.0, false); + let y2 = point2window(y2, self.screenspace.1, self.realspace.1, false); + + let x1 = x1 as i32; + let y1 = y1 as i32; + let w = (x2 as i32 - x1) as u32; + let h = (y2 as i32 - y1) as u32; + + use sdl2_mt::rect::Rect; - self.renderer - .rectangle(x1 as i16, y1 as i16, x2 as i16, y2 as i16, self.color) + let window_id = self.window_id; + let color = self.color; + self.sdlh + .run_on_ui_thread(Box::new(move |_sdl, windows| { + let canvas = windows.get_mut(&window_id).unwrap(); + canvas.set_draw_color(color); + canvas.draw_rect(Rect::new(x1, y1, w, h)).unwrap(); + })) + .unwrap(); + } + + fn present(&mut self) { + let window_id = self.window_id; + self.sdlh + .run_on_ui_thread(Box::new(move |_sdl, windows| { + let canvas = windows.get_mut(&window_id).unwrap(); + canvas.present(); + })) .unwrap(); } /// Returns the next pending event fn get_events(&mut self) -> Vec { - let mut events = Vec::new(); + use std::sync::mpsc::channel; + + let (tx, rx) = channel(); + + let window_id = self.window_id; + + self.sdlh + .handle_ui_events(Box::new(move |_sdl, _windows, event| { + match event { + &SdlEvent::Quit { .. } => tx.send(Event::Quit).unwrap(), + + &SdlEvent::MouseWheel { x, y, .. } => { + tx.send(Event::MouseScroll(x, y)).unwrap(); + } + + &SdlEvent::KeyDown { + window_id, + keycode: Some(keycode), + .. + } => { + tx.send(Event::KeyDown(keycode as i32)).unwrap(); + } + &SdlEvent::Window { + window_id, + win_event: sdl2_mt::event::WindowEvent::Resized(new_w, new_h), + .. + } => { + tx.send(Event::Resize(new_w as f64, new_h as f64)).unwrap(); + } + _ => return false, + } + true + })) + .unwrap(); - for event in self.events.poll_iter() { - match event { - SdlEvent::Quit { .. } => return vec![Event::Quit], + let events: Vec<_> = rx.iter().collect(); - SdlEvent::KeyDown { keycode: Some(keycode), .. } => { - events.push(Event::KeyDown(keycode as i32)); - } - SdlEvent::Window { win_event: sdl2::event::WindowEvent::Resized(new_w, new_h), .. } => { - events.push(Event::Resize(new_w as f64, new_h as f64)); - } - _ => {} - } + // let's find the last window resize event and save its values + if let Some(&Event::Resize(w, h)) = + events.iter().rev().find(|&event| match event { + &Event::Resize(_, _) => true, + _ => false, + }) + { + self.realspace = Range2d(Range { min: 0.0, max: w }, Range { min: 0.0, max: h }); } - return events; + events } } diff --git a/src/lib.rs b/src/lib.rs index 1644c9c..125e1e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,11 +29,17 @@ //! } //! ``` -#[macro_use] -extern crate sdl2; +#[cfg(feature = "use-sdl2")] +extern crate sdl2_mt; +#[cfg(feature = "use-sdl2")] +pub use sdl2_mt::init as sdl2_init; +#[cfg(feature = "use-sdl2")] +pub mod draw_sdl; -mod draw; -mod draw_sdl; +#[cfg(feature = "use-image")] +extern crate image; + +pub mod draw; mod plot; pub mod plotter; pub mod plotbuilder; diff --git a/src/plot.rs b/src/plot.rs index 9d389fd..bd081cd 100644 --- a/src/plot.rs +++ b/src/plot.rs @@ -2,33 +2,14 @@ //! //! Users of **dataplotlib** should not need to access **plot**. -use sdl2; - -use sdl2::event::Event; -use sdl2::pixels; -use sdl2::keyboard::Keycode; - -use sdl2::gfx::primitives::DrawRenderer; -use sdl2::video::Window; - -use std::cmp::min; use std::time::Duration; use std::{mem, thread, f64}; use plotbuilder::*; +use draw::{Drawable, Event, Range, Range2d}; -pub struct Plot { -} - -// pt: a point on a 1 dimensional line segment -// min: the closest point to render on the line segment -// max: the farthest point to render on the line segment -// length: the length of the 1 dimensional window space -// space: the offset from the beginning of the line segment -fn point2plot(pt: f64, min: f64, max: f64, length: f64, space: f64) -> i16 { - (((pt - min) / (max - min)) * (length - space) + space) as i16 -} +pub struct Plot {} fn get_max(user_max: Option, values: &Vec) -> f64 { if let Some(max) = user_max { @@ -58,30 +39,24 @@ fn get_min(user_min: Option, values: &Vec) -> f64 { } } -fn f32_4_to_color(col: [f32; 4]) -> pixels::Color { - pixels::Color::RGBA((col[0] * 255f32) as u8, - (col[1] * 255f32) as u8, - (col[2] * 255f32) as u8, - (col[3] * 255f32) as u8) +fn f32_4_to_color(col: [f32; 4]) -> [u8; 4] { + [ + (col[0] * 255f32) as u8, + (col[2] * 255f32) as u8, + (col[1] * 255f32) as u8, + (col[3] * 255f32) as u8, + ] } -fn draw_borders(bordercol: pixels::Color, bgcol: pixels::Color, space: f64, m: f64, renderer: &mut sdl2::render::Renderer) { - renderer.set_draw_color(bordercol); +fn draw_borders(bordercol: [u8; 4], bgcol: [u8; 4], space: (f64, f64), m: (f64, f64), renderer: &mut Drawable) { + renderer.set_color(bordercol); renderer.clear(); - renderer.rectangle((space - 1.0) as i16, - (space - 1.0) as i16, - (m - 1.0) as i16, - (m - 1.0) as i16, - pixels::Color::RGBA(0, 0, 255, 255)) - .unwrap(); - - renderer.rectangle((space + 1.0) as i16, - (space + 1.0) as i16, - (m + 1.0) as i16, - (m + 1.0) as i16, - bgcol) - .unwrap(); + renderer.set_color(bgcol); + renderer.rectangle(space, m); + + renderer.set_color([0, 0, 255, 255]); + renderer.unfilled_rectangle(space, m); } fn set_xy(xy: &Vec<(f64, f64)>, x_vector: &mut Vec>, y_vector: &mut Vec>) { @@ -96,85 +71,168 @@ fn set_xy(xy: &Vec<(f64, f64)>, x_vector: &mut Vec>, y_vector: &mut Vec } } -fn draw_plots(sdl_context: sdl2::Sdl, window: Window, xs: &Vec>, ys: &Vec>, colors: &Vec<[f32; 4]>, plot_bounds: [f64; 4]) { +fn clip_line(mut a: (f64, f64), mut b: (f64, f64), view: Range2d) -> Option<((f64, f64), (f64, f64))> { + //trivial accept + if view.contains(a) && view.contains(b) { + return Some((a, b)); + } + + //trivial reject: + // check if both are to the right of the graph + if a.0 > view.0.max && b.0 > view.0.max + // check if both are to the left + || a.0 < view.0.min && b.0 < view.0.min + // check if both are above + || a.1 > view.1.max && b.1 > view.1.max + // check if both are below + || a.1 < view.1.min && b.1 < view.1.min + { + return None; + } + + let slope = (b.1 - a.1) / (b.0 - a.0); + + let ymin = slope * (view.0.min - a.0) + a.1; + let ymax = slope * (view.0.max - a.0) + a.1; + + if a.0 < view.0.min { + a = (view.0.min, ymin); + } else if b.0 < view.0.min { + b = (view.0.min, ymin); + } else if a.0 > view.0.max { + a = (view.0.max, ymax); + } else if b.0 > view.0.max { + b = (view.0.max, ymax); + } + + let slope = 1. / slope; + + let xmin = slope * (view.1.min - a.1) + a.0; + let xmax = slope * (view.1.max - a.1) + a.0; + + if a.1 < view.1.min { + a = (xmin, view.1.min); + } else if b.1 < view.1.min { + b = (xmin, view.1.min); + } else if a.1 > view.1.max { + a = (xmax, view.1.max); + } else if b.1 > view.1.max { + b = (xmax, view.1.max); + } + + return clip_line(a, b, view); +} + +fn draw_plots(renderer: &mut Drawable, xs: &Vec>, ys: &Vec>, colors: &Vec<[f32; 4]>, plot_bounds: [f64; 4]) { let bordercol = f32_4_to_color([0.95, 0.95, 0.95, 1.0]); let bgcol = f32_4_to_color([1.0, 1.0, 1.0, 1.0]); let margin = 0.05; - let invmargin = 1.0 - margin; - - let x_max = plot_bounds[0]; - let y_max = plot_bounds[1]; - let x_min = plot_bounds[2]; - let y_min = plot_bounds[3]; - - let mut renderer = window.renderer().build().unwrap(); - let (mut w, mut h) = renderer.output_size().unwrap(); - - let mut events = sdl_context.event_pump().unwrap(); - - let mut update_frame = |w, h| { - // println!("(w, h) = ({}, {})", w, h); - let m = min(w, h) as f64; - let space = m * margin; - let m = m * invmargin; - - renderer.set_draw_color(bgcol); - renderer.clear(); - draw_borders(bordercol, bgcol, space, m, &mut renderer); - - let y0 = (m + space) as i16 - point2plot(0.0, y_min, y_max, m, space); - let xn = m; - println!("xn: {}", xn); - renderer.thick_line(space as i16, - y0, - xn as i16, - y0, - 2, - pixels::Color::RGBA(0, 0, 0, 255)) - .unwrap(); + + let w = Range { + min: plot_bounds[2], + max: plot_bounds[0], + }; + + let h = Range { + min: plot_bounds[3], + max: plot_bounds[1], + }; + + renderer.set_view(Range2d(w, h)); + + let update_frame = |renderer: &mut Drawable| { + let Range2d(w, h) = renderer.get_view(); + + // calculate margins around plot + let w_marg = w.size() * margin; + let h_marg = h.size() * margin; + + // set up a "fake" plot view that has extra margins + let w_fake = Range { + min: w.min - w_marg, + max: w.max + w_marg, + }; + let h_fake = Range { + min: h.min - h_marg, + max: h.max + h_marg, + }; + renderer.set_view(Range2d(w_fake, h_fake)); + + // the borders are just the edges of the real view + let border_min = (w.min, h.min); + let border_max = (w.max, h.max); + + draw_borders(bordercol, bgcol, border_min, border_max, renderer); for i in 0..colors.len() { let color = colors[i]; let color_rgba = f32_4_to_color(color); + renderer.set_color(color_rgba); - let y_inv = (m + space) as i16; - let yt: Vec = ys[i].iter().map(|y| y_inv - point2plot(*y, y_min, y_max, m, space)).collect(); - let xt: Vec = xs[i].iter().map(|x| point2plot(*x, x_min, x_max, m, space)).collect(); + let yt = &ys[i]; + let xt = &xs[i]; // The number of points let len = xs[i].len(); - for i in 0..len - 1 { - let (xa, ya) = (xt[i + 0], yt[i + 0]); - let (xb, yb) = (xt[i + 1], yt[i + 1]); - renderer.thick_line(xa, ya, xb, yb, 2, color_rgba).unwrap(); + for j in 0..len - 1 { + let a = (xt[j + 0], yt[j + 0]); + let b = (xt[j + 1], yt[j + 1]); + + if let Some(((xa, ya), (xb, yb))) = clip_line(a, b, Range2d(w, h)) { + renderer.thick_line((xa, ya), (xb, yb), 2); + } } } + renderer.present(); + + // reset the view to the real view + renderer.set_view(Range2d(w, h)); }; - update_frame(w, h); + + update_frame(renderer); 'main: loop { - for event in events.poll_iter() { + let mut update = false; + for event in renderer.get_events() { match event { Event::Quit { .. } => break 'main, - Event::KeyDown { keycode: Some(keycode), .. } => { - if keycode == Keycode::Escape { + Event::KeyDown(keycode) => { + if keycode == 1 { + //Keycode::Escape { break 'main; } } - Event::Window { win_event: sdl2::event::WindowEvent::Resized(new_w, new_h), .. } => { - w = new_w as u32; - h = new_h as u32; - update_frame(w, h); + Event::MouseScroll(_x, y) => { + let multiplier = (y as f64) / 10.0; + let Range2d(w, h) = renderer.get_view(); + let w_offset = w.size() * multiplier; + let new_w = Range { + min: w.min - w_offset, + max: w.max + w_offset, + }; + let h_offset = h.size() * multiplier; + let new_h = Range { + min: h.min - h_offset, + max: h.max + h_offset, + }; + renderer.set_view(Range2d(new_w, new_h)); + update = true; + } + Event::Resize(_, _) => { + update = true; } _ => {} } } - thread::sleep(Duration::from_millis(30)); + if update { + update_frame(renderer); + } + thread::sleep(Duration::from_millis(16)); } } @@ -194,31 +252,19 @@ fn get_plot_bounds(plot_builder: &PlotBuilder2D, xs: &Vec>, ys: &Vec) { let mut pvs = Vec::new(); mem::swap(&mut plot_builder.pvs, &mut pvs); @@ -239,11 +285,6 @@ impl Plot { // [MAX_X, MAX_Y, MIN_X, MIN_Y] let plot_bounds: [f64; 4] = get_plot_bounds(&plot_builder, &x_points, &y_points); - draw_plots(sdl_context, - window, - &x_points, - &y_points, - &colors, - plot_bounds); + draw_plots(&mut *renderer, &x_points, &y_points, &colors, plot_bounds); } } diff --git a/src/plotter.rs b/src/plotter.rs index 20bd954..a0aa312 100644 --- a/src/plotter.rs +++ b/src/plotter.rs @@ -9,6 +9,7 @@ use std::thread; use plotbuilder::PlotBuilder2D; use plot::Plot; +use draw; pub struct Plotter { plots: Vec>, @@ -21,20 +22,38 @@ impl Plotter { } /// `plot2d` is currently the only supported plotting function. It takes a `PlotBuilder2D` containing all needed information. - pub fn plot2d(&mut self, plotbuilder: PlotBuilder2D) { - self.plots.push(thread::spawn(move || { - Plot::new2d(plotbuilder); - })); + pub fn plot2d(&mut self, plotbuilder: PlotBuilder2D, drawable: Box) { + self.plots.push(thread::spawn( + move || { Plot::new2d(plotbuilder, drawable); }, + )); + } + + /// The `disown` function allows the thread that owns the `Plotter` to keep going without either `join`ing manually or letting the `Drop` trait force a `join`. + pub fn disown(self) { + ::std::mem::forget(self); } /// The `join` function allows the thread that owns the `Plotter` to wait until the user has closed all open plot windows before continuing. - pub fn join(self) { - for t in self.plots { + pub fn join(mut self) { + self._join(); + } + + /// The internal version of `join` that only really takes a mutable reference to allow `join`ing during `Drop`. + fn _join(&mut self) { + let mut plots = vec![]; + ::std::mem::swap(&mut self.plots, &mut plots); + for t in plots { let _ = t.join(); } } } +impl Drop for Plotter { + fn drop(&mut self) { + self._join(); + } +} + #[cfg(test)] mod test { use super::*;