use crate::{
ecs::{
components::buffer::{EditorView, FocusedEditorView, ViewEntity},
schedules::EditorStartupSet,
},
features::ui::STATUS_BAR_HEIGHT,
fonts::{EDITOR_FONT_SIZE, editor_font},
render::ManagedText,
};
use bevy::camera::ScalingMode;
use bevy::prelude::{
App, AssetServer, Assets, Camera3d, Color, Commands, Entity, IntoScheduleConfigs,
IsDefaultUiCamera, Justify, Mesh, Mesh3d, MeshMaterial3d, Node, OrthographicProjection,
Plane3d, Plugin, PositionType, Projection, Query, Res, ResMut, StandardMaterial, Startup, Text,
TextColor, TextLayout, Transform, Val, Vec2, Vec3, With, default,
};
use crate::features::ui::style::scene_text_color;
const BACKGROUND_PLANE_HALF_SIZE: f32 = 64.0;
const CAMERA_VIEWPORT_HEIGHT: f32 = 12.0;
const CAMERA_HEIGHT: f32 = 10.0;
const LABEL_INSET: f32 = 32.0;
#[derive(Clone, Copy, Debug, Default)]
pub struct EditorViewPlugin;
impl Plugin for EditorViewPlugin {
fn build(&self, app: &mut App) {
let _app = app.add_systems(Startup, setup_scene.in_set(EditorStartupSet::Scene));
}
}
pub fn setup_scene(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
focused_view: Query<Entity, (With<EditorView>, With<FocusedEditorView>)>,
) {
let asset_server = asset_server.into_inner();
let Some(render_target) = focused_view.iter().next() else {
return;
};
let _plane_entity = commands
.spawn((
Mesh3d(meshes.add(Plane3d::new(
Vec3::Y,
Vec2::splat(BACKGROUND_PLANE_HALF_SIZE),
))),
MeshMaterial3d(materials.add(Color::srgb_u8(0x00, 0x1E, 0x27))),
))
.id();
let _camera_entity = commands
.spawn((
Camera3d::default(),
Projection::from(OrthographicProjection {
scaling_mode: ScalingMode::FixedVertical {
viewport_height: CAMERA_VIEWPORT_HEIGHT,
},
..OrthographicProjection::default_3d()
}),
Transform::from_xyz(0.0, CAMERA_HEIGHT, 0.0).looking_at(Vec3::ZERO, Vec3::Z),
IsDefaultUiCamera,
))
.id();
let _text_entity = commands
.spawn((Node {
position_type: PositionType::Absolute,
left: Val::Px(LABEL_INSET),
top: Val::Px(LABEL_INSET),
right: Val::Px(LABEL_INSET),
bottom: Val::Px(STATUS_BAR_HEIGHT + LABEL_INSET),
..default()
},))
.with_children(|parent| {
let _managed_text_entity = parent
.spawn((
Text::default(),
editor_font(asset_server, EDITOR_FONT_SIZE),
TextColor(scene_text_color()),
TextLayout::new_with_justify(Justify::Left),
ManagedText {
target: ViewEntity(render_target),
},
))
.id();
})
.id();
}
#[cfg(test)]
mod tests {
use super::{EditorViewPlugin, ManagedText};
use crate::{
buffer::BufferFile,
ecs::{
buffer::{BufferPlugin, InitialEditorBuffer},
components::buffer::{EditorView, FocusedEditorView, ViewEntity},
},
text_stream::TextByteStream,
};
use bevy::asset::AssetApp;
use bevy::prelude::{
App, AssetPlugin, Assets, Entity, Font, Mesh, MinimalPlugins, StandardMaterial, With,
};
fn editor_view_entity(app: &mut App) -> Entity {
let mut query = app
.world_mut()
.query_filtered::<Entity, (With<EditorView>, With<FocusedEditorView>)>();
query
.iter(app.world())
.next()
.expect("focused editor view should exist")
}
#[test]
fn scene_startup_targets_spawned_focused_buffer() {
let mut app = App::new();
let _app = app
.insert_resource(InitialEditorBuffer {
stream: TextByteStream::new("scene text"),
file: BufferFile::scratch(0),
})
.add_plugins(MinimalPlugins)
.add_plugins(AssetPlugin {
watch_for_changes_override: Some(false),
..AssetPlugin::default()
})
.init_resource::<Assets<Mesh>>()
.init_resource::<Assets<StandardMaterial>>()
.init_asset::<Font>()
.add_plugins(crate::ecs::EditorCorePlugin)
.add_plugins(BufferPlugin)
.add_plugins(EditorViewPlugin);
app.update();
let target = editor_view_entity(&mut app);
let mut managed_text_query = app.world_mut().query::<&ManagedText>();
let managed_text = managed_text_query
.single(app.world())
.expect("scene should spawn one managed text node");
assert_eq!(managed_text.target, ViewEntity(target));
}
}