ui/src/states.rs
2025-03-05 07:59:38 +01:00

541 lines
16 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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, RwLock, Weak};
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()
}
}
struct State {
name: String,
top_level_gui: Arc<dyn TopLevelGui>,
on_activate: RwLock<Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>>,
on_deactivate: RwLock<Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>>,
next_tab: RwLock<Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>>,
previous_tab: RwLock<Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>>,
decline: RwLock<Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>>,
}
/// Opaque handle for a State
/// only used for updating callbacks
#[derive(Clone)]
pub struct StateHandle {
state: Weak<State>,
}
impl StateHandle {
pub fn update<'a>(&self, update_type: StateUpdateType<'a>) -> Result<()> {
self.state.upgrade().unwrap().update(update_type)
}
}
impl GetElement<Button> for StateHandle {
fn element(&self, id: &str) -> Result<Arc<Button>> {
let state = self.state.upgrade().unwrap();
match state.top_level_gui.elements() {
Some(elements) => elements.element(id),
None => panic!("state ({}) has no elements", state.name),
}
}
}
impl GetElement<Grid> for StateHandle {
fn element(&self, id: &str) -> Result<Arc<Grid>> {
let state = self.state.upgrade().unwrap();
match state.top_level_gui.elements() {
Some(elements) => elements.element(id),
None => panic!("state ({}) has no elements", state.name),
}
}
}
impl GetElement<Label> for StateHandle {
fn element(&self, id: &str) -> Result<Arc<Label>> {
let state = self.state.upgrade().unwrap();
match state.top_level_gui.elements() {
Some(elements) => elements.element(id),
None => panic!("state ({}) has no elements", state.name),
}
}
}
impl GetElement<TextField> for StateHandle {
fn element(&self, id: &str) -> Result<Arc<TextField>> {
let state = self.state.upgrade().unwrap();
match state.top_level_gui.elements() {
Some(elements) => elements.element(id),
None => panic!("state ({}) has no elements", state.name),
}
}
}
impl GetElement<MultiLineTextField> for StateHandle {
fn element(&self, id: &str) -> Result<Arc<MultiLineTextField>> {
let state = self.state.upgrade().unwrap();
match state.top_level_gui.elements() {
Some(elements) => elements.element(id),
None => panic!("state ({}) has no elements", state.name),
}
}
}
impl GetElement<Icon> for StateHandle {
fn element(&self, id: &str) -> Result<Arc<Icon>> {
let state = self.state.upgrade().unwrap();
match state.top_level_gui.elements() {
Some(elements) => elements.element(id),
None => panic!("state ({}) has no elements", state.name),
}
}
}
impl GetElement<ProgressBar> for StateHandle {
fn element(&self, id: &str) -> Result<Arc<ProgressBar>> {
let state = self.state.upgrade().unwrap();
match state.top_level_gui.elements() {
Some(elements) => elements.element(id),
None => panic!("state ({}) has no elements", state.name),
}
}
}
/// 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 = unsafe { remove_life_time_mut(world.resources.get_mut::<GuiHandler>()) };
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.deactivate(world)?;
}
// set new state, either no state or requested state
match state {
Some(state) => {
state.activate(world)?;
gui_handler.set_top_gui(Some(state.clone()));
if logging {
println!("Change UI State to {}", state.name);
}
*current = Some(state);
}
None => {
gui_handler.set_top_gui(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(StateHandle {
state: Arc::downgrade(&self.get_state(id)?),
})
}
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 =
unsafe { remove_life_time_mut(world.resources.get_mut::<GuiHandler>()) };
Self::_set_state(
weak_state.as_ref().map(|w| w.upgrade()).flatten(),
&mut *current.lock().unwrap(),
world,
gui_handler,
logging,
)?;
}
Ok(())
}))
}
}
impl State {
fn new<'a>(
world: &mut World,
name: &str,
creation_type: CreationType<'a>,
) -> Result<Arc<Self>> {
let gui = match creation_type {
CreationType::File(path) => GuiBuilder::new(world, path)?,
CreationType::TopGui(top_gui) => top_gui,
};
Ok(Arc::new(State {
name: name.to_string(),
top_level_gui: gui,
on_activate: RwLock::new(None),
on_deactivate: RwLock::new(None),
next_tab: RwLock::new(None),
previous_tab: RwLock::new(None),
decline: RwLock::new(None),
}))
}
fn update<'a>(&self, update_type: StateUpdateType<'a>) -> Result<()> {
match update_type {
StateUpdateType::NextTab(next_tab) => self.set_next_tab(next_tab),
StateUpdateType::PreviousTab(previous_tab) => self.set_previous_tab(previous_tab),
StateUpdateType::Decline(decline) => self.set_decline(decline),
StateUpdateType::OnActivate(oa) => self.set_on_activate(oa),
StateUpdateType::OnDeactivate(oda) => self.set_on_deactivate(oda),
StateUpdateType::ClickCallbacks(cbs) => {
let functionality = self.functionality()?;
functionality.set_click_callbacks(cbs)?;
}
StateUpdateType::SelectCallbacks(cbs) => {
let functionality = self.functionality()?;
functionality.set_select_callbacks(cbs)?;
}
StateUpdateType::CustomClickCallbacks(cbs) => {
let functionality = self.functionality()?;
functionality.set_custom_callbacks(cbs)?;
}
StateUpdateType::VecCallbacks(vcbs) => {
let functionality = self.functionality()?;
functionality.set_vec_callbacks(vcbs)?;
}
}
Ok(())
}
fn functionality(&self) -> Result<&dyn Functionality> {
match self.top_level_gui.functionality() {
Some(functionality) => Ok(functionality),
None => panic!("state {}' does not implement Functionality", &self.name),
}
}
fn activate(&self, world: &mut World) -> Result<()> {
self.top_level_gui.enable(world)?;
if let Some(activate) = self.on_activate.read().unwrap().as_ref() {
(activate)(world)?;
}
Ok(())
}
fn deactivate(&self, world: &mut World) -> Result<()> {
self.top_level_gui.disable(world)?;
if let Some(deactivate) = self.on_deactivate.read().unwrap().as_ref() {
(deactivate)(world)?;
}
Ok(())
}
fn set_on_activate(
&self,
on_activate: Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>,
) {
*self.on_activate.write().unwrap() = on_activate;
}
fn set_on_deactivate(
&self,
on_deactivate: Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>,
) {
*self.on_deactivate.write().unwrap() = on_deactivate;
}
fn set_previous_tab(
&self,
previous_tab: Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>,
) {
*self.previous_tab.write().unwrap() = previous_tab;
}
fn set_next_tab(&self, next_tab: Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>) {
*self.next_tab.write().unwrap() = next_tab;
}
fn set_decline(&self, decline: Option<Box<dyn Fn(&mut World) -> Result<()> + Send + Sync>>) {
*self.decline.write().unwrap() = decline;
}
}
impl TopGui for State {
fn decline(&self, world: &mut World) -> Result<()> {
match self.decline.read().unwrap().as_ref() {
Some(decline) => {
(decline)(world)?;
}
None => {
if let Some(top_gui) = self.top_level_gui.top_gui() {
top_gui.decline(world)?;
}
}
}
Ok(())
}
fn next_tab(&self, world: &mut World, second_level: bool) -> Result<()> {
match self.next_tab.read().unwrap().as_ref() {
Some(next_tab) => {
(next_tab)(world)?;
}
None => {
if let Some(top_gui) = self.top_level_gui.top_gui() {
top_gui.next_tab(world, second_level)?;
}
}
}
Ok(())
}
fn previous_tab(&self, world: &mut World, second_level: bool) -> Result<()> {
match self.previous_tab.read().unwrap().as_ref() {
Some(previous_tab) => {
(previous_tab)(world)?;
}
None => {
if let Some(top_gui) = self.top_level_gui.top_gui() {
top_gui.previous_tab(world, second_level)?;
}
}
}
Ok(())
}
}