1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
//! Navigation events and requests.
//!
//! The navigation system works through bevy's `Events` system. Basically, it is
//! a system with one input and two outputs:
//! * Input [`Events<NavRequst>`](https://docs.rs/bevy/0.6.0/bevy/app/struct.Events.html),
//! tells the navigation system what to do. Your app should have a system
//! that writes to a [`EventWriter<NavRequest>`](https://docs.rs/bevy/0.6.0/bevy/app/struct.EventWriter.html)
//! based on inputs or internal game state. Usually, the default input systems specified
//! in [`crate::systems`] do that for you. But you can add your own requests
//! on top of the ones the default systems send. For example to unlock the UI with
//! [`NavRequest::Free`].
//! * Output [`Focusable`](crate::Focusable) components. The navigation system
//! updates the focusables component according to the focus state of the
//! navigation system. See examples directory for how to read those
//! * Output [`EventReader<NavEvent>`](https://docs.rs/bevy/0.6.0/bevy/app/struct.EventReader.html),
//! contains specific information about what the navigation system is doing.
use bevy::ecs::entity::Entity;
use bevy::math::Vec2;
use non_empty_vec::NonEmpty;
/// Requests to send to the navigation system to update focus.
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum NavRequest {
/// Move in 2d in provided direction.
Move(Direction),
/// Move within the encompassing [`NavMenu::BoundScope`](crate::NavMenu::BoundScope).
ScopeMove(ScopeDirection),
/// Enter submenu if any [`NavMenu::reachable_from`](crate::NavMenu::reachable_from)
/// the currently focused entity.
Action,
/// Leave this submenu to enter the one it is [`reachable_from`](crate::NavMenu::reachable_from).
Cancel,
/// Move the focus to any arbitrary [`Focusable`](crate::Focusable) entity.
FocusOn(Entity),
/// Unlocks the navigation system.
///
/// A [`NavEvent::Unlocked`] will be emitted as a response if the
/// navigation system was indeed locked.
Free,
}
/// Direction for movement in [`NavMenu::BoundScope`](crate::NavMenu::BoundScope) menus.
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum ScopeDirection {
Next,
Previous,
}
/// 2d direction to move in normal menus
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Direction {
South,
North,
East,
West,
}
impl Direction {
pub(crate) fn is_in(&self, reference: Vec2, other: Vec2) -> bool {
let coord = other - reference;
use Direction::*;
match self {
South => coord.y < coord.x && coord.y < -coord.x,
North => coord.y > coord.x && coord.y > -coord.x,
East => coord.y < coord.x && coord.y > -coord.x,
West => coord.y > coord.x && coord.y < -coord.x,
}
}
}
/// Events emitted by the navigation system.
///
/// Useful if you want to react to [`NavEvent::NoChanges`] event, for example
/// when a "start game" button is focused and the [`NavRequest::Action`] is
/// pressed.
#[derive(Debug, Clone)]
pub enum NavEvent {
/// Tells the app which element is the first one to be focused.
///
/// This will be sent whenever the number of focused elements go from 0 to 1.
/// Meaning: whenever you spawn a new UI with [`crate::Focusable`] elements.
InitiallyFocused(Entity),
/// Focus changed
///
/// ## Notes
///
/// Both `to` and `from` are ascending, meaning that the focused and newly
/// focused elements are the first of their respective vectors.
///
/// [`NonEmpty`] enables you to safely check `to.first()` or `from.first()`
/// without returning an option. It is guaranteed that there is at least
/// one element.
FocusChanged {
/// The list of elements that has become active after the focus
/// change
to: NonEmpty<Entity>,
/// The list of active elements from the focused one to the last
/// active which is affected by the focus change
from: NonEmpty<Entity>,
},
/// The [`NavRequest`] didn't lead to any change in focus.
NoChanges {
/// The list of active elements from the focused one to the last
/// active which is affected by the focus change
from: NonEmpty<Entity>,
/// The [`NavRequest`] that didn't do anything
request: NavRequest,
},
/// A [lock focusable](crate::Focusable::lock) has been triggered
///
/// Once the navigation plugin enters a locked state, the only way to exit
/// it is to send a [`NavRequest::Free`].
Locked(Entity),
/// A [lock focusable](crate::Focusable::lock) has been triggered
///
/// Once the navigation plugin enters a locked state, the only way to exit
/// it is to send a [`NavRequest::Free`].
Unlocked(Entity),
}
impl NavEvent {
/// Convenience function to construct a `FocusChanged` with a single `to`
///
/// Usually the `NavEvent::FocusChanged.to` field has a unique value.
pub(crate) fn focus_changed(to: Entity, from: NonEmpty<Entity>) -> NavEvent {
NavEvent::FocusChanged {
from,
to: NonEmpty::new(to),
}
}
}