//! `Selectable` is a property to select an item per button
use super::executable::Executable;
use crate::prelude::*;
use anyhow::Result;
use std::sync::{
Arc, RwLock, Weak,
atomic::{AtomicBool, AtomicI32, Ordering::SeqCst},
};
/// `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
pub struct Selectable<'a> {
selected: AtomicBool,
east_neighbour: RwLock>>>,
west_neighbour: RwLock >>>,
north_neighbour: RwLock >>>,
south_neighbour: RwLock >>>,
#[cfg(feature = "audio")]
select_audible: RwLock >>,
#[cfg(feature = "audio")]
click_audible: RwLock >>,
ui_layer: AtomicI32,
isolate: bool,
// used when clicked
executable: Arc>,
// used internally by button
selected_changed_executable: Arc>,
// exposed externally for event
on_select_executable: Arc>,
// used for custom buttons
custom_callback:
RwLock Result + Send + Sync>>>,
}
impl<'a> Selectable<'a> {
/// Factory method for `Selectable`, returns `Arc`.
///
/// # Arguments
///
/// * `executable` is a `Arc` instance
pub fn new(
executable: Arc>,
selected_changed_executable: Arc>,
on_select_executable: Arc>,
isolate: bool,
) -> Arc {
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),
isolate,
})
}
/// Add method, has to be explicitly called, otherwise it will remain in memory.
///
/// # Arguments
///
/// * `selectable` is a `&Arc` instance that is going to be added
pub fn add(self: &Arc, gui_handler: &mut GuiHandler<'_>) -> Result<()> {
gui_handler.add_selectable(self.ui_layer.load(SeqCst), self.clone())
}
/// Delete method, has to be explicitly called, otherwise it will remain in memory.
///
/// # Arguments
///
/// * `selectable` is a `&Arc` instance that is going to be deleted
pub fn delete(self: &Arc, gui_handler: &mut GuiHandler<'_>) -> Result<()> {
gui_handler.delete_selectable(self.ui_layer.load(SeqCst), self)?;
self.set_selected(gui_handler, false)?;
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` instance
pub fn select(self: &Arc, gui_handler: &mut GuiHandler<'_>) -> Result<()> {
gui_handler.set_selectable(Some(self.clone()))
}
pub fn set_custom_callback(&self, custom_callback: F)
where
F: Fn(ControllerButton) -> Result + Send + Sync + 'static,
{
*self.custom_callback.write().unwrap() = Some(Box::new(custom_callback));
}
#[cfg(feature = "audio")]
pub fn set_select_audible(&self, audible: Option>) {
*self.select_audible.write().unwrap() = audible;
}
#[cfg(feature = "audio")]
pub fn set_click_audible(&self, audible: Option>) {
*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
pub fn set_selected(&self, gui_handler: &mut GuiHandler<'_>, selected: bool) -> Result<()> {
if self.selected() != selected {
self.selected.store(selected, SeqCst);
self.selected_changed_executable
.execute(gui_handler, selected)?;
self.on_select_executable.execute(gui_handler, selected)?;
#[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
pub(crate) fn click_event(&self, gui_handler: &mut GuiHandler<'_>) -> Result<()> {
#[cfg(feature = "audio")]
{
if let Some(audible) = self.click_audible.read().unwrap().as_ref() {
audible.play()?;
}
}
self.executable.execute(gui_handler, ())?;
Ok(())
}
pub(crate) fn custom_click_event(&self, button: ControllerButton) -> Result {
if let Some(custom_callback) = self.custom_callback.read().unwrap().as_ref() {
if custom_callback(button)? {
#[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
pub fn east_neighbour(&self) -> Option>> {
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
pub fn set_east_neighbour(&self, selectable: Option<&Arc>>) {
if self.isolate {
return;
}
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
pub fn west_neighbour(&self) -> Option>> {
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
pub fn set_west_neighbour(&self, selectable: Option<&Arc>>) {
if self.isolate {
return;
}
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
pub fn north_neighbour(&self) -> Option>> {
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
pub fn set_north_neighbour(&self, selectable: Option<&Arc>>) {
if self.isolate {
return;
}
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
pub fn south_neighbour(&self) -> Option>> {
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
pub fn set_south_neighbour(&self, selectable: Option<&Arc>>) {
if self.isolate {
return;
}
let mut south_neighbour = self.south_neighbour.write().unwrap();
match selectable {
Some(selectable) => *south_neighbour = Some(Arc::downgrade(selectable)),
None => *south_neighbour = None,
}
}
pub fn connect_vertically<'c>(upper: &Arc>, lower: &Arc>) {
if upper.isolate || lower.isolate {
return;
}
*upper.south_neighbour.write().unwrap() = Some(Arc::downgrade(lower));
*lower.north_neighbour.write().unwrap() = Some(Arc::downgrade(upper));
}
pub fn connect_horizontally<'c>(left: &Arc>, right: &Arc>) {
if left.isolate || right.isolate {
return;
}
*left.east_neighbour.write().unwrap() = Some(Arc::downgrade(right));
*right.west_neighbour.write().unwrap() = Some(Arc::downgrade(left));
}
}