pub mod appearance_section;
pub mod display_section;
pub mod editor_section;
pub mod indexing_section;
pub mod server_section;
pub mod sorting_section;
pub mod theme_picker;
pub mod vault_section;
pub mod workspaces_section;
use ratatui::crossterm::event::{KeyCode, KeyEvent, MouseEvent, MouseEventKind};
use ratatui::layout::{Position, Rect};
use crate::components::clickable::is_left_press;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SectionClick {
Row(usize),
Control { row: usize, key: KeyCode },
}
#[derive(Default)]
pub struct ClickMap {
targets: Vec<(Rect, SectionClick)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SectionMouse {
Select(usize),
Key(usize, KeyEvent),
Wheel(KeyEvent),
None,
}
impl SectionMouse {
pub fn into_key(self, mut select: impl FnMut(usize)) -> Option<KeyEvent> {
match self {
SectionMouse::Select(row) => {
select(row);
None
}
SectionMouse::Key(row, key) => {
select(row);
Some(key)
}
SectionMouse::Wheel(key) => Some(key),
SectionMouse::None => None,
}
}
}
impl ClickMap {
pub fn list_rows(&mut self, inner: Rect, offset: usize, len: usize) {
for (line, row) in (offset..len).enumerate() {
let y = inner.y as usize + line;
if y >= inner.bottom() as usize {
break;
}
self.row(Rect::new(inner.x, y as u16, inner.width, 1), row);
}
}
pub fn clear(&mut self) {
self.targets.clear();
}
pub fn row(&mut self, rect: Rect, row: usize) {
if !rect.is_empty() {
self.targets.push((rect, SectionClick::Row(row)));
}
}
pub fn control(&mut self, rect: Rect, row: usize, key: KeyCode) {
if !rect.is_empty() {
crate::components::clickable::register(rect);
self.targets
.push((rect, SectionClick::Control { row, key }));
}
}
pub fn resolve(
&self,
m: &MouseEvent,
selected: Option<usize>,
activate: Option<KeyCode>,
) -> SectionMouse {
match m.kind {
MouseEventKind::ScrollUp => return SectionMouse::Wheel(KeyEvent::from(KeyCode::Up)),
MouseEventKind::ScrollDown => {
return SectionMouse::Wheel(KeyEvent::from(KeyCode::Down));
}
_ => {}
}
if !is_left_press(m) {
return SectionMouse::None;
}
let pos = Position::new(m.column, m.row);
match self.targets.iter().rev().find(|(r, _)| r.contains(pos)) {
Some((_, SectionClick::Control { row, key })) => {
SectionMouse::Key(*row, KeyEvent::from(*key))
}
Some((_, SectionClick::Row(row))) => match activate {
Some(key) if selected == Some(*row) => SectionMouse::Key(*row, KeyEvent::from(key)),
_ => SectionMouse::Select(*row),
},
None => SectionMouse::None,
}
}
}
pub fn text_rect(row: Rect, col: u16, width: u16) -> Rect {
let x = row.x.saturating_add(col);
if x >= row.right() {
return Rect::default();
}
Rect::new(x, row.y, width.min(row.right() - x), 1)
}
#[cfg(test)]
mod tests {
use super::*;
use ratatui::crossterm::event::{KeyModifiers, MouseButton};
fn press(col: u16, row: u16) -> MouseEvent {
MouseEvent {
kind: MouseEventKind::Down(MouseButton::Left),
column: col,
row,
modifiers: KeyModifiers::NONE,
}
}
#[test]
fn rows_select_controls_act_and_reclick_activates() {
let mut map = ClickMap::default();
map.row(Rect::new(0, 0, 20, 1), 0);
map.row(Rect::new(0, 1, 20, 1), 1);
map.control(Rect::new(10, 1, 3, 1), 1, KeyCode::Right);
let enter = Some(KeyCode::Enter);
assert_eq!(
map.resolve(&press(2, 1), Some(0), enter),
SectionMouse::Select(1)
);
assert_eq!(
map.resolve(&press(2, 1), Some(1), enter),
SectionMouse::Key(1, KeyEvent::from(KeyCode::Enter)),
"re-clicking the selected row runs its key"
);
assert_eq!(
map.resolve(&press(11, 1), Some(0), enter),
SectionMouse::Key(1, KeyEvent::from(KeyCode::Right)),
"a control wins over its row"
);
assert_eq!(
map.resolve(&press(2, 1), Some(1), None),
SectionMouse::Select(1)
);
assert_eq!(
map.resolve(&press(2, 5), Some(1), enter),
SectionMouse::None
);
}
#[test]
fn text_rect_clips_to_the_row() {
let row = Rect::new(5, 2, 10, 1);
assert_eq!(text_rect(row, 2, 4), Rect::new(7, 2, 4, 1));
assert_eq!(text_rect(row, 8, 4), Rect::new(13, 2, 2, 1));
assert_eq!(text_rect(row, 12, 4), Rect::default());
}
}