Added both the build() and the begin()+end() options for Plot

- Copied the trick from imgui-rs to notice if a begin() is left
  unmatched
- Changed plot_line to be a struct, but might go away from that
  again. Initial idea was to add things like setting markers and
  colors to this as struct methods, but it seems this will lead
  to lots of code overhead compared to the pushing and popping
  stylevars approach taken in the C++ implementation.
This commit is contained in:
4bb4 2020-08-08 15:40:04 +02:00
parent b832924fad
commit a747fa2d91
2 changed files with 80 additions and 30 deletions

View file

@ -13,8 +13,7 @@ use sys::imgui::im_str;
/// Struct to represent an ImPlot. This is the main construct used to contain all kinds of plots in ImPlot. /// Struct to represent an ImPlot. This is the main construct used to contain all kinds of plots in ImPlot.
/// ///
/// `Plot` is to be used (within an imgui window) with the following pattern: /// `Plot` is to be used (within an imgui window) with the following pattern:
/// ```rust /// ```no_run
/// # // This doctest fails because we don't have an imgui context to run things in here
/// # use implot; /// # use implot;
/// implot::Plot::new("my title") /// implot::Plot::new("my title")
/// .size(300.0, 200.0) // other things such as .x_label("some_label") can be added too /// .size(300.0, 200.0) // other things such as .x_label("some_label") can be added too
@ -91,10 +90,15 @@ impl Plot {
self self
} }
/// Attempt to show the plot. Only do things with it and call `end()` after that /// Attempt to show the plot. If this returns a token, the plot will actually
/// if this returns `true`. Not to be used directly, use `build` instead. /// be drawn. In this case, use the drawing functionality to draw things on the
fn begin(&self) -> bool { /// plot, and then call `end()` on the token when done with the plot.
unsafe { /// If none was returned, that means the plot is not rendered.
///
/// For a convenient implementation of all this, use [`build()`](struct.Plot.html#method.build)
/// instead.
pub fn begin(&self) -> Option<PlotToken> {
let should_render = unsafe {
sys::ImPlot_BeginPlot( sys::ImPlot_BeginPlot(
im_str!("{}", self.title).as_ptr(), im_str!("{}", self.title).as_ptr(),
im_str!("{}", self.x_label).as_ptr(), im_str!("{}", self.x_label).as_ptr(),
@ -109,12 +113,18 @@ impl Plot {
self.x2_flags, self.x2_flags,
self.y2_flags, self.y2_flags,
) )
} };
}
/// End (this) plot. This gets called from build() if should_render {
fn end(&self) { Some(PlotToken {
unsafe { sys::ImPlot_EndPlot() } plot_title: self.title.clone(),
has_ended: false,
})
} else {
// In contrast with imgui windows, end() does not have to be
// called if we don't render. This is more like an imgui popup modal.
None
}
} }
/// Creates a window and runs a closure to construct the contents. /// Creates a window and runs a closure to construct the contents.
@ -122,27 +132,67 @@ impl Plot {
/// Note: the closure is not called if ImPlot::BeginPlot() returned /// Note: the closure is not called if ImPlot::BeginPlot() returned
/// false - TODO(4bb4) figure out if this is if things are not rendered /// false - TODO(4bb4) figure out if this is if things are not rendered
pub fn build<F: FnOnce()>(self, f: F) { pub fn build<F: FnOnce()>(self, f: F) {
if self.begin() { if let Some(token) = self.begin() {
f(); f();
self.end() token.end()
} }
} }
} }
// TODO(4bb4) convert to struct and add methods to set title and flags /// Tracks a plot that must be ended by calling `.end()`
pub struct PlotToken {
/// For better error messages
plot_title: String,
/// Whether end() has been called on this already or not
has_ended: bool,
}
impl PlotToken {
/// End a previously begin()'ed plot.
pub fn end(mut self) {
self.has_ended = true;
unsafe { sys::ImPlot_EndPlot() };
}
}
impl Drop for PlotToken {
fn drop(&mut self) {
if !self.has_ended && !std::thread::panicking() {
panic!(
"Warning: A PlotToken for plot \"{}\" was not called end() on",
self.plot_title
);
}
}
}
/// Struct to provide functionality for plotting a line in a plot.
pub struct PlotLine {
/// Label to show in the legend for this line
label: String,
}
impl PlotLine {
pub fn new(label: &str) -> Self {
PlotLine {
label: label.to_owned(),
}
}
/// Plot a line. Use this in closures passed to [`Plot::build()`](struct.Plot.html#method.build) /// Plot a line. Use this in closures passed to [`Plot::build()`](struct.Plot.html#method.build)
pub fn plot_line(x: &Vec<f64>, y: &Vec<f64>, label: &str) { pub fn plot(&self, x: &Vec<f64>, y: &Vec<f64>) {
unsafe { unsafe {
implot_sys::ImPlot_PlotLinedoublePtrdoublePtr( implot_sys::ImPlot_PlotLinedoublePtrdoublePtr(
im_str!("{}", label).as_ptr() as *const i8, im_str!("{}", self.label).as_ptr() as *const i8,
x.as_ptr(), x.as_ptr(),
y.as_ptr(), y.as_ptr(),
x.len().min(y.len()) as i32, // "as" casts saturate as of Rust 1.45 x.len().min(y.len()) as i32, // "as" casts saturate as of Rust 1.45. This is safe here.
0, 0, // No offset
8, std::mem::size_of::<f64>() as i32, // Stride, set to one f64 for the standard use case
); );
} }
} }
}
/// Show the demo window for poking around what functionality implot has to /// Show the demo window for poking around what functionality implot has to
/// offer. Note that not all of this is necessarily implemented in implot-rs /// offer. Note that not all of this is necessarily implemented in implot-rs

View file

@ -1,5 +1,5 @@
use imgui::*; use imgui::*;
use implot; use implot::{Plot, PlotLine};
mod support; mod support;
@ -21,17 +21,17 @@ fn main() {
ui.checkbox(im_str!("Show demo"), &mut showing_demo); ui.checkbox(im_str!("Show demo"), &mut showing_demo);
// Draw a plot // Draw a plot
implot::Plot::new("Demo plot") Plot::new("Demo plot")
.size(400.0, 300.0) .size(400.0, 300.0)
.x_label("awesome x label") .x_label("awesome x label")
.y_label("awesome y label") .y_label("awesome y label")
.build(|| { .build(|| {
implot::plot_line(&vec![2.0, 2.0], &vec![2.0, 1.0], "Left eye"); PlotLine::new("Left eye").plot(&vec![2.0, 2.0], &vec![2.0, 1.0]);
implot::plot_line(&vec![4.0, 4.0], &vec![2.0, 1.0], "Right eye"); PlotLine::new("Right eye").plot(&vec![4.0, 4.0], &vec![2.0, 1.0]);
let x_values = vec![1.0, 2.0, 4.0, 5.0]; let x_values = vec![1.0, 2.0, 4.0, 5.0];
let y_values = vec![1.0, 0.0, 0.0, 1.0]; let y_values = vec![1.0, 0.0, 0.0, 1.0];
implot::plot_line(&x_values, &y_values, "Mouth"); PlotLine::new("Mouth").plot(&x_values, &y_values);
}); });
}); });