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

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>();
}
})
}