use clack_plugin::plugin::PluginError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum GuiLifecycle {
#[default]
Hidden,
ShowRequested,
Active,
HideRequested,
Destroyed,
}
impl GuiLifecycle {
pub fn transition(&mut self, event: GuiEvent) -> Result<(), PluginError> {
let next = match (*self, event) {
(GuiLifecycle::ShowRequested, GuiEvent::WindowReady) => GuiLifecycle::Active,
(GuiLifecycle::HideRequested, GuiEvent::WindowHidden) => GuiLifecycle::Hidden,
(GuiLifecycle::Hidden, GuiEvent::Show) => GuiLifecycle::ShowRequested,
(GuiLifecycle::Active, GuiEvent::Hide) => GuiLifecycle::HideRequested,
(GuiLifecycle::ShowRequested, GuiEvent::UserClosed)
| (GuiLifecycle::Active, GuiEvent::UserClosed) => GuiLifecycle::Hidden,
(GuiLifecycle::ShowRequested, GuiEvent::WindowFailed) => GuiLifecycle::Hidden,
(_, GuiEvent::Destroy) => GuiLifecycle::Destroyed,
(GuiLifecycle::Destroyed, _) => GuiLifecycle::Destroyed,
(current, event) => {
let err: &'static str = Box::leak(
format!(
"GUI lifecycle: illegal transition {:?} -> {:?}",
current, event
)
.into_boxed_str(),
);
log::warn!("{err}");
return Err(PluginError::Message(err));
}
};
*self = next;
Ok(())
}
pub fn can_show(&self) -> bool {
matches!(self, GuiLifecycle::Hidden)
}
pub fn can_hide(&self) -> bool {
matches!(self, GuiLifecycle::Active)
}
pub fn is_active(&self) -> bool {
matches!(self, GuiLifecycle::Active)
}
pub fn is_destroyed(&self) -> bool {
matches!(self, GuiLifecycle::Destroyed)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GuiEvent {
Show,
Hide,
Destroy,
WindowReady,
WindowHidden,
UserClosed,
WindowFailed,
}
#[cfg(test)]
mod lifecycle_tests {
use super::*;
#[test]
fn test_happy_path_hidden_to_active() {
let mut state = GuiLifecycle::Hidden;
state.transition(GuiEvent::Show).unwrap();
assert_eq!(state, GuiLifecycle::ShowRequested);
state.transition(GuiEvent::WindowReady).unwrap();
assert_eq!(state, GuiLifecycle::Active);
}
#[test]
fn test_happy_path_active_to_hidden() {
let mut state = GuiLifecycle::Active;
state.transition(GuiEvent::Hide).unwrap();
assert_eq!(state, GuiLifecycle::HideRequested);
state.transition(GuiEvent::WindowHidden).unwrap();
assert_eq!(state, GuiLifecycle::Hidden);
}
#[test]
fn test_user_closes_from_active() {
let mut state = GuiLifecycle::Active;
state.transition(GuiEvent::UserClosed).unwrap();
assert_eq!(state, GuiLifecycle::Hidden);
}
#[test]
fn test_user_closes_from_show_requested() {
let mut state = GuiLifecycle::ShowRequested;
state.transition(GuiEvent::UserClosed).unwrap();
assert_eq!(state, GuiLifecycle::Hidden);
}
#[test]
fn test_destroy_from_any_state() {
for start in [
GuiLifecycle::Hidden,
GuiLifecycle::ShowRequested,
GuiLifecycle::Active,
GuiLifecycle::HideRequested,
] {
let mut state = start;
state.transition(GuiEvent::Destroy).unwrap();
assert_eq!(state, GuiLifecycle::Destroyed);
}
}
#[test]
fn test_destroyed_is_terminal() {
let mut state = GuiLifecycle::Destroyed;
state.transition(GuiEvent::Show).unwrap(); assert_eq!(state, GuiLifecycle::Destroyed);
state.transition(GuiEvent::Hide).unwrap();
assert_eq!(state, GuiLifecycle::Destroyed);
state.transition(GuiEvent::UserClosed).unwrap();
assert_eq!(state, GuiLifecycle::Destroyed);
}
#[test]
fn test_illegal_transitions() {
assert!(GuiLifecycle::Active.transition(GuiEvent::Show).is_err());
assert!(GuiLifecycle::Hidden.transition(GuiEvent::Hide).is_err());
let mut state = GuiLifecycle::ShowRequested;
assert!(state.transition(GuiEvent::Show).is_err());
assert!(
GuiLifecycle::Hidden
.transition(GuiEvent::WindowReady)
.is_err()
);
assert!(
GuiLifecycle::Active
.transition(GuiEvent::WindowHidden)
.is_err()
);
}
#[test]
fn test_window_failed_recovery() {
let mut state = GuiLifecycle::ShowRequested;
state.transition(GuiEvent::WindowFailed).unwrap();
assert_eq!(state, GuiLifecycle::Hidden);
state.transition(GuiEvent::Show).unwrap();
assert_eq!(state, GuiLifecycle::ShowRequested);
}
#[test]
fn test_can_show_can_hide_helpers() {
assert!(GuiLifecycle::Hidden.can_show());
assert!(!GuiLifecycle::Active.can_show());
assert!(GuiLifecycle::Active.can_hide());
assert!(!GuiLifecycle::Hidden.can_hide());
assert!(GuiLifecycle::Active.is_active());
assert!(!GuiLifecycle::Hidden.is_active());
}
}