Skip to main content

dear_implot/
style.rs

1// Style and theming for plots
2
3use crate::sys;
4use dear_imgui_rs::{with_scratch_txt, with_scratch_txt_two};
5use std::borrow::Cow;
6use std::os::raw::c_char;
7
8/// Style variables that can be modified
9#[repr(i32)]
10#[derive(Copy, Clone, Debug, PartialEq, Eq)]
11pub enum StyleVar {
12    PlotDefaultSize = sys::ImPlotStyleVar_PlotDefaultSize as i32,
13    PlotMinSize = sys::ImPlotStyleVar_PlotMinSize as i32,
14    PlotBorderSize = sys::ImPlotStyleVar_PlotBorderSize as i32,
15    MinorAlpha = sys::ImPlotStyleVar_MinorAlpha as i32,
16    MajorTickLen = sys::ImPlotStyleVar_MajorTickLen as i32,
17    MinorTickLen = sys::ImPlotStyleVar_MinorTickLen as i32,
18    MajorTickSize = sys::ImPlotStyleVar_MajorTickSize as i32,
19    MinorTickSize = sys::ImPlotStyleVar_MinorTickSize as i32,
20    MajorGridSize = sys::ImPlotStyleVar_MajorGridSize as i32,
21    MinorGridSize = sys::ImPlotStyleVar_MinorGridSize as i32,
22    PlotPadding = sys::ImPlotStyleVar_PlotPadding as i32,
23    LabelPadding = sys::ImPlotStyleVar_LabelPadding as i32,
24    LegendPadding = sys::ImPlotStyleVar_LegendPadding as i32,
25    LegendInnerPadding = sys::ImPlotStyleVar_LegendInnerPadding as i32,
26    LegendSpacing = sys::ImPlotStyleVar_LegendSpacing as i32,
27    MousePosPadding = sys::ImPlotStyleVar_MousePosPadding as i32,
28    AnnotationPadding = sys::ImPlotStyleVar_AnnotationPadding as i32,
29    FitPadding = sys::ImPlotStyleVar_FitPadding as i32,
30    DigitalPadding = sys::ImPlotStyleVar_DigitalPadding as i32,
31    DigitalSpacing = sys::ImPlotStyleVar_DigitalSpacing as i32,
32}
33
34/// Token for managing style variable changes
35pub struct StyleVarToken {
36    was_popped: bool,
37}
38
39impl StyleVarToken {
40    /// Pop this style variable from the stack
41    pub fn pop(mut self) {
42        if self.was_popped {
43            panic!("Attempted to pop a style var token twice.");
44        }
45        self.was_popped = true;
46        unsafe {
47            sys::ImPlot_PopStyleVar(1);
48        }
49    }
50}
51
52impl Drop for StyleVarToken {
53    fn drop(&mut self) {
54        if !self.was_popped {
55            unsafe {
56                sys::ImPlot_PopStyleVar(1);
57            }
58        }
59    }
60}
61
62/// Token for managing style color changes
63pub struct StyleColorToken {
64    was_popped: bool,
65}
66
67impl StyleColorToken {
68    /// Pop this style color from the stack
69    pub fn pop(mut self) {
70        if self.was_popped {
71            panic!("Attempted to pop a style color token twice.");
72        }
73        self.was_popped = true;
74        unsafe {
75            sys::ImPlot_PopStyleColor(1);
76        }
77    }
78}
79
80impl Drop for StyleColorToken {
81    fn drop(&mut self) {
82        if !self.was_popped {
83            unsafe {
84                sys::ImPlot_PopStyleColor(1);
85            }
86        }
87    }
88}
89
90/// One-shot array-backed item style overrides for the next plot submission.
91///
92/// This mirrors the new per-item array fields added to `ImPlotSpec` without storing
93/// borrowed pointers beyond the closure passed to [`with_next_plot_item_array_style`].
94#[derive(Debug, Clone, Default, PartialEq)]
95pub struct PlotItemArrayStyle<'a> {
96    line_colors: Option<Cow<'a, [u32]>>,
97    fill_colors: Option<Cow<'a, [u32]>>,
98    marker_sizes: Option<Cow<'a, [f32]>>,
99    marker_line_colors: Option<Cow<'a, [u32]>>,
100    marker_fill_colors: Option<Cow<'a, [u32]>>,
101}
102
103impl<'a> PlotItemArrayStyle<'a> {
104    /// Create an empty array-style override.
105    pub fn new() -> Self {
106        Self::default()
107    }
108
109    /// Override per-index line colors using Dear ImGui packed colors (`ImU32` / ABGR).
110    pub fn with_line_colors(mut self, colors: &'a [u32]) -> Self {
111        self.line_colors = Some(Cow::Borrowed(colors));
112        self
113    }
114
115    /// Override per-index fill colors using Dear ImGui packed colors (`ImU32` / ABGR).
116    pub fn with_fill_colors(mut self, colors: &'a [u32]) -> Self {
117        self.fill_colors = Some(Cow::Borrowed(colors));
118        self
119    }
120
121    /// Override per-index marker sizes in pixels.
122    pub fn with_marker_sizes(mut self, sizes: &'a [f32]) -> Self {
123        self.marker_sizes = Some(Cow::Borrowed(sizes));
124        self
125    }
126
127    /// Override per-index marker outline colors using Dear ImGui packed colors (`ImU32` / ABGR).
128    pub fn with_marker_line_colors(mut self, colors: &'a [u32]) -> Self {
129        self.marker_line_colors = Some(Cow::Borrowed(colors));
130        self
131    }
132
133    /// Override per-index marker fill colors using Dear ImGui packed colors (`ImU32` / ABGR).
134    pub fn with_marker_fill_colors(mut self, colors: &'a [u32]) -> Self {
135        self.marker_fill_colors = Some(Cow::Borrowed(colors));
136        self
137    }
138
139    fn apply_to_spec(&self, spec: &mut sys::ImPlotSpec_c) {
140        spec.LineColors = self
141            .line_colors
142            .as_ref()
143            .map_or(std::ptr::null_mut(), |colors| colors.as_ptr() as *mut _);
144        spec.FillColors = self
145            .fill_colors
146            .as_ref()
147            .map_or(std::ptr::null_mut(), |colors| colors.as_ptr() as *mut _);
148        spec.MarkerSizes = self
149            .marker_sizes
150            .as_ref()
151            .map_or(std::ptr::null_mut(), |sizes| sizes.as_ptr() as *mut _);
152        spec.MarkerLineColors = self
153            .marker_line_colors
154            .as_ref()
155            .map_or(std::ptr::null_mut(), |colors| colors.as_ptr() as *mut _);
156        spec.MarkerFillColors = self
157            .marker_fill_colors
158            .as_ref()
159            .map_or(std::ptr::null_mut(), |colors| colors.as_ptr() as *mut _);
160    }
161}
162
163/// Apply array-backed item styling to the next plot submission executed inside `f`.
164///
165/// This is intentionally closure-scoped so borrowed slices stay valid for the entire
166/// duration of the next plot call and cannot leak into later frames accidentally.
167pub fn with_next_plot_item_array_style<'a, R>(
168    style: PlotItemArrayStyle<'a>,
169    f: impl FnOnce() -> R,
170) -> R {
171    let previous = crate::plots::take_next_plot_spec();
172    let mut spec = previous.unwrap_or_else(crate::plots::default_plot_spec);
173    style.apply_to_spec(&mut spec);
174    crate::plots::set_next_plot_spec(Some(spec));
175
176    let out = f();
177
178    if crate::plots::take_next_plot_spec().is_some() {
179        crate::plots::set_next_plot_spec(previous);
180    }
181
182    out
183}
184
185/// Push a float style variable to the stack
186pub fn push_style_var_f32(var: StyleVar, value: f32) -> StyleVarToken {
187    unsafe {
188        sys::ImPlot_PushStyleVar_Float(var as sys::ImPlotStyleVar, value);
189    }
190    StyleVarToken { was_popped: false }
191}
192
193/// Push an integer style variable to the stack (converted to float)
194pub fn push_style_var_i32(var: StyleVar, value: i32) -> StyleVarToken {
195    unsafe {
196        sys::ImPlot_PushStyleVar_Int(var as sys::ImPlotStyleVar, value);
197    }
198    StyleVarToken { was_popped: false }
199}
200
201/// Push a Vec2 style variable to the stack
202pub fn push_style_var_vec2(var: StyleVar, value: [f32; 2]) -> StyleVarToken {
203    unsafe {
204        sys::ImPlot_PushStyleVar_Vec2(
205            var as sys::ImPlotStyleVar,
206            sys::ImVec2_c {
207                x: value[0],
208                y: value[1],
209            },
210        );
211    }
212    StyleVarToken { was_popped: false }
213}
214
215/// Push a style color to the stack
216pub fn push_style_color(element: crate::PlotColorElement, color: [f32; 4]) -> StyleColorToken {
217    unsafe {
218        // Convert color to ImU32 format (RGBA)
219        let r = (color[0] * 255.0) as u32;
220        let g = (color[1] * 255.0) as u32;
221        let b = (color[2] * 255.0) as u32;
222        let a = (color[3] * 255.0) as u32;
223        let color_u32 = (a << 24) | (b << 16) | (g << 8) | r;
224
225        sys::ImPlot_PushStyleColor_U32(element as sys::ImPlotCol, color_u32);
226    }
227    StyleColorToken { was_popped: false }
228}
229
230/// Push a colormap to the stack
231pub fn push_colormap(preset: crate::Colormap) {
232    unsafe {
233        sys::ImPlot_PushColormap_PlotColormap(preset as sys::ImPlotColormap);
234    }
235}
236
237/// Pop a colormap from the stack
238pub fn pop_colormap(count: i32) {
239    unsafe {
240        sys::ImPlot_PopColormap(count);
241    }
242}
243
244/// Add a custom colormap from a vector of colors
245pub fn add_colormap(name: &str, colors: &[sys::ImVec4], qualitative: bool) -> sys::ImPlotColormap {
246    assert!(!name.contains('\0'), "colormap name contained NUL");
247    let count = i32::try_from(colors.len()).expect("colormap contained too many colors");
248    with_scratch_txt(name, |ptr| unsafe {
249        sys::ImPlot_AddColormap_Vec4Ptr(ptr, colors.as_ptr(), count, qualitative)
250            as sys::ImPlotColormap
251    })
252}
253
254// Style editor / selectors and input-map helpers
255
256/// Show the ImPlot style editor window
257pub fn show_style_editor() {
258    unsafe { sys::ImPlot_ShowStyleEditor(std::ptr::null_mut()) }
259}
260
261/// Show the ImPlot style selector combo; returns true if selection changed
262pub fn show_style_selector(label: &str) -> bool {
263    let label = if label.contains('\0') { "" } else { label };
264    with_scratch_txt(label, |ptr| unsafe { sys::ImPlot_ShowStyleSelector(ptr) })
265}
266
267/// Show the ImPlot colormap selector combo; returns true if selection changed
268pub fn show_colormap_selector(label: &str) -> bool {
269    let label = if label.contains('\0') { "" } else { label };
270    with_scratch_txt(label, |ptr| unsafe {
271        sys::ImPlot_ShowColormapSelector(ptr)
272    })
273}
274
275/// Show the ImPlot input-map selector combo; returns true if selection changed
276pub fn show_input_map_selector(label: &str) -> bool {
277    let label = if label.contains('\0') { "" } else { label };
278    with_scratch_txt(label, |ptr| unsafe {
279        sys::ImPlot_ShowInputMapSelector(ptr)
280    })
281}
282
283/// Map input to defaults
284pub fn map_input_default() {
285    unsafe { sys::ImPlot_MapInputDefault(sys::ImPlot_GetInputMap()) }
286}
287
288/// Map input to reversed scheme
289pub fn map_input_reverse() {
290    unsafe { sys::ImPlot_MapInputReverse(sys::ImPlot_GetInputMap()) }
291}
292
293// Colormap widgets
294
295/// Draw a colormap scale widget
296pub fn colormap_scale(
297    label: &str,
298    scale_min: f64,
299    scale_max: f64,
300    height: f32,
301    cmap: Option<sys::ImPlotColormap>,
302) {
303    let label = if label.contains('\0') { "" } else { label };
304    let size = sys::ImVec2_c { x: 0.0, y: height };
305    let fmt_ptr: *const c_char = std::ptr::null();
306    let flags: i32 = 0; // ImPlotColormapScaleFlags_None
307    with_scratch_txt(label, |ptr| unsafe {
308        sys::ImPlot_ColormapScale(
309            ptr,
310            scale_min,
311            scale_max,
312            size,
313            fmt_ptr,
314            flags,
315            cmap.unwrap_or(0),
316        )
317    })
318}
319
320/// Draw a colormap slider; returns true if selection changed
321pub fn colormap_slider(
322    label: &str,
323    t: &mut f32,
324    out_color: &mut sys::ImVec4,
325    format: Option<&str>,
326    cmap: sys::ImPlotColormap,
327) -> bool {
328    let label = if label.contains('\0') { "" } else { label };
329    let format = format.filter(|s| !s.contains('\0'));
330
331    match format {
332        Some(fmt) => with_scratch_txt_two(label, fmt, |label_ptr, fmt_ptr| unsafe {
333            sys::ImPlot_ColormapSlider(
334                label_ptr,
335                t as *mut f32,
336                out_color as *mut sys::ImVec4,
337                fmt_ptr,
338                cmap,
339            )
340        }),
341        None => with_scratch_txt(label, |label_ptr| unsafe {
342            sys::ImPlot_ColormapSlider(
343                label_ptr,
344                t as *mut f32,
345                out_color as *mut sys::ImVec4,
346                std::ptr::null(),
347                cmap,
348            )
349        }),
350    }
351}
352
353/// Draw a colormap picker button; returns true if clicked
354pub fn colormap_button(label: &str, size: [f32; 2], cmap: sys::ImPlotColormap) -> bool {
355    let label = if label.contains('\0') { "" } else { label };
356    let sz = sys::ImVec2_c {
357        x: size[0],
358        y: size[1],
359    };
360    with_scratch_txt(label, |ptr| unsafe {
361        sys::ImPlot_ColormapButton(ptr, sz, cmap)
362    })
363}
364
365#[cfg(test)]
366mod tests {
367    use super::{PlotItemArrayStyle, with_next_plot_item_array_style};
368
369    #[test]
370    fn next_plot_item_array_style_is_consumed_by_next_spec() {
371        let line_colors = [0x01020304u32, 0x05060708];
372        let fill_colors = [0x11121314u32];
373        let marker_sizes = [2.0f32, 4.0, 8.0];
374
375        with_next_plot_item_array_style(
376            PlotItemArrayStyle::new()
377                .with_line_colors(&line_colors)
378                .with_fill_colors(&fill_colors)
379                .with_marker_sizes(&marker_sizes),
380            || {
381                let spec = crate::plots::plot_spec_from(7, 3, 16);
382                assert_eq!(spec.Flags, 7);
383                assert_eq!(spec.Offset, 3);
384                assert_eq!(spec.Stride, 16);
385                assert_eq!(spec.LineColors, line_colors.as_ptr() as *mut _);
386                assert_eq!(spec.FillColors, fill_colors.as_ptr() as *mut _);
387                assert_eq!(spec.MarkerSizes, marker_sizes.as_ptr() as *mut _);
388            },
389        );
390
391        let spec = crate::plots::plot_spec_from(0, 0, crate::IMPLOT_AUTO);
392        assert!(spec.LineColors.is_null());
393        assert!(spec.FillColors.is_null());
394        assert!(spec.MarkerSizes.is_null());
395    }
396
397    #[test]
398    fn next_plot_item_array_style_is_restored_if_unused() {
399        let line_colors = [0xAABBCCDDu32];
400
401        with_next_plot_item_array_style(
402            PlotItemArrayStyle::new().with_line_colors(&line_colors),
403            || {},
404        );
405
406        let spec = crate::plots::plot_spec_from(0, 0, crate::IMPLOT_AUTO);
407        assert!(spec.LineColors.is_null());
408    }
409}