user: hook up screen_legend from ScreenView::label()
Build legend string from actual screen labels instead of hardcoded constant. Computed once at startup via OnceLock, accessible from all screen draw methods. Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
parent
68f115b880
commit
6f000bd0f6
3 changed files with 80 additions and 67 deletions
126
src/user/chat.rs
126
src/user/chat.rs
|
|
@ -159,71 +159,7 @@ impl InteractScreen {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl ScreenView for InteractScreen {
|
|
||||||
fn label(&self) -> &'static str { "interact" }
|
|
||||||
|
|
||||||
fn tick(&mut self, frame: &mut Frame, area: Rect,
|
|
||||||
key: Option<KeyEvent>, app: &mut App) -> Option<ScreenAction> {
|
|
||||||
// Handle keys
|
|
||||||
if let Some(key) = key {
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Esc => return Some(ScreenAction::Hotkey(HotkeyAction::Interrupt)),
|
|
||||||
KeyCode::Enter if !key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::SHIFT) => {
|
|
||||||
let input: String = self.textarea.lines().join("\n");
|
|
||||||
if !input.is_empty() {
|
|
||||||
if self.input_history.last().map_or(true, |h| h != &input) {
|
|
||||||
self.input_history.push(input.clone());
|
|
||||||
}
|
|
||||||
self.history_index = None;
|
|
||||||
// TODO: push to submitted via app or return action
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Up if key.modifiers.contains(KeyModifiers::CONTROL) => self.scroll_active_up(3),
|
|
||||||
KeyCode::Down if key.modifiers.contains(KeyModifiers::CONTROL) => self.scroll_active_down(3),
|
|
||||||
KeyCode::Up => {
|
|
||||||
if !self.input_history.is_empty() {
|
|
||||||
let idx = match self.history_index { None => self.input_history.len() - 1, Some(i) => i.saturating_sub(1) };
|
|
||||||
self.history_index = Some(idx);
|
|
||||||
let mut ta = new_textarea(self.input_history[idx].lines().map(String::from).collect());
|
|
||||||
ta.move_cursor(tui_textarea::CursorMove::End);
|
|
||||||
self.textarea = ta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Down => {
|
|
||||||
if let Some(idx) = self.history_index {
|
|
||||||
if idx + 1 < self.input_history.len() {
|
|
||||||
self.history_index = Some(idx + 1);
|
|
||||||
let mut ta = new_textarea(self.input_history[idx + 1].lines().map(String::from).collect());
|
|
||||||
ta.move_cursor(tui_textarea::CursorMove::End);
|
|
||||||
self.textarea = ta;
|
|
||||||
} else {
|
|
||||||
self.history_index = None;
|
|
||||||
self.textarea = new_textarea(vec![String::new()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::PageUp => self.scroll_active_up(10),
|
|
||||||
KeyCode::PageDown => self.scroll_active_down(10),
|
|
||||||
KeyCode::Tab => {
|
|
||||||
self.active_pane = match self.active_pane {
|
|
||||||
ActivePane::Autonomous => ActivePane::Tools,
|
|
||||||
ActivePane::Tools => ActivePane::Conversation,
|
|
||||||
ActivePane::Conversation => ActivePane::Autonomous,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
_ => { self.textarea.input(key); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw
|
|
||||||
self.draw_main(frame, area, app);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InteractScreen {
|
|
||||||
/// Draw the main (F1) screen — four-pane layout with status bar.
|
/// Draw the main (F1) screen — four-pane layout with status bar.
|
||||||
pub(crate) fn draw_main(&mut self, frame: &mut Frame, size: Rect, app: &App) {
|
pub(crate) fn draw_main(&mut self, frame: &mut Frame, size: Rect, app: &App) {
|
||||||
// Main layout: content area + active tools overlay + status bar
|
// Main layout: content area + active tools overlay + status bar
|
||||||
|
|
@ -407,6 +343,68 @@ impl InteractScreen {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ScreenView for InteractScreen {
|
||||||
|
fn label(&self) -> &'static str { "interact" }
|
||||||
|
|
||||||
|
fn tick(&mut self, frame: &mut Frame, area: Rect,
|
||||||
|
key: Option<KeyEvent>, app: &mut App) -> Option<ScreenAction> {
|
||||||
|
// Handle keys
|
||||||
|
if let Some(key) = key {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Esc => return Some(ScreenAction::Hotkey(HotkeyAction::Interrupt)),
|
||||||
|
KeyCode::Enter if !key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::SHIFT) => {
|
||||||
|
let input: String = self.textarea.lines().join("\n");
|
||||||
|
if !input.is_empty() {
|
||||||
|
if self.input_history.last().map_or(true, |h| h != &input) {
|
||||||
|
self.input_history.push(input.clone());
|
||||||
|
}
|
||||||
|
self.history_index = None;
|
||||||
|
// TODO: push to submitted via app or return action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Up if key.modifiers.contains(KeyModifiers::CONTROL) => self.scroll_active_up(3),
|
||||||
|
KeyCode::Down if key.modifiers.contains(KeyModifiers::CONTROL) => self.scroll_active_down(3),
|
||||||
|
KeyCode::Up => {
|
||||||
|
if !self.input_history.is_empty() {
|
||||||
|
let idx = match self.history_index { None => self.input_history.len() - 1, Some(i) => i.saturating_sub(1) };
|
||||||
|
self.history_index = Some(idx);
|
||||||
|
let mut ta = new_textarea(self.input_history[idx].lines().map(String::from).collect());
|
||||||
|
ta.move_cursor(tui_textarea::CursorMove::End);
|
||||||
|
self.textarea = ta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Down => {
|
||||||
|
if let Some(idx) = self.history_index {
|
||||||
|
if idx + 1 < self.input_history.len() {
|
||||||
|
self.history_index = Some(idx + 1);
|
||||||
|
let mut ta = new_textarea(self.input_history[idx + 1].lines().map(String::from).collect());
|
||||||
|
ta.move_cursor(tui_textarea::CursorMove::End);
|
||||||
|
self.textarea = ta;
|
||||||
|
} else {
|
||||||
|
self.history_index = None;
|
||||||
|
self.textarea = new_textarea(vec![String::new()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::PageUp => self.scroll_active_up(10),
|
||||||
|
KeyCode::PageDown => self.scroll_active_down(10),
|
||||||
|
KeyCode::Tab => {
|
||||||
|
self.active_pane = match self.active_pane {
|
||||||
|
ActivePane::Autonomous => ActivePane::Tools,
|
||||||
|
ActivePane::Tools => ActivePane::Conversation,
|
||||||
|
ActivePane::Conversation => ActivePane::Autonomous,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => { self.textarea.input(key); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
self.draw_main(frame, area, app);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Draw the conversation pane with a two-column layout: marker gutter + text.
|
/// Draw the conversation pane with a two-column layout: marker gutter + text.
|
||||||
/// The gutter shows a marker at turn boundaries, aligned with the input gutter.
|
/// The gutter shows a marker at turn boundaries, aligned with the input gutter.
|
||||||
fn draw_conversation_pane(
|
fn draw_conversation_pane(
|
||||||
|
|
|
||||||
|
|
@ -391,6 +391,7 @@ pub async fn run(
|
||||||
Box::new(crate::user::thalamus::ThalamusScreen::new()),
|
Box::new(crate::user::thalamus::ThalamusScreen::new()),
|
||||||
];
|
];
|
||||||
let mut active_screen: usize = 0; // 0 = interact, 1-4 = overlay
|
let mut active_screen: usize = 0; // 0 = interact, 1-4 = overlay
|
||||||
|
tui::set_screen_legend(tui::screen_legend_from(&interact, &screens));
|
||||||
|
|
||||||
let mut terminal = tui::init_terminal()?;
|
let mut terminal = tui::init_terminal()?;
|
||||||
let mut reader = EventStream::new();
|
let mut reader = EventStream::new();
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,24 @@ use std::io;
|
||||||
|
|
||||||
use crate::user::ui_channel::{ContextInfo, SharedContextState, StatusInfo};
|
use crate::user::ui_channel::{ContextInfo, SharedContextState, StatusInfo};
|
||||||
|
|
||||||
/// Build the screen legend from the screen table.
|
/// Build the screen legend from screen labels.
|
||||||
|
pub(crate) fn screen_legend_from(interact: &dyn ScreenView, screens: &[Box<dyn ScreenView>]) -> String {
|
||||||
|
let mut parts = vec![format!("F1={}", interact.label())];
|
||||||
|
for (i, s) in screens.iter().enumerate() {
|
||||||
|
parts.push(format!("F{}={}", i + 2, s.label()));
|
||||||
|
}
|
||||||
|
format!(" {} ", parts.join(" "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cached legend — set once at startup by event_loop
|
||||||
|
static SCREEN_LEGEND: std::sync::OnceLock<String> = std::sync::OnceLock::new();
|
||||||
|
|
||||||
|
pub(crate) fn set_screen_legend(legend: String) {
|
||||||
|
let _ = SCREEN_LEGEND.set(legend);
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn screen_legend() -> String {
|
pub(crate) fn screen_legend() -> String {
|
||||||
// Built from the SCREENS table in event_loop
|
SCREEN_LEGEND.get().cloned().unwrap_or_default()
|
||||||
" F1=interact F2=conscious F3=subconscious F4=unconscious F5=thalamus ".to_string()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn strip_ansi(text: &str) -> String {
|
pub(crate) fn strip_ansi(text: &str) -> String {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue