use std::fmt;
use std::time::Instant;
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub enum KeyboardMode {
#[default]
Normal,
WindowMode(WindowSubMode),
}
impl KeyboardMode {
pub fn is_window_mode(&self) -> bool {
matches!(self, KeyboardMode::WindowMode(_))
}
#[allow(dead_code)]
pub fn sub_mode(&self) -> Option<WindowSubMode> {
match self {
KeyboardMode::WindowMode(sub) => Some(*sub),
KeyboardMode::Normal => None,
}
}
pub fn toggle(&mut self) {
*self = match self {
KeyboardMode::Normal => KeyboardMode::WindowMode(WindowSubMode::Navigation),
KeyboardMode::WindowMode(_) => KeyboardMode::Normal,
};
}
pub fn enter_sub_mode(&mut self, sub_mode: WindowSubMode) {
if self.is_window_mode() {
*self = KeyboardMode::WindowMode(sub_mode);
}
}
pub fn return_to_navigation(&mut self) {
if self.is_window_mode() {
*self = KeyboardMode::WindowMode(WindowSubMode::Navigation);
}
}
pub fn exit_to_normal(&mut self) {
*self = KeyboardMode::Normal;
}
}
impl fmt::Display for KeyboardMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
KeyboardMode::Normal => write!(f, ""),
KeyboardMode::WindowMode(sub) => write!(f, "{}", sub),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub enum WindowSubMode {
#[default]
Navigation,
Move,
Resize(ResizeDirection),
}
impl fmt::Display for WindowSubMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WindowSubMode::Navigation => write!(f, "[WIN]"),
WindowSubMode::Move => write!(f, "[WIN:MOVE]"),
WindowSubMode::Resize(_) => write!(f, "[WIN:SIZE]"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub enum ResizeDirection {
#[default]
Default,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SnapPosition {
BottomLeft, BottomCenter, BottomRight, MiddleLeft, Center, MiddleRight, TopLeft, TopCenter, TopRight,
FullLeft, FullRight, FullTop, FullBottom, }
impl SnapPosition {
#[allow(dead_code)]
pub fn from_numpad(key: char) -> Option<Self> {
match key {
'1' => Some(SnapPosition::BottomLeft),
'2' => Some(SnapPosition::BottomCenter),
'3' => Some(SnapPosition::BottomRight),
'4' => Some(SnapPosition::MiddleLeft),
'5' => Some(SnapPosition::Center),
'6' => Some(SnapPosition::MiddleRight),
'7' => Some(SnapPosition::TopLeft),
'8' => Some(SnapPosition::TopCenter),
'9' => Some(SnapPosition::TopRight),
_ => None,
}
}
pub fn calculate_rect(
&self,
buffer_width: u16,
buffer_height: u16,
top_bar_y: u16,
) -> (u16, u16, u16, u16) {
let usable_height = buffer_height.saturating_sub(top_bar_y + 1); let half_width = buffer_width / 2;
let half_height = usable_height / 2;
let third_width = buffer_width / 3;
let third_height = usable_height / 3;
match self {
SnapPosition::TopLeft => (0, top_bar_y, half_width, half_height),
SnapPosition::TopRight => (
half_width,
top_bar_y,
buffer_width - half_width,
half_height,
),
SnapPosition::BottomLeft => (
0,
top_bar_y + half_height,
half_width,
usable_height - half_height,
),
SnapPosition::BottomRight => (
half_width,
top_bar_y + half_height,
buffer_width - half_width,
usable_height - half_height,
),
SnapPosition::TopCenter => (third_width, top_bar_y, third_width, half_height),
SnapPosition::BottomCenter => (
third_width,
top_bar_y + half_height,
third_width,
usable_height - half_height,
),
SnapPosition::MiddleLeft => (0, top_bar_y + third_height, half_width, third_height),
SnapPosition::MiddleRight => (
half_width,
top_bar_y + third_height,
half_width,
third_height,
),
SnapPosition::Center => (
third_width,
top_bar_y + third_height,
third_width,
third_height,
),
SnapPosition::FullLeft => (0, top_bar_y, half_width, usable_height),
SnapPosition::FullRight => (
half_width,
top_bar_y,
buffer_width - half_width,
usable_height,
),
SnapPosition::FullTop => (0, top_bar_y, buffer_width, half_height),
SnapPosition::FullBottom => (
0,
top_bar_y + half_height,
buffer_width,
usable_height - half_height,
),
}
}
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Direction {
Left,
Right,
Up,
Down,
}
#[derive(Clone, Debug, Default)]
pub struct MovementState {
last_press: Option<Instant>,
}
impl MovementState {
pub fn new() -> Self {
Self { last_press: None }
}
pub fn get_step(&mut self) -> u16 {
let now = Instant::now();
let step = match self.last_press {
None => 1, Some(last) => {
let elapsed = now.duration_since(last).as_millis();
match elapsed {
0..=50 => 8, 51..=100 => 4, 101..=200 => 2, _ => 1, }
}
};
self.last_press = Some(now);
step
}
pub fn reset(&mut self) {
self.last_press = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_keyboard_mode_toggle() {
let mut mode = KeyboardMode::Normal;
assert!(!mode.is_window_mode());
mode.toggle();
assert!(mode.is_window_mode());
assert_eq!(mode.sub_mode(), Some(WindowSubMode::Navigation));
mode.toggle();
assert!(!mode.is_window_mode());
}
#[test]
fn test_sub_mode_transitions() {
let mut mode = KeyboardMode::WindowMode(WindowSubMode::Navigation);
mode.enter_sub_mode(WindowSubMode::Move);
assert_eq!(mode.sub_mode(), Some(WindowSubMode::Move));
mode.return_to_navigation();
assert_eq!(mode.sub_mode(), Some(WindowSubMode::Navigation));
}
#[test]
fn test_snap_position_from_numpad() {
assert_eq!(
SnapPosition::from_numpad('1'),
Some(SnapPosition::BottomLeft)
);
assert_eq!(SnapPosition::from_numpad('5'), Some(SnapPosition::Center));
assert_eq!(SnapPosition::from_numpad('9'), Some(SnapPosition::TopRight));
assert_eq!(SnapPosition::from_numpad('0'), None);
}
}