From a257332f5911fee5f22e5053b5066f19353a38b2 Mon Sep 17 00:00:00 2001 From: 4bb4 <67376761+4bb4@users.noreply.github.com> Date: Sun, 30 Aug 2020 11:01:28 +0200 Subject: [PATCH] Added marker, stylevar and querying support --- README.md | 15 +- implot-examples/examples/hello_world.rs | 40 ++-- src/lib.rs | 231 ++++++++++++++++++++---- 3 files changed, 225 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 815a5db..e6723da 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,9 @@ If you spot any design inconsistencies or papercuts, feel free to open an issue. Currently a work in progress. The author is open to collaboration, if you'd like to help, feel free to reach out via a Github issue. +Note that the API is not stabilized yet and expected to change as development progresses. +Once there are actual releases on crates.io, semantic versioning will be followed. + At this point, raw bindings are working in implot-sys, and more idiomatic interfaces for plot creation as well a subset of the functionality for plots are implemented. See below for an overview of the progress. @@ -46,20 +49,20 @@ See below for an overview of the progress. - [ ] Plot customization - [x] Axis flags - [x] Styling colors - - [ ] Markers - - [ ] Styling variables + - [x] Styling variables - [ ] Colormaps - [ ] Plot querying - [x] is hovered - [x] mouse position in plot - [x] plot limits - - [ ] is queried - - [ ] GetPlotQuery + - [x] is queried + - [x] get plot query + - [ ] Choice of y axis - [ ] Utils - [x] Plot limit setting - - [ ] imgui-rs style safe push/pop stacks + - [x] imgui-rs style safe push/pop stacks - [ ] Plot tick setting - - [ ] Plot y axis setting for subsequent elements + - [ ] Set Y axis setting for subsequent elements - [ ] Plot position and size reading - [ ] Pixel to plot position - [ ] Plot to pixel position diff --git a/implot-examples/examples/hello_world.rs b/implot-examples/examples/hello_world.rs index 7762a9b..e996f91 100644 --- a/implot-examples/examples/hello_world.rs +++ b/implot-examples/examples/hello_world.rs @@ -1,10 +1,14 @@ +// This is a somewhat messy example that currently just gets used to test out functionality as it +// is built. It will be taken apart into separate examples with clearer demo purposes later. + use imgui::*; use implot::{ - get_plot_limits, get_plot_mouse_position, is_plot_hovered, pop_style_color, push_style_color, + get_plot_limits, get_plot_mouse_position, get_plot_query, is_plot_hovered, is_plot_queried, + push_style_color, push_style_var_f32, push_style_var_u32, }; use implot::{ - AxisFlags, ImPlotLimits, ImPlotPoint, ImPlotRange, Plot, PlotColorElement, PlotFlags, PlotLine, - PlotText, + AxisFlags, ImPlotLimits, ImPlotPoint, ImPlotRange, Marker, Plot, PlotColorElement, PlotFlags, + PlotLine, PlotText, StyleVar, }; mod support; @@ -29,9 +33,10 @@ fn main() { // Create some containers for exfiltrating data from the closure below let mut hover_pos: Option = None; let mut plot_limits: Option = None; + let mut query_limits: Option = None; // Draw a plot - push_style_color(&PlotColorElement::PLOT_BG, 1.0, 1.0, 1.0, 0.2); + let style = push_style_color(&PlotColorElement::PlotBg, 1.0, 1.0, 1.0, 0.2); Plot::new("Demo plot") .size(400.0, 300.0) .x_label("awesome x label") @@ -48,8 +53,15 @@ fn main() { .with_y_axis_flags(&(AxisFlags::DEFAULT | AxisFlags::INVERT)) .build(|| { // Line plotting + let markerchoice = + push_style_var_u32(&StyleVar::Marker, Marker::CROSS.bits()); PlotLine::new("Left eye").plot(&vec![2.0, 2.0], &vec![2.0, 1.0]); + markerchoice.pop(); + + let lineweight = push_style_var_f32(&StyleVar::LineWeight, 5.0); PlotLine::new("Right eye").plot(&vec![4.0, 4.0], &vec![2.0, 1.0]); + lineweight.pop(); + let x_values = vec![1.0, 2.0, 4.0, 5.0]; let y_values = vec![1.0, 0.0, 0.0, 1.0]; PlotLine::new("Mouth").plot(&x_values, &y_values); @@ -63,6 +75,10 @@ fn main() { if is_plot_hovered() { hover_pos = Some(get_plot_mouse_position()); } + + if is_plot_queried() { + query_limits = Some(get_plot_query()); + } plot_limits = Some(get_plot_limits()); }); @@ -73,18 +89,12 @@ fn main() { ui.text(im_str!("hovered at {}, {}", pos.x, pos.y)); } if let Some(limits) = plot_limits { - ui.text(im_str!( - "X limits are {:+10.3}, {:+10.3}", - limits.X.Min, - limits.X.Max - )); - ui.text(im_str!( - "Y limits are {:+10.3}, {:+10.3}", - limits.Y.Min, - limits.Y.Max - )); + ui.text(im_str!("Plot limits are {:#?}", limits)); } - pop_style_color(1); + if let Some(query) = query_limits { + ui.text(im_str!("Query limits are {:#?}", query)); + } + style.pop(); }); if showing_demo { diff --git a/src/lib.rs b/src/lib.rs index ded3277..398cd99 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ //! pub extern crate implot_sys as sys; use bitflags::bitflags; +use std::convert::TryFrom; use sys::imgui::im_str; pub use sys::imgui::Condition; // TODO(4bb4) facade-wrap these @@ -18,6 +19,9 @@ const DEFAULT_PLOT_SIZE_X: f32 = 400.0; const DEFAULT_PLOT_SIZE_Y: f32 = 400.0; // --- Enum definitions -------------------------------------------------------------------------- +// Things that are to be combined like flags are done using bitflags, and things that are meant +// as enumerations in the traditional sense are plain enums. + bitflags! { /// Window hover check option flags. Documentation copied from implot.h for convenience. #[repr(transparent)] @@ -82,42 +86,91 @@ bitflags! { } bitflags! { - /// Colorable plot elements. These are called "ImPlotCol" in ImPlot itself, but I found that - /// name somewhat confusing because we are not referring to colors, but _which_ thing can - /// be colored - hence I added the "Element". + /// Axis flags. Documentation copied from implot.h for convenience. #[repr(transparent)] - pub struct PlotColorElement: u32 { - /// Plot line/outline color (defaults to next unused color in current colormap) - const LINE = sys::ImPlotCol__ImPlotCol_Line; - /// Plot fill color for bars (defaults to the current line color) - const FILL = sys::ImPlotCol__ImPlotCol_Fill; - /// Marker outline color (defaults to the current line color) - const MARKER_OUTLINE = sys::ImPlotCol__ImPlotCol_MarkerOutline; - /// Marker fill color (defaults to the current line color) - const MARKER_FILL = sys::ImPlotCol__ImPlotCol_MarkerFill; - /// Error bar color (defaults to text color) - const ERROR_BAR = sys::ImPlotCol__ImPlotCol_ErrorBar; - /// Plot frame background color (defaults to FRAME_BG) - const FRAME_BG = sys::ImPlotCol__ImPlotCol_FrameBg; - /// Plot area background color (defaults to WINDOW_BG) - const PLOT_BG = sys::ImPlotCol__ImPlotCol_PlotBg; - /// Plot area border color (defaults to text color) - const PLOT_BORDER = sys::ImPlotCol__ImPlotCol_PlotBorder; - /// X-axis grid/label color (defaults to 25% text color) - const X_AXIS = sys::ImPlotCol__ImPlotCol_XAxis; - /// Y-axis grid/label color (defaults to 25% text color) - const Y_AXIS = sys::ImPlotCol__ImPlotCol_YAxis; - /// 2nd y-axis grid/label color (defaults to 25% text color) - const Y_AXIS2 = sys::ImPlotCol__ImPlotCol_YAxis2; - /// 3rd y-axis grid/label color (defaults to 25% text color) - const Y_AXIS3 = sys::ImPlotCol__ImPlotCol_YAxis3; - /// Box-selection color (defaults to yellow) - const SELECTION = sys::ImPlotCol__ImPlotCol_Selection; - /// Box-query color (defaults to green) - const QUERY = sys::ImPlotCol__ImPlotCol_Query; + pub struct Marker: u32 { + /// no marker + const NONE = sys::ImPlotMarker__ImPlotMarker_None; + /// a circle marker will be rendered at each point + const CIRCLE = sys::ImPlotMarker__ImPlotMarker_Circle; + /// a square maker will be rendered at each point + const SQUARE = sys::ImPlotMarker__ImPlotMarker_Square; + /// a diamond marker will be rendered at each point + const DIAMOND = sys::ImPlotMarker__ImPlotMarker_Diamond; + /// an upward-pointing triangle marker will up rendered at each point + const UP = sys::ImPlotMarker__ImPlotMarker_Up; + /// an downward-pointing triangle marker will up rendered at each point + const DOWN = sys::ImPlotMarker__ImPlotMarker_Down; + /// an leftward-pointing triangle marker will up rendered at each point + const LEFT = sys::ImPlotMarker__ImPlotMarker_Left; + /// an rightward-pointing triangle marker will up rendered at each point + const RIGHT = sys::ImPlotMarker__ImPlotMarker_Right; + /// a cross marker will be rendered at each point (not filled) + const CROSS = sys::ImPlotMarker__ImPlotMarker_Cross; + /// a plus marker will be rendered at each point (not filled) + const PLUS = sys::ImPlotMarker__ImPlotMarker_Plus; + /// a asterisk marker will be rendered at each point (not filled) + const ASTERISK = sys::ImPlotMarker__ImPlotMarker_Asterisk; } } +/// Colorable plot elements. These are called "ImPlotCol" in ImPlot itself, but I found that +/// name somewhat confusing because we are not referring to colors, but _which_ thing can +/// be colored - hence I added the "Element". +#[repr(u32)] +#[derive(Copy, Clone, Debug)] +pub enum PlotColorElement { + /// Plot line/outline color (defaults to next unused color in current colormap) + Line = sys::ImPlotCol__ImPlotCol_Line, + /// Plot fill color for bars (defaults to the current line color) + Fill = sys::ImPlotCol__ImPlotCol_Fill, + /// Marker outline color (defaults to the current line color) + MarkerOutline = sys::ImPlotCol__ImPlotCol_MarkerOutline, + /// Marker fill color (defaults to the current line color) + MarkerFill = sys::ImPlotCol__ImPlotCol_MarkerFill, + /// Error bar color (defaults to text color) + ErrorBar = sys::ImPlotCol__ImPlotCol_ErrorBar, + /// Plot frame background color (defaults to FRAME_BG) + FrameBg = sys::ImPlotCol__ImPlotCol_FrameBg, + /// Plot area background color (defaults to WINDOW_BG) + PlotBg = sys::ImPlotCol__ImPlotCol_PlotBg, + /// Plot area border color (defaults to text color) + PlotBorder = sys::ImPlotCol__ImPlotCol_PlotBorder, + /// X-axis grid/label color (defaults to 25% text color) + XAxis = sys::ImPlotCol__ImPlotCol_XAxis, + /// Y-axis grid/label color (defaults to 25% text color) + YAxis = sys::ImPlotCol__ImPlotCol_YAxis, + /// 2nd y-axis grid/label color (defaults to 25% text color) + YAxis2 = sys::ImPlotCol__ImPlotCol_YAxis2, + /// 3rd y-axis grid/label color (defaults to 25% text color) + YAxis3 = sys::ImPlotCol__ImPlotCol_YAxis3, + /// Box-selection color (defaults to yellow) + Selection = sys::ImPlotCol__ImPlotCol_Selection, + /// Box-query color (defaults to green) + Query = sys::ImPlotCol__ImPlotCol_Query, +} + +#[repr(u32)] +#[derive(Copy, Clone, Debug)] +pub enum StyleVar { + /// f32, line weight in pixels + LineWeight = sys::ImPlotStyleVar__ImPlotStyleVar_LineWeight, + /// u32, marker specification + Marker = sys::ImPlotStyleVar__ImPlotStyleVar_Marker, + /// f32, marker size in pixels (roughly the marker's "radius") + MarkerSize = sys::ImPlotStyleVar__ImPlotStyleVar_MarkerSize, + /// f32, outline weight of markers in pixels + MarkerWeight = sys::ImPlotStyleVar__ImPlotStyleVar_MarkerWeight, + /// f32, error bar whisker width in pixels + ErrorBarSize = sys::ImPlotStyleVar__ImPlotStyleVar_ErrorBarSize, + /// f32, error bar whisker weight in pixels + ErrorBarWeight = sys::ImPlotStyleVar__ImPlotStyleVar_ErrorBarWeight, + /// f32, digital channels bit height (at 1) in pixels + DigitalBitHeight = sys::ImPlotStyleVar__ImPlotStyleVar_DigitalBitHeight, + /// f32, digital channels bit padding gap in pixels + DigitalBitGap = sys::ImPlotStyleVar__ImPlotStyleVar_DigitalBitGap, +} + // --- Main plot structure ----------------------------------------------------------------------- /// Struct to represent an ImPlot. This is the main construct used to contain all kinds of plots in ImPlot. /// @@ -448,12 +501,24 @@ impl PlotText { // Currently not in a struct yet. imgui-rs has some smarts about dealing with stacks, in particular // leak detection, which I'd like to replicate here at some point. /// Push a style color to the stack, giving an element and the four components of the color. -/// The components should be between 0.0 (no intensity) and 1.0 (full intensity) -pub fn push_style_color(element: &PlotColorElement, red: f32, green: f32, blue: f32, alpha: f32) { - // TODO this is actually unsafe, safe-wrap this like in imgui-rs' stacks.rs +/// The components should be between 0.0 (no intensity) and 1.0 (full intensity). +/// The return value is a token that gets used for removing the style color from the stack again: +/// ```no_run +/// # use implot::{push_style_color, PlotColorElement}; +/// let pushed_var = push_style_color(&PlotColorElement::Line, 1.0, 1.0, 1.0, 0.2); +/// // Plot some things +/// pushed_var.pop(); +/// ``` +pub fn push_style_color( + element: &PlotColorElement, + red: f32, + green: f32, + blue: f32, + alpha: f32, +) -> StyleColorToken { unsafe { sys::ImPlot_PushStyleColorVec4( - element.bits() as sys::ImPlotCol, + *element as sys::ImPlotCol, sys::ImVec4 { x: red, y: green, @@ -462,12 +527,86 @@ pub fn push_style_color(element: &PlotColorElement, red: f32, green: f32, blue: }, ); } + StyleColorToken { was_popped: false } } -/// Pop a given number of previously-pushed style color from the stack. -pub fn pop_style_color(count: i32) { - // TODO this is actually unsafe, safe-wrap this like in imgui-rs' stacks.rs - unsafe { sys::ImPlot_PopStyleColor(count) } +/// Tracks a change pushed to the style color stack +pub struct StyleColorToken { + /// Whether this token has been popped or not. + /// TODO(4bb4) figure out if it is a good idea to warn about this not being popped when it is + /// dropped - this may not be a good idea since users may want to push some style vars for + /// longer durations. + was_popped: bool, +} + +impl StyleColorToken { + pub fn pop(mut self) { + if self.was_popped { + panic!("Attempted to pop a style color token twice.") + } + self.was_popped = true; + unsafe { + sys::ImPlot_PopStyleColor(1); + } + } +} + +/// Push a f32 style variable to the stack. The returned token is used for removing +/// the variable from the stack again: +/// ```no_run +/// # use implot::{push_style_var_f32, StyleVar}; +/// let pushed_var = push_style_var_f32(&StyleVar::LineWeight, 11.0); +/// // Plot some things +/// pushed_var.pop(); +/// ``` +pub fn push_style_var_f32(element: &StyleVar, value: f32) -> StyleVarToken { + unsafe { + sys::ImPlot_PushStyleVarFloat(*element as sys::ImPlotStyleVar, value); + } + StyleVarToken { was_popped: false } +} + +/// Push an u32 style variable to the stack. The only u32 style variable is Marker +/// at the moment, for that, use something like +/// ```no_run +/// # use implot::{push_style_var_u32, StyleVar, Marker}; +/// let markerchoice = push_style_var_u32(&StyleVar::Marker, Marker::CROSS.bits()); +/// // plot things +/// markerchoice.pop() +/// ``` +pub fn push_style_var_u32(element: &StyleVar, value: u32) -> StyleVarToken { + // It is a bit funky that we take an i32 here, but the enum that gets created + // by bindgen contains u32 values, so we do the same but convert them to the + // internal i32 values here. Since this could overflow if a too large u32 value + // was passed, we do a safe conversion here, panicking if it fails. + let value_i32 = + i32::try_from(value).expect("Invalid style variable passed, has to fit in an i32"); + unsafe { + sys::ImPlot_PushStyleVarInt(*element as sys::ImPlotStyleVar, value_i32); + } + StyleVarToken { was_popped: false } +} + +/// Tracks a change pushed to the style variable stack +pub struct StyleVarToken { + /// Whether this token has been popped or not. + /// TODO(4bb4) figure out if it is a good idea to warn about this not being popped when it is + /// dropped - this may not be a good idea since users may want to push some style vars for + /// longer durations. + was_popped: bool, +} + +impl StyleVarToken { + /// Pop this token from the stack. + pub fn pop(mut self) { + if self.was_popped { + panic!("Attempted to pop a style var token twice.") + } + self.was_popped = true; + unsafe { + sys::ImPlot_PopStyleVar(1); + } + } } // --- Miscellaneous ----------------------------------------------------------------------------- @@ -476,6 +615,11 @@ pub fn is_plot_hovered() -> bool { unsafe { sys::ImPlot_IsPlotHovered() } } +/// Returns true if the current or most recent plot is queried +pub fn is_plot_queried() -> bool { + unsafe { sys::ImPlot_IsPlotQueried() } +} + /// Returns the mouse position in x,y coordinates of the current or most recent plot. Currently /// pertains to whatever Y axis was most recently selected. TODO(4bb4) add y axis selection pub fn get_plot_mouse_position() -> ImPlotPoint { @@ -490,6 +634,13 @@ pub fn get_plot_limits() -> ImPlotLimits { unsafe { sys::ImPlot_GetPlotLimits(y_axis_selection) } } +/// Returns the query limits of the current or most recent plot. Currently pertains to whatever Y +/// axis was most recently selected. TODO(4bb4) add y axis selection +pub fn get_plot_query() -> ImPlotLimits { + let y_axis_selection = 0; + unsafe { sys::ImPlot_GetPlotQuery(y_axis_selection) } +} + // --- Demo window ------------------------------------------------------------------------------- /// 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