Skip to main content

dear_implot/context/
core.rs

1use super::ui::PlotUi;
2use crate::sys;
3use dear_imgui_rs::{Context as ImGuiContext, Ui};
4use dear_imgui_sys as imgui_sys;
5
6/// ImPlot context that manages the plotting state
7///
8/// This context is separate from the Dear ImGui context but works alongside it.
9/// You need both contexts to create plots.
10pub struct PlotContext {
11    raw: *mut sys::ImPlotContext,
12    imgui_ctx_raw: *mut imgui_sys::ImGuiContext,
13    imgui_alive: Option<dear_imgui_rs::ContextAliveToken>,
14    owns_context: bool,
15}
16
17#[derive(Clone, Copy)]
18pub(crate) struct PlotContextBinding {
19    plot_ctx_raw: *mut sys::ImPlotContext,
20    imgui_ctx_raw: *mut imgui_sys::ImGuiContext,
21}
22
23#[must_use = "dropping the guard restores the previous Dear ImGui/ImPlot contexts"]
24pub(crate) struct PlotContextBindingGuard {
25    prev_imgui_ctx_raw: *mut imgui_sys::ImGuiContext,
26    prev_plot_ctx_raw: *mut sys::ImPlotContext,
27    restore_imgui: bool,
28    restore_plot: bool,
29}
30
31impl PlotContextBinding {
32    pub(crate) fn bind(&self, caller: &str) -> PlotContextBindingGuard {
33        assert!(
34            !self.imgui_ctx_raw.is_null(),
35            "{caller} requires an active ImGui context"
36        );
37        assert!(
38            !self.plot_ctx_raw.is_null(),
39            "{caller} requires an active ImPlot context"
40        );
41        let prev_imgui_ctx_raw = unsafe { imgui_sys::igGetCurrentContext() };
42        let prev_plot_ctx_raw = unsafe { sys::ImPlot_GetCurrentContext() };
43        let restore_imgui = prev_imgui_ctx_raw != self.imgui_ctx_raw;
44        let restore_plot = prev_plot_ctx_raw != self.plot_ctx_raw;
45        unsafe {
46            if restore_imgui {
47                imgui_sys::igSetCurrentContext(self.imgui_ctx_raw);
48            }
49            sys::ImPlot_SetImGuiContext(self.imgui_ctx_raw);
50            sys::ImPlot_SetCurrentContext(self.plot_ctx_raw);
51        }
52        PlotContextBindingGuard {
53            prev_imgui_ctx_raw,
54            prev_plot_ctx_raw,
55            restore_imgui,
56            restore_plot,
57        }
58    }
59}
60
61impl Drop for PlotContextBindingGuard {
62    fn drop(&mut self) {
63        unsafe {
64            if self.restore_plot {
65                sys::ImPlot_SetCurrentContext(self.prev_plot_ctx_raw);
66            }
67            if self.restore_imgui {
68                sys::ImPlot_SetImGuiContext(self.prev_imgui_ctx_raw);
69                imgui_sys::igSetCurrentContext(self.prev_imgui_ctx_raw);
70            }
71        }
72    }
73}
74
75impl PlotContext {
76    /// Try to create a new ImPlot context
77    ///
78    /// This should be called after creating the Dear ImGui context.
79    /// The ImPlot context will use the same Dear ImGui context internally.
80    pub fn try_create(imgui_ctx: &ImGuiContext) -> dear_imgui_rs::ImGuiResult<Self> {
81        let imgui_ctx_raw = imgui_ctx.as_raw();
82        let imgui_alive = Some(imgui_ctx.alive_token());
83        let prev_imgui = unsafe { imgui_sys::igGetCurrentContext() };
84        let prev_plot = unsafe { sys::ImPlot_GetCurrentContext() };
85
86        // Bind ImPlot to the ImGui context before creating.
87        // On some toolchains/platforms, not setting this can lead to crashes
88        // if ImPlot initialization queries ImGui state during CreateContext.
89        unsafe {
90            if prev_imgui != imgui_ctx_raw {
91                imgui_sys::igSetCurrentContext(imgui_ctx_raw);
92            }
93            sys::ImPlot_SetImGuiContext(imgui_ctx_raw);
94        }
95
96        let raw = unsafe { sys::ImPlot_CreateContext() };
97        unsafe {
98            if sys::ImPlot_GetCurrentContext() != prev_plot {
99                sys::ImPlot_SetCurrentContext(prev_plot);
100            }
101            if prev_imgui != imgui_ctx_raw {
102                imgui_sys::igSetCurrentContext(prev_imgui);
103            }
104        }
105        if raw.is_null() {
106            return Err(dear_imgui_rs::ImGuiError::context_creation(
107                "ImPlot_CreateContext returned null",
108            ));
109        }
110
111        Ok(Self {
112            raw,
113            imgui_ctx_raw,
114            imgui_alive,
115            owns_context: true,
116        })
117    }
118
119    /// Create a new ImPlot context (panics on error)
120    pub fn create(imgui_ctx: &ImGuiContext) -> Self {
121        Self::try_create(imgui_ctx).expect("Failed to create ImPlot context")
122    }
123
124    pub(crate) fn assert_imgui_alive(&self) {
125        if let Some(alive) = &self.imgui_alive {
126            assert!(
127                alive.is_alive(),
128                "dear-implot: ImGui context has been dropped"
129            );
130        }
131    }
132
133    pub(crate) fn binding(&self) -> PlotContextBinding {
134        PlotContextBinding {
135            plot_ctx_raw: self.raw,
136            imgui_ctx_raw: self.imgui_ctx_raw,
137        }
138    }
139
140    pub(crate) fn imgui_alive_token(&self) -> Option<dear_imgui_rs::ContextAliveToken> {
141        self.imgui_alive.clone()
142    }
143
144    /// Get a PlotUi for creating plots
145    ///
146    /// This borrows both the ImPlot context and the Dear ImGui Ui,
147    /// ensuring that plots can only be created when both are available.
148    pub fn get_plot_ui<'ui>(&'ui self, ui: &'ui Ui) -> PlotUi<'ui> {
149        let ui_ctx_raw = ui.with_bound_context(|| unsafe { imgui_sys::igGetCurrentContext() });
150        assert_eq!(
151            ui_ctx_raw, self.imgui_ctx_raw,
152            "dear-implot: PlotContext::get_plot_ui() requires a Ui from the owning ImGui context"
153        );
154        PlotUi { context: self, ui }
155    }
156
157    /// Get the raw ImPlot context pointer
158    ///
159    /// # Safety
160    ///
161    /// The caller must ensure the pointer is used safely and not stored
162    /// beyond the lifetime of this context.
163    pub unsafe fn raw(&self) -> *mut sys::ImPlotContext {
164        self.raw
165    }
166}
167
168impl Drop for PlotContext {
169    fn drop(&mut self) {
170        if !self.owns_context || self.raw.is_null() {
171            return;
172        }
173
174        if let Some(alive) = &self.imgui_alive {
175            if !alive.is_alive() {
176                // Avoid calling into ImGui allocators after the context has been dropped.
177                // Best-effort: leak the ImPlot context instead of risking UB.
178                return;
179            }
180        }
181
182        unsafe {
183            let prev_imgui = imgui_sys::igGetCurrentContext();
184            let prev_plot = sys::ImPlot_GetCurrentContext();
185            let restore_plot = if prev_plot == self.raw {
186                std::ptr::null_mut()
187            } else {
188                prev_plot
189            };
190            imgui_sys::igSetCurrentContext(self.imgui_ctx_raw);
191            sys::ImPlot_SetImGuiContext(self.imgui_ctx_raw);
192            sys::ImPlot_DestroyContext(self.raw);
193            sys::ImPlot_SetCurrentContext(restore_plot);
194            imgui_sys::igSetCurrentContext(prev_imgui);
195        }
196    }
197}
198
199// ImPlot context is tied to Dear ImGui and not thread-safe to send/share.