//! egui ⇄ engine glue for the editor. //! //! The engine core stays UI-agnostic; all egui wiring lives here in the editor. //! [`EguiLayer`] owns the [`egui_winit`] input state and the [`egui_wgpu`] //! renderer, translates window events, and paints a built UI into the frame's //! surface view (recorded with `LoadOp::Load`, so it composites on top of the //! engine's clear). use egui_wgpu::{Renderer, RendererOptions, ScreenDescriptor}; use egui_winit::State; use oxide_engine::wgpu; use oxide_engine::winit::event::WindowEvent; use oxide_engine::winit::window::Window; /// Holds the egui input state and GPU renderer for one window. pub struct EguiLayer { state: State, renderer: Renderer, } impl EguiLayer { /// Creates the layer for `window`, building a renderer that targets the /// given surface format. pub fn new( window: &Window, device: &wgpu::Device, surface_format: wgpu::TextureFormat, ) -> Self { let context = egui::Context::default(); let state = State::new( context, egui::ViewportId::ROOT, window, Some(window.scale_factor() as f32), None, None, ); // Defaults: no MSAA, no depth/stencil, dithering on — matches the // editor's flat clear-color surface. let renderer = Renderer::new(device, surface_format, RendererOptions::default()); Self { state, renderer } } /// Feeds a window event to egui. Returns `true` if egui consumed it (e.g. /// a click landed on a panel), so the caller can suppress its own handling. pub fn on_window_event(&mut self, window: &Window, event: &WindowEvent) -> bool { self.state.on_window_event(window, event).consumed } /// Whether the pointer is currently over a **floating** egui area — a /// `Window` (Preferences, Layer Names, Groups, …) or other non-background /// layer — rather than empty space or the background dock. /// /// The viewport is painted under a transparent dock area (background /// order), so a geometric "cursor inside the viewport rect" test can't tell /// that a floating panel is sitting on top of it. The host uses this to /// suppress viewport orbit/pan/zoom (and stray WASD while typing in a panel /// that overlaps the viewport). pub fn pointer_over_floating(&self) -> bool { let ctx = self.state.egui_ctx(); let Some(pos) = ctx.pointer_latest_pos() else { return false; }; ctx.layer_id_at(pos) .map(|layer| layer.order > egui::Order::Background) .unwrap_or(false) } /// Builds the UI via `build_ui` and paints it into `view`. /// /// `build_ui` receives the root [`egui::Ui`]; panels are shown inside it /// (egui 0.34's `show_inside` model). It may be called more than once per /// frame if egui needs an extra layout pass, so it must be idempotent. #[allow(clippy::too_many_arguments)] pub fn paint( &mut self, window: &Window, device: &wgpu::Device, queue: &wgpu::Queue, view: &wgpu::TextureView, size: (u32, u32), build_ui: impl FnMut(&mut egui::Ui), ) { let raw_input = self.state.take_egui_input(window); let context = self.state.egui_ctx().clone(); let output = context.run_ui(raw_input, build_ui); self.state .handle_platform_output(window, output.platform_output); let primitives = context.tessellate(output.shapes, output.pixels_per_point); let screen = ScreenDescriptor { size_in_pixels: [size.0.max(1), size.1.max(1)], pixels_per_point: output.pixels_per_point, }; for (id, delta) in &output.textures_delta.set { self.renderer.update_texture(device, queue, *id, delta); } let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("oxide.editor.egui.encoder"), }); // egui may emit its own command buffers (for paint callbacks); submit // those ahead of our pass. let user_buffers = self.renderer .update_buffers(device, queue, &mut encoder, &primitives, &screen); { let pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("oxide.editor.egui.pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view, resolve_target: None, depth_slice: None, ops: wgpu::Operations { // Load: keep the engine's clear; draw the UI over it. load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, multiview_mask: None, }); // egui-wgpu wants a 'static pass; the encoder outlives it here. let mut pass = pass.forget_lifetime(); self.renderer.render(&mut pass, &primitives, &screen); } for id in &output.textures_delta.free { self.renderer.free_texture(id); } queue.submit( user_buffers .into_iter() .chain(std::iter::once(encoder.finish())), ); } }