Blacksite/crates/editor/src/ui/toolbar.rs
Rbanh b5e561904c
Some checks failed
CI / Format, lint, test, build (push) Has been cancelled
Upgrade to Bevy 0.19 and harden editor workflows
2026-07-09 23:43:47 -04:00

172 lines
6.4 KiB
Rust

//! Top editor toolbar (below menu bar): spawn/file left, transport center.
use bevy::prelude::*;
use bevy_egui::egui;
use egui_phosphor_icons::icons;
use crate::history::spawn_with_history;
use crate::scene_io::{SceneIo, SceneIoRequest};
use crate::state::{EditorMode, PlayPaused, PlayPossession};
use crate::ui::UiState;
use crate::viewport::brush_tool::start_draw_brush_tool;
use crate::viewport::{editor_spawn_ground_position, snap_translation, ViewportSettings};
use shared::PrimitiveShape;
use super::helpers::{
light_snapshot, primitive_snapshot, toggle_play_mode, toggle_play_paused, toggle_possession,
};
use super::widgets::{icon_button, panel_toolbar_row, toolbar_separator, transport_button_large};
/// Toolbar strip height (menu bar is separate).
const TOOLBAR_HEIGHT: f32 = 56.0;
const TRANSPORT_WIDTH: f32 = 152.0;
const TRANSPORT_SLOT_STEP: f32 = 54.0;
pub fn editor_toolbar_panel(world: &mut World, root_ui: &mut egui::Ui) {
egui::Panel::top("editor_toolbar")
.exact_size(TOOLBAR_HEIGHT)
.show_inside(root_ui, |ui| {
toolbar_ui(world, ui);
});
}
pub fn toolbar_ui(world: &mut World, ui: &mut egui::Ui) {
let (toolbar_rect, _) = ui.allocate_exact_size(
egui::vec2(ui.available_width(), TOOLBAR_HEIGHT - 4.0),
egui::Sense::hover(),
);
let center_y = toolbar_rect.center().y;
let left_rect = egui::Rect::from_min_max(
egui::pos2(toolbar_rect.left() + 8.0, center_y - 18.0),
egui::pos2(toolbar_rect.right(), center_y + 18.0),
);
let transport_rect = egui::Rect::from_center_size(
egui::pos2(toolbar_rect.center().x, center_y),
egui::vec2(TRANSPORT_WIDTH, 44.0),
);
let mut left_ui = ui.new_child(
egui::UiBuilder::new()
.id_salt("toolbar_left")
.max_rect(left_rect)
.layout(egui::Layout::left_to_right(egui::Align::Center)),
);
left_ui.spacing_mut().item_spacing.x = 6.0;
left_toolbar(world, &mut left_ui);
let mut transport_ui = ui.new_child(
egui::UiBuilder::new()
.id_salt("toolbar_transport")
.max_rect(transport_rect)
.layout(egui::Layout::left_to_right(egui::Align::Center)),
);
transport_ui.spacing_mut().item_spacing.x = 10.0;
transport_controls(world, &mut transport_ui);
}
fn left_toolbar(world: &mut World, ui: &mut egui::Ui) {
panel_toolbar_row(ui, |ui| {
if icon_button(ui, icons::CUBE, "Spawn cube").clicked() {
spawn_primitive_at_camera(world, PrimitiveShape::Box, "Cube");
}
if icon_button(ui, icons::MOUNTAINS, "Spawn ramp").clicked() {
spawn_primitive_at_camera(world, PrimitiveShape::Ramp, "Ramp");
}
if icon_button(ui, icons::SPHERE, "Spawn sphere").clicked() {
spawn_primitive_at_camera(world, PrimitiveShape::Sphere, "Sphere");
}
if icon_button(ui, icons::LIGHTBULB, "Spawn point light").clicked() {
let pos = camera_ground_spawn(world);
spawn_with_history(world, light_snapshot("Point Light", pos));
}
if icon_button(ui, icons::PENCIL_SIMPLE_LINE, "Draw brush (B)").clicked() {
start_draw_brush_tool(world);
}
toolbar_separator(ui);
if icon_button(ui, icons::FILE, "New scene").clicked() {
world.resource_mut::<SceneIo>().request = Some(SceneIoRequest::New);
}
if icon_button(ui, icons::FOLDER_OPEN, "Open scene").clicked() {
world.resource_mut::<SceneIo>().request = Some(SceneIoRequest::Open);
}
if icon_button(ui, icons::FLOPPY_DISK, "Save scene").clicked() {
world.resource_mut::<SceneIo>().request = Some(SceneIoRequest::Save);
}
if icon_button(ui, icons::COPY, "Save scene as").clicked() {
world.resource_mut::<SceneIo>().request = Some(SceneIoRequest::SaveAs);
}
});
}
fn transport_controls(world: &mut World, ui: &mut egui::Ui) {
let mode = *world.resource::<State<EditorMode>>().get();
match mode {
EditorMode::Editing => {
ui.add_space(TRANSPORT_SLOT_STEP);
let play_clicked = ui
.push_id("transport_center", |ui| {
transport_button_large(ui, icons::PLAY, "Play (F5)", true).clicked()
})
.inner;
if play_clicked {
toggle_play_mode(world);
}
}
EditorMode::Playing => {
let paused = world.resource::<PlayPaused>().0;
let pause_icon = if paused { icons::PLAY } else { icons::PAUSE };
let pause_tip = if paused {
"Resume simulation (F6)"
} else {
"Pause simulation (F6)"
};
let pause_clicked = ui
.push_id("transport_left", |ui| {
transport_button_large(ui, pause_icon, pause_tip, false).clicked()
})
.inner;
if pause_clicked {
toggle_play_paused(world);
}
let stop_clicked = ui
.push_id("transport_center", |ui| {
transport_button_large(ui, icons::STOP, "Stop and return to Edit (F5)", false)
.clicked()
})
.inner;
if stop_clicked {
toggle_play_mode(world);
}
let possession = *world.resource::<PlayPossession>();
let (eject_icon, eject_tip) = match possession {
PlayPossession::Possessed => (icons::SIGN_OUT, "Eject from player (F8)"),
PlayPossession::Ejected => (icons::USER, "Possess player (F8)"),
};
let possession_clicked = ui
.push_id("transport_right", |ui| {
transport_button_large(ui, eject_icon, eject_tip, false).clicked()
})
.inner;
if possession_clicked {
toggle_possession(world);
}
}
}
}
fn camera_ground_spawn(world: &mut World) -> Vec3 {
let rect = world.resource::<UiState>().viewport_rect;
let settings = world.resource::<ViewportSettings>().clone();
let pos = editor_spawn_ground_position(world, rect);
snap_translation(pos, &settings)
}
fn spawn_primitive_at_camera(world: &mut World, shape: PrimitiveShape, name: &str) {
let pos = camera_ground_spawn(world);
spawn_with_history(world, primitive_snapshot(name, shape, pos));
}