ListStatusBoosters(String),
}
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub enum OverlayActivity {
+ GetUserToExamine,
+}
+
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Activity {
NonUtil(NonUtilityActivity),
Util(UtilityActivity),
+ Overlay(OverlayActivity),
}
impl From<NonUtilityActivity> for Activity {
impl From<UtilityActivity> for Activity {
fn from(value: UtilityActivity) -> Self { Activity::Util(value) }
}
+impl From<OverlayActivity> for Activity {
+ fn from(value: OverlayActivity) -> Self { Activity::Overlay(value) }
+}
#[derive(PartialEq, Eq, Debug)]
pub struct ActivityStack {
nonutil: Vec<NonUtilityActivity>, // not counting MainMenu at the top
util: Option<UtilityActivity>,
+ initial_util: Option<UtilityActivity>,
+ overlay: Option<OverlayActivity>,
}
impl Activity {
ActivityStack {
nonutil: Vec::new(),
util: None,
+ initial_util: None,
+ overlay: None,
}
}
}
}
+ pub fn new_event(&mut self) {
+ // Save the utility activity we were in at the start of an
+ // event. That way we can restore it when moving to an
+ // overlay, which means that for example Alt+E (desugaring as
+ // a very rapid ESC + E) can put the GetUserToExamine overlay
+ // on top of whatever you were previously looking at, rather
+ // than the less helpful utilities menu.
+ self.initial_util = self.util.clone();
+ }
+
pub fn goto(&mut self, act: Activity) {
match act {
- Activity::Util(x) => self.util = Some(x),
+ Activity::Util(x) => {
+ self.overlay = None;
+ self.util = Some(x);
+ },
Activity::NonUtil(x) => {
self.util = None;
+ self.overlay = None;
match x {
NonUtilityActivity::MainMenu => self.nonutil.clear(),
y => {
self.nonutil.push(y);
},
}
- }
+ },
+ Activity::Overlay(x) => {
+ self.util = self.initial_util.clone();
+ self.overlay = Some(x);
+ },
}
}
},
}
}
+
+ pub fn overlay(&self) -> Option<Activity> {
+ match &self.overlay {
+ Some(x) => Some(Activity::Overlay(x.clone())),
+ _ => None,
+ }
+ }
}
#[test]
assert_eq!(stk, ActivityStack {
nonutil: vec! {},
util: None,
+ initial_util: None,
+ overlay: None,
});
stk.goto(NonUtilityActivity::HomeTimelineFile.into());
NonUtilityActivity::HomeTimelineFile,
},
util: None,
+ initial_util: None,
+ overlay: None,
});
stk.goto(NonUtilityActivity::HomeTimelineFile.into());
NonUtilityActivity::HomeTimelineFile,
},
util: None,
+ initial_util: None,
+ overlay: None,
});
stk.goto(NonUtilityActivity::MainMenu.into());
assert_eq!(stk, ActivityStack {
nonutil: vec! {},
util: None,
+ initial_util: None,
+ overlay: None,
});
stk.goto(NonUtilityActivity::HomeTimelineFile.into());
NonUtilityActivity::HomeTimelineFile,
},
util: None,
+ initial_util: None,
+ overlay: None,
});
stk.goto(UtilityActivity::UtilsMenu.into());
NonUtilityActivity::HomeTimelineFile,
},
util: Some(UtilityActivity::UtilsMenu),
+ initial_util: None,
+ overlay: None,
});
stk.goto(UtilityActivity::ReadMentions.into());
NonUtilityActivity::HomeTimelineFile,
},
util: Some(UtilityActivity::ReadMentions),
+ initial_util: None,
+ overlay: None,
});
stk.pop();
NonUtilityActivity::HomeTimelineFile,
},
util: None,
+ initial_util: None,
+ overlay: None,
});
stk.goto(UtilityActivity::ReadMentions.into());
NonUtilityActivity::HomeTimelineFile,
},
util: Some(UtilityActivity::ReadMentions),
+ initial_util: None,
+ overlay: None,
});
stk.goto(NonUtilityActivity::HomeTimelineFile.into());
NonUtilityActivity::HomeTimelineFile,
},
util: None,
+ initial_util: None,
+ overlay: None,
});
stk.pop();
assert_eq!(stk, ActivityStack {
nonutil: vec! {},
util: None,
+ initial_util: None,
+ overlay: None,
});
stk.pop();
assert_eq!(stk, ActivityStack {
nonutil: vec! {},
util: None,
+ initial_util: None,
+ overlay: None,
});
}
prelude::{Buffer, CrosstermBackend, Rect, Terminal},
style::{Style, Color, Modifier},
};
+use std::cmp::min;
use std::collections::HashSet;
use std::io::{Stdout, Write, stdout};
use unicode_width::UnicodeWidthStr;
match ev {
Event::Key(key) => {
if key.kind == KeyEventKind::Press {
+ state.new_event();
for ourkey in Self::translate_keypress(key) {
match state.handle_keypress(
ourkey, &mut self.client) {
struct TuiLogicalState {
activity_stack: ActivityStack,
activity_state: Box<dyn ActivityState>,
+ overlay_activity_state: Option<Box<dyn ActivityState>>,
last_area: Option<Rect>,
}
TuiLogicalState {
activity_stack: activity_stack,
activity_state: activity_state,
+ overlay_activity_state: None,
last_area: None,
}
}
fn draw_frame(&mut self, area: Rect, buf: &mut Buffer)
-> Option<(usize, usize)> {
+ let (w, h) = (area.width as usize, area.height as usize);
+
if self.last_area != Some(area) {
self.last_area = Some(area);
- self.activity_state.resize(
- area.width as usize, area.height as usize);
+ self.activity_state.resize(w, h);
+ if let Some(ref mut state) = self.overlay_activity_state {
+ state.resize(w, h);
+ }
}
- let (lines, cursorpos) = self.activity_state.draw(
- area.width as usize, area.height as usize);
+ let (lines, cursorpos) = self.activity_state.draw(w, h);
+ let mut cursorpos = cursorpos;
buf.reset();
let mut last_x = 0;
let mut last_y = 0;
for (y, line) in lines.iter().enumerate() {
- if y >= area.height as usize {
+ if y >= h {
break;
}
ratatui_set_string(buf, 0, y, &line.slice());
last_y = y;
last_x = line.width();
}
- match cursorpos {
+
+ let mut cursorcoords = match cursorpos {
CursorPosition::None => None,
CursorPosition::At(x, y) => Some((x, y)),
CursorPosition::End => Some((last_x, last_y)),
+ };
+
+ 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());
+ for (line, y) in lines.iter().zip(ytop..) {
+ if y >= h {
+ break;
+ }
+ // Clear the whole previous line in case the new one is shorter
+ for x in 0..area.width {
+ buf.get_mut(x, y as u16).reset();
+ }
+ ratatui_set_string(buf, 0, y, &line.slice());
+ last_y = y;
+ last_x = line.width();
+ }
+ cursorcoords = match cursorpos {
+ CursorPosition::None => None,
+ CursorPosition::At(x, y) => Some((x, y + ytop)),
+ CursorPosition::End => Some((last_x, last_y)),
+ };
}
+
+ cursorcoords
+ }
+
+ fn new_event(&mut self) {
+ self.activity_stack.new_event();
}
fn handle_keypress(&mut self, key: OurKey, client: &mut Client) ->
OurKey::Escape => LogicalAction::Goto(
UtilityActivity::UtilsMenu.into()),
- _ => self.activity_state.handle_keypress(key, client),
+ _ => if let Some(ref mut state) = self.overlay_activity_state {
+ state.handle_keypress(key, client)
+ } else {
+ self.activity_state.handle_keypress(key, client)
+ }
};
match logact {
fn changed_activity(&mut self, client: &mut Client) {
self.activity_state = new_activity_state(
self.activity_stack.top(), client);
+ self.overlay_activity_state = match self.activity_stack.overlay() {
+ Some(activity) => Some(new_activity_state(activity, client)),
+ None => None,
+ };
if let Some(area) = self.last_area {
- self.activity_state.resize(
- area.width as usize, area.height as usize);
+ 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 {
+ state.resize(w, h);
+ }
}
}
}