use crate::prelude::*; use anyhow::Result; use assetpath::AssetPath; use ecs::World; use utilities::prelude::remove_life_time_mut; 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 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 Result<()> + Clone + Send + Sync> FutureStateChange for F { fn clone_box<'a>(&self) -> Box where Self: 'a, { Box::new(self.clone()) } } impl<'a> Clone for Box { 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 Result<()> + Send + Sync>>), /// Updates the callback which is executed on previous tab event PreviousTab(Option Result<()> + Send + Sync>>), /// Updates the callback which is executed on decline event Decline(Option Result<()> + Send + Sync>>), /// Updates callbacks by identifier ClickCallbacks(Vec<(&'a str, Box Result<()> + Send + Sync>)>), /// Updates callbacks by identifier SelectCallbacks( Vec<( &'a str, Box Result<()> + Send + Sync>, )>, ), /// Updates callbacks by identifier CustomClickCallbacks( Vec<( &'a str, Box Result + Send + Sync>, )>, ), /// Updates callbacks by identifier with input parameters VecCallbacks( Vec<( &'a str, Box Result<()> + Send + Sync>, )>, ), /// Updates the callback which is executed when this state gets activated on state change OnActivate(Option Result<()> + Send + Sync>>), /// Updates the callback which is executed when this state gets deactivated on state change OnDeactivate(Option 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), } impl<'a> From<&'a AssetPath> for CreationType<'a> { fn from(s: &'a AssetPath) -> Self { Self::File(s) } } impl<'a, T: TopLevelGui + 'static> From> for CreationType<'a> { fn from(top_level_gui: Arc) -> Self { Self::TopGui(top_level_gui) } } impl<'a, T: TopLevelGui + 'static> From 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>, current_state: Arc>>>, control_top_gui: bool, log_state_change: bool, } impl States { /// Creates an empty States struct pub fn new() -> Result { 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 { 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>, ) -> 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> { 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>) -> Result<()> { let gui_handler = unsafe { remove_life_time_mut(world.resources.get_mut::()) }; 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>, current: &mut Option>, 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 { Ok(self.get_state(id)?.into()) } fn get_state(&self, id: &str) -> Result> { 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>, ) -> Result> { let state: Option> = 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 = unsafe { remove_life_time_mut(world.resources.get_mut::()) }; Self::_set_state( weak_state.as_ref().map(|w| w.upgrade()).flatten(), &mut *current.lock().unwrap(), world, gui_handler, logging, )?; } Ok(()) })) } }