ui/src/states.rs
2025-04-05 10:24:07 +02:00

277 lines
7.8 KiB
Rust

use crate::prelude::*;
use anyhow::Result;
use assetpath::AssetPath;
use ecs::*;
use std::any::Any;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
pub trait FutureStateChange: Fn(&mut World) -> Result<()> + Send + Sync {
fn clone_box<'a>(&self) -> Box<dyn 'a + FutureStateChange>
where
Self: 'a;
fn as_fn(&self) -> &(dyn Fn(&mut World) -> Result<()> + Send + Sync)
where
Self: Sized,
{
self
}
fn as_static(
&'static self,
) -> &'static (dyn Fn(&mut World) -> Result<()> + Send + Sync + 'static)
where
Self: Sized,
{
self
}
}
impl<F: Fn(&mut World) -> Result<()> + Clone + Send + Sync> FutureStateChange for F {
fn clone_box<'a>(&self) -> Box<dyn 'a + FutureStateChange>
where
Self: 'a,
{
Box::new(self.clone())
}
}
impl<'a> Clone for Box<dyn 'a + FutureStateChange> {
fn clone(&self) -> Self {
(**self).clone_box()
}
}
/// Update type
pub enum StateUpdateType<'a> {
/// Updates the callback which is executed on next tab event
NextTab(Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>),
/// Updates the callback which is executed on previous tab event
PreviousTab(Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>),
/// Updates the callback which is executed on decline event
Decline(Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>),
/// Updates callbacks by identifier
ClickCallbacks(Vec<(&'a str, Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>)>),
/// Updates callbacks by identifier
SelectCallbacks(
Vec<(
&'a str,
Box<dyn Fn(&mut World, bool) -> Result<()> + Send + Sync>,
)>,
),
/// Updates callbacks by identifier
CustomClickCallbacks(
Vec<(
&'a str,
Box<dyn Fn(&mut World, ControllerButton) -> Result<bool> + Send + Sync>,
)>,
),
/// Updates callbacks by identifier with input parameters
VecCallbacks(
Vec<(
&'a str,
Box<dyn Fn(&mut World, &dyn Any) -> Result<()> + Send + Sync>,
)>,
),
/// Updates the callback which is executed when this state gets activated on state change
OnActivate(Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>),
/// Updates the callback which is executed when this state gets deactivated on state change
OnDeactivate(Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>),
}
/// Type of the state to be created
pub enum CreationType<'a> {
/// Path to an xml-gui file, that state is created internally
File(&'a AssetPath),
/// A Arc<> that is already created and implements `TopLevelGui`
TopGui(Arc<dyn TopLevelGui>),
}
impl<'a> From<&'a AssetPath> for CreationType<'a> {
fn from(s: &'a AssetPath) -> Self {
Self::File(s)
}
}
impl<'a, T: TopLevelGui + 'static> From<Arc<T>> for CreationType<'a> {
fn from(top_level_gui: Arc<T>) -> Self {
Self::TopGui(top_level_gui)
}
}
impl<'a, T: TopLevelGui + 'static> From<T> for CreationType<'a> {
fn from(top_level_gui: T) -> Self {
Self::TopGui(Arc::new(top_level_gui))
}
}
/// Collection and handler for your UI (basically a state machine)
pub struct States {
states: HashMap<String, Arc<State>>,
current_state: Arc<Mutex<Option<Arc<State>>>>,
control_top_gui: bool,
log_state_change: bool,
}
impl States {
/// Creates an empty States struct
pub fn new() -> Result<Self> {
Ok(States {
states: HashMap::new(),
current_state: Arc::new(Mutex::new(None)),
control_top_gui: true,
log_state_change: false,
})
}
pub fn change_logging(mut self, logging: bool) -> Self {
self.log_state_change = logging;
self
}
/// Sets a flag if states should set and unset top gui of the gui handler
pub fn set_control_top_gui(mut self, control_top_gui: bool) -> Self {
self.control_top_gui = control_top_gui;
self
}
/// lists available states
pub fn states(&self) -> impl Iterator<Item = &str> {
self.states.keys().map(|s| s.as_str())
}
/// Adds a single state
pub fn add_state<'a>(
&mut self,
world: &mut World,
id: &str,
creation_type: impl Into<CreationType<'a>>,
) -> Result<()> {
self.states
.insert(id.to_string(), State::new(world, id, creation_type.into())?);
Ok(())
}
/// Returns the name of the current state if possible
pub fn current_state(&self) -> Result<Option<String>> {
match self.current_state.lock().unwrap().as_ref() {
Some(state) => Ok(Some(state.name.clone())),
None => Ok(None),
}
}
/// Changes the state
///
/// # Arguments
///
/// * `id` - Set state with the given identifier or None
pub fn set_state<'b>(&self, world: &mut World, id: impl Into<Option<&'b str>>) -> Result<()> {
let gui_handler: &mut GuiHandler = world.resources.get_mut_unchecked();
Self::_set_state(
id.into().map(|id| self.get_state(id)).transpose()?,
&mut *self.current_state.lock().unwrap(),
world,
gui_handler,
self.log_state_change,
)
}
fn _set_state(
state: Option<Arc<State>>,
current: &mut Option<Arc<State>>,
world: &mut World,
gui_handler: &mut GuiHandler,
logging: bool,
) -> Result<()> {
if let Some(old_state) = current {
// check if requested state is already in use
if let Some(new_state) = &state {
if new_state.name == old_state.name {
return Ok(());
}
}
// execute deactivate on old state
old_state.disable(world)?;
}
// set new state, either no state or requested state
match state {
Some(state) => {
state.enable(world)?;
gui_handler.set_top_gui(world, Some(state.clone()))?;
if logging {
println!("Change UI State to {}", state.name);
}
*current = Some(state);
}
None => {
gui_handler.set_top_gui(world, None)?;
if logging {
println!("Change UI State to None");
}
*current = None;
}
}
Ok(())
}
/// Retrieve a StateHandle
pub fn state_handle(&self, id: &str) -> Result<StateHandle> {
Ok(self.get_state(id)?.into())
}
fn get_state(&self, id: &str) -> Result<Arc<State>> {
match self.states.get(id) {
Some(state) => Ok(state.clone()),
None => Err(anyhow::Error::msg(format!("UiState not found: {}", id))),
}
}
pub fn future_state_change<'b>(
&self,
id: impl Into<Option<&'b str>>,
) -> Result<Box<dyn FutureStateChange>> {
let state: Option<Arc<State>> = id.into().map(|id| self.get_state(id)).transpose()?;
let weak_state = state.map(|s| Arc::downgrade(&s));
let weak_current_state = Arc::downgrade(&self.current_state);
let logging = self.log_state_change;
Ok(Box::new(move |world: &mut World| {
if let Some(current) = weak_current_state.upgrade() {
let gui_handler: &mut GuiHandler = world.resources.get_mut_unchecked();
Self::_set_state(
weak_state.as_ref().map(|w| w.upgrade()).flatten(),
&mut *current.lock().unwrap(),
world,
gui_handler,
logging,
)?;
}
Ok(())
}))
}
}