Skip to main content

egui_sdl2/
lib.rs

1//! # egui-sdl2
2//!
3//! Integration between [`egui`](https://github.com/emilk/egui) and
4//! [`sdl2`](https://github.com/Rust-SDL2/rust-sdl2).
5//!
6//! ## Features
7//! - Translate SDL2 events into [`egui`] events.
8//! - Handle [`egui::PlatformOutput`] (clipboard, cursor updates, links).
9//! - Render with the SDL2 software renderer via [`sdl2::render::Canvas`] (`canvas-backend` feature).
10//! - Render with OpenGL via [`glow`] (`glow-backend` feature).
11//! - Render with WebGPU via [`wgpu`](https://github.com/gfx-rs/wgpu) (`wgpu-backend` feature).
12//! - Or let [`EguiWindow`] pick: it walks a list of renderers and keeps the
13//!   first the device actually supports.
14//!
15//! ## Usage
16//! ```no_run
17//! // Create SDL2 window and canvas:
18//! let sdl = sdl2::init().unwrap();
19//! let video = sdl.video().unwrap();
20//! let window = video.window("Egui SDL2 Canvas", 800, 600).build().unwrap();
21//! let mut canvas = window.into_canvas().build().unwrap();
22//! // Create egui renderer; the canvas stays yours:
23//! let mut egui = egui_sdl2::EguiCanvas::new(&canvas);
24//! let mut event_pump = sdl.event_pump().unwrap();
25//! loop {
26//!    // Feed SDL2 events into egui:
27//!    for event in event_pump.poll_iter() {
28//!        egui.on_event(&canvas, &event);
29//!    }
30//!    // Call `run` + `paint` each frame, over anything you drew yourself:
31//!    egui.run(|ctx: &egui::Context| {});
32//!    canvas.clear();
33//!    egui.paint(&mut canvas);
34//!    canvas.present();
35//!    std::thread::sleep(std::time::Duration::from_secs_f64(1.0 / 60.0));
36//!}
37//! ```
38
39pub use egui;
40#[cfg(feature = "glow-backend")]
41pub use egui_glow;
42pub use sdl2;
43
44#[cfg(feature = "canvas-backend")]
45pub mod canvas;
46#[cfg(feature = "glow-backend")]
47pub mod glow;
48pub mod rotation;
49pub mod state;
50#[cfg(feature = "wgpu-backend")]
51pub mod wgpu;
52#[cfg(any(
53    feature = "glow-backend",
54    feature = "canvas-backend",
55    feature = "wgpu-backend"
56))]
57pub mod window;
58
59#[cfg(feature = "canvas-backend")]
60pub use canvas::EguiCanvas;
61#[cfg(feature = "glow-backend")]
62pub use glow::*;
63pub use rotation::Rotation;
64pub use state::*;
65#[cfg(feature = "wgpu-backend")]
66pub use wgpu::EguiWgpu;
67#[cfg(any(
68    feature = "glow-backend",
69    feature = "canvas-backend",
70    feature = "wgpu-backend"
71))]
72pub use window::{EguiWindow, Renderer};
73
74/// The results of running one frame of `egui`.
75///
76/// `EguiRunOutput` collects the renderable shapes, texture updates, and scale
77/// factor from a single `egui` run. It also provides convenience methods for
78/// updating its contents from an `egui::Context` and for draining the data
79/// when it is time to render.
80///
81/// This is typically created once per backend instance and reused across frames.
82pub struct EguiRunOutput {
83    /// The clipped shapes that should be rendered for the current frame.
84    ///
85    /// This is produced by egui’s tessellation step and represents what should
86    /// be drawn to the screen.
87    pub shapes: Vec<egui::epaint::ClippedShape>,
88
89    /// The logical-to-physical pixel scaling factor used by egui in this frame.
90    ///
91    /// Backends should respect this when converting coordinates to pixels.
92    pub pixels_per_point: f32,
93
94    /// The delta of texture updates required for this frame.
95    ///
96    /// Includes new textures to upload and old textures to free.
97    pub textures_delta: egui::TexturesDelta,
98
99    /// How long until egui wants the ROOT viewport repainted, as reported by
100    /// the last [`Self::update`] (egui's `ViewportOutput::repaint_delay`).
101    ///
102    /// `Duration::ZERO` means egui needs another frame *immediately* — e.g. the
103    /// frame just run was a sizing pass for a freshly shown [`egui::Area`], whose
104    /// real (positioned, visible) frame must follow. `Duration::MAX` means egui
105    /// is idle and the backend may block on input. Event-driven backends should
106    /// fold this into their idle wait so animations and first-frame layout show
107    /// without an extra input event.
108    pub repaint_delay: std::time::Duration,
109}
110
111impl Default for EguiRunOutput {
112    /// Creates an empty `EguiRunOutput` with no shapes, no texture updates,
113    /// and a scale factor of `1.0`.
114    fn default() -> Self {
115        Self {
116            shapes: Default::default(),
117            pixels_per_point: 1.0,
118            textures_delta: Default::default(),
119            repaint_delay: std::time::Duration::MAX,
120        }
121    }
122}
123
124impl Drop for EguiRunOutput {
125    fn drop(&mut self) {
126        // An app may quit between `run` and `paint`; deltas never painted are
127        // dropped on purpose, which egui 0.36's own drop check asserts against.
128        self.textures_delta.clear();
129    }
130}
131
132impl EguiRunOutput {
133    /// Run `egui` for one frame and update this output with the results.
134    ///
135    /// # Parameters
136    /// - `ctx`: The [`egui::Context`] used to run the UI.
137    /// - `state`: A backend state that provides input for egui and
138    ///   handles platform output (clipboard, cursor, etc.).
139    /// - `run_ui`: A closure that builds the UI using the given `egui::Context`.
140    ///
141    /// # Behavior
142    /// - Takes input events from `state`.
143    /// - Runs egui with the provided `run_ui` closure.
144    /// - Handles platform output via `state`.
145    /// - Stores the frame’s shapes, texture updates, and scale factor
146    ///   in this `EguiRunOutput`.
147    #[inline]
148    pub fn update(
149        &mut self,
150        ctx: &egui::Context,
151        state: &mut State,
152        mut run_ui: impl FnMut(&egui::Context),
153    ) {
154        self.update_ui(ctx, state, |ui| run_ui(ui.ctx()));
155    }
156
157    /// Like [`Self::update`], but hands the closure egui's root [`egui::Ui`].
158    ///
159    /// Panels ([`egui::CentralPanel`], [`egui::TopBottomPanel`], …) are shown into
160    /// a `Ui`, so a full-screen layout needs this rather than [`Self::update`].
161    #[inline]
162    pub fn update_ui(
163        &mut self,
164        ctx: &egui::Context,
165        state: &mut State,
166        run_ui: impl FnMut(&mut egui::Ui),
167    ) {
168        let raw_input = state.take_egui_input();
169        let egui::FullOutput {
170            platform_output,
171            viewport_output,
172            textures_delta,
173            shapes,
174            pixels_per_point,
175        } = ctx.run_ui(raw_input, run_ui);
176        state.handle_platform_output(platform_output);
177
178        self.shapes = shapes;
179        self.textures_delta.append(textures_delta);
180        self.pixels_per_point = pixels_per_point;
181        // Surface egui's own repaint timing for the ROOT viewport so event-driven
182        // backends don't drop it: a sizing pass for a freshly shown anchored Area
183        // reports `ZERO` here, asking for the follow-up frame that actually paints
184        // it. `MAX` if egui didn't report (idle — wait on input).
185        self.repaint_delay = viewport_output
186            .get(&egui::ViewportId::ROOT)
187            .map_or(std::time::Duration::MAX, |v| v.repaint_delay);
188    }
189
190    /// Take ownership of the texture updates and shapes for the current frame.
191    ///
192    /// This clears both fields in the struct, leaving them empty for the next frame.
193    ///
194    /// # Returns
195    /// - `(textures_delta, shapes)` where:
196    ///   - `textures_delta`: The [`egui::TexturesDelta`] with texture uploads/free requests.
197    ///   - `shapes`: The tessellated shapes that should be rendered.
198    #[inline]
199    pub fn take(&mut self) -> (egui::TexturesDelta, Vec<egui::epaint::ClippedShape>) {
200        let textures_delta = std::mem::take(&mut self.textures_delta);
201        let shapes = std::mem::take(&mut self.shapes);
202
203        (textures_delta, shapes)
204    }
205}