533 lines
16 KiB
Rust
533 lines
16 KiB
Rust
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(())
|
||
}
|
||
}
|