use crossterm::event::{
Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseButton, MouseEvent, MouseEventKind,
};
use crate::app::{App, Screen, SensitiveString};
use crate::state::{dispatch_input, AppInput};
pub fn feed_events(
app: &mut App,
events: impl IntoIterator<Item = Event>,
password_store: &mut Option<SensitiveString>,
) -> bool {
for event in events {
if handle_event(app, event, password_store) {
return true;
}
if app.screen == Screen::Quitting {
return true;
}
}
false
}
pub fn handle_event(
app: &mut App,
event: Event,
password_store: &mut Option<SensitiveString>,
) -> bool {
match event {
Event::Key(key) if key.kind == KeyEventKind::Press => match translate_key(key) {
Some(input) => dispatch_input(app, input, password_store),
None => false,
},
Event::Mouse(mouse) => handle_mouse(app, mouse, password_store),
_ => false,
}
}
fn translate_key(key: KeyEvent) -> Option<AppInput> {
if key.modifiers.contains(KeyModifiers::CONTROL) {
return if key.code == KeyCode::Char('c') {
Some(AppInput::CtrlC)
} else {
None
};
}
match key.code {
KeyCode::Char(c) => Some(AppInput::Char(c)),
KeyCode::Enter => Some(AppInput::Enter),
KeyCode::Esc => Some(AppInput::Esc),
KeyCode::Backspace => Some(AppInput::Backspace),
KeyCode::Tab => Some(AppInput::Tab),
KeyCode::Up => Some(AppInput::Up),
KeyCode::Down => Some(AppInput::Down),
_ => None,
}
}
fn handle_mouse(
app: &mut App,
mouse: MouseEvent,
_password_store: &mut Option<SensitiveString>,
) -> bool {
let screen = app.screen.clone();
let (term_cols, _) = crossterm::terminal::size().unwrap_or((80, 24));
let profile_panel_width = term_cols / 4;
match screen {
Screen::Dashboard => match mouse.kind {
MouseEventKind::ScrollDown => {
if mouse.column < profile_panel_width {
if !app.profiles.is_empty() {
app.profile_cursor = (app.profile_cursor + 1) % app.profiles.len();
}
} else {
let len = app.filtered_keys().len();
if len > 0 {
app.secret_cursor = (app.secret_cursor + 1) % len;
}
}
}
MouseEventKind::ScrollUp => {
if mouse.column < profile_panel_width {
if !app.profiles.is_empty() {
app.profile_cursor =
(app.profile_cursor + app.profiles.len() - 1) % app.profiles.len();
}
} else {
let len = app.filtered_keys().len();
if len > 0 {
app.secret_cursor = (app.secret_cursor + len - 1) % len;
}
}
}
MouseEventKind::Down(MouseButton::Left) => {
if mouse.column >= profile_panel_width && mouse.row >= 2 {
let index = (mouse.row as usize).saturating_sub(2);
let len = app.filtered_keys().len();
if index < len {
app.secret_cursor = index;
}
}
}
_ => {}
},
Screen::AuditLog => match mouse.kind {
MouseEventKind::ScrollDown => {
if app.audit_scroll + 1 < app.audit_lines.len() {
app.audit_scroll += 1;
}
}
MouseEventKind::ScrollUp => {
app.audit_scroll = app.audit_scroll.saturating_sub(1);
}
_ => {}
},
Screen::History { .. } => match mouse.kind {
MouseEventKind::ScrollDown => {
if app.history_scroll + 1 < app.history_entries.len() {
app.history_scroll += 1;
}
}
MouseEventKind::ScrollUp => {
app.history_scroll = app.history_scroll.saturating_sub(1);
}
_ => {}
},
Screen::MoveSecret { .. } | Screen::NsBulk { .. } => {} _ => {}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::AppInput;
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
#[test]
fn translate_key_maps_printable_and_special_keys() {
assert_eq!(
translate_key(KeyEvent::new(KeyCode::Char('z'), KeyModifiers::NONE)),
Some(AppInput::Char('z'))
);
assert_eq!(
translate_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)),
Some(AppInput::Enter)
);
assert_eq!(
translate_key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE)),
Some(AppInput::Esc)
);
assert_eq!(
translate_key(KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE)),
Some(AppInput::Backspace)
);
assert_eq!(
translate_key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE)),
Some(AppInput::Tab)
);
assert_eq!(
translate_key(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE)),
Some(AppInput::Up)
);
assert_eq!(
translate_key(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE)),
Some(AppInput::Down)
);
}
#[test]
fn translate_key_ctrl_c_only_control_sequence() {
assert_eq!(
translate_key(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL)),
Some(AppInput::CtrlC)
);
assert_eq!(
translate_key(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL)),
None
);
assert_eq!(
translate_key(KeyEvent::new(KeyCode::PageUp, KeyModifiers::CONTROL)),
None
);
}
#[test]
fn translate_key_ignores_unhandled_codes() {
assert_eq!(
translate_key(KeyEvent::new(KeyCode::F(1), KeyModifiers::NONE)),
None
);
}
#[test]
fn handle_event_ignores_key_release_and_repeat() {
let mut app = crate::app::App::new();
app.update_rx = None;
app.update_available = None;
app.screen = crate::app::Screen::Login;
let mut pw: Option<crate::app::SensitiveString> = None;
let ev_release = Event::Key(KeyEvent::new_with_kind(
KeyCode::Char('q'),
KeyModifiers::NONE,
KeyEventKind::Release,
));
assert!(!handle_event(&mut app, ev_release, &mut pw));
let ev_repeat = Event::Key(KeyEvent::new_with_kind(
KeyCode::Char('q'),
KeyModifiers::NONE,
KeyEventKind::Repeat,
));
assert!(!handle_event(&mut app, ev_repeat, &mut pw));
}
}