use bevy::prelude::*;
use bevy::ui::UiSystems;
use crate::message::ReactAppExt;
use crate::protocol::NodeId;
mod console;
#[cfg(test)]
mod js_tables;
mod layers;
mod panel;
mod pick;
mod settings;
mod stats;
#[cfg(test)]
mod test_util;
use console::{
emit_console, emit_runtime_warnings, on_console_clear_message, on_console_open_message,
};
use layers::{emit_layers, on_layers_open_message};
use panel::{
apply_dock_reservation, on_dock_message, on_open_message, on_overlay_message,
on_panel_root_message, send_window_size, toggle_on_key,
};
use pick::{
drive_pick_mode, on_highlight_message, on_pick_message, on_select_message, position_highlight,
spawn_highlight_overlay,
};
use settings::{
DevtoolsPersistence, flush_settings_on_exit, load_settings, on_settings_message, save_settings,
send_restore,
};
use stats::{DevtoolsTimers, emit_batch_stats, mark_post_layout, mark_pre_layout};
#[derive(Resource, Clone)]
pub struct DevtoolsConfig {
pub enabled: bool,
pub toggle_key: KeyCode,
pub settings_path: Option<std::path::PathBuf>,
}
impl Default for DevtoolsConfig {
fn default() -> Self {
Self {
enabled: true,
toggle_key: KeyCode::F12,
settings_path: Some(std::path::PathBuf::from(".bevy-react-devtools.json")),
}
}
}
pub struct DevtoolsPlugin {
config: DevtoolsConfig,
}
impl DevtoolsPlugin {
pub fn new(config: DevtoolsConfig) -> Self {
Self { config }
}
}
impl Plugin for DevtoolsPlugin {
fn build(&self, app: &mut App) {
if !cfg!(debug_assertions) {
debug!("DevtoolsPlugin is inert in release builds");
return;
}
app.init_resource::<ButtonInput<KeyCode>>();
app.init_resource::<ButtonInput<MouseButton>>();
app.init_resource::<crate::layer::LayersRegistry>();
app.init_resource::<crate::layer::LayerMembership>();
crate::diag::arm_runtime();
let loaded = load_settings(self.config.settings_path.as_deref());
app.insert_resource(DevtoolsState {
show_selection_overlay: loaded.as_ref().is_none_or(|s| s.overlay),
..Default::default()
})
.insert_resource(DevtoolsPersistence::from_loaded(loaded))
.init_resource::<DevtoolsTimers>()
.insert_resource(self.config.clone())
.add_react_handler(on_open_message)
.add_react_handler(on_pick_message)
.add_react_handler(on_select_message)
.add_react_handler(on_highlight_message)
.add_react_handler(on_overlay_message)
.add_react_handler(on_panel_root_message)
.add_react_handler(on_dock_message)
.add_react_handler(on_settings_message)
.add_react_handler(on_layers_open_message)
.add_react_handler(on_console_open_message)
.add_react_handler(on_console_clear_message)
.add_systems(Startup, spawn_highlight_overlay)
.add_systems(
Update,
(
toggle_on_key,
send_window_size,
position_highlight,
apply_dock_reservation,
send_restore,
save_settings,
emit_runtime_warnings,
emit_console,
),
)
.add_systems(Last, flush_settings_on_exit)
.add_systems(
Update,
drive_pick_mode
.in_set(crate::plugin::PointerCaptureSet)
.after(crate::reconcile::collect_pointer_events),
)
.add_systems(
PostUpdate,
(
mark_pre_layout
.after(UiSystems::Content)
.before(UiSystems::Layout),
mark_post_layout.after(UiSystems::PostLayout),
emit_batch_stats.after(mark_post_layout),
emit_layers
.after(crate::layer::sync_layer_geometry)
.after(crate::layer::resolve_layer_repaints),
),
);
}
}
#[derive(Resource)]
pub(crate) struct DevtoolsState {
pub open: bool,
pub pick: bool,
pub selected: Option<NodeId>,
pub tree_hover: Option<NodeId>,
pub pick_hover: Option<NodeId>,
pub show_selection_overlay: bool,
pub panel_root: Option<NodeId>,
pub dock_side: Option<DockSide>,
pub dock_width: f32,
pub layers_tab_open: bool,
pub console_tab_open: bool,
pub console_last_seq: Option<u64>,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum DockSide {
Left,
Right,
}
impl Default for DevtoolsState {
fn default() -> Self {
Self {
open: false,
pick: false,
selected: None,
tree_hover: None,
pick_hover: None,
show_selection_overlay: true,
panel_root: None,
dock_side: None,
dock_width: 0.0,
layers_tab_open: false,
console_tab_open: false,
console_last_seq: None,
}
}
}