Skip to main content

astrelis_egui/
lib.rs

1//! Egui integration for Astrelis.
2//!
3//! Provides immediate mode GUI rendering using egui on top of the astrelis-render wrapper.
4
5mod state;
6
7use astrelis_core::profiling::profile_function;
8use astrelis_render::{Frame as RenderFrame, RenderWindow};
9use astrelis_winit::event::EventBatch;
10use state::State;
11
12// Re-export egui types
13pub use egui::{
14    self, Align, Align2, Color32, Context as EguiContext, CornerRadius, FontFamily, FontId, Frame,
15    Id, Key, Label, Layout, Margin, Modifiers, Pos2, Rect, Response, RichText, Sense, Slider,
16    Stroke, Style, TextEdit, TextStyle, Ui, Vec2, Visuals, Widget,
17};
18pub use state::EventResponse;
19
20pub struct Egui {
21    context: egui::Context,
22    renderer: egui_wgpu::Renderer,
23    state: State,
24    full_output: Option<egui::FullOutput>,
25}
26
27impl Egui {
28    pub fn new(window: &RenderWindow, graphics_ctx: &astrelis_render::GraphicsContext) -> Self {
29        let context = egui::Context::default();
30        let id = context.viewport_id();
31
32        let visuals = egui::Visuals::dark();
33        context.set_visuals(visuals);
34
35        let state = State::new(context.clone(), id, None, None);
36
37        let renderer = egui_wgpu::Renderer::new(
38            graphics_ctx.device(),
39            window.context().surface_config().format,
40            egui_wgpu::RendererOptions {
41                msaa_samples: 1,
42                depth_stencil_format: None,
43                dithering: false,
44                ..Default::default()
45            },
46        );
47
48        Self {
49            context,
50            renderer,
51            state,
52            full_output: None,
53        }
54    }
55
56    /// Begin UI frame and run the GUI closure.
57    pub fn ui(&mut self, window: &RenderWindow, gui: impl FnMut(&egui::Context)) {
58        profile_function!();
59        let raw_input = self.state.take_input(window);
60        self.full_output.replace(self.context.run(raw_input, gui));
61    }
62
63    /// Render egui to the current frame.
64    ///
65    /// This method uses frame information directly without needing the window.
66    pub fn render(&mut self, frame: &RenderFrame<'_>) {
67        profile_function!();
68
69        if self.full_output.is_none() {
70            return;
71        }
72
73        let full_output = self.full_output.take().unwrap();
74        // Note: platform_output handling (cursor changes, clipboard, etc.) is a TODO
75        let _ = full_output.platform_output;
76
77        let device = frame.device();
78        let queue = frame.queue();
79
80        let tris = self
81            .context
82            .tessellate(full_output.shapes, full_output.pixels_per_point);
83
84        for (id, image_delta) in &full_output.textures_delta.set {
85            self.renderer
86                .update_texture(device, queue, *id, image_delta);
87        }
88
89        let (width, height) = frame.size();
90        let screen_descriptor = egui_wgpu::ScreenDescriptor {
91            size_in_pixels: [width, height],
92            pixels_per_point: full_output.pixels_per_point,
93        };
94
95        // Create encoder for buffer updates
96        let mut encoder = frame.create_encoder(Some("Egui Buffer Update"));
97        self.renderer
98            .update_buffers(device, queue, &mut encoder, &tris, &screen_descriptor);
99
100        // Create render pass
101        {
102            let surface_view = frame.surface_view();
103            let mut rpass = encoder
104                .begin_render_pass(&wgpu::RenderPassDescriptor {
105                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
106                        view: surface_view,
107                        resolve_target: None,
108                        ops: wgpu::Operations {
109                            load: wgpu::LoadOp::Load,
110                            store: wgpu::StoreOp::Store,
111                        },
112                        depth_slice: None,
113                    })],
114                    depth_stencil_attachment: None,
115                    label: Some("Egui Render Pass"),
116                    timestamp_writes: None,
117                    occlusion_query_set: None,
118                })
119                .forget_lifetime();
120
121            self.renderer.render(&mut rpass, &tris, &screen_descriptor);
122        }
123
124        // Add command buffer to frame
125        frame.add_command_buffer(encoder.finish());
126
127        for x in &full_output.textures_delta.free {
128            self.renderer.free_texture(x)
129        }
130    }
131
132    /// Process events from the event batch.
133    pub fn handle_events(&mut self, window: &RenderWindow, events: &mut EventBatch) -> bool {
134        profile_function!();
135        let mut any_consumed = false;
136
137        events.dispatch(|event| {
138            let response = self.state.on_event(window, event);
139            if response.consumed {
140                any_consumed = true;
141            }
142            let mut status = astrelis_winit::event::HandleStatus::empty();
143            if response.repaint || response.consumed {
144                status |= astrelis_winit::event::HandleStatus::HANDLED;
145            }
146            if response.consumed {
147                status |= astrelis_winit::event::HandleStatus::CONSUMED;
148            }
149            status
150        });
151
152        any_consumed
153    }
154
155    /// Get the egui context for direct access.
156    pub fn context(&self) -> &egui::Context {
157        &self.context
158    }
159
160    /// Register a wgpu texture with egui for rendering.
161    /// Returns a texture ID that can be used in egui image widgets.
162    pub fn register_wgpu_texture(
163        &mut self,
164        device: &wgpu::Device,
165        texture: &wgpu::TextureView,
166        filter: wgpu::FilterMode,
167    ) -> egui::TextureId {
168        self.renderer
169            .register_native_texture(device, texture, filter)
170    }
171
172    /// Unregister a texture from egui.
173    pub fn unregister_texture(&mut self, id: egui::TextureId) {
174        self.renderer.free_texture(&id);
175    }
176}