From cc05c7c434f6312012c80e1ffc0882042aa20367 Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Sat, 26 Aug 2023 08:16:26 -0700 Subject: [PATCH] Ok the UI actually feels pretty nice... saving my place before I fuck it up --- bin/editor.rs | 75 ++++++++++------ src/ui.rs | 241 +++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 256 insertions(+), 60 deletions(-) diff --git a/bin/editor.rs b/bin/editor.rs index f6ff1e9..39317e3 100644 --- a/bin/editor.rs +++ b/bin/editor.rs @@ -36,6 +36,7 @@ use bevy::{ asset::{AssetLoader, LoadContext, LoadedAsset}, audio::PlaybackMode, gltf::Gltf, + input::{keyboard::KeyboardInput, ButtonState}, prelude::*, utils::BoxedFuture, }; @@ -60,7 +61,7 @@ fn main() { .init_asset_loader::() .add_event::>() .add_event::>() - .add_systems(Startup, initialize_ui) + .add_systems(Startup, (initialize_ui, welcome_message)) .add_systems( Update, ( @@ -116,7 +117,7 @@ fn initialize_ui(mut commands: Commands) { let base_style = Style { border: UiRect::all(Val::Px(1.0)), - padding: UiRect::all(Val::Px(5.0)), + padding: UiRect::all(Val::Px(1.0)), overflow: Overflow::clip(), flex_direction: FlexDirection::Column, ..default() @@ -125,9 +126,12 @@ fn initialize_ui(mut commands: Commands) { commands .spawn(NodeBundle { style: Style { + top: Val::Px(0.0), + left: Val::Px(0.0), + position_type: PositionType::Absolute, border: UiRect::all(Val::Px(1.0)), - margin: UiRect::all(Val::Px(5.0)), - padding: UiRect::all(Val::Px(5.0)), + margin: UiRect::all(Val::Px(1.0)), + padding: UiRect::all(Val::Px(1.0)), flex_direction: FlexDirection::Column, overflow: Overflow::clip(), ..default() @@ -141,8 +145,8 @@ fn initialize_ui(mut commands: Commands) { .spawn((NodeBundle { style: Style { border: UiRect::all(Val::Px(1.0)), - margin: UiRect::all(Val::Px(5.0)), - padding: UiRect::all(Val::Px(5.0)), + margin: UiRect::all(Val::Px(1.0)), + padding: UiRect::all(Val::Px(1.0)), flex_direction: FlexDirection::Row, overflow: Overflow::clip(), ..default() @@ -161,8 +165,8 @@ fn initialize_ui(mut commands: Commands) { NodeBundle { style: Style { border: UiRect::all(Val::Px(1.0)), - margin: UiRect::all(Val::Px(5.0)), - padding: UiRect::all(Val::Px(5.0)), + margin: UiRect::all(Val::Px(1.0)), + padding: UiRect::all(Val::Px(1.0)), flex_direction: FlexDirection::Column, overflow: Overflow::clip(), justify_content: JustifyContent::FlexStart, @@ -178,37 +182,31 @@ fn initialize_ui(mut commands: Commands) { content_containers.push(spawn_tab_container::( "Font", parent, - &base_style, ui::Select::Single, )); content_containers.push(spawn_tab_container::( "Audio", parent, - &base_style, ui::Select::Multi, )); content_containers.push(spawn_tab_container::( "Gltf", parent, - &base_style, ui::Select::Single, )); content_containers.push(spawn_tab_container::( "Scene", parent, - &base_style, ui::Select::Single, )); content_containers.push(spawn_tab_container::( "Animation", parent, - &base_style, ui::Select::Multi, )); content_containers.push(spawn_tab_container::( "Camera", parent, - &base_style, ui::Select::Single, )); }); @@ -219,8 +217,8 @@ fn initialize_ui(mut commands: Commands) { NodeBundle { style: Style { border: UiRect::all(Val::Px(1.0)), - margin: UiRect::all(Val::Px(5.0)), - padding: UiRect::all(Val::Px(5.0)), + margin: UiRect::all(Val::Px(1.0)), + padding: UiRect::all(Val::Px(1.0)), flex_direction: FlexDirection::Column, overflow: Overflow::clip(), ..default() @@ -246,7 +244,6 @@ fn initialize_ui(mut commands: Commands) { b.clone(), ui::Title { text: name.clone() }, ui::Collapse { target: *target }, - ui::Active, )); }); }); @@ -256,10 +253,13 @@ fn initialize_ui(mut commands: Commands) { NodeBundle { style: Style { border: UiRect::all(Val::Px(1.0)), - margin: UiRect::all(Val::Px(5.0)), - padding: UiRect::all(Val::Px(5.0)), + margin: UiRect::all(Val::Px(1.0)), + padding: UiRect::all(Val::Px(1.0)), flex_direction: FlexDirection::Row, overflow: Overflow::clip(), + align_items: AlignItems::Center, + align_content: AlignContent::Center, + justify_content: JustifyContent::SpaceBetween, ..default() }, background_color: Color::ALICE_BLUE.into(), @@ -275,28 +275,47 @@ fn initialize_ui(mut commands: Commands) { }); } +fn welcome_message(mut writer: EventWriter) { + writer.send(ui::Alert::Info( + "Welcome to the Monologue Trees editor!".into(), + )); + writer.send(ui::Alert::Info( + [ + "Import assets by dragging and dropping files into the editor", + "", + "Supported file types (for now):", + "* 3D: .gltf, .glb", + "* Audio: .ogg", + "* Font: .ttf, .otf", + ] + .join("\n") + .into(), + )); +} + fn spawn_tab_container( title: &'static str, parent: &mut ChildBuilder, - base_style: &Style, select: ui::Select, ) -> (String, Entity) { ( title.into(), + // Content node parent .spawn(( NodeBundle { style: Style { display: Display::None, - top: Val::Px(0.0), - ..base_style.clone() + border: UiRect::all(Val::Px(1.0)), + margin: UiRect::all(Val::Px(1.0)), + padding: UiRect::all(Val::Px(1.0)), + flex_direction: FlexDirection::Column, + ..default() }, background_color: Color::WHITE.into(), border_color: Color::BLACK.into(), - z_index: ZIndex::Local(100), ..default() }, - ui::Title { text: title.into() }, T::default(), ui::Scroll, Interaction::default(), @@ -461,8 +480,8 @@ mod assets { ButtonBundle { style: Style { border: UiRect::all(Val::Px(1.0)), - margin: UiRect::all(Val::Px(5.0)), - padding: UiRect::all(Val::Px(5.0)), + margin: UiRect::all(Val::Px(1.0)), + padding: UiRect::all(Val::Px(1.0)), ..default() }, border_color: Color::BLACK.into(), @@ -486,8 +505,8 @@ mod assets { ButtonBundle { style: Style { border: UiRect::all(Val::Px(1.0)), - margin: UiRect::all(Val::Px(5.0)), - padding: UiRect::all(Val::Px(5.0)), + margin: UiRect::all(Val::Px(1.0)), + padding: UiRect::all(Val::Px(1.0)), ..default() }, border_color: Color::BLACK.into(), diff --git a/src/ui.rs b/src/ui.rs index 4407842..42192e9 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -29,21 +29,25 @@ pub struct GameUiPlugin; impl Plugin for GameUiPlugin { fn build(&self, app: &mut App) { - app.add_systems( - Update, - ( - init_titles, - manage_titles, - manage_button_interaction, - manage_select_active, - manage_cursor, - manage_scroll, - manage_collapse_hiding, - manage_collapse_active, - show_child_number, - manage_sort, - ), - ); + app.add_event::() + .add_systems(Startup, init_alerts) + .add_systems( + Update, + ( + create_titles, + manage_titles, + manage_button_interaction, + manage_select_active, + manage_cursor, + manage_scroll, + manage_collapse_hiding, + manage_collapse_active, + show_child_number, + manage_sort, + spawn_alert, + ), + ) + .add_systems(PostUpdate, close_window); } } @@ -69,11 +73,25 @@ mod title { pub target: Entity, } - pub fn init_titles( - events: Query<(Entity, &Title, Option<&Note>, Option<&Minimize>), Added>, + #[derive(Debug, Component)] + pub struct Close { + pub target: Entity, + } + + pub fn create_titles( + events: Query< + ( + Entity, + &Title, + Option<&Note>, + Option<&Minimize>, + Option<&Close>, + ), + Added<Title>, + >, mut commands: Commands, ) { - events.for_each(|(entity, title, note, minimize)| { + events.for_each(|(entity, title, note, minimize, close)| { commands.entity(entity).with_children(|parent| { parent.spawn(( TextBundle { @@ -102,8 +120,9 @@ mod title { ..default() }, style: Style { - margin: UiRect::all(Val::Px(5.0)), - padding: UiRect::all(Val::Px(5.0)), + margin: UiRect::all(Val::Px(3.0)), + padding: UiRect::all(Val::Px(3.0)), + left: Val::Px(0.0), ..default() }, ..default() @@ -111,31 +130,82 @@ mod title { Sorting(0), TitleText, )); - if let Some(target) = minimize { - parent.spawn(( - ButtonBundle { + if minimize.is_some() || close.is_some() { + parent + .spawn(NodeBundle { style: Style { - border: UiRect::all(Val::Px(1.0)), - margin: UiRect::all(Val::Px(5.0)), - padding: UiRect::all(Val::Px(5.0)), + // border: UiRect::all(Val::Px(1.0)), + // margin: UiRect::all(Val::Px(1.0)), + // padding: UiRect::all(Val::Px(1.0)), right: Val::Px(0.0), ..default() }, background_color: Color::WHITE.into(), border_color: Color::BLACK.into(), ..default() - }, - Collapse { - target: target.target, - }, - )); + }) + .with_children(|parent| { + if let Some(Minimize { target }) = minimize { + parent + .spawn(( + ButtonBundle { + style: Style { + border: UiRect::all(Val::Px(1.0)), + // margin: UiRect::all(Val::Px(1.0)), + padding: UiRect::all(Val::Px(1.0)), + ..default() + }, + background_color: Color::WHITE.into(), + border_color: Color::BLACK.into(), + ..default() + }, + Collapse { target: *target }, + Active, + )) + .with_children(|parent| { + parent.spawn(TextBundle::from_section( + "-", + TextStyle { + color: Color::BLACK, + ..default() + }, + )); + }); + } + if let Some(Close { target }) = close { + parent + .spawn(( + ButtonBundle { + style: Style { + border: UiRect::all(Val::Px(1.0)), + // margin: UiRect::all(Val::Px(1.0)), + padding: UiRect::all(Val::Px(1.0)), + ..default() + }, + background_color: Color::WHITE.into(), + border_color: Color::BLACK.into(), + ..default() + }, + Close { target: *target }, + )) + .with_children(|parent| { + parent.spawn(TextBundle::from_section( + "x", + TextStyle { + color: Color::BLACK, + ..default() + }, + )); + }); + } + }); } }); }); } pub fn manage_titles( - events: Query<(&Title, Option<&Note>, &Children), Changed<Title>>, + events: Query<(&Title, Option<&Note>, &Children), Or<(Changed<Title>, Changed<Note>)>>, mut texts: Query<&mut Text, With<TitleText>>, ) { events.iter().for_each(|(title, note, children)| { @@ -169,6 +239,18 @@ mod title { }) }) } + + pub fn close_window( + events: Query<(&Interaction, &Close), (With<Button>, Changed<Interaction>)>, + mut commands: Commands, + ) { + events + .iter() + .filter(|(&interaction, _)| interaction == Interaction::Pressed) + .for_each(|(_, Close { target })| { + commands.entity(*target).despawn_recursive(); + }); + } } pub use collapse::*; @@ -426,3 +508,98 @@ mod sort { }) } } + +pub use alert::*; +pub mod alert { + use super::*; + + #[derive(Debug, Event)] + pub enum Alert { + Info(String), + Warn(String), + Danger(String), + } + + #[derive(Debug, Component)] + pub struct AlertsWidget; + + pub fn init_alerts(mut commands: Commands) { + commands.spawn(( + NodeBundle { + style: Style { + top: Val::Px(0.0), + right: Val::Px(0.0), + position_type: PositionType::Absolute, + width: Val::Percent(33.0), + padding: UiRect::all(Val::Px(1.0)), + margin: UiRect::all(Val::Px(1.0)), + border: UiRect::all(Val::Px(1.0)), + flex_direction: FlexDirection::Column, + justify_items: JustifyItems::Center, + ..default() + }, + border_color: Color::WHITE.into(), + ..default() + }, + AlertsWidget, + )); + } + + pub fn spawn_alert( + mut events: EventReader<Alert>, + root: Query<Entity, With<AlertsWidget>>, + mut commands: Commands, + ) { + events.iter().for_each(|alert| { + info!("Processing alert {:?}", alert); + + let (color, text) = match alert { + Alert::Info(text) => (Color::BLUE, text), + Alert::Warn(text) => (Color::ORANGE, text), + Alert::Danger(text) => (Color::RED, text), + }; + commands.entity(root.single()).with_children(|parent| { + parent + .spawn(NodeBundle { + style: Style { + width: Val::Percent(100.0), + padding: UiRect::all(Val::Px(1.0)), + margin: UiRect::all(Val::Px(1.0)), + border: UiRect::all(Val::Px(2.0)), + flex_direction: FlexDirection::Column, + justify_self: JustifySelf::Center, + ..default() + }, + border_color: color.into(), + ..default() + }) + .with_children(|parent| { + parent.spawn(( + NodeBundle { + style: Style { + padding: UiRect::all(Val::Px(1.0)), + margin: UiRect::all(Val::Px(1.0)), + border: UiRect::all(Val::Px(1.0)), + flex_direction: FlexDirection::Row, + align_items: AlignItems::Center, + align_content: AlignContent::Center, + justify_content: JustifyContent::SpaceBetween, + ..default() + }, + background_color: color.into(), + ..default() + }, + Title { + text: "Alert".into(), + }, + Close { + target: parent.parent_entity(), + }, + Sorting(0), + )); + parent.spawn(TextBundle::from_section(text, TextStyle { ..default() })); + }); + }); + }); + } +}