diff --git a/crates/modalkit-ratatui/examples/editor.rs b/crates/modalkit-ratatui/examples/editor.rs index fb422cb..7ac9e6f 100644 --- a/crates/modalkit-ratatui/examples/editor.rs +++ b/crates/modalkit-ratatui/examples/editor.rs @@ -175,6 +175,13 @@ impl TerminalCursor for EditorWindow { EditorWindow::Listing(ls) => ls.get_term_cursor(), } } + + fn hide_term_cursor(&self) -> bool { + match self { + EditorWindow::Text(tbox) => tbox.hide_term_cursor(), + EditorWindow::Listing(ls) => ls.hide_term_cursor(), + } + } } impl WindowOps for EditorWindow { @@ -739,6 +746,9 @@ impl Editor { render_cursor(f, sstate, cursor); })?; + if sstate.hide_term_cursor() { + term.hide_cursor()?; + } Ok(()) } diff --git a/crates/modalkit-ratatui/src/lib.rs b/crates/modalkit-ratatui/src/lib.rs index 5963d6e..1e9002c 100644 --- a/crates/modalkit-ratatui/src/lib.rs +++ b/crates/modalkit-ratatui/src/lib.rs @@ -128,6 +128,13 @@ pub trait TerminalCursor { /// Returns the current offset of the cursor, relative to the upper left corner of the /// terminal. fn get_term_cursor(&self) -> Option; + + /// Returns whether the cursor should be invisible. + /// + /// Accessibility tools like screen readers need the terminal cursor to be placed in a + /// meaningful way to be useful. Consider placing a hidden cursor to make these tools work + /// correctly. + fn hide_term_cursor(&self) -> bool; } /// A widget whose content can be scrolled in multiple ways. diff --git a/crates/modalkit-ratatui/src/list.rs b/crates/modalkit-ratatui/src/list.rs index 4e35f53..cf3db2d 100644 --- a/crates/modalkit-ratatui/src/list.rs +++ b/crates/modalkit-ratatui/src/list.rs @@ -159,6 +159,7 @@ where items: Vec, cursor: ListCursor, viewctx: ViewportContext, + term_cursor: (u16, u16), /// Tracks the jumplist for this window. jumped: HistoryList, @@ -191,6 +192,7 @@ where id, items, cursor: 0.into(), + term_cursor: (0, 0), viewctx, jumped: HistoryList::new(0.into(), 100), } @@ -1137,8 +1139,10 @@ where I: ApplicationInfo, { fn get_term_cursor(&self) -> Option<(u16, u16)> { - // We highlight the selected text, but don't show the cursor. - return None; + self.term_cursor.into() + } + fn hide_term_cursor(&self) -> bool { + true } } @@ -1154,6 +1158,7 @@ where cursor: self.cursor.clone(), viewctx: self.viewctx.clone(), jumped: self.jumped.clone(), + term_cursor: (0, 0), } } @@ -1236,6 +1241,7 @@ where if state.is_empty() { if let Some(msg) = self.empty_message { Paragraph::new(msg).alignment(self.empty_alignment).render(area, buf); + state.term_cursor = (area.left(), area.top()); return; } } @@ -1299,9 +1305,13 @@ where let mut y = area.top(); let x = area.left(); - for (_, _, txt) in lines.into_iter() { + for (idx, row, txt) in lines.into_iter() { let _ = buf.set_line(x, y, &txt, area.width); + if row == 0 && idx == state.cursor.position { + state.term_cursor = (x, y); + } + y += 1; } } diff --git a/crates/modalkit-ratatui/src/screen.rs b/crates/modalkit-ratatui/src/screen.rs index 9ac3d5b..939ff19 100644 --- a/crates/modalkit-ratatui/src/screen.rs +++ b/crates/modalkit-ratatui/src/screen.rs @@ -626,6 +626,19 @@ where }, } } + + fn hide_term_cursor(&self) -> bool { + match self.focused { + CurrentFocus::Command => self.cmdbar.hide_term_cursor(), + CurrentFocus::Window => { + if let Some(w) = self.current_window() { + w.hide_term_cursor() + } else { + false + } + }, + } + } } impl Jumpable for ScreenState diff --git a/crates/modalkit-ratatui/src/textbox.rs b/crates/modalkit-ratatui/src/textbox.rs index 884eb2a..9c66e8b 100644 --- a/crates/modalkit-ratatui/src/textbox.rs +++ b/crates/modalkit-ratatui/src/textbox.rs @@ -574,6 +574,10 @@ where self.term_cursor.into() } + + fn hide_term_cursor(&self) -> bool { + false + } } impl WindowOps for TextBoxState diff --git a/crates/modalkit-ratatui/src/windows/layout.rs b/crates/modalkit-ratatui/src/windows/layout.rs index 112605c..4fbaed0 100644 --- a/crates/modalkit-ratatui/src/windows/layout.rs +++ b/crates/modalkit-ratatui/src/windows/layout.rs @@ -2188,6 +2188,10 @@ mod tests { fn get_term_cursor(&self) -> Option<(u16, u16)> { (self.term_area.left(), self.term_area.top()).into() } + + fn hide_term_cursor(&self) -> bool { + false + } } impl WindowOps for TestWindow { diff --git a/crates/modalkit-ratatui/src/windows/slot.rs b/crates/modalkit-ratatui/src/windows/slot.rs index 21dab44..d64e116 100644 --- a/crates/modalkit-ratatui/src/windows/slot.rs +++ b/crates/modalkit-ratatui/src/windows/slot.rs @@ -153,6 +153,9 @@ where fn get_term_cursor(&self) -> Option { self.current.get_term_cursor() } + fn hide_term_cursor(&self) -> bool { + self.current.hide_term_cursor() + } } impl WindowOps for WindowSlot