Import Oxide engine (Stages 0–10) under MIT license
Full project snapshot migrated to new Gitea remote without history: engine, editor, physics, script, examples, tests, docs, and assets. Relicensed from GPLv3 to MIT and updated repo URLs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
//! 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())),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user