Compare commits

..

5 Commits

2
Cargo.lock generated

@ -2226,7 +2226,9 @@ dependencies = [
"bevy",
"bevy_rapier3d",
"chrono",
"itertools 0.13.0",
"lipsum",
"rand",
"serde",
"thiserror 2.0.12",
"walkdir",

@ -19,7 +19,9 @@ features = ["wayland", "dynamic_linking"]
[dev-dependencies]
lipsum = "*"
rand = "*"
itertools = "*"
[build-dependencies]
chrono = "*"
walkdir = "*"
chrono = "*"

@ -1,11 +1,23 @@
#![feature(iter_intersperse)]
//! This example illustrates scrolling in Bevy UI.
use bevy::picking::hover::HoverMap;
use games::*;
use lipsum::*;
use rand::*;
fn main() {
let mut app = App::new();
app.add_plugins(BaseGamePlugin::default())
.add_systems(Startup, (setup_list, setup_nav_tree));
.add_systems(Startup, (setup_list, setup_nav_tree))
.add_systems(
Update,
(
hide_menu,
control_menu.run_if(on_event::<Pointer<Over>>.or(on_event::<Pointer<Out>>)),
),
);
app.run();
}
@ -36,12 +48,14 @@ fn setup_nav_tree(mut commands: Commands) {
// Nav Tree
commands
.spawn((
Text::new("+"),
Node {
position_type: PositionType::Absolute,
right: Val::Px(0.0),
top: Val::Percent(50.0),
align_self: AlignSelf::Start,
justify_self: JustifySelf::End,
min_width: Val::Px(25.0),
min_height: Val::Px(25.0),
align_content: AlignContent::Center,
justify_content: JustifyContent::Center,
..default()
},
BackgroundColor(RED.into()),
@ -49,26 +63,106 @@ fn setup_nav_tree(mut commands: Commands) {
.with_children(|parent| {
parent
.spawn((
Name::new("Buttons"),
NavState::default(),
Node {
position_type: PositionType::Absolute,
right: Val::Px(25.0),
min_width: Val::Px(25.0),
min_height: Val::Px(25.0),
flex_direction: FlexDirection::Column,
right: Val::Percent(100.0),
width: Val::Auto,
..default()
},
Visibility::Hidden,
BackgroundColor(ORANGE.into()),
))
.with_children(|parent| {
(0..10).for_each(|_| {
let title: String = lipsum_title_with_rng(thread_rng())
.split_whitespace()
.take(2)
.intersperse(" ")
.collect();
parent.spawn((
Text::new(title),
Node {
width: Val::Auto,
margin: UiRect::all(Val::Px(5.0)),
..default()
},
BackgroundColor(ORANGE_RED.into()),
Button,
));
});
})
.with_children(|parent| {
parent.spawn((
Name::new("Preview"),
NavState::default(),
Text::new(lipsum_with_rng(thread_rng(), 50)),
Node {
position_type: PositionType::Absolute,
right: Val::Px(25.0),
min_width: Val::Px(25.0),
min_height: Val::Px(25.0),
right: Val::Percent(100.0),
..default()
},
BackgroundColor(YELLOW.into()),
Visibility::Hidden,
));
});
});
}
// When you pointer over the '+' make the entire menu visible
fn hide_menu(mut nodes: Query<(Entity, &mut Visibility, &NavState), Changed<NavState>>) {
nodes.iter_mut().for_each(|(e, mut v, n)| {
*v = match n {
NavState::Open => Visibility::Inherited,
NavState::Closed => Visibility::Hidden,
};
});
}
// When you pointer goes off of the '+' or any of it's children make the entire menu invisible
fn control_menu(
mut over_events: EventReader<Pointer<Over>>,
mut out_events: EventReader<Pointer<Out>>,
children: Query<&ChildOf>,
parents: Query<&Children>,
mut nav: Query<&mut NavState>,
hover_map: Res<HoverMap>,
) {
over_events.read().for_each(|over| {
let root = children.root_ancestor(over.target);
parents.iter_descendants(root).for_each(|child| {
if let Ok(mut n) = nav.get_mut(child) {
*n = NavState::Open;
}
});
});
// Gives us the entiies covered by the HoverMap
let is_hovered: Vec<&Entity> = hover_map
.iter()
.flat_map(|(_, submap)| {
let x: Vec<&Entity> = submap.iter().map(|(node, _)| node).collect();
x
})
.collect();
// For all pointer out events
out_events.read().for_each(|out| {
// If a relative of out.target is hovered, do nothing
// Otherwise set to closed
let root = children.root_ancestor(out.target);
let tree_still_hovered = parents
.iter_descendants(root)
.any(|child| is_hovered.contains(&&child));
if !tree_still_hovered {
parents.iter_descendants(root).for_each(|child| {
if let Ok(mut n) = nav.get_mut(child) {
*n = NavState::Closed;
}
})
}
})
}

@ -1,10 +1,11 @@
#![allow(dead_code)]
#![allow(clippy::type_complexity)]
#![allow(clippy::too_many_arguments)]
#![feature(trim_prefix_suffix)]
mod mono;
use bevy::platform::hash::RandomState;
use bevy::{picking::hover::HoverMap, platform::hash::RandomState};
use games::*;
use mono::*;
use std::hash::BuildHasher;
@ -43,18 +44,19 @@ fn main() {
.run_if(on_event::<Pointer<Click>>),
spawn_debug_buttons.run_if(on_event::<AssetEvent<Monologue>>),
dialog_engine.run_if(on_event::<DialogEvent>),
mouse_wheel_scroll.run_if(on_event::<MouseWheel>),
auto_scroll.run_if(any_component_added::<DialogOption>),
dialog_box_visibility.run_if(state_changed::<DialogState>),
monologue_asset_tooltip
.run_if(on_event::<Pointer<Over>>.or(on_event::<Pointer<Out>>)),
scale_window.run_if(on_event::<WindowResized>),
hide_menu.run_if(any_component_changed::<NavState>),
clear_monologue.run_if(any_component_changed::<NavState>),
control_menu.run_if(on_event::<Pointer<Over>>.or(on_event::<Pointer<Out>>)),
),
)
.add_observer(add_dialog_option)
.add_observer(add_tree_monologue)
.add_observer(remove_tree_monologue)
.add_observer(hide_monologue_preview)
.add_observer(populate_tree)
.add_observer(show_monologue_list)
.add_observer(hide_monologue_list)
@ -101,6 +103,7 @@ fn init_ui(mut commands: Commands) {
..default()
},
))
.observe(scroll)
.observe(hover_dialog_box_over)
.observe(hover_dialog_box_out);
}
@ -115,46 +118,64 @@ struct MonologuesList;
struct MonologuePreview;
/// Panel for selecting which monologue tree to spawn
/// TODO: When monologue is loaded, add a button for it in this
/// TODO: When mouse over button, preview it in the "MonologuePreview" box
fn init_debug_ui(mut commands: Commands) {
commands
let button = commands
.spawn((
Name::new("Monologue Assignment Menu"),
Text::new("+ Monologue"),
Node {
max_height: Val::Percent(90.0),
align_self: AlignSelf::Center,
align_self: AlignSelf::Start,
justify_self: JustifySelf::Start,
min_width: Val::Px(25.0),
min_height: Val::Px(25.0),
..default()
},
MonologuesContainer,
GlobalZIndex(i32::MAX - 1),
BackgroundColor(PINK.into()),
BackgroundColor(RED.into()),
DebuggingState::On,
MonologuesContainer,
))
.with_children(|parent| {
.id();
commands
.spawn((
NavParent(button),
NavState::default(),
Name::new("Container"),
Node {
flex_direction: FlexDirection::Row,
top: Val::Px(25.0),
height: Val::Percent(90.0),
..default()
},
BackgroundColor(BLACK.into()),
)).with_children(|parent| {
parent.spawn((
Name::new("Buttons"),
Node {
height: Val::Percent(100.0),
flex_direction: FlexDirection::Column,
padding: UiRect::all(Val::Px(10.0)),
height: Val::Percent(100.0),
overflow: Overflow::scroll_y(),
..default()
},
BackgroundColor(PINK.with_alpha(0.9).into()),
ScrollPosition::default(),
BackgroundColor(ORANGE.into()),
MonologuesList,
));
)).observe(scroll);
parent.spawn((
Name::new("Preview"),
MonologuePreview,
NavParent(button),
NavState::default(),
Node {
height: Val::Percent(100.0),
flex_direction: FlexDirection::Column,
padding: UiRect::all(Val::Px(10.0)),
overflow: Overflow::scroll_y(),
max_width: Val::Percent(50.0),
..default()
},
BackgroundColor(ORANGE.with_alpha(0.9).into()),
MonologuePreview,
BackgroundColor(ORANGE_RED.into()),
));
});
});
}
fn hover_dialog_box_over(
@ -186,22 +207,6 @@ fn position_camera(mut query: Query<&mut Transform, (With<Camera>, With<Camera3d
})
}
/// Allows user to scroll (with mouse whell) through old dialog
fn mouse_wheel_scroll(
mut events: EventReader<MouseWheel>,
mut scroll_position: Query<&mut ScrollPosition>,
) {
events.read().for_each(|MouseWheel { unit, y, .. }| {
let offset_y = match unit {
MouseScrollUnit::Line => 10.0 * y,
MouseScrollUnit::Pixel => 1.0 * y,
};
scroll_position
.iter_mut()
.for_each(|mut pos| pos.offset_y += offset_y);
});
}
/// Automatically scrolls dialog when a new batch of options are added
fn auto_scroll(
added: Query<Entity, Added<DialogOption>>,
@ -562,7 +567,7 @@ fn spawn_debug_buttons(
parent
.spawn((
Button,
Text::new(handle.path().unwrap().to_string()),
Text::new(handle.path().unwrap().to_string().trim_prefix("trees/").trim_suffix(".mono")),
TextLayout::new(JustifyText::Left, LineBreak::WordBoundary),
TreeMonologue(handle.clone()),
MonologuesList,
@ -573,9 +578,6 @@ fn spawn_debug_buttons(
.observe(toggle_debug_button_color_over)
.observe(toggle_debug_button_color_out);
});
// Spawn a tree too to make the game feel like it's doing something at startup
commands.spawn((Tree, TreeMonologue(handle.clone())));
}
});
}
@ -607,22 +609,29 @@ fn preview_monologue(
) {
// Get the handle for this button's monologuie
if let Ok(TreeMonologue(handle)) = tree_monologue.get(trigger.target()) {
// Get the monologue data
if let Some(monologue) = monologues.get(handle) {
commands.entity(*container).despawn_related::<Children>();
// Spawn the monologue
let mut i = 0;
commands.entity(*container).with_children(|parent| {
let mut i = 0;
while let Some(batch) = monologue.get(i) {
parent.spawn((Text::new("---"), MonologuePreview));
debug!("---");
for (n, item) in batch.lines.iter().enumerate() {
parent.spawn((Text::new(format!("{i}.{n}: {item}")), MonologuePreview));
debug!("{i}.{n}: {item}");
}
// TODO: Just implement iter_batches or something
i += 1;
}
parent.spawn((Text::new("---"), MonologuePreview));
debug!("---");
});
}
}
@ -714,26 +723,6 @@ fn populate_tree(
.observe(drag_tree);
}
fn hide_monologue_preview(
trigger: Trigger<Pointer<Over>>,
preview: Single<Entity, (With<MonologuePreview>, Without<Button>, Without<Text>)>,
query: Query<
Entity,
Or<(
With<MonologuePreview>,
With<MonologuesList>,
With<MonologuesContainer>,
With<Window>,
)>,
>,
mut commands: Commands,
) {
if !query.contains(trigger.target()) {
info!("Processing over: {:?}", trigger.target());
commands.entity(*preview).despawn_related::<Children>();
}
}
fn drag_tree(
trigger: Trigger<Pointer<Drag>>,
state: Res<State<DebuggingState>>,
@ -757,3 +746,78 @@ fn drag_tree(
}
}
}
// When you pointer over the '+' make the entire menu visible
fn hide_menu(
mut nodes: Query<(Entity, &mut Visibility, &NavState), Changed<NavState>>
) {
nodes.iter_mut().for_each(|(e, mut v, n)| {
*v = match n {
NavState::Open => Visibility::Inherited,
NavState::Closed => Visibility::Hidden,
};
});
}
fn clear_monologue(
mut nodes: Query<(Entity, &NavState), (Changed<NavState>, With<MonologuePreview>, Without<Text>)>,
mut commands: Commands,
) {
nodes.iter_mut().for_each(|(e, n)| {
if matches!(n, NavState::Closed) {
commands.entity(e).despawn_related::<Children>();
}
});
}
// When you pointer goes off of the '+' or any of it's children make the entire menu invisible
fn control_menu(
mut over_events: EventReader<Pointer<Over>>,
mut out_events: EventReader<Pointer<Out>>,
nav_children: Query<&NavParent>,
children: Query<&ChildOf>,
nav_parents: Query<&NavChildren>,
parents: Query<&Children>,
mut nav: Query<&mut NavState>,
hover_map: Res<HoverMap>,
) {
over_events.read().for_each(|over| {
let root = nav_children.root_ancestor(over.target);
nav_parents.iter_descendants(root).for_each(|child| {
if let Ok(mut n) = nav.get_mut(child) {
*n = NavState::Open;
}
});
});
// Gives us the enities covered by the HoverMap
let is_hovered: Vec<&Entity> = hover_map
.iter()
.flat_map(|(_, submap)| {
let x: Vec<&Entity> = submap.iter().map(|(node, _)| node).collect();
x
})
.collect();
// For all pointer out events
out_events.read().for_each(|out| {
// If a relative of out.target is hovered, do nothing
// Otherwise set to closed
let root = children.root_ancestor(out.target);
let tree_still_hovered = parents
.iter_descendants(root)
.any(|child| is_hovered.contains(&&child));
if !tree_still_hovered {
if let Ok(mut n) = nav.get_mut(root) {
*n = NavState::Closed;
}
parents.iter_descendants(root).for_each(|child| {
if let Ok(mut n) = nav.get_mut(child) {
*n = NavState::Closed;
}
})
}
})
}

@ -132,6 +132,7 @@ fn init_debug_ui(mut commands: Commands) {
commands.spawn((
DebuggingState::On,
Text("Tooltip Placeholder".into()),
Pickable::IGNORE,
TextColor(WHITE.into()),
SyncResource::<ToolTip>::default(),
BackgroundColor(BLACK.with_alpha(0.9).into()),
@ -309,11 +310,7 @@ impl Display for Fps {
}
}
fn track_fps(
time: Res<Time>,
mut fps: ResMut<Fps>,
mut history: Local<VecDeque<f32>>,
) {
fn track_fps(time: Res<Time>, mut fps: ResMut<Fps>, mut history: Local<VecDeque<f32>>) {
// Get the time to render the last frame
let d = time.delta_secs();
@ -338,9 +335,6 @@ impl Display for EntityCount {
}
}
fn track_entity_count(
query: Query<Entity>,
mut count: ResMut<EntityCount>,
) {
fn track_entity_count(query: Query<Entity>, mut count: ResMut<EntityCount>) {
count.0 = query.iter().len();
}

@ -42,11 +42,21 @@ pub fn scroll(trigger: Trigger<Pointer<Scroll>>, mut scrollers: Query<&mut Scrol
}
}
/// Track who your parent is for navigation in the UI
/// Think the file menu is a child of the "file" button
#[derive(Component, Debug)]
#[relationship(relationship_target = NavParent)]
pub(crate) struct NavChild(Entity);
#[relationship(relationship_target = NavChildren)]
pub struct NavParent(pub Entity);
/// Back-relationship for nav-parent
#[derive(Component, Debug)]
#[relationship_target(relationship = NavChild)]
pub(crate) struct NavParent(Vec<Entity>);
#[relationship_target(relationship = NavParent)]
pub struct NavChildren(Vec<Entity>);
/// Track if a navigation [sub]tree is open or closed
#[derive(Component, Debug, Default)]
pub enum NavState {
Open,
#[default]
Closed,
}

Loading…
Cancel
Save