296 lines
8.9 KiB
Rust
296 lines
8.9 KiB
Rust
// Debug Overlay - egui Debug/Developer UI
|
|
// Production-ready debug overlay patterns for game engines
|
|
//
|
|
// Last Updated: 2026-03-14
|
|
// egui Version: 0.27+
|
|
// Rust Version: 1.85+
|
|
|
|
use egui::{Context, Color32, FontId, TextStyle, Align, Layout};
|
|
use std::sync::{Arc, Mutex};
|
|
use std::collections::VecDeque;
|
|
|
|
/// DebugOverlay - Main debug overlay structure
|
|
/// Conditional compilation for production safety
|
|
#[cfg(debug)]
|
|
#[derive(Clone, Debug)]
|
|
pub struct DebugOverlay {
|
|
/// egui context reference
|
|
pub ctx: Arc<Context>,
|
|
/// FPS counter
|
|
pub fps: f32,
|
|
/// Frame time
|
|
pub frame_time_ms: f32,
|
|
/// Entity inspector state
|
|
pub inspector_open: bool,
|
|
/// Console log buffer
|
|
pub log_buffer: VecDeque<String>,
|
|
/// Debug state
|
|
pub debug_state: DebugState,
|
|
/// Accessibility config
|
|
pub accessibility: DebugAccessibility,
|
|
}
|
|
|
|
/// DebugState - Debug overlay state machine
|
|
#[cfg(debug)]
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct DebugState {
|
|
/// Overlay enabled
|
|
pub enabled: bool,
|
|
/// FPS counter enabled
|
|
pub fps_enabled: bool,
|
|
/// Inspector enabled
|
|
pub inspector_enabled: bool,
|
|
/// Console enabled
|
|
pub console_enabled: bool,
|
|
/// Performance metrics enabled
|
|
pub perf_metrics_enabled: bool,
|
|
/// Memory tracking enabled
|
|
pub memory_tracking_enabled: bool,
|
|
}
|
|
|
|
/// DebugAccessibility - Debug overlay accessibility
|
|
#[cfg(debug)]
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct DebugAccessibility {
|
|
/// High contrast theme
|
|
pub high_contrast: bool,
|
|
/// Large text mode
|
|
pub large_text: bool,
|
|
/// Keyboard navigation
|
|
pub keyboard_navigation: bool,
|
|
/// Screen reader output
|
|
pub screen_reader_output: bool,
|
|
}
|
|
|
|
/// Implementation: DebugOverlay
|
|
#[cfg(debug)]
|
|
impl DebugOverlay {
|
|
/// Create new debug overlay
|
|
pub fn new(ctx: Context) -> Self {
|
|
Self {
|
|
ctx: Arc::new(ctx),
|
|
fps: 0.0,
|
|
frame_time_ms: 0.0,
|
|
inspector_open: false,
|
|
log_buffer: VecDeque::with_capacity(100),
|
|
debug_state: DebugState::default(),
|
|
accessibility: DebugAccessibility::default(),
|
|
}
|
|
}
|
|
|
|
/// Initialize debug overlay
|
|
pub fn init(&mut self) {
|
|
self.debug_state.enabled = true;
|
|
self.debug_state.fps_enabled = true;
|
|
self.debug_state.console_enabled = true;
|
|
|
|
// Apply accessibility theme
|
|
if self.accessibility.high_contrast {
|
|
self.apply_high_contrast_theme();
|
|
}
|
|
|
|
log::info!("Debug overlay initialized");
|
|
}
|
|
|
|
/// Apply high contrast theme - accessibility
|
|
fn apply_high_contrast_theme(&mut self) {
|
|
let mut style = (*self.ctx.style()).clone();
|
|
style.colors[egui::Spacing::Background] = Color32::BLACK;
|
|
style.colors[egui::Spacing::Foreground] = Color32::WHITE;
|
|
style.colors[egui::Spacing::PanelBackground] = Color32::BLACK;
|
|
self.ctx.set_style(style);
|
|
}
|
|
|
|
/// Update FPS counter
|
|
pub fn update_fps(&mut self, fps: f32, frame_time_ms: f32) {
|
|
self.fps = fps;
|
|
self.frame_time_ms = frame_time_ms;
|
|
}
|
|
|
|
/// Log message to console buffer
|
|
pub fn log(&mut self, message: String) {
|
|
self.log_buffer.push_front(message);
|
|
if self.log_buffer.len() > 100 {
|
|
self.log_buffer.pop_back();
|
|
}
|
|
}
|
|
|
|
/// Render debug overlay
|
|
pub fn render(&self) {
|
|
let ctx = &self.ctx;
|
|
|
|
// Top panel - FPS counter
|
|
egui::TopBottomPanel::top("debug_top_panel").show(ctx, |ui| {
|
|
ui.horizontal(|ui| {
|
|
if self.debug_state.fps_enabled {
|
|
ui.colored_label(
|
|
Color32::GREEN,
|
|
format!("FPS: {:.1} | Frame: {:.2}ms", self.fps, self.frame_time_ms),
|
|
);
|
|
}
|
|
|
|
if self.debug_state.perf_metrics_enabled {
|
|
ui.label(format!("Memory: {} MB", self.get_memory_usage()));
|
|
}
|
|
});
|
|
});
|
|
|
|
// Left panel - Entity inspector
|
|
if self.inspector_open && self.debug_state.inspector_enabled {
|
|
egui::SidePanel::left("debug_inspector").show(ctx, |ui| {
|
|
ui.heading("Entity Inspector");
|
|
ui.label("Entity count: {}", self.get_entity_count());
|
|
ui.label("Active systems: {}", self.get_system_count());
|
|
|
|
// Entity list with accessibility
|
|
ui.vertical(|ui| {
|
|
ui.set_accessibility(self.accessibility.keyboard_navigation);
|
|
for i in 0..self.get_entity_count() {
|
|
ui.label(format("Entity {}", i));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Bottom panel - Console
|
|
if self.debug_state.console_enabled {
|
|
egui::TopBottomPanel::bottom("debug_console").show(ctx, |ui| {
|
|
ui.set_min_height(150.0);
|
|
ui.heading("Debug Console");
|
|
|
|
// Log buffer with scrolling
|
|
egui::ScrollArea::vertical().show(ui, |ui| {
|
|
for log in &self.log_buffer {
|
|
ui.label(log);
|
|
}
|
|
});
|
|
|
|
// Input field
|
|
ui.horizontal(|ui| {
|
|
ui.label("> ");
|
|
ui.add(egui::TextEdit::singleline(""));
|
|
});
|
|
});
|
|
}
|
|
|
|
// Window - Performance metrics
|
|
if self.debug_state.perf_metrics_enabled {
|
|
egui::Window::new("Performance").show(ctx, |ui| {
|
|
ui.heading("Performance Metrics");
|
|
ui.label(format!("FPS: {:.1}", self.fps));
|
|
ui.label(format!("Frame time: {:.2}ms", self.frame_time_ms));
|
|
ui.label(format!("Memory: {} MB", self.get_memory_usage()));
|
|
ui.label(format!("Entities: {}", self.get_entity_count()));
|
|
ui.label(format!("Systems: {}", self.get_system_count()));
|
|
});
|
|
}
|
|
|
|
// Window - Memory tracking
|
|
if self.debug_state.memory_tracking_enabled {
|
|
egui::Window::new("Memory").show(ctx, |ui| {
|
|
ui.heading("Memory Tracking");
|
|
ui.label(format("Total: {} MB", self.get_memory_usage()));
|
|
ui.label(format("Allocations: {}", self.get_allocation_count()));
|
|
ui.label(format("Dealocations: {}", self.get_deallocation_count()));
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Get memory usage - mock implementation
|
|
fn get_memory_usage(&self) -> u32 {
|
|
50 // Mock value
|
|
}
|
|
|
|
/// Get entity count - mock implementation
|
|
fn get_entity_count(&self) -> usize {
|
|
100 // Mock value
|
|
}
|
|
|
|
/// Get system count - mock implementation
|
|
fn get_system_count(&self) -> usize {
|
|
10 // Mock value
|
|
}
|
|
|
|
/// Get allocation count - mock implementation
|
|
fn get_allocation_count(&self) -> usize {
|
|
1000 // Mock value
|
|
}
|
|
|
|
/// Get deallocation count - mock implementation
|
|
fn get_deallocation_count(&self) -> usize {
|
|
950 // Mock value
|
|
}
|
|
|
|
/// Toggle inspector
|
|
pub fn toggle_inspector(&mut self) {
|
|
self.inspector_open = !self.inspector_open;
|
|
self.log(format!("Inspector: {}", self.inspector_open));
|
|
}
|
|
|
|
/// Toggle console
|
|
pub fn toggle_console(&mut self) {
|
|
self.debug_state.console_enabled = !self.debug_state.console_enabled;
|
|
self.log(format!("Console: {}", self.debug_state.console_enabled));
|
|
}
|
|
|
|
/// Toggle FPS counter
|
|
pub fn toggle_fps(&mut self) {
|
|
self.debug_state.fps_enabled = !self.debug_state.fps_enabled;
|
|
self.log(format!("FPS counter: {}", self.debug_state.fps_enabled));
|
|
}
|
|
|
|
/// Enable accessibility
|
|
pub fn enable_accessibility(&mut self, enabled: bool) {
|
|
self.accessibility.high_contrast = enabled;
|
|
self.accessibility.keyboard_navigation = enabled;
|
|
self.accessibility.screen_reader_output = enabled;
|
|
|
|
if enabled {
|
|
self.apply_high_contrast_theme();
|
|
}
|
|
|
|
self.log(format!("Accessibility: {}", enabled));
|
|
}
|
|
}
|
|
|
|
/// Debug overlay system - Main render loop
|
|
#[cfg(debug)]
|
|
pub fn debug_overlay_system(
|
|
mut overlay: ResMut<DebugOverlay>,
|
|
fps: Res<FpsCounter>,
|
|
frame_time: Res<FrameTime>,
|
|
) {
|
|
// Update FPS
|
|
overlay.update_fps(fps.value, frame_time.value_ms);
|
|
|
|
// Render overlay
|
|
overlay.render();
|
|
}
|
|
|
|
/// FPS Counter - Resource
|
|
#[derive(Resource, Debug)]
|
|
pub struct FpsCounter {
|
|
pub value: f32,
|
|
}
|
|
|
|
/// Frame Time - Resource
|
|
#[derive(Resource, Debug)]
|
|
pub struct FrameTime {
|
|
pub value_ms: f32,
|
|
}
|
|
|
|
/// Resource marker for debug overlay
|
|
#[derive(Resource, Debug)]
|
|
pub struct DebugOverlayMarker;
|
|
|
|
/// Conditional compilation for production builds
|
|
#[cfg(not(debug))]
|
|
pub struct DebugOverlay; // Empty stub
|
|
|
|
#[cfg(not(debug))]
|
|
impl DebugOverlay {
|
|
pub fn new(_ctx: Context) -> Self { Self }
|
|
pub fn init(&mut self) {}
|
|
pub fn render(&self) {}
|
|
pub fn log(&mut self, _message: String) {}
|
|
} |