use super::client::{Boosts, Replies};
use super::file::SearchDirection;
-#[derive(PartialEq, Eq, Debug, Clone)]
+#[derive(PartialEq, Eq, Debug, Clone, Hash)]
pub enum NonUtilityActivity {
MainMenu,
HomeTimelineFile,
LoginMenu,
}
-#[derive(PartialEq, Eq, Debug, Clone)]
+#[derive(PartialEq, Eq, Debug, Clone, Hash)]
pub enum UtilityActivity {
UtilsMenu,
ReadMentions,
InstanceRules,
}
-#[derive(PartialEq, Eq, Debug, Clone)]
+#[derive(PartialEq, Eq, Debug, Clone, Hash)]
pub enum OverlayActivity {
GetUserToExamine,
GetPostIdToRead,
GetSearchExpression(SearchDirection),
}
-#[derive(PartialEq, Eq, Debug, Clone)]
+#[derive(PartialEq, Eq, Debug, Clone, Hash)]
pub enum Activity {
NonUtil(NonUtilityActivity),
Util(UtilityActivity),
self.pop();
self.goto(act);
}
+
+ pub fn iter(&self) -> impl Iterator<Item = Activity> + '_ {
+ self.nonutil
+ .iter()
+ .cloned()
+ .map(Activity::NonUtil)
+ .chain(self.util.iter().cloned().map(Activity::Util))
+ .chain(self.initial_util.iter().cloned().map(Activity::Util))
+ .chain(self.overlay.iter().cloned().map(Activity::Overlay))
+ }
}
#[test]
finish_account_setup(&mut client, cfgloc)?;
}
- let mut state = TuiLogicalState::new(&client, cfgloc.clone());
+ let mut state = TuiLogicalState::new(&mut client, cfgloc.clone());
state.load_ldb()?;
stdout().execute(EnterAlternateScreen)?;
struct TuiLogicalState {
activity_stack: ActivityStack,
- activity_state: Box<dyn ActivityState>,
- overlay_activity_state: Option<Box<dyn ActivityState>>,
+ activity_states: HashMap<Activity, Box<dyn ActivityState>>,
last_area: Option<Rect>,
file_positions: HashMap<FeedId, SavedFilePos>,
unfolded_posts: Rc<RefCell<HashSet<String>>>,
}
impl TuiLogicalState {
- fn new(client: &Client, cfgloc: ConfigLocation) -> Self {
- let (activity, activity_state) = if client.auth.is_logged_in() {
- (NonUtilityActivity::MainMenu, main_menu(client))
+ fn new(client: &mut Client, cfgloc: ConfigLocation) -> Self {
+ let activity = if client.auth.is_logged_in() {
+ NonUtilityActivity::MainMenu
} else {
- (NonUtilityActivity::LoginMenu, login_menu(cfgloc.clone()))
+ NonUtilityActivity::LoginMenu
};
- let activity_stack = ActivityStack::new(activity);
- TuiLogicalState {
- activity_stack,
- activity_state,
- overlay_activity_state: None,
+ let mut st = TuiLogicalState {
+ activity_stack: ActivityStack::new(activity),
+ activity_states: HashMap::new(),
last_area: None,
file_positions: HashMap::new(),
cfgloc,
unfolded_posts: Rc::new(RefCell::new(HashSet::new())),
- }
+ };
+ st.changed_activity(client, None, false);
+
+ st
}
fn draw_frame(
if self.last_area != Some(area) {
self.last_area = Some(area);
- self.activity_state.resize(w, h);
- if let Some(ref mut state) = self.overlay_activity_state {
+ self.activity_state_mut().resize(w, h);
+ if let Some(ref mut state) = self.overlay_activity_state_mut() {
state.resize(w, h);
}
}
- let (lines, cursorpos) = self.activity_state.draw(w, h);
+ let (lines, cursorpos) = self.activity_state().draw(w, h);
let mut cursorpos = cursorpos;
buf.reset();
let mut last_x = 0;
CursorPosition::End => Some((last_x, last_y)),
};
- if let Some(state) = &self.overlay_activity_state {
+ if let Some(state) = &self.overlay_activity_state() {
let (lines, overlay_cursorpos) = state.draw(w, h);
cursorpos = overlay_cursorpos;
let ytop = h - min(h, lines.len());
OurKey::Ctrl('L') => return PhysicalAction::Refresh,
_ => {
- if let Some(ref mut state) = self.overlay_activity_state {
+ if let Some(state) = self.overlay_activity_state_mut() {
state.handle_keypress(key, client)
} else {
- self.activity_state.handle_keypress(key, client)
+ self.activity_state_mut().handle_keypress(key, client)
}
}
};
}
LogicalAction::GotSearchExpression(dir, regex) => {
self.pop_overlay_activity();
- self.activity_state.got_search_expression(dir, regex)
+ self.activity_state_mut().got_search_expression(dir, regex)
}
LogicalAction::Error(_) => break PhysicalAction::Beep, // FIXME: Error Log
LogicalAction::PostComposed(post) => {
feeds_updated: HashSet<FeedId>,
client: &mut Client,
) -> PhysicalAction {
- self.activity_state
+ self.activity_state_mut()
.handle_feed_updates(&feeds_updated, client);
if feeds_updated.contains(&FeedId::Mentions) {
}
fn checkpoint_ldb(&mut self) {
- if let Some((feed_id, saved_pos)) =
- self.activity_state.save_file_position()
- {
- let changed =
- self.file_positions.get(&feed_id) != Some(&saved_pos);
- self.file_positions.insert(feed_id, saved_pos);
- if changed {
- // FIXME: maybe suddenly change our mind and go to the
- // Error Log
- self.save_ldb().unwrap();
+ let mut changed = false;
+ for state in self.activity_states.values_mut() {
+ if let Some((feed_id, saved_pos)) = state.save_file_position() {
+ if self.file_positions.get(&feed_id) != Some(&saved_pos) {
+ self.file_positions.insert(feed_id, saved_pos);
+ changed = true;
+ }
}
}
+
+ if changed {
+ // FIXME: maybe suddenly change our mind and go to the
+ // Error Log
+ self.save_ldb().unwrap();
+ }
+ }
+
+ fn ensure_activity_state(
+ &mut self,
+ act: Activity,
+ client: &mut Client,
+ post: Option<Post>,
+ is_interrupt: bool,
+ ) {
+ if !self.activity_states.contains_key(&act) {
+ self.activity_states.insert(
+ act.clone(),
+ self.new_activity_state(act, client, post, is_interrupt),
+ );
+ }
}
fn changed_activity(
is_interrupt: bool,
) {
self.checkpoint_ldb();
- self.activity_state = self.new_activity_state(
+
+ self.ensure_activity_state(
self.activity_stack.top(),
client,
post,
is_interrupt,
);
- self.overlay_activity_state = match self.activity_stack.overlay() {
- Some(activity) => {
- Some(self.new_activity_state(activity, client, None, false))
- }
- None => None,
- };
+ if let Some(act) = self.activity_stack.overlay() {
+ self.ensure_activity_state(act, client, None, false);
+ }
+
+ self.gc_activity_states();
+
if let Some(area) = self.last_area {
let (w, h) = (area.width as usize, area.height as usize);
- self.activity_state.resize(w, h);
- if let Some(ref mut state) = self.overlay_activity_state {
+ self.activity_state_mut().resize(w, h);
+ if let Some(state) = self.overlay_activity_state_mut() {
state.resize(w, h);
}
}
}
+ fn gc_activity_states(&mut self) {
+ let states: HashSet<_> = self.activity_stack.iter().collect();
+ self.activity_states.retain(|k, _| states.contains(k));
+ }
+
fn pop_overlay_activity(&mut self) {
self.activity_stack.pop_overlay();
- self.overlay_activity_state = None;
+ self.gc_activity_states();
}
fn new_activity_state(
}
}
}
+
+ fn activity_state(&self) -> &dyn ActivityState {
+ self.activity_states
+ .get(&self.activity_stack.top())
+ .expect("every current activity ought to have an entry here")
+ .as_ref()
+ }
+ fn activity_state_mut(&mut self) -> &mut dyn ActivityState {
+ self.activity_states
+ .get_mut(&self.activity_stack.top())
+ .expect("every current activity ought to have an entry here")
+ .as_mut()
+ }
+ fn overlay_activity_state(&self) -> Option<&dyn ActivityState> {
+ match self.activity_stack.overlay() {
+ Some(activity) => Some(
+ self.activity_states
+ .get(&activity)
+ .expect(
+ "every current activity ought to have an entry here",
+ )
+ .as_ref(),
+ ),
+ None => None,
+ }
+ }
+ fn overlay_activity_state_mut(
+ &mut self,
+ ) -> Option<&mut (dyn ActivityState + '_)> {
+ match self.activity_stack.overlay() {
+ Some(activity) => Some(
+ self.activity_states
+ .get_mut(&activity)
+ .expect(
+ "every current activity ought to have an entry here",
+ )
+ .as_mut(),
+ ),
+ None => None,
+ }
+ }
}