Skip to main content

dear_implot/context/
core.rs

1use super::ui::PlotUi;
2use crate::sys;
3use dear_imgui_rs::{Context as ImGuiContext, ContextBinding, ContextBindingError, Ui};
4
5/// ImPlot context that manages the plotting state
6///
7/// This context is separate from the Dear ImGui context but works alongside it.
8/// You need both contexts to create plots.
9pub struct PlotContext {
10    raw: *mut sys::ImPlotContext,
11    imgui_binding: ContextBinding,
12    owns_context: bool,
13}
14
15#[derive(Clone)]
16pub(crate) struct PlotContextBinding {
17    plot_ctx_raw: *mut sys::ImPlotContext,
18    imgui_binding: ContextBinding,
19}
20
21#[must_use = "dropping the guard restores the previous ImPlot context"]
22struct PlotContextGuard {
23    prev_plot_ctx_raw: *mut sys::ImPlotContext,
24    restore_plot: bool,
25}
26
27impl PlotContextBinding {
28    pub(crate) fn with_bound_context<R>(&self, caller: &str, f: impl FnOnce() -> R) -> R {
29        self.try_with_bound_context(f)
30            .unwrap_or_else(|error| panic!("{caller}: {error}"))
31    }
32
33    pub(crate) fn try_with_bound_context<R>(
34        &self,
35        f: impl FnOnce() -> R,
36    ) -> Result<R, ContextBindingError> {
37        self.imgui_binding.try_with_bound_context(|| {
38            let _guard = PlotContextGuard::bind(self.plot_ctx_raw);
39            f()
40        })
41    }
42}
43
44impl PlotContextGuard {
45    fn bind(plot_ctx_raw: *mut sys::ImPlotContext) -> Self {
46        assert!(
47            !plot_ctx_raw.is_null(),
48            "dear-implot requires an active ImPlot context"
49        );
50        let prev_plot_ctx_raw = unsafe { sys::ImPlot_GetCurrentContext() };
51        let restore_plot = prev_plot_ctx_raw != plot_ctx_raw;
52        unsafe {
53            sys::ImPlot_SetCurrentContext(plot_ctx_raw);
54        }
55        Self {
56            prev_plot_ctx_raw,
57            restore_plot,
58        }
59    }
60}
61
62impl Drop for PlotContextGuard {
63    fn drop(&mut self) {
64        if self.restore_plot {
65            unsafe {
66                sys::ImPlot_SetCurrentContext(self.prev_plot_ctx_raw);
67            }
68        }
69    }
70}
71
72impl PlotContext {
73    /// Try to create a new ImPlot context
74    ///
75    /// This should be called after creating the Dear ImGui context.
76    /// The ImPlot context will use the same Dear ImGui context internally.
77    pub fn try_create(imgui_ctx: &ImGuiContext) -> dear_imgui_rs::ImGuiResult<Self> {
78        let imgui_binding = imgui_ctx.binding();
79        let raw = imgui_binding.with_bound_context(|| unsafe {
80            let prev_plot = sys::ImPlot_GetCurrentContext();
81            let raw = sys::ImPlot_CreateContext();
82            if sys::ImPlot_GetCurrentContext() != prev_plot {
83                sys::ImPlot_SetCurrentContext(prev_plot);
84            }
85            raw
86        });
87        if raw.is_null() {
88            return Err(dear_imgui_rs::ImGuiError::context_creation(
89                "ImPlot_CreateContext returned null",
90            ));
91        }
92
93        Ok(Self {
94            raw,
95            imgui_binding,
96            owns_context: true,
97        })
98    }
99
100    /// Create a new ImPlot context (panics on error)
101    pub fn create(imgui_ctx: &ImGuiContext) -> Self {
102        Self::try_create(imgui_ctx).expect("Failed to create ImPlot context")
103    }
104
105    pub(crate) fn binding(&self) -> PlotContextBinding {
106        PlotContextBinding {
107            plot_ctx_raw: self.raw,
108            imgui_binding: self.imgui_binding.clone(),
109        }
110    }
111
112    /// Get a PlotUi for creating plots
113    ///
114    /// This borrows both the ImPlot context and the Dear ImGui Ui,
115    /// ensuring that plots can only be created when both are available.
116    pub fn get_plot_ui<'ui>(&'ui self, ui: &'ui Ui) -> PlotUi<'ui> {
117        assert_eq!(
118            ui.context_id(),
119            self.imgui_binding.id(),
120            "dear-implot: PlotContext::get_plot_ui() requires a Ui from the owning ImGui context"
121        );
122        PlotUi { context: self, ui }
123    }
124
125    /// Get the raw ImPlot context pointer
126    ///
127    /// # Safety
128    ///
129    /// The caller must ensure the pointer is used safely and not stored
130    /// beyond the lifetime of this context.
131    pub unsafe fn raw(&self) -> *mut sys::ImPlotContext {
132        self.raw
133    }
134}
135
136impl Drop for PlotContext {
137    fn drop(&mut self) {
138        if !self.owns_context || self.raw.is_null() {
139            return;
140        }
141
142        let _ = self.imgui_binding.try_with_bound_context(|| unsafe {
143            let prev_plot = sys::ImPlot_GetCurrentContext();
144            let restore_plot = if prev_plot == self.raw {
145                std::ptr::null_mut()
146            } else {
147                prev_plot
148            };
149            sys::ImPlot_DestroyContext(self.raw);
150            sys::ImPlot_SetCurrentContext(restore_plot);
151        });
152    }
153}
154
155// ImPlot context is tied to Dear ImGui and not thread-safe to send/share.