2023-01-16 09:53:52 +00:00
|
|
|
use crate::prelude::*;
|
|
|
|
use anyhow::Result;
|
|
|
|
use assetpath::AssetPath;
|
2025-03-04 17:18:08 +00:00
|
|
|
use ecs::World;
|
|
|
|
use utilities::prelude::remove_life_time_mut;
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
use std::any::Any;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
2025-03-08 06:24:08 +00:00
|
|
|
use std::sync::{Arc, Mutex};
|
2023-01-16 09:53:52 +00:00
|
|
|
|
2025-03-04 17:18:08 +00:00
|
|
|
pub trait FutureStateChange: Fn(&mut World) -> Result<()> + Send + Sync {
|
2024-04-04 06:23:46 +00:00
|
|
|
fn clone_box<'a>(&self) -> Box<dyn 'a + FutureStateChange>
|
|
|
|
where
|
|
|
|
Self: 'a;
|
2024-04-11 11:35:44 +00:00
|
|
|
|
2025-03-04 17:18:08 +00:00
|
|
|
fn as_fn(&self) -> &(dyn Fn(&mut World) -> Result<()> + Send + Sync)
|
2024-04-11 11:35:44 +00:00
|
|
|
where
|
|
|
|
Self: Sized,
|
|
|
|
{
|
|
|
|
self
|
|
|
|
}
|
2024-04-16 08:19:58 +00:00
|
|
|
|
2025-03-04 13:25:35 +00:00
|
|
|
fn as_static(
|
|
|
|
&'static self,
|
2025-03-04 17:18:08 +00:00
|
|
|
) -> &'static (dyn Fn(&mut World) -> Result<()> + Send + Sync + 'static)
|
2024-04-16 08:19:58 +00:00
|
|
|
where
|
|
|
|
Self: Sized,
|
|
|
|
{
|
|
|
|
self
|
|
|
|
}
|
2024-04-04 06:23:46 +00:00
|
|
|
}
|
|
|
|
|
2025-03-04 17:18:08 +00:00
|
|
|
impl<F: Fn(&mut World) -> Result<()> + Clone + Send + Sync> FutureStateChange for F {
|
2024-04-04 06:23:46 +00:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-16 09:53:52 +00:00
|
|
|
/// Update type
|
|
|
|
pub enum StateUpdateType<'a> {
|
|
|
|
/// Updates the callback which is executed on next tab event
|
2025-03-04 17:18:08 +00:00
|
|
|
NextTab(Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>),
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
/// Updates the callback which is executed on previous tab event
|
2025-03-04 17:18:08 +00:00
|
|
|
PreviousTab(Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>),
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
/// Updates the callback which is executed on decline event
|
2025-03-04 17:18:08 +00:00
|
|
|
Decline(Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>),
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
/// Updates callbacks by identifier
|
2025-03-04 17:18:08 +00:00
|
|
|
ClickCallbacks(Vec<(&'a str, Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>)>),
|
|
|
|
|
|
|
|
/// Updates callbacks by identifier
|
|
|
|
SelectCallbacks(
|
2025-03-04 13:25:35 +00:00
|
|
|
Vec<(
|
|
|
|
&'a str,
|
2025-03-04 17:18:08 +00:00
|
|
|
Box<dyn Fn(&mut World, bool) -> Result<()> + Send + Sync>,
|
2025-03-04 13:25:35 +00:00
|
|
|
)>,
|
|
|
|
),
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
/// Updates callbacks by identifier
|
2025-03-04 17:18:08 +00:00
|
|
|
CustomClickCallbacks(
|
|
|
|
Vec<(
|
|
|
|
&'a str,
|
|
|
|
Box<dyn Fn(&mut World, ControllerButton) -> Result<bool> + Send + Sync>,
|
|
|
|
)>,
|
|
|
|
),
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
/// Updates callbacks by identifier with input parameters
|
2025-03-04 17:18:08 +00:00
|
|
|
VecCallbacks(
|
|
|
|
Vec<(
|
|
|
|
&'a str,
|
|
|
|
Box<dyn Fn(&mut World, &dyn Any) -> Result<()> + Send + Sync>,
|
|
|
|
)>,
|
|
|
|
),
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
/// Updates the callback which is executed when this state gets activated on state change
|
2025-03-04 17:18:08 +00:00
|
|
|
OnActivate(Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>),
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
/// Updates the callback which is executed when this state gets deactivated on state change
|
2025-03-04 17:18:08 +00:00
|
|
|
OnDeactivate(Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>),
|
2023-01-16 09:53:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// 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 {
|
2024-03-27 12:02:53 +00:00
|
|
|
states: HashMap<String, Arc<State>>,
|
2024-03-27 12:49:22 +00:00
|
|
|
current_state: Arc<Mutex<Option<Arc<State>>>>,
|
2023-01-16 09:53:52 +00:00
|
|
|
|
2024-03-27 12:49:22 +00:00
|
|
|
control_top_gui: bool,
|
|
|
|
log_state_change: bool,
|
2023-01-16 09:53:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl States {
|
|
|
|
/// Creates an empty States struct
|
2025-03-04 13:25:35 +00:00
|
|
|
pub fn new() -> Result<Self> {
|
2023-01-16 09:53:52 +00:00
|
|
|
Ok(States {
|
2024-03-27 12:02:53 +00:00
|
|
|
states: HashMap::new(),
|
2024-03-27 12:49:22 +00:00
|
|
|
current_state: Arc::new(Mutex::new(None)),
|
2023-01-16 09:53:52 +00:00
|
|
|
|
2024-03-27 12:49:22 +00:00
|
|
|
control_top_gui: true,
|
|
|
|
log_state_change: false,
|
2023-01-16 09:53:52 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-03-27 12:49:22 +00:00
|
|
|
pub fn change_logging(mut self, logging: bool) -> Self {
|
|
|
|
self.log_state_change = logging;
|
|
|
|
self
|
2023-01-16 09:53:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets a flag if states should set and unset top gui of the gui handler
|
2024-03-27 12:49:22 +00:00
|
|
|
pub fn set_control_top_gui(mut self, control_top_gui: bool) -> Self {
|
|
|
|
self.control_top_gui = control_top_gui;
|
|
|
|
self
|
2023-01-16 09:53:52 +00:00
|
|
|
}
|
|
|
|
|
2024-08-26 09:46:43 +00:00
|
|
|
/// lists available states
|
|
|
|
pub fn states(&self) -> impl Iterator<Item = &str> {
|
|
|
|
self.states.keys().map(|s| s.as_str())
|
|
|
|
}
|
|
|
|
|
2023-01-16 09:53:52 +00:00
|
|
|
/// Adds a single state
|
|
|
|
pub fn add_state<'a>(
|
2024-03-27 12:02:53 +00:00
|
|
|
&mut self,
|
2025-03-05 06:59:38 +00:00
|
|
|
world: &mut World,
|
2023-01-16 09:53:52 +00:00
|
|
|
id: &str,
|
|
|
|
creation_type: impl Into<CreationType<'a>>,
|
|
|
|
) -> Result<()> {
|
2025-03-05 06:59:38 +00:00
|
|
|
self.states
|
|
|
|
.insert(id.to_string(), State::new(world, id, creation_type.into())?);
|
2023-01-16 09:53:52 +00:00
|
|
|
|
|
|
|
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
|
2025-03-04 17:18:08 +00:00
|
|
|
pub fn set_state<'b>(&self, world: &mut World, id: impl Into<Option<&'b str>>) -> Result<()> {
|
|
|
|
let gui_handler = unsafe { remove_life_time_mut(world.resources.get_mut::<GuiHandler>()) };
|
|
|
|
|
2024-03-27 12:49:22 +00:00
|
|
|
Self::_set_state(
|
|
|
|
id.into().map(|id| self.get_state(id)).transpose()?,
|
|
|
|
&mut *self.current_state.lock().unwrap(),
|
2025-03-04 17:18:08 +00:00
|
|
|
world,
|
2025-03-04 13:25:35 +00:00
|
|
|
gui_handler,
|
2024-03-27 12:49:22 +00:00
|
|
|
self.log_state_change,
|
|
|
|
)
|
|
|
|
}
|
2023-01-16 09:53:52 +00:00
|
|
|
|
2024-03-27 12:49:22 +00:00
|
|
|
fn _set_state(
|
|
|
|
state: Option<Arc<State>>,
|
|
|
|
current: &mut Option<Arc<State>>,
|
2025-03-04 17:18:08 +00:00
|
|
|
world: &mut World,
|
2025-03-04 13:25:35 +00:00
|
|
|
gui_handler: &mut GuiHandler,
|
2024-03-27 12:49:22 +00:00
|
|
|
logging: bool,
|
|
|
|
) -> Result<()> {
|
|
|
|
if let Some(old_state) = current {
|
2023-01-16 09:53:52 +00:00
|
|
|
// check if requested state is already in use
|
2024-03-27 12:49:22 +00:00
|
|
|
if let Some(new_state) = &state {
|
|
|
|
if new_state.name == old_state.name {
|
2023-01-16 09:53:52 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// execute deactivate on old state
|
2025-03-08 06:24:08 +00:00
|
|
|
old_state.disable(world)?;
|
2023-01-16 09:53:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// set new state, either no state or requested state
|
2024-03-27 12:49:22 +00:00
|
|
|
match state {
|
|
|
|
Some(state) => {
|
2025-03-08 06:24:08 +00:00
|
|
|
state.enable(world)?;
|
|
|
|
gui_handler.set_top_gui(world, Some(state.clone()))?;
|
2023-01-16 09:53:52 +00:00
|
|
|
|
2024-03-27 12:49:22 +00:00
|
|
|
if logging {
|
|
|
|
println!("Change UI State to {}", state.name);
|
2023-01-16 09:53:52 +00:00
|
|
|
}
|
2024-03-27 12:49:22 +00:00
|
|
|
|
|
|
|
*current = Some(state);
|
2023-01-16 09:53:52 +00:00
|
|
|
}
|
|
|
|
None => {
|
2025-03-08 06:24:08 +00:00
|
|
|
gui_handler.set_top_gui(world, None)?;
|
2023-01-16 09:53:52 +00:00
|
|
|
|
2024-03-27 12:49:22 +00:00
|
|
|
if logging {
|
2023-01-16 09:53:52 +00:00
|
|
|
println!("Change UI State to None");
|
|
|
|
}
|
2024-03-27 12:49:22 +00:00
|
|
|
|
|
|
|
*current = None;
|
2023-01-16 09:53:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Retrieve a StateHandle
|
|
|
|
pub fn state_handle(&self, id: &str) -> Result<StateHandle> {
|
2025-03-08 06:24:08 +00:00
|
|
|
Ok(self.get_state(id)?.into())
|
2023-01-16 09:53:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn get_state(&self, id: &str) -> Result<Arc<State>> {
|
2024-03-27 12:02:53 +00:00
|
|
|
match self.states.get(id) {
|
2023-01-16 09:53:52 +00:00
|
|
|
Some(state) => Ok(state.clone()),
|
|
|
|
None => Err(anyhow::Error::msg(format!("UiState not found: {}", id))),
|
|
|
|
}
|
|
|
|
}
|
2024-03-27 12:49:22 +00:00
|
|
|
|
|
|
|
pub fn future_state_change<'b>(
|
|
|
|
&self,
|
|
|
|
id: impl Into<Option<&'b str>>,
|
2024-04-04 06:23:46 +00:00
|
|
|
) -> Result<Box<dyn FutureStateChange>> {
|
2024-04-04 12:58:54 +00:00
|
|
|
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);
|
2024-03-27 12:49:22 +00:00
|
|
|
let logging = self.log_state_change;
|
|
|
|
|
2025-03-04 17:18:08 +00:00
|
|
|
Ok(Box::new(move |world: &mut World| {
|
2024-04-18 06:02:12 +00:00
|
|
|
if let Some(current) = weak_current_state.upgrade() {
|
2025-03-04 17:18:08 +00:00
|
|
|
let gui_handler =
|
|
|
|
unsafe { remove_life_time_mut(world.resources.get_mut::<GuiHandler>()) };
|
|
|
|
|
2024-04-18 06:02:12 +00:00
|
|
|
Self::_set_state(
|
|
|
|
weak_state.as_ref().map(|w| w.upgrade()).flatten(),
|
|
|
|
&mut *current.lock().unwrap(),
|
2025-03-04 17:18:08 +00:00
|
|
|
world,
|
2025-03-04 13:25:35 +00:00
|
|
|
gui_handler,
|
2024-04-18 06:02:12 +00:00
|
|
|
logging,
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
2024-04-04 06:23:46 +00:00
|
|
|
}))
|
2024-03-27 12:49:22 +00:00
|
|
|
}
|
2023-01-16 09:53:52 +00:00
|
|
|
}
|