From 17992a9fcd089430aa37d534f8dc4a5886df85a2 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 27 Dec 2023 16:31:26 +0000 Subject: [PATCH] Implement a specialised stack of UI activities. I think this mostly matches Monochrome's rules about what activities you can and can't have stacked on top of each other. To wit, you basically get an arbitrary keypath of menus from the Main Menu, plus optionally a single utilities-menu thing pushed on top of that. Utilities things replace each other rather than stacking, so that popping the stack returns to the non-utility below it. (An exception on real Monochrome is that popping from Send Message replaces it with the Users On screen - also a utility-class activity - rather than popping back to the topmost non-util. Mastodon has no such thing, so we can omit that special case here. If anything like that were needed anyway, it could be special-cased inside pop().) --- src/activity_stack.rs | 191 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 2 files changed, 192 insertions(+) create mode 100644 src/activity_stack.rs diff --git a/src/activity_stack.rs b/src/activity_stack.rs new file mode 100644 index 0000000..21d7af5 --- /dev/null +++ b/src/activity_stack.rs @@ -0,0 +1,191 @@ +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum NonUtilityActivity { + MainMenu, + HomeTimelineFile, +} + +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum UtilityActivity { + UtilsMenu, + ReadMentions, + LogsMenu1, + LogsMenu2, + ExitMenu, + ExamineUser(String), + ListUserFollowers(String), + ListUserFollowees(String), + InfoStatus(String), + ListStatusFavouriters(String), + ListStatusBoosters(String), +} + +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum Activity { + NonUtil(NonUtilityActivity), + Util(UtilityActivity), +} + +impl From for Activity { + fn from(value: NonUtilityActivity) -> Self { Activity::NonUtil(value) } +} +impl From for Activity { + fn from(value: UtilityActivity) -> Self { Activity::Util(value) } +} + +#[derive(PartialEq, Eq, Debug)] +pub struct ActivityStack { + nonutil: Vec, // not counting MainMenu at the top + util: Option, +} + +impl ActivityStack { + pub fn new() -> Self { + ActivityStack { + nonutil: Vec::new(), + util: None, + } + } + + pub fn pop(&mut self) { + if self.util.is_some() { + self.util = None; + } else { + // deliberately ignore failed pop at root + let _ = self.nonutil.pop(); + } + } + + pub fn goto(&mut self, act: Activity) { + match act { + Activity::Util(x) => self.util = Some(x), + Activity::NonUtil(x) => { + self.util = None; + match x { + NonUtilityActivity::MainMenu => self.nonutil.clear(), + y => { + let trunc = match self.nonutil.iter() + .position(|z| *z == y) { + Some(pos) => pos, + None => self.nonutil.len(), + }; + self.nonutil.truncate(trunc); + self.nonutil.push(y); + }, + } + } + } + } + + pub fn top(&self) -> Activity { + match &self.util { + Some(x) => Activity::Util(x.clone()), + _ => match self.nonutil.last() { + Some(y) => Activity::NonUtil(y.clone()), + _ => Activity::NonUtil(NonUtilityActivity::MainMenu), + }, + } + } +} + +#[test] +fn test() { + let mut stk = ActivityStack::new(); + + assert_eq!(stk, ActivityStack { + nonutil: vec! {}, + util: None, + }); + + stk.goto(NonUtilityActivity::HomeTimelineFile.into()); + + assert_eq!(stk, ActivityStack { + nonutil: vec! { + NonUtilityActivity::HomeTimelineFile, + }, + util: None, + }); + + stk.goto(NonUtilityActivity::HomeTimelineFile.into()); + + assert_eq!(stk, ActivityStack { + nonutil: vec! { + NonUtilityActivity::HomeTimelineFile, + }, + util: None, + }); + + stk.goto(NonUtilityActivity::MainMenu.into()); + + assert_eq!(stk, ActivityStack { + nonutil: vec! {}, + util: None, + }); + + stk.goto(NonUtilityActivity::HomeTimelineFile.into()); + + assert_eq!(stk, ActivityStack { + nonutil: vec! { + NonUtilityActivity::HomeTimelineFile, + }, + util: None, + }); + + stk.goto(UtilityActivity::UtilsMenu.into()); + + assert_eq!(stk, ActivityStack { + nonutil: vec! { + NonUtilityActivity::HomeTimelineFile, + }, + util: Some(UtilityActivity::UtilsMenu), + }); + + stk.goto(UtilityActivity::ReadMentions.into()); + + assert_eq!(stk, ActivityStack { + nonutil: vec! { + NonUtilityActivity::HomeTimelineFile, + }, + util: Some(UtilityActivity::ReadMentions), + }); + + stk.pop(); + + assert_eq!(stk, ActivityStack { + nonutil: vec! { + NonUtilityActivity::HomeTimelineFile, + }, + util: None, + }); + + stk.goto(UtilityActivity::ReadMentions.into()); + + assert_eq!(stk, ActivityStack { + nonutil: vec! { + NonUtilityActivity::HomeTimelineFile, + }, + util: Some(UtilityActivity::ReadMentions), + }); + + stk.goto(NonUtilityActivity::HomeTimelineFile.into()); + + assert_eq!(stk, ActivityStack { + nonutil: vec! { + NonUtilityActivity::HomeTimelineFile, + }, + util: None, + }); + + stk.pop(); + + assert_eq!(stk, ActivityStack { + nonutil: vec! {}, + util: None, + }); + + stk.pop(); + + assert_eq!(stk, ActivityStack { + nonutil: vec! {}, + util: None, + }); +} diff --git a/src/lib.rs b/src/lib.rs index 50b086d..f87dbfe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,3 +8,4 @@ pub mod scan_re; pub mod coloured_string; pub mod text; pub mod client; +pub mod activity_stack; -- 2.30.2