433 lines
13 KiB
Rust
433 lines
13 KiB
Rust
|
use crate::prelude::*;
|
|||
|
use anyhow::Result;
|
|||
|
use assetpath::AssetPath;
|
|||
|
|
|||
|
use std::any::Any;
|
|||
|
use std::collections::HashMap;
|
|||
|
|
|||
|
use std::sync::{
|
|||
|
atomic::{AtomicBool, Ordering::SeqCst},
|
|||
|
Arc, Mutex, RwLock,
|
|||
|
};
|
|||
|
|
|||
|
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
|
|||
|
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>,
|
|||
|
|
|||
|
states: Mutex<HashMap<String, Arc<State>>>,
|
|||
|
current_state: Mutex<Option<Arc<State>>>,
|
|||
|
|
|||
|
control_top_gui: AtomicBool,
|
|||
|
|
|||
|
log_state_change: AtomicBool,
|
|||
|
}
|
|||
|
|
|||
|
impl States {
|
|||
|
/// Creates an empty States struct
|
|||
|
pub fn new(gui_handler: Arc<GuiHandler>) -> Result<Self> {
|
|||
|
Ok(States {
|
|||
|
gui_handler,
|
|||
|
|
|||
|
states: Mutex::new(HashMap::new()),
|
|||
|
current_state: Mutex::new(None),
|
|||
|
|
|||
|
control_top_gui: AtomicBool::new(true),
|
|||
|
|
|||
|
log_state_change: AtomicBool::new(false),
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
pub fn change_logging(&self, logging: bool) {
|
|||
|
self.log_state_change.store(logging, SeqCst);
|
|||
|
}
|
|||
|
|
|||
|
/// Sets a flag if states should set and unset top gui of the gui handler
|
|||
|
pub fn set_control_top_gui(&self, control_top_gui: bool) {
|
|||
|
self.control_top_gui.store(control_top_gui, SeqCst);
|
|||
|
}
|
|||
|
|
|||
|
/// Adds a single state
|
|||
|
pub fn add_state<'a>(
|
|||
|
&self,
|
|||
|
id: &str,
|
|||
|
creation_type: impl Into<CreationType<'a>>,
|
|||
|
) -> Result<()> {
|
|||
|
self.states.lock().unwrap().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<()> {
|
|||
|
let id = id.into();
|
|||
|
|
|||
|
if let Some(old_state) = self.current_state.lock().unwrap().as_ref() {
|
|||
|
// check if requested state is already in use
|
|||
|
if let Some(name) = id {
|
|||
|
if name == old_state.name {
|
|||
|
return Ok(());
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// execute deactivate on old state
|
|||
|
old_state.deactivate()?;
|
|||
|
}
|
|||
|
|
|||
|
// set new state, either no state or requested state
|
|||
|
match id {
|
|||
|
Some(name) => {
|
|||
|
let state: Arc<State> = self.get_state(name)?;
|
|||
|
|
|||
|
state.activate()?;
|
|||
|
|
|||
|
if self.control_top_gui.load(SeqCst) {
|
|||
|
self.gui_handler.set_top_gui(Some(state.clone()));
|
|||
|
}
|
|||
|
|
|||
|
*self.current_state.lock().unwrap() = Some(state);
|
|||
|
|
|||
|
if self.log_state_change.load(SeqCst) {
|
|||
|
println!("Change UI State to {}", name);
|
|||
|
}
|
|||
|
}
|
|||
|
None => {
|
|||
|
if self.control_top_gui.load(SeqCst) {
|
|||
|
self.gui_handler.set_top_gui(None);
|
|||
|
}
|
|||
|
|
|||
|
*self.current_state.lock().unwrap() = None;
|
|||
|
|
|||
|
if self.log_state_change.load(SeqCst) {
|
|||
|
println!("Change UI State to None");
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
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>> {
|
|||
|
let states = self.states.lock().unwrap();
|
|||
|
|
|||
|
match states.get(id) {
|
|||
|
Some(state) => Ok(state.clone()),
|
|||
|
None => Err(anyhow::Error::msg(format!("UiState not found: {}", id))),
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
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(())
|
|||
|
}
|
|||
|
}
|