use crate::*; #[derive(Clone, Copy, Debug, PartialEq)] pub enum FocusState { Focused(T), Entered(T), } impl FocusState { pub fn inner (&self) -> T { match self { Self::Focused(inner) => *inner, Self::Entered(inner) => *inner, } } pub fn set_inner (&mut self, inner: T) { *self = match self { Self::Focused(_) => Self::Focused(inner), Self::Entered(_) => Self::Entered(inner), } } pub fn is_focused (&self) -> bool { matches!(self, Self::Focused(_)) } pub fn is_entered (&self) -> bool { matches!(self, Self::Entered(_)) } pub fn focus (&mut self) { *self = Self::Focused(self.inner()) } pub fn enter (&mut self) { *self = Self::Entered(self.inner()) } } #[derive(Copy, Clone, PartialEq, Debug)] pub enum FocusCommand { Up, Down, Left, Right, Next, Prev, Enter, Exit, Set(T) } impl Command for FocusCommand { fn execute (self, state: &mut F) -> Perhaps> { match self { Self::Next => { state.focus_next(); }, Self::Prev => { state.focus_prev(); }, Self::Up => { state.focus_up(); }, Self::Down => { state.focus_down(); }, Self::Left => { state.focus_left(); }, Self::Right => { state.focus_right(); }, Self::Enter => { state.focus_enter(); }, Self::Exit => { state.focus_exit(); }, Self::Set(to) => { state.set_focused(to); }, } Ok(None) } } /// Trait for things that have focusable subparts. pub trait HasFocus { type Item: Copy + PartialEq + Debug + Send + Sync; /// Get the currently focused item. fn focused (&self) -> Self::Item; /// Get the currently focused item. fn set_focused (&mut self, to: Self::Item); /// Loop forward until a specific item is focused. fn focus_to (&mut self, to: Self::Item) { self.set_focused(to); self.focus_updated(); } /// Run this on focus update fn focus_updated (&mut self) {} } /// Trait for things that have enterable subparts. pub trait HasEnter: HasFocus { /// Get the currently focused item. fn entered (&self) -> bool; /// Get the currently focused item. fn set_entered (&mut self, entered: bool); /// Enter into the currently focused component fn focus_enter (&mut self) { self.set_entered(true); self.focus_updated(); } /// Exit the currently entered component fn focus_exit (&mut self) { self.set_entered(false); self.focus_updated(); } } /// Trait for things that implement directional navigation between focusable elements. pub trait FocusGrid: HasFocus { fn focus_layout (&self) -> &[&[Self::Item]]; fn focus_cursor (&self) -> (usize, usize); fn focus_cursor_mut (&mut self) -> &mut (usize, usize); fn focus_current (&self) -> Self::Item { let (x, y) = self.focus_cursor(); self.focus_layout()[y][x] } fn focus_update (&mut self) { self.focus_to(self.focus_current()); self.focus_updated() } fn focus_up (&mut self) { let original_focused = self.focused(); let (_, original_y) = self.focus_cursor(); loop { let (x, y) = self.focus_cursor(); let next_y = if y == 0 { self.focus_layout().len().saturating_sub(1) } else { y - 1 }; if next_y == original_y { break } let next_x = if self.focus_layout()[y].len() == self.focus_layout()[next_y].len() { x } else { ((x as f32 / self.focus_layout()[original_y].len() as f32) * self.focus_layout()[next_y].len() as f32) as usize }; *self.focus_cursor_mut() = (next_x, next_y); if self.focus_current() != original_focused { break } } self.focus_update(); } fn focus_down (&mut self) { let original_focused = self.focused(); let (_, original_y) = self.focus_cursor(); loop { let (x, y) = self.focus_cursor(); let next_y = if y >= self.focus_layout().len().saturating_sub(1) { 0 } else { y + 1 }; if next_y == original_y { break } let next_x = if self.focus_layout()[y].len() == self.focus_layout()[next_y].len() { x } else { ((x as f32 / self.focus_layout()[original_y].len() as f32) * self.focus_layout()[next_y].len() as f32) as usize }; *self.focus_cursor_mut() = (next_x, next_y); if self.focus_current() != original_focused { break } } self.focus_update(); } fn focus_left (&mut self) { let original_focused = self.focused(); let (original_x, y) = self.focus_cursor(); loop { let x = self.focus_cursor().0; let next_x = if x == 0 { self.focus_layout()[y].len().saturating_sub(1) } else { x - 1 }; if next_x == original_x { break } *self.focus_cursor_mut() = (next_x, y); if self.focus_current() != original_focused { break } } self.focus_update(); } fn focus_right (&mut self) { let original_focused = self.focused(); let (original_x, y) = self.focus_cursor(); loop { let x = self.focus_cursor().0; let next_x = if x >= self.focus_layout()[y].len().saturating_sub(1) { 0 } else { x + 1 }; if next_x == original_x { break } self.focus_cursor_mut().0 = next_x; if self.focus_current() != original_focused { break } } self.focus_update(); } } /// Trait for things that implement next/prev navigation between focusable elements. pub trait FocusOrder { /// Focus the next item. fn focus_next (&mut self); /// Focus the previous item. fn focus_prev (&mut self); } /// Next/prev navigation for directional focusables works in the given way. impl FocusOrder for T { /// Focus the next item. fn focus_next (&mut self) { let current = self.focused(); let (x, y) = self.focus_cursor(); if x < self.focus_layout()[y].len().saturating_sub(1) { self.focus_right(); } else { self.focus_down(); self.focus_cursor_mut().0 = 0; } if self.focused() == current { // FIXME: prevent infinite loop self.focus_next() } self.focus_exit(); self.focus_update(); } /// Focus the previous item. fn focus_prev (&mut self) { let current = self.focused(); let (x, _) = self.focus_cursor(); if x > 0 { self.focus_left(); } else { self.focus_up(); let (_, y) = self.focus_cursor(); let next_x = self.focus_layout()[y].len().saturating_sub(1); self.focus_cursor_mut().0 = next_x; } if self.focused() == current { // FIXME: prevent infinite loop self.focus_prev() } self.focus_exit(); self.focus_update(); } } pub trait FocusWrap { fn wrap > (self, focus: T, content: &'_ W) -> impl Render + '_; } pub fn to_focus_command (input: &TuiInput) -> Option> { Some(match input.event() { key_pat!(Tab) => FocusCommand::Next, key_pat!(Shift-Tab) => FocusCommand::Prev, key_pat!(BackTab) => FocusCommand::Prev, key_pat!(Shift-BackTab) => FocusCommand::Prev, key_pat!(Up) => FocusCommand::Up, key_pat!(Down) => FocusCommand::Down, key_pat!(Left) => FocusCommand::Left, key_pat!(Right) => FocusCommand::Right, key_pat!(Enter) => FocusCommand::Enter, key_pat!(Esc) => FocusCommand::Exit, _ => return None }) } #[macro_export] macro_rules! impl_focus { ($Struct:ident $Focus:ident $Grid:expr $(=> [$self:ident : $update_focus:expr])?) => { impl HasFocus for $Struct { type Item = $Focus; /// Get the currently focused item. fn focused (&self) -> Self::Item { self.focus.inner() } /// Get the currently focused item. fn set_focused (&mut self, to: Self::Item) { self.focus.set_inner(to) } $(fn focus_updated (&mut $self) { $update_focus })? } impl HasEnter for $Struct { /// Get the currently focused item. fn entered (&self) -> bool { self.focus.is_entered() } /// Get the currently focused item. fn set_entered (&mut self, entered: bool) { if entered { self.focus.to_entered() } else { self.focus.to_focused() } } } impl FocusGrid for $Struct { fn focus_cursor (&self) -> (usize, usize) { self.cursor } fn focus_cursor_mut (&mut self) -> &mut (usize, usize) { &mut self.cursor } fn focus_layout (&self) -> &[&[$Focus]] { use $Focus::*; &$Grid } } } }