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
23impl PlotContextBinding {
24    pub(crate) fn bind(self, caller: &str) {
25        assert!(
26            !self.imgui_ctx_raw.is_null(),
27            "{caller} requires an active ImGui context"
28        );
29        assert!(
30            !self.plot_ctx_raw.is_null(),
31            "{caller} requires an active ImPlot context"
32        );
33        assert_eq!(
34            unsafe { imgui_sys::igGetCurrentContext() },
35            self.imgui_ctx_raw,
36            "{caller} must be used with the currently-active ImGui context"
37        );
38        unsafe {
39            sys::ImPlot_SetImGuiContext(self.imgui_ctx_raw);
40            sys::ImPlot_SetCurrentContext(self.plot_ctx_raw);
41        }
42    }
43}
44
45impl PlotContext {
46    /// Try to create a new ImPlot context
47    ///
48    /// This should be called after creating the Dear ImGui context.
49    /// The ImPlot context will use the same Dear ImGui context internally.
50    pub fn try_create(imgui_ctx: &ImGuiContext) -> dear_imgui_rs::ImGuiResult<Self> {
51        let imgui_ctx_raw = imgui_ctx.as_raw();
52        let imgui_alive = Some(imgui_ctx.alive_token());
53        assert_eq!(
54            unsafe { imgui_sys::igGetCurrentContext() },
55            imgui_ctx_raw,
56            "dear-implot: PlotContext must be created with the currently-active ImGui context"
57        );
58
59        // Bind ImPlot to the ImGui context before creating.
60        // On some toolchains/platforms, not setting this can lead to crashes
61        // if ImPlot initialization queries ImGui state during CreateContext.
62        unsafe { sys::ImPlot_SetImGuiContext(imgui_ctx_raw) };
63
64        let raw = unsafe { sys::ImPlot_CreateContext() };
65        if raw.is_null() {
66            return Err(dear_imgui_rs::ImGuiError::context_creation(
67                "ImPlot_CreateContext returned null",
68            ));
69        }
70
71        // Ensure the newly created context is current (defensive, CreateContext should do this).
72        unsafe { sys::ImPlot_SetCurrentContext(raw) };
73
74        Ok(Self {
75            raw,
76            imgui_ctx_raw,
77            imgui_alive,
78            owns_context: true,
79        })
80    }
81
82    /// Create a new ImPlot context (panics on error)
83    pub fn create(imgui_ctx: &ImGuiContext) -> Self {
84        Self::try_create(imgui_ctx).expect("Failed to create ImPlot context")
85    }
86
87    /// Get the current ImPlot context as a non-owning raw-context wrapper.
88    ///
89    /// Returns None if no context is current
90    ///
91    /// # Safety
92    ///
93    /// The returned value does not own the current ImPlot context and cannot prove that the
94    /// associated ImGui context remains alive. The caller must ensure the raw ImPlot and ImGui
95    /// contexts outlive the returned wrapper and are used on the same thread/context stack.
96    pub unsafe fn current() -> Option<Self> {
97        let raw = unsafe { sys::ImPlot_GetCurrentContext() };
98        if raw.is_null() {
99            None
100        } else {
101            Some(Self {
102                raw,
103                imgui_ctx_raw: unsafe { imgui_sys::igGetCurrentContext() },
104                imgui_alive: None,
105                owns_context: false,
106            })
107        }
108    }
109
110    /// Set this context as the current ImPlot context
111    pub fn set_as_current(&self) {
112        self.assert_imgui_alive();
113        self.binding().bind("dear-implot: PlotContext");
114    }
115
116    pub(crate) fn assert_imgui_alive(&self) {
117        if let Some(alive) = &self.imgui_alive {
118            assert!(
119                alive.is_alive(),
120                "dear-implot: ImGui context has been dropped"
121            );
122        }
123    }
124
125    pub(crate) fn binding(&self) -> PlotContextBinding {
126        PlotContextBinding {
127            plot_ctx_raw: self.raw,
128            imgui_ctx_raw: self.imgui_ctx_raw,
129        }
130    }
131
132    pub(crate) fn imgui_alive_token(&self) -> Option<dear_imgui_rs::ContextAliveToken> {
133        self.imgui_alive.clone()
134    }
135
136    /// Get a PlotUi for creating plots
137    ///
138    /// This borrows both the ImPlot context and the Dear ImGui Ui,
139    /// ensuring that plots can only be created when both are available.
140    pub fn get_plot_ui<'ui>(&'ui self, ui: &'ui Ui) -> PlotUi<'ui> {
141        self.set_as_current();
142        PlotUi { context: self, ui }
143    }
144
145    /// Get the raw ImPlot context pointer
146    ///
147    /// # Safety
148    ///
149    /// The caller must ensure the pointer is used safely and not stored
150    /// beyond the lifetime of this context.
151    pub unsafe fn raw(&self) -> *mut sys::ImPlotContext {
152        self.raw
153    }
154}
155
156impl Drop for PlotContext {
157    fn drop(&mut self) {
158        if !self.owns_context || self.raw.is_null() {
159            return;
160        }
161
162        if let Some(alive) = &self.imgui_alive {
163            if !alive.is_alive() {
164                // Avoid calling into ImGui allocators after the context has been dropped.
165                // Best-effort: leak the ImPlot context instead of risking UB.
166                return;
167            }
168        }
169
170        unsafe {
171            let prev_imgui = imgui_sys::igGetCurrentContext();
172            imgui_sys::igSetCurrentContext(self.imgui_ctx_raw);
173            sys::ImPlot_SetImGuiContext(self.imgui_ctx_raw);
174
175            if sys::ImPlot_GetCurrentContext() == self.raw {
176                sys::ImPlot_SetCurrentContext(std::ptr::null_mut());
177            }
178            sys::ImPlot_DestroyContext(self.raw);
179
180            imgui_sys::igSetCurrentContext(prev_imgui);
181        }
182    }
183}
184
185// ImPlot context is tied to Dear ImGui and not thread-safe to send/share.