ui/src/states.rs
2024-04-11 13:35:44 +02:00

508 lines
15 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 std::any::Any;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, RwLock, Weak};
pub trait FutureStateChange: Fn() -> Result<()> + Send + Sync {
fn clone_box<'a>(&self) -> Box<dyn 'a + FutureStateChange>
where
Self: 'a;
fn as_fn(&self) -> &(dyn Fn() -> Result<()> + Send + Sync)
where
Self: Sized,
{
self
}
}
impl<F: Fn() -> 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() -> Result<()> + Send + Sync>>>,
on_deactivate: RwLock<Option<Box<dyn Fn() -> Result<()> + Send + Sync>>>,
next_tab: RwLock<Option<Box<dyn Fn() -> Result<()> + Send + Sync>>>,
previous_tab: RwLock<Option<Box<dyn Fn() -> Result<()> + Send + Sync>>>,
decline: RwLock<Option<Box<dyn Fn() -> 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() -> Result<()> + Send + Sync>>),
/// Updates the callback which is executed on previous tab event
PreviousTab(Option<Box<dyn Fn() -> Result<()> + Send + Sync>>),
/// Updates the callback which is executed on decline event
Decline(Option<Box<dyn Fn() -> Result<()> + Send + Sync>>),
/// Updates callbacks by identifier
ClickCallbacks(Vec<(&'a str, Box<dyn Fn() -> Result<()> + Send + Sync>)>),
/// Updates callbacks by identifier
SelectCallbacks(Vec<(&'a str, Box<CustomCallback<bool, ()>>)>),
/// Updates callbacks by identifier
CustomClickCallbacks(Vec<(&'a str, Box<CustomCallback<ControllerButton, bool>>)>),
/// Updates callbacks by identifier with input parameters
VecCallbacks(Vec<(&'a str, Box<dyn Fn(&dyn Any) -> Result<()> + Send + Sync>)>),
/// Updates the callback which is executed when this state gets activated on state change
OnActivate(Option<Box<dyn Fn() -> Result<()> + Send + Sync>>),
/// Updates the callback which is executed when this state gets deactivated on state change
OnDeactivate(Option<Box<dyn Fn() -> 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 {
gui_handler: Arc<GuiHandler>,
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(gui_handler: Arc<GuiHandler>) -> Result<Self> {
Ok(States {
gui_handler,
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
}
/// Adds a single state
pub fn add_state<'a>(
&mut self,
id: &str,
creation_type: impl Into<CreationType<'a>>,
) -> Result<()> {
self.states.insert(
id.to_string(),
State::new(&self.gui_handler, 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, id: impl Into<Option<&'b str>>) -> Result<()> {
Self::_set_state(
id.into().map(|id| self.get_state(id)).transpose()?,
&mut *self.current_state.lock().unwrap(),
if self.control_top_gui {
Some(self.gui_handler.clone())
} else {
None
},
self.log_state_change,
)
}
fn _set_state(
state: Option<Arc<State>>,
current: &mut Option<Arc<State>>,
gui_handler: Option<Arc<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()?;
}
// set new state, either no state or requested state
match state {
Some(state) => {
state.activate()?;
if let Some(gui_handler) = gui_handler {
gui_handler.set_top_gui(Some(state.clone()));
}
if logging {
println!("Change UI State to {}", state.name);
}
*current = Some(state);
}
None => {
if let Some(gui_handler) = gui_handler {
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 gui_handler = if self.control_top_gui {
Some(Arc::downgrade(&self.gui_handler))
} else {
None
};
let logging = self.log_state_change;
Ok(Box::new(move || {
Self::_set_state(
weak_state.as_ref().map(|w| w.upgrade()).flatten(),
&mut *weak_current_state.upgrade().unwrap().lock().unwrap(),
gui_handler.as_ref().map(|h| h.upgrade()).flatten(),
logging,
)
}))
}
}
impl State {
fn new<'a>(
gui_handler: &Arc<GuiHandler>,
name: &str,
creation_type: CreationType<'a>,
) -> Result<Arc<Self>> {
let gui = match creation_type {
CreationType::File(path) => GuiBuilder::new(gui_handler, 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) -> Result<()> {
self.top_level_gui.enable()?;
if let Some(activate) = self.on_activate.read().unwrap().as_ref() {
(activate)()?;
}
Ok(())
}
fn deactivate(&self) -> Result<()> {
self.top_level_gui.disable()?;
if let Some(deactivate) = self.on_deactivate.read().unwrap().as_ref() {
(deactivate)()?;
}
Ok(())
}
fn set_on_activate(&self, on_activate: Option<Box<dyn Fn() -> Result<()> + Send + Sync>>) {
*self.on_activate.write().unwrap() = on_activate;
}
fn set_on_deactivate(&self, on_deactivate: Option<Box<dyn Fn() -> Result<()> + Send + Sync>>) {
*self.on_deactivate.write().unwrap() = on_deactivate;
}
fn set_previous_tab(&self, previous_tab: Option<Box<dyn Fn() -> Result<()> + Send + Sync>>) {
*self.previous_tab.write().unwrap() = previous_tab;
}
fn set_next_tab(&self, next_tab: Option<Box<dyn Fn() -> Result<()> + Send + Sync>>) {
*self.next_tab.write().unwrap() = next_tab;
}
fn set_decline(&self, decline: Option<Box<dyn Fn() -> Result<()> + Send + Sync>>) {
*self.decline.write().unwrap() = decline;
}
}
impl TopGui for State {
fn decline(&self) -> Result<()> {
match self.decline.read().unwrap().as_ref() {
Some(decline) => {
(decline)()?;
}
None => {
if let Some(top_gui) = self.top_level_gui.top_gui() {
top_gui.decline()?;
}
}
}
Ok(())
}
fn next_tab(&self, second_level: bool) -> Result<()> {
match self.next_tab.read().unwrap().as_ref() {
Some(next_tab) => {
(next_tab)()?;
}
None => {
if let Some(top_gui) = self.top_level_gui.top_gui() {
top_gui.next_tab(second_level)?;
}
}
}
Ok(())
}
fn previous_tab(&self, second_level: bool) -> Result<()> {
match self.previous_tab.read().unwrap().as_ref() {
Some(previous_tab) => {
(previous_tab)()?;
}
None => {
if let Some(top_gui) = self.top_level_gui.top_gui() {
top_gui.previous_tab(second_level)?;
}
}
}
Ok(())
}
}