ui/src/states.rs

466 lines
14 KiB
Rust
Raw Normal View History

2023-01-16 09:53:52 +00:00
use crate::prelude::*;
use anyhow::Result;
use assetpath::AssetPath;
use std::any::Any;
use std::collections::HashMap;
2024-03-27 12:49:22 +00:00
use std::sync::{Arc, Mutex, RwLock};
2023-01-16 09:53:52 +00:00
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
2024-03-27 15:07:01 +00:00
#[derive(Clone)]
2023-01-16 09:53:52 +00:00
pub struct StateHandle {
state: Arc<State>,
}
impl StateHandle {
pub fn update<'a>(&self, update_type: StateUpdateType<'a>) -> Result<()> {
self.state.update(update_type)
}
}
impl GetElement<Button> for StateHandle {
fn element(&self, id: &str) -> Result<Arc<Button>> {
match self.state.top_level_gui.elements() {
Some(elements) => elements.element(id),
None => panic!("state ({}) has no elements", self.state.name),
}
}
}
impl GetElement<Grid> for StateHandle {
fn element(&self, id: &str) -> Result<Arc<Grid>> {
match self.state.top_level_gui.elements() {
Some(elements) => elements.element(id),
None => panic!("state ({}) has no elements", self.state.name),
}
}
}
impl GetElement<Label> for StateHandle {
fn element(&self, id: &str) -> Result<Arc<Label>> {
match self.state.top_level_gui.elements() {
Some(elements) => elements.element(id),
None => panic!("state ({}) has no elements", self.state.name),
}
}
}
impl GetElement<TextField> for StateHandle {
fn element(&self, id: &str) -> Result<Arc<TextField>> {
match self.state.top_level_gui.elements() {
Some(elements) => elements.element(id),
None => panic!("state ({}) has no elements", self.state.name),
}
}
}
impl GetElement<MultiLineTextField> for StateHandle {
fn element(&self, id: &str) -> Result<Arc<MultiLineTextField>> {
match self.state.top_level_gui.elements() {
Some(elements) => elements.element(id),
None => panic!("state ({}) has no elements", self.state.name),
}
}
}
impl GetElement<Icon> for StateHandle {
fn element(&self, id: &str) -> Result<Arc<Icon>> {
match self.state.top_level_gui.elements() {
Some(elements) => elements.element(id),
None => panic!("state ({}) has no elements", self.state.name),
}
}
}
impl GetElement<ProgressBar> for StateHandle {
fn element(&self, id: &str) -> Result<Arc<ProgressBar>> {
match self.state.top_level_gui.elements() {
Some(elements) => elements.element(id),
None => panic!("state ({}) has no elements", self.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>,
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
pub fn new(gui_handler: Arc<GuiHandler>) -> Result<Self> {
Ok(States {
gui_handler,
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
}
/// Adds a single state
pub fn add_state<'a>(
2024-03-27 12:02:53 +00:00
&mut self,
2023-01-16 09:53:52 +00:00
id: &str,
creation_type: impl Into<CreationType<'a>>,
) -> Result<()> {
2024-03-27 12:02:53 +00:00
self.states.insert(
2023-01-16 09:53:52 +00:00
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<()> {
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(),
if self.control_top_gui {
Some(self.gui_handler.clone())
} else {
None
},
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>>,
gui_handler: Option<Arc<GuiHandler>>,
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
old_state.deactivate()?;
}
// set new state, either no state or requested state
2024-03-27 12:49:22 +00:00
match state {
Some(state) => {
2023-01-16 09:53:52 +00:00
state.activate()?;
2024-03-27 12:49:22 +00:00
if let Some(gui_handler) = gui_handler {
gui_handler.set_top_gui(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 => {
2024-03-27 12:49:22 +00:00
if let Some(gui_handler) = gui_handler {
gui_handler.set_top_gui(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> {
Ok(StateHandle {
state: self.get_state(id)?,
})
}
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>>,
) -> Result<impl Fn() -> Result<()>> {
let state = id.into().map(|id| self.get_state(id)).transpose()?;
let current_state = self.current_state.clone();
let gui_handler = if self.control_top_gui {
Some(self.gui_handler.clone())
} else {
None
};
let logging = self.log_state_change;
Ok(move || {
Self::_set_state(
state.clone(),
&mut *current_state.lock().unwrap(),
gui_handler.clone(),
logging,
)
})
}
2023-01-16 09:53:52 +00:00
}
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(())
}
}