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),
        }
    }
}