ui/src/states.rs
2025-03-04 14:25:35 +01:00

533 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 std::any::Any;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, RwLock, Weak};
pub trait FutureStateChange: Fn(&mut GuiHandler) -> Result<()> + Send + Sync {
fn clone_box<'a>(&self) -> Box<dyn 'a + FutureStateChange>
where
Self: 'a;
fn as_fn(&self) -> &(dyn Fn(&mut GuiHandler) -> Result<()> + Send + Sync)
where
Self: Sized,
{
self
}
fn as_static(
&'static self,
) -> &'static (dyn Fn(&mut GuiHandler) -> Result<()> + Send + Sync + 'static)
where
Self: Sized,
{
self
}
}
impl<F: Fn(&mut GuiHandler) -> 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 GuiHandler) -> Result<()> + Send + Sync>>>,
on_deactivate: RwLock<Option<Box<dyn Fn(&mut GuiHandler) -> Result<()> + Send + Sync>>>,
next_tab: RwLock<Option<Box<dyn Fn(&mut GuiHandler) -> Result<()> + Send + Sync>>>,
previous_tab: RwLock<Option<Box<dyn Fn(&mut GuiHandler) -> Result<()> + Send + Sync>>>,
decline: RwLock<Option<Box<dyn Fn(&mut GuiHandler) -> 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 GuiHandler) -> Result<()> + Send + Sync>>),
/// Updates the callback which is executed on previous tab event
PreviousTab(Option<Box<dyn Fn(&mut GuiHandler) -> Result<()> + Send + Sync>>),
/// Updates the callback which is executed on decline event
Decline(Option<Box<dyn Fn(&mut GuiHandler) -> Result<()> + Send + Sync>>),
/// Updates callbacks by identifier
ClickCallbacks(
Vec<(
&'a str,
Box<dyn Fn(&mut GuiHandler) -> 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(&mut GuiHandler) -> Result<()> + Send + Sync>>),
/// Updates the callback which is executed when this state gets deactivated on state change
OnDeactivate(Option<Box<dyn Fn(&mut GuiHandler) -> 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,
gui_handler: &mut GuiHandler,
id: &str,
creation_type: impl Into<CreationType<'a>>,
) -> Result<()> {
self.states.insert(
id.to_string(),
State::new(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,
gui_handler: &mut GuiHandler,
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(),
gui_handler,
self.log_state_change,
)
}
fn _set_state(
state: Option<Arc<State>>,
current: &mut Option<Arc<State>>,
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(gui_handler)?;
}
// set new state, either no state or requested state
match state {
Some(state) => {
state.activate(gui_handler)?;
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 |gui_handler: &mut GuiHandler| {
if let Some(current) = weak_current_state.upgrade() {
Self::_set_state(
weak_state.as_ref().map(|w| w.upgrade()).flatten(),
&mut *current.lock().unwrap(),
gui_handler,
logging,
)?;
}
Ok(())
}))
}
}
impl State {
fn new<'a>(
gui_handler: &mut 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, gui_handler: &mut GuiHandler) -> Result<()> {
self.top_level_gui.enable(gui_handler)?;
if let Some(activate) = self.on_activate.read().unwrap().as_ref() {
(activate)(gui_handler)?;
}
Ok(())
}
fn deactivate(&self, gui_handler: &mut GuiHandler) -> Result<()> {
self.top_level_gui.disable(gui_handler)?;
if let Some(deactivate) = self.on_deactivate.read().unwrap().as_ref() {
(deactivate)(gui_handler)?;
}
Ok(())
}
fn set_on_activate(
&self,
on_activate: Option<Box<dyn Fn(&mut GuiHandler) -> Result<()> + Send + Sync>>,
) {
*self.on_activate.write().unwrap() = on_activate;
}
fn set_on_deactivate(
&self,
on_deactivate: Option<Box<dyn Fn(&mut GuiHandler) -> Result<()> + Send + Sync>>,
) {
*self.on_deactivate.write().unwrap() = on_deactivate;
}
fn set_previous_tab(
&self,
previous_tab: Option<Box<dyn Fn(&mut GuiHandler) -> Result<()> + Send + Sync>>,
) {
*self.previous_tab.write().unwrap() = previous_tab;
}
fn set_next_tab(
&self,
next_tab: Option<Box<dyn Fn(&mut GuiHandler) -> Result<()> + Send + Sync>>,
) {
*self.next_tab.write().unwrap() = next_tab;
}
fn set_decline(
&self,
decline: Option<Box<dyn Fn(&mut GuiHandler) -> Result<()> + Send + Sync>>,
) {
*self.decline.write().unwrap() = decline;
}
}
impl TopGui for State {
fn decline(&self, gui_handler: &mut GuiHandler) -> Result<()> {
match self.decline.read().unwrap().as_ref() {
Some(decline) => {
(decline)(gui_handler)?;
}
None => {
if let Some(top_gui) = self.top_level_gui.top_gui() {
top_gui.decline(gui_handler)?;
}
}
}
Ok(())
}
fn next_tab(&self, gui_handler: &mut GuiHandler, second_level: bool) -> Result<()> {
match self.next_tab.read().unwrap().as_ref() {
Some(next_tab) => {
(next_tab)(gui_handler)?;
}
None => {
if let Some(top_gui) = self.top_level_gui.top_gui() {
top_gui.next_tab(gui_handler, second_level)?;
}
}
}
Ok(())
}
fn previous_tab(&self, gui_handler: &mut GuiHandler, second_level: bool) -> Result<()> {
match self.previous_tab.read().unwrap().as_ref() {
Some(previous_tab) => {
(previous_tab)(gui_handler)?;
}
None => {
if let Some(top_gui) = self.top_level_gui.top_gui() {
top_gui.previous_tab(gui_handler, second_level)?;
}
}
}
Ok(())
}
}