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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
//! Contains menu-related components.

use std::borrow::Cow;

use bevy::core::Name;
use bevy::ecs::{entity::Entity, prelude::Component};
#[cfg(feature = "bevy_reflect")]
use bevy::reflect::Reflect;

/// Add this component to a menu entity so that all [`Focusable`]s
/// within that menus gets added the `T` component automatically.
///
/// [`Focusable`]: crate::prelude::Focusable
#[derive(Component)]
pub struct NavMarker<T: Component>(pub T);

/// Tell the navigation system to turn this UI node into a menu.
///
/// Note that `MenuBuilder` is replaced by a private component when encoutered.
#[doc(alias = "NavMenu")]
#[derive(Component, Debug, Clone)]
pub enum MenuBuilder {
    /// Create a menu as reachable from a [`Focusable`]
    /// with a [`Name`] component.
    ///
    /// This is useful if, for example, you just want to spawn your UI without
    /// keeping track of entity ids of buttons that leads to submenus.
    ///
    /// See [`MenuBuilder::from_named`] for an easier to use method
    /// if you don't have a [`Name`] ready to use.
    ///
    /// # Important
    ///
    /// You must ensure this doesn't create a cycle. Eg: you shouldn't be able
    /// to reach `MenuSetting` X from [`Focusable`] Y if there is a path from
    /// `MenuSetting` X to `Focusable` Y.
    ///
    /// [`Focusable`]: crate::prelude::Focusable
    NamedParent(Name),

    /// Create a menu as reachable from a given [`Focusable`].
    ///
    /// When requesting [`NavRequest::Action`] when `Entity` is focused,
    /// the focus will be changed to a focusable within this menu.
    ///
    /// # Important
    ///
    /// You must ensure this doesn't create a cycle. Eg: you shouldn't be able
    /// to reach `MenuSetting` X from `Focusable` Y if there is a path from
    /// `MenuSetting` X to `Focusable` Y.
    ///
    /// [`Focusable`]: crate::prelude::Focusable
    /// [`NavRequest::Action`]: crate::prelude::NavRequest::Action
    EntityParent(Entity),

    /// Create a menu with no parents.
    ///
    /// No [`Focusable`] will "lead to" this menu.
    /// You either need to programmatically give focus to this menu tree
    /// with [`NavRequest::FocusOn`]
    /// or have only one root menu.
    ///
    /// [`Focusable`]: crate::prelude::Focusable
    /// [`NavRequest::FocusOn`]: crate::prelude::NavRequest::FocusOn
    Root,
}
impl MenuBuilder {
    /// Create a [`MenuBuilder::NamedParent`] directly from `String` or `&str`.
    pub fn from_named(parent: impl Into<Cow<'static, str>>) -> Self {
        Self::NamedParent(Name::new(parent))
    }
}
impl From<Option<Entity>> for MenuBuilder {
    fn from(parent: Option<Entity>) -> Self {
        match parent {
            Some(parent) => MenuBuilder::EntityParent(parent),
            None => MenuBuilder::Root,
        }
    }
}
impl TryFrom<&MenuBuilder> for Option<Entity> {
    type Error = ();
    fn try_from(value: &MenuBuilder) -> Result<Self, Self::Error> {
        match value {
            MenuBuilder::EntityParent(parent) => Ok(Some(*parent)),
            MenuBuilder::NamedParent(_) => Err(()),
            MenuBuilder::Root => Ok(None),
        }
    }
}

/// A menu that isolate children [`Focusable`]s from other focusables
/// and specify navigation method within itself.
///
/// # Usage
///
/// A `MenuSetting` can be used to:
/// * Prevent navigation from one specific submenu to another
/// * Specify if 2d navigation wraps around the screen,
///   see [`MenuSetting::wrapping`].
/// * Specify "scope menus" such that sending a [`NavRequest::ScopeMove`]
///   when the focused element is a [`Focusable`] nested within this `MenuSetting`
///   will move cursor within this menu.
///   See [`MenuSetting::scope`].
/// * Specify _submenus_ and specify from where those submenus are reachable.
/// * Specify which entity will be the parents of this [`MenuSetting`].
///   See [`MenuBuilder`].
///
/// If you want to specify which [`Focusable`] should be focused first
/// when entering a menu,
/// you should mark one of the children of this menu with [`Focusable::prioritized`].
///
/// # Limitations
///
/// Menu navigation relies heavily on the bevy hierarchy being consistent.
/// You might get inconsistent state under the folowing conditions:
///
/// - You despawned an entity in the [`FocusState::Active`] state
/// - You changed the parent of a [`Focusable`] member of a menu to a new menu.
///         
/// The navigation system might still work as expected,
/// however, [`Focusable::state`] may be missleading
/// for the length of one frame.
///
/// # Panics
///
/// **Menu loops will cause a panic**.
/// A menu loop is a way to go from menu A to menu B and
/// then from menu B to menu A while never going back.
///
/// Don't worry though, menu loops are really hard to make by accident,
/// and it will only panic if you use a `NavRequest::FocusOn(entity)`
/// where `entity` is inside a menu loop.
///
/// [`NavRequest`]: crate::prelude::NavRequest
/// [`Focusable`]: crate::prelude::Focusable
/// [`FocusState::Active`]: crate::prelude::FocusState::Active
/// [`Focusable::state`]: crate::prelude::Focusable::state
/// [`Focusable::prioritized`]: crate::prelude::Focusable::prioritized
/// [`NavRequest::ScopeMove`]: crate::prelude::NavRequest::ScopeMove
/// [`NavRequest`]: crate::prelude::NavRequest
#[doc(alias = "NavMenu")]
#[derive(Clone, Default, Component, Debug, Copy, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct MenuSetting {
    /// Whether to wrap navigation.
    ///
    /// When the player moves to a direction where there aren't any focusables,
    /// if this is true, the focus will "wrap" to the other direction of the screen.
    pub wrapping: bool,

    /// Whether this is a scope menu.
    ///
    /// A scope menu is controlled with [`NavRequest::ScopeMove`]
    /// even when the focused element is not in this menu, but in a submenu
    /// reachable from this one.
    ///
    /// [`NavRequest::ScopeMove`]: crate::prelude::NavRequest::ScopeMove
    pub scope: bool,
}
impl MenuSetting {
    pub(crate) fn bound(&self) -> bool {
        !self.wrapping
    }
    pub(crate) fn is_2d(&self) -> bool {
        !self.is_scope()
    }
    pub(crate) fn is_scope(&self) -> bool {
        self.scope
    }
    /// Create a new non-wrapping, non-scopped [`MenuSetting`],
    /// those are the default values.
    ///
    /// To create a wrapping/scopped menu, you can use:
    /// `MenuSetting::new().wrapping().scope()`.
    pub fn new() -> Self {
        Self::default()
    }
    /// Set [`wrapping`] to true.
    ///
    /// [`wrapping`]: Self::wrapping
    pub fn wrapping(mut self) -> Self {
        self.wrapping = true;
        self
    }
    /// Set `scope` to true.
    ///
    /// [`scope`]: Self::scope
    pub fn scope(mut self) -> Self {
        self.scope = true;
        self
    }
}