You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
222 lines
6.4 KiB
Rust
222 lines
6.4 KiB
Rust
use super::*;
|
|
|
|
pub(crate) struct BaseUiPlugin;
|
|
|
|
impl Plugin for BaseUiPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.init_resource::<Style>().add_systems(
|
|
Update,
|
|
(
|
|
add_ui_node.run_if(any_component_added::<Node>),
|
|
add_ui_button
|
|
.run_if(any_component_added::<Button>)
|
|
.after(add_ui_node),
|
|
add_ui_text
|
|
.run_if(any_component_added::<Text>)
|
|
.after(add_ui_node),
|
|
navs_on_top.run_if(any_component_changed::<NavState>),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// A resource for supplying style info, e.g., color pallette
|
|
#[derive(Resource)]
|
|
pub struct Style {
|
|
primary: Color,
|
|
secondary: Color,
|
|
accent: Color,
|
|
}
|
|
|
|
impl Default for Style {
|
|
fn default() -> Self {
|
|
Style {
|
|
primary: WHITE.into(),
|
|
secondary: BLACK.into(),
|
|
accent: ORANGE_RED.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn add_ui_button(added: Query<Entity, Added<Button>>, mut commands: Commands) {
|
|
fn over(
|
|
trigger: Trigger<Pointer<Over>>,
|
|
mut query: Query<&mut BorderColor>,
|
|
style: Res<Style>,
|
|
) {
|
|
if let Ok(mut bc) = query.get_mut(trigger.target()) {
|
|
debug!("pointer over {:?}", trigger.target());
|
|
bc.0 = style.accent;
|
|
}
|
|
}
|
|
|
|
fn out(trigger: Trigger<Pointer<Out>>, mut query: Query<&mut BorderColor>, style: Res<Style>) {
|
|
if let Ok(mut bc) = query.get_mut(trigger.target()) {
|
|
debug!("pointer out {:?}", trigger.target());
|
|
bc.0 = style.secondary;
|
|
}
|
|
}
|
|
|
|
fn pressed(
|
|
trigger: Trigger<Pointer<Pressed>>,
|
|
mut query: Query<&mut BackgroundColor>,
|
|
style: Res<Style>,
|
|
) {
|
|
if let Ok(mut bg) = query.get_mut(trigger.target()) {
|
|
debug!("pointer pressed {:?}", trigger.target());
|
|
bg.0 = style.accent;
|
|
}
|
|
}
|
|
|
|
fn released(
|
|
trigger: Trigger<Pointer<Released>>,
|
|
mut query: Query<&mut BackgroundColor>,
|
|
style: Res<Style>,
|
|
) {
|
|
if let Ok(mut bg) = query.get_mut(trigger.target()) {
|
|
debug!("pointer released {:?}", trigger.target());
|
|
bg.0 = style.primary;
|
|
}
|
|
}
|
|
|
|
added.iter().for_each(|e| {
|
|
debug!("Updating button: {:?}", e);
|
|
// Observe with hover over/out + click reactors
|
|
commands
|
|
.entity(e)
|
|
.observe(over)
|
|
.observe(out)
|
|
.observe(pressed)
|
|
.observe(released);
|
|
})
|
|
}
|
|
|
|
fn add_ui_text(added: Query<Entity, Added<Text>>, mut commands: Commands) {
|
|
fn pressed(
|
|
trigger: Trigger<Pointer<Pressed>>,
|
|
mut query: Query<(&mut TextColor, &ChildOf)>,
|
|
buttons: Query<Entity, With<Button>>,
|
|
style: Res<Style>,
|
|
) {
|
|
// ONLY DO THIS IF CHILD OF BUTTON
|
|
if let Ok((mut tc, ChildOf(p))) = query.get_mut(trigger.target())
|
|
&& buttons.contains(*p)
|
|
{
|
|
debug!("pointer pressed {:?}", trigger.target());
|
|
tc.0 = style.primary;
|
|
}
|
|
}
|
|
|
|
fn released(
|
|
trigger: Trigger<Pointer<Released>>,
|
|
mut query: Query<(&mut TextColor, &ChildOf)>,
|
|
buttons: Query<Entity, With<Button>>,
|
|
style: Res<Style>,
|
|
) {
|
|
// ONLY DO THIS IF CHILD OF BUTTON
|
|
if let Ok((mut tc, ChildOf(p))) = query.get_mut(trigger.target())
|
|
&& buttons.contains(*p)
|
|
{
|
|
debug!("pointer released {:?}", trigger.target());
|
|
tc.0 = style.secondary;
|
|
}
|
|
}
|
|
|
|
added.iter().for_each(|e| {
|
|
debug!("Updating text: {:?}", e);
|
|
// Observe with hover over/out + click reactors
|
|
commands.entity(e).observe(pressed).observe(released);
|
|
})
|
|
}
|
|
|
|
fn add_ui_node(
|
|
mut added: Query<(Entity, &mut Node), Added<Node>>,
|
|
style: Res<Style>,
|
|
text: Query<Entity, With<Text>>,
|
|
mut commands: Commands,
|
|
) {
|
|
added.iter_mut().for_each(|(e, mut n)| {
|
|
debug!("Updating node: {:?}", e);
|
|
|
|
// Update Node border
|
|
n.border = UiRect::all(Val::Px(2.0));
|
|
|
|
let mut this = commands.entity(e);
|
|
|
|
if text.contains(e) {
|
|
// Only change text color for text
|
|
this.insert(TextColor(style.secondary));
|
|
} else {
|
|
// Add extra stuff for non-text nodes
|
|
this.insert(BackgroundColor(style.primary));
|
|
this.insert(BorderColor(style.secondary));
|
|
this.insert(BorderRadius::all(Val::Px(5.0)));
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Marker component for handling Resource -> Ui Sync
|
|
#[derive(Component, Default, Debug)]
|
|
pub struct SyncResource<R: Resource + Default + Display>(R);
|
|
|
|
/// Sync a generic resource to the UI
|
|
///
|
|
/// Mostly useful for quick n' dirty getting data to the user
|
|
pub fn sync_resource_to_ui<R: Resource + Default + Display>(
|
|
mut q: Query<(&mut Text, &mut Visibility), With<SyncResource<R>>>,
|
|
r: Res<R>,
|
|
) {
|
|
q.iter_mut().for_each(|(mut t, mut v)| {
|
|
t.0 = format!("{}", *r);
|
|
*v = Visibility::Inherited;
|
|
});
|
|
}
|
|
|
|
/// Updates the scroll position of scrollable nodes in response to mouse input
|
|
pub fn scroll(trigger: Trigger<Pointer<Scroll>>, mut scrollers: Query<&mut ScrollPosition>) {
|
|
let Pointer {
|
|
event: Scroll { unit, x, y, .. },
|
|
..
|
|
} = trigger.event();
|
|
|
|
let (dx, dy) = match unit {
|
|
MouseScrollUnit::Line => (x * 16.0, y * 16.0),
|
|
MouseScrollUnit::Pixel => (x * 1., y * 1.),
|
|
};
|
|
|
|
if let Ok(mut pos) = scrollers.get_mut(trigger.target()) {
|
|
pos.offset_x -= dx;
|
|
pos.offset_y -= dy;
|
|
}
|
|
}
|
|
|
|
/// 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 = NavChildren)]
|
|
pub struct NavParent(pub Entity);
|
|
|
|
/// Back-relationship for nav-parent
|
|
#[derive(Component, Debug)]
|
|
#[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,
|
|
}
|
|
|
|
fn navs_on_top(changed: Query<(Entity, &NavState), Changed<NavState>>, mut commands: Commands) {
|
|
changed.iter().for_each(|(e, ns)| match ns {
|
|
NavState::Open => {
|
|
commands.entity(e).insert(GlobalZIndex(i32::MAX / 2));
|
|
}
|
|
NavState::Closed => {
|
|
commands.entity(e).remove::<GlobalZIndex>();
|
|
}
|
|
})
|
|
}
|