2023-01-16 09:53:52 +00:00
|
|
|
//! `Selectable` is a property to select an item per button
|
|
|
|
|
|
|
|
use super::executable::Executable;
|
|
|
|
use crate::prelude::*;
|
|
|
|
use anyhow::Result;
|
2025-04-09 20:18:56 +00:00
|
|
|
use ecs::*;
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
use std::sync::{
|
|
|
|
Arc, RwLock, Weak,
|
2025-03-04 08:11:05 +00:00
|
|
|
atomic::{AtomicBool, AtomicI32, Ordering::SeqCst},
|
2023-01-16 09:53:52 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/// `Selectable` gives the ability to navigate per button or controller to
|
|
|
|
/// optionally adjacent neighbour Selectables and to execute a closure
|
|
|
|
/// when the current Selectable is pressed
|
2025-03-04 11:25:02 +00:00
|
|
|
pub struct Selectable {
|
2023-01-16 09:53:52 +00:00
|
|
|
selected: AtomicBool,
|
|
|
|
|
2025-03-04 11:25:02 +00:00
|
|
|
east_neighbour: RwLock<Option<Weak<Selectable>>>,
|
|
|
|
west_neighbour: RwLock<Option<Weak<Selectable>>>,
|
|
|
|
north_neighbour: RwLock<Option<Weak<Selectable>>>,
|
|
|
|
south_neighbour: RwLock<Option<Weak<Selectable>>>,
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
#[cfg(feature = "audio")]
|
|
|
|
select_audible: RwLock<Option<Arc<Audible>>>,
|
|
|
|
|
|
|
|
#[cfg(feature = "audio")]
|
|
|
|
click_audible: RwLock<Option<Arc<Audible>>>,
|
|
|
|
|
|
|
|
ui_layer: AtomicI32,
|
2024-05-17 13:12:48 +00:00
|
|
|
isolate: bool,
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
// used when clicked
|
2025-03-04 11:25:02 +00:00
|
|
|
executable: Arc<Executable<()>>,
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
// used internally by button
|
2025-03-04 11:25:02 +00:00
|
|
|
selected_changed_executable: Arc<Executable<bool>>,
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
// exposed externally for event
|
2025-03-04 11:25:02 +00:00
|
|
|
on_select_executable: Arc<Executable<bool>>,
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
// used for custom buttons
|
2025-03-04 17:18:08 +00:00
|
|
|
custom_callback:
|
2025-04-09 20:18:56 +00:00
|
|
|
RwLock<Option<Box<dyn Fn(&mut Commands, ControllerButton) -> Result<bool> + Send + Sync>>>,
|
2023-01-16 09:53:52 +00:00
|
|
|
}
|
|
|
|
|
2025-03-04 11:25:02 +00:00
|
|
|
impl Selectable {
|
2023-01-16 09:53:52 +00:00
|
|
|
/// Factory method for `Selectable`, returns `Arc<Selectable>`.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `executable` is a `Arc<Executable>` instance
|
|
|
|
pub fn new(
|
2025-03-04 11:25:02 +00:00
|
|
|
executable: Arc<Executable<()>>,
|
|
|
|
selected_changed_executable: Arc<Executable<bool>>,
|
|
|
|
on_select_executable: Arc<Executable<bool>>,
|
2024-05-17 13:12:48 +00:00
|
|
|
isolate: bool,
|
2023-01-16 09:53:52 +00:00
|
|
|
) -> Arc<Self> {
|
|
|
|
Arc::new(Selectable {
|
|
|
|
selected: AtomicBool::new(false),
|
|
|
|
|
|
|
|
east_neighbour: RwLock::new(None),
|
|
|
|
west_neighbour: RwLock::new(None),
|
|
|
|
north_neighbour: RwLock::new(None),
|
|
|
|
south_neighbour: RwLock::new(None),
|
|
|
|
|
|
|
|
#[cfg(feature = "audio")]
|
|
|
|
select_audible: RwLock::new(None),
|
|
|
|
|
|
|
|
#[cfg(feature = "audio")]
|
|
|
|
click_audible: RwLock::new(None),
|
|
|
|
|
|
|
|
executable,
|
|
|
|
selected_changed_executable,
|
|
|
|
on_select_executable,
|
|
|
|
|
|
|
|
custom_callback: RwLock::new(None),
|
|
|
|
|
|
|
|
ui_layer: AtomicI32::new(0),
|
2024-05-17 13:12:48 +00:00
|
|
|
isolate,
|
2023-01-16 09:53:52 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Add method, has to be explicitly called, otherwise it will remain in memory.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `selectable` is a `&Arc<Selectable>` instance that is going to be added
|
2025-03-04 11:25:02 +00:00
|
|
|
pub fn add(self: &Arc<Self>, gui_handler: &mut GuiHandler) -> Result<()> {
|
2025-03-04 08:11:05 +00:00
|
|
|
gui_handler.add_selectable(self.ui_layer.load(SeqCst), self.clone())
|
2023-01-16 09:53:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Delete method, has to be explicitly called, otherwise it will remain in memory.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `selectable` is a `&Arc<Selectable>` instance that is going to be deleted
|
2025-03-04 11:25:02 +00:00
|
|
|
pub fn delete(self: &Arc<Self>, gui_handler: &mut GuiHandler) -> Result<()> {
|
2025-03-04 08:11:05 +00:00
|
|
|
gui_handler.delete_selectable(self.ui_layer.load(SeqCst), self)?;
|
|
|
|
self.set_selected(gui_handler, false)?;
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_ui_layer(&self, ui_layer: i32) {
|
|
|
|
self.ui_layer.store(ui_layer, SeqCst);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Selects this `Selectable`
|
|
|
|
///
|
|
|
|
/// # Argument
|
|
|
|
///
|
|
|
|
/// * `selectable` is a `Arc<Selectable>` instance
|
2025-03-04 11:25:02 +00:00
|
|
|
pub fn select(self: &Arc<Self>, gui_handler: &mut GuiHandler) -> Result<()> {
|
2025-03-04 08:11:05 +00:00
|
|
|
gui_handler.set_selectable(Some(self.clone()))
|
2023-01-16 09:53:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_custom_callback<F>(&self, custom_callback: F)
|
|
|
|
where
|
2025-04-09 20:18:56 +00:00
|
|
|
F: Fn(&mut Commands, ControllerButton) -> Result<bool> + Send + Sync + 'static,
|
2023-01-16 09:53:52 +00:00
|
|
|
{
|
|
|
|
*self.custom_callback.write().unwrap() = Some(Box::new(custom_callback));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "audio")]
|
|
|
|
pub fn set_select_audible(&self, audible: Option<Arc<Audible>>) {
|
|
|
|
*self.select_audible.write().unwrap() = audible;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "audio")]
|
|
|
|
pub fn set_click_audible(&self, audible: Option<Arc<Audible>>) {
|
|
|
|
*self.click_audible.write().unwrap() = audible;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the value of selected and calls the callback if the value changed.
|
|
|
|
/// Generally used by the `GuiHandler`.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `selected` is the new selected state
|
2025-03-04 11:25:02 +00:00
|
|
|
pub fn set_selected(&self, gui_handler: &mut GuiHandler, selected: bool) -> Result<()> {
|
2023-01-16 09:53:52 +00:00
|
|
|
if self.selected() != selected {
|
|
|
|
self.selected.store(selected, SeqCst);
|
|
|
|
|
2025-03-04 08:11:05 +00:00
|
|
|
self.selected_changed_executable
|
|
|
|
.execute(gui_handler, selected)?;
|
|
|
|
self.on_select_executable.execute(gui_handler, selected)?;
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
#[cfg(feature = "audio")]
|
|
|
|
{
|
|
|
|
if self.selected() {
|
|
|
|
if let Some(audible) = self.select_audible.read().unwrap().as_ref() {
|
|
|
|
audible.play()?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the selected state. Generally used by the `GuiHandler`.
|
|
|
|
pub fn selected(&self) -> bool {
|
|
|
|
self.selected.load(SeqCst)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Executes the `Executable`'s callback
|
2025-03-04 11:25:02 +00:00
|
|
|
pub(crate) fn click_event(&self, gui_handler: &mut GuiHandler) -> Result<()> {
|
2023-01-16 09:53:52 +00:00
|
|
|
#[cfg(feature = "audio")]
|
|
|
|
{
|
|
|
|
if let Some(audible) = self.click_audible.read().unwrap().as_ref() {
|
|
|
|
audible.play()?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-04 08:11:05 +00:00
|
|
|
self.executable.execute(gui_handler, ())?;
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2025-03-04 17:18:08 +00:00
|
|
|
pub(crate) fn custom_click_event(
|
|
|
|
&self,
|
2025-04-09 20:18:56 +00:00
|
|
|
commands: &mut Commands,
|
2025-03-04 17:18:08 +00:00
|
|
|
button: ControllerButton,
|
|
|
|
) -> Result<bool> {
|
2023-01-16 09:53:52 +00:00
|
|
|
if let Some(custom_callback) = self.custom_callback.read().unwrap().as_ref() {
|
2025-04-09 20:18:56 +00:00
|
|
|
if custom_callback(commands, button)? {
|
2023-01-16 09:53:52 +00:00
|
|
|
#[cfg(feature = "audio")]
|
|
|
|
{
|
|
|
|
if let Some(audible) = self.click_audible.read().unwrap().as_ref() {
|
|
|
|
audible.play()?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the current east neighbour, if possible
|
2025-03-04 11:25:02 +00:00
|
|
|
pub fn east_neighbour(&self) -> Option<Arc<Selectable>> {
|
2023-01-16 09:53:52 +00:00
|
|
|
if let Some(weak_neighbour) = self.east_neighbour.read().unwrap().as_ref() {
|
|
|
|
if let Some(neighbour) = weak_neighbour.upgrade() {
|
|
|
|
return Some(neighbour);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Replaces the current east neighbour
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `selectable` the new east neighbour
|
2025-03-04 11:25:02 +00:00
|
|
|
pub fn set_east_neighbour(&self, selectable: Option<&Arc<Selectable>>) {
|
2024-05-17 13:12:48 +00:00
|
|
|
if self.isolate {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-01-16 09:53:52 +00:00
|
|
|
let mut east_neighbour = self.east_neighbour.write().unwrap();
|
|
|
|
|
|
|
|
match selectable {
|
|
|
|
Some(selectable) => *east_neighbour = Some(Arc::downgrade(selectable)),
|
|
|
|
None => *east_neighbour = None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the current west neighbour, if possible
|
2025-03-04 11:25:02 +00:00
|
|
|
pub fn west_neighbour(&self) -> Option<Arc<Selectable>> {
|
2023-01-16 09:53:52 +00:00
|
|
|
if let Some(weak_neighbour) = self.west_neighbour.read().unwrap().as_ref() {
|
|
|
|
if let Some(neighbour) = weak_neighbour.upgrade() {
|
|
|
|
return Some(neighbour);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Replaces the current west neighbour
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `selectable` the new west neighbour
|
2025-03-04 11:25:02 +00:00
|
|
|
pub fn set_west_neighbour(&self, selectable: Option<&Arc<Selectable>>) {
|
2024-05-17 13:12:48 +00:00
|
|
|
if self.isolate {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-01-16 09:53:52 +00:00
|
|
|
let mut west_neighbour = self.west_neighbour.write().unwrap();
|
|
|
|
|
|
|
|
match selectable {
|
|
|
|
Some(selectable) => *west_neighbour = Some(Arc::downgrade(selectable)),
|
|
|
|
None => *west_neighbour = None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the current north neighbour, if possible
|
2025-03-04 11:25:02 +00:00
|
|
|
pub fn north_neighbour(&self) -> Option<Arc<Selectable>> {
|
2023-01-16 09:53:52 +00:00
|
|
|
if let Some(weak_neighbour) = self.north_neighbour.read().unwrap().as_ref() {
|
|
|
|
if let Some(neighbour) = weak_neighbour.upgrade() {
|
|
|
|
return Some(neighbour);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Replaces the current north neighbour
|
|
|
|
///
|
|
|
|
/// # Argumnents
|
|
|
|
///
|
|
|
|
/// * `selectable` the new north neighbour
|
2025-03-04 11:25:02 +00:00
|
|
|
pub fn set_north_neighbour(&self, selectable: Option<&Arc<Selectable>>) {
|
2024-05-17 13:12:48 +00:00
|
|
|
if self.isolate {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-01-16 09:53:52 +00:00
|
|
|
let mut north_neighbour = self.north_neighbour.write().unwrap();
|
|
|
|
|
|
|
|
match selectable {
|
|
|
|
Some(selectable) => *north_neighbour = Some(Arc::downgrade(selectable)),
|
|
|
|
None => *north_neighbour = None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the current south neighbour, if possible
|
2025-03-04 11:25:02 +00:00
|
|
|
pub fn south_neighbour(&self) -> Option<Arc<Selectable>> {
|
2023-01-16 09:53:52 +00:00
|
|
|
if let Some(weak_neighbour) = self.south_neighbour.read().unwrap().as_ref() {
|
|
|
|
if let Some(neighbour) = weak_neighbour.upgrade() {
|
|
|
|
return Some(neighbour);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Replaces the current south neighbour
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `selectable` the new south neighbour
|
2025-03-04 11:25:02 +00:00
|
|
|
pub fn set_south_neighbour(&self, selectable: Option<&Arc<Selectable>>) {
|
2024-05-17 13:12:48 +00:00
|
|
|
if self.isolate {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-01-16 09:53:52 +00:00
|
|
|
let mut south_neighbour = self.south_neighbour.write().unwrap();
|
|
|
|
|
|
|
|
match selectable {
|
|
|
|
Some(selectable) => *south_neighbour = Some(Arc::downgrade(selectable)),
|
|
|
|
None => *south_neighbour = None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-04 11:25:02 +00:00
|
|
|
pub fn connect_vertically<'c>(upper: &Arc<Selectable>, lower: &Arc<Selectable>) {
|
2024-05-17 13:12:48 +00:00
|
|
|
if upper.isolate || lower.isolate {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-01-16 09:53:52 +00:00
|
|
|
*upper.south_neighbour.write().unwrap() = Some(Arc::downgrade(lower));
|
|
|
|
*lower.north_neighbour.write().unwrap() = Some(Arc::downgrade(upper));
|
|
|
|
}
|
|
|
|
|
2025-03-04 11:25:02 +00:00
|
|
|
pub fn connect_horizontally<'c>(left: &Arc<Selectable>, right: &Arc<Selectable>) {
|
2024-05-17 13:12:48 +00:00
|
|
|
if left.isolate || right.isolate {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-01-16 09:53:52 +00:00
|
|
|
*left.east_neighbour.write().unwrap() = Some(Arc::downgrade(right));
|
|
|
|
*right.west_neighbour.write().unwrap() = Some(Arc::downgrade(left));
|
|
|
|
}
|
|
|
|
}
|