diff --git a/crates/owlen-core/src/theme.rs b/crates/owlen-core/src/theme.rs index 410e0ac..d194126 100644 --- a/crates/owlen-core/src/theme.rs +++ b/crates/owlen-core/src/theme.rs @@ -532,52 +532,52 @@ fn default_dark() -> Theme { name: "default_dark".to_string(), text: Color::White, background: Color::Black, - focused_panel_border: Color::LightMagenta, - unfocused_panel_border: Color::Rgb(95, 20, 135), - focus_beacon_fg: Theme::default_focus_beacon_fg(), - focus_beacon_bg: Theme::default_focus_beacon_bg(), - unfocused_beacon_fg: Theme::default_unfocused_beacon_fg(), + focused_panel_border: Color::Rgb(216, 160, 255), + unfocused_panel_border: Color::Rgb(137, 82, 204), + focus_beacon_fg: Color::Rgb(248, 229, 255), + focus_beacon_bg: Color::Rgb(38, 10, 58), + unfocused_beacon_fg: Color::Rgb(130, 130, 130), pane_header_active: Theme::default_pane_header_active(), - pane_header_inactive: Theme::default_pane_header_inactive(), - pane_hint_text: Theme::default_pane_hint_text(), + pane_header_inactive: Color::Rgb(210, 210, 210), + pane_hint_text: Color::Rgb(210, 210, 210), user_message_role: Color::LightBlue, assistant_message_role: Color::Yellow, - tool_output: Color::Gray, - thinking_panel_title: Color::LightMagenta, - command_bar_background: Color::Black, - status_background: Color::Black, - mode_normal: Color::LightBlue, - mode_editing: Color::LightGreen, - mode_model_selection: Color::LightYellow, - mode_provider_selection: Color::LightCyan, - mode_help: Color::LightMagenta, - mode_visual: Color::Magenta, - mode_command: Color::Yellow, - selection_bg: Color::LightBlue, + tool_output: Color::Rgb(200, 200, 200), + thinking_panel_title: Color::Rgb(234, 182, 255), + command_bar_background: Color::Rgb(10, 10, 10), + status_background: Color::Rgb(12, 12, 12), + mode_normal: Color::Rgb(117, 200, 255), + mode_editing: Color::Rgb(144, 242, 170), + mode_model_selection: Color::Rgb(255, 226, 140), + mode_provider_selection: Color::Rgb(164, 235, 255), + mode_help: Color::Rgb(234, 182, 255), + mode_visual: Color::Rgb(255, 170, 255), + mode_command: Color::Rgb(255, 220, 120), + selection_bg: Color::Rgb(56, 140, 240), selection_fg: Color::Black, - cursor: Color::Magenta, + cursor: Color::Rgb(255, 196, 255), code_block_background: Color::Rgb(25, 25, 25), - code_block_border: Color::LightMagenta, + code_block_border: Color::Rgb(216, 160, 255), code_block_text: Color::White, - code_block_keyword: Color::Yellow, - code_block_string: Color::LightGreen, - code_block_comment: Color::Gray, - placeholder: Color::DarkGray, + code_block_keyword: Color::Rgb(255, 220, 120), + code_block_string: Color::Rgb(144, 242, 170), + code_block_comment: Color::Rgb(170, 170, 170), + placeholder: Color::Rgb(180, 180, 180), error: Color::Red, - info: Color::LightGreen, - agent_thought: Color::LightBlue, - agent_action: Color::Yellow, - agent_action_input: Color::LightCyan, - agent_observation: Color::LightGreen, - agent_final_answer: Color::Magenta, + info: Color::Rgb(144, 242, 170), + agent_thought: Color::Rgb(117, 200, 255), + agent_action: Color::Rgb(255, 220, 120), + agent_action_input: Color::Rgb(164, 235, 255), + agent_observation: Color::Rgb(144, 242, 170), + agent_final_answer: Color::Rgb(255, 170, 255), agent_badge_running_fg: Color::Black, agent_badge_running_bg: Color::Yellow, agent_badge_idle_fg: Color::Black, agent_badge_idle_bg: Color::Cyan, operating_chat_fg: Color::Black, - operating_chat_bg: Color::Blue, + operating_chat_bg: Color::Rgb(117, 200, 255), operating_code_fg: Color::Black, - operating_code_bg: Color::Magenta, + operating_code_bg: Color::Rgb(255, 170, 255), } } diff --git a/crates/owlen-tui/src/chat_app.rs b/crates/owlen-tui/src/chat_app.rs index f74925e..86efe0e 100644 --- a/crates/owlen-tui/src/chat_app.rs +++ b/crates/owlen-tui/src/chat_app.rs @@ -2604,6 +2604,39 @@ impl ChatApp { self.focused_panel = order[prev_index]; } + pub fn focus_panel(&mut self, target: FocusedPanel) -> bool { + match target { + FocusedPanel::Files => { + if self.file_panel_collapsed { + self.expand_file_panel(); + if self.file_panel_collapsed { + return false; + } + } + } + FocusedPanel::Code => { + if !self.should_show_code_view() { + return false; + } + } + FocusedPanel::Thinking => { + if self.current_thinking.is_none() && self.agent_actions.is_none() { + return false; + } + } + FocusedPanel::Chat | FocusedPanel::Input => {} + } + + let order = self.focus_sequence(); + if !order.contains(&target) { + return false; + } + + self.focused_panel = target; + self.ensure_focus_valid(); + true + } + /// Sync textarea content to input buffer fn sync_textarea_to_buffer(&mut self) { let text = self.textarea.lines().join("\n"); @@ -5007,6 +5040,65 @@ impl ChatApp { }; self.status = format!("Focus: {}", panel_name); } + (KeyCode::Char('1'), modifiers) + if modifiers.contains(KeyModifiers::CONTROL) + || modifiers.contains(KeyModifiers::ALT) => + { + if self.focus_panel(FocusedPanel::Files) { + self.status = "Focus: Files (Ctrl+1)".to_string(); + self.error = None; + } else if self.is_code_mode() { + self.status = "Files panel is collapsed — use :files to reopen" + .to_string(); + } + return Ok(AppState::Running); + } + (KeyCode::Char('2'), modifiers) + if modifiers.contains(KeyModifiers::CONTROL) + || modifiers.contains(KeyModifiers::ALT) => + { + if self.focus_panel(FocusedPanel::Chat) { + self.status = "Focus: Chat (Ctrl+2)".to_string(); + self.error = None; + } + return Ok(AppState::Running); + } + (KeyCode::Char('3'), modifiers) + if modifiers.contains(KeyModifiers::CONTROL) + || modifiers.contains(KeyModifiers::ALT) => + { + if self.focus_panel(FocusedPanel::Code) { + self.status = "Focus: Code (Ctrl+3)".to_string(); + self.error = None; + } else { + self.status = + "Open a file to focus the code workspace".to_string(); + } + return Ok(AppState::Running); + } + (KeyCode::Char('4'), modifiers) + if modifiers.contains(KeyModifiers::CONTROL) + || modifiers.contains(KeyModifiers::ALT) => + { + if self.focus_panel(FocusedPanel::Thinking) { + self.status = "Focus: Thinking (Ctrl+4)".to_string(); + self.error = None; + } else { + self.status = "No reasoning panel to focus yet".to_string(); + } + return Ok(AppState::Running); + } + (KeyCode::Char('5'), modifiers) + if modifiers.contains(KeyModifiers::CONTROL) + || modifiers.contains(KeyModifiers::ALT) => + { + if self.focus_panel(FocusedPanel::Input) { + self.status = + "Focus: Input (Ctrl+5) — press i to edit".to_string(); + self.error = None; + } + return Ok(AppState::Running); + } (KeyCode::Char('m'), KeyModifiers::NONE) => { if let Err(err) = self.show_model_picker().await { self.error = Some(err.to_string()); diff --git a/crates/owlen-tui/src/ui.rs b/crates/owlen-tui/src/ui.rs index e41a76a..d9f7ffb 100644 --- a/crates/owlen-tui/src/ui.rs +++ b/crates/owlen-tui/src/ui.rs @@ -597,7 +597,7 @@ fn render_file_tree(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) { } title_spans.push(Span::styled( - " ↩ open · o split↓ · O split→ · t tab · y abs · Y rel · A dir · r ren · m move · d del · . $EDITOR · gh hidden · / fuzzy search", + " ↩ open · Ctrl+1 focus · o split↓ · O split→ · t tab · y abs · Y rel · A dir · r ren · m move · d del · . $EDITOR · gh hidden · / fuzzy search", panel_hint_style(has_focus, &theme), )); @@ -1453,7 +1453,7 @@ fn render_messages(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) { let mut title_spans = panel_title_spans("Chat", true, has_focus, &theme); title_spans.push(Span::raw(" ")); title_spans.push(Span::styled( - "PgUp/PgDn scroll · g/G jump · s save", + "PgUp/PgDn scroll · g/G jump · s save · Ctrl+2 focus", panel_hint_style(has_focus, &theme), )); @@ -1568,7 +1568,7 @@ fn render_thinking(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) { let mut title_spans = panel_title_spans("💭 Thinking", true, has_focus, &theme); title_spans.push(Span::raw(" ")); title_spans.push(Span::styled( - "Esc close", + "Esc close · Ctrl+4 focus", panel_hint_style(has_focus, &theme), )); @@ -1774,7 +1774,7 @@ fn render_agent_actions(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) { let mut title_spans = panel_title_spans("🤖 Agent Actions", true, has_focus, &theme); title_spans.push(Span::raw(" ")); title_spans.push(Span::styled( - "Pause ▸ p · Resume ▸ r", + "Pause ▸ p · Resume ▸ r · Ctrl+4 focus", panel_hint_style(has_focus, &theme), )); @@ -1800,16 +1800,22 @@ fn render_input(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) { let (label, hint) = match app.mode() { InputMode::Editing => ( "Input", - Some("Enter send · Shift+Enter newline · Esc normal"), + Some("Enter send · Shift+Enter newline · Esc normal · Ctrl+5 focus"), ), - InputMode::Visual => ("Visual Select", Some("y yank · d cut · Esc cancel")), - InputMode::Command => ("Command", Some("Enter run · Esc cancel")), + InputMode::Visual => ( + "Visual Select", + Some("y yank · d cut · Esc cancel · Ctrl+5 focus"), + ), + InputMode::Command => ("Command", Some("Enter run · Esc cancel · Ctrl+5 focus")), InputMode::RepoSearch => ( "Repo Search", - Some("Enter run · Alt+Enter scratch · Esc close"), + Some("Enter run · Alt+Enter scratch · Esc close · Ctrl+5 focus"), ), - InputMode::SymbolSearch => ("Symbol Search", Some("Type @name · Esc close")), - _ => ("Input", Some("Press i to start typing")), + InputMode::SymbolSearch => ( + "Symbol Search", + Some("Type @name · Esc close · Ctrl+5 focus"), + ), + _ => ("Input", Some("Press i to start typing · Ctrl+5 focus")), }; let is_active = matches!( @@ -2019,12 +2025,12 @@ fn render_status(frame: &mut Frame<'_>, area: Rect, app: &ChatApp) { owlen_core::mode::Mode::Code => ("CODE", theme.operating_code_fg, theme.operating_code_bg), }; - let focus_label = match app.focused_panel() { - FocusedPanel::Files => "FILES", - FocusedPanel::Chat => "CHAT", - FocusedPanel::Thinking => "THINK", - FocusedPanel::Input => "INPUT", - FocusedPanel::Code => "CODE", + let (focus_label, focus_hint) = match app.focused_panel() { + FocusedPanel::Files => ("FILES", "Ctrl+1"), + FocusedPanel::Chat => ("CHAT", "Ctrl+2"), + FocusedPanel::Thinking => ("THINK", "Ctrl+4"), + FocusedPanel::Input => ("INPUT", "Ctrl+5"), + FocusedPanel::Code => ("CODE", "Ctrl+3"), }; let mut left_spans = vec![ @@ -2043,7 +2049,7 @@ fn render_status(frame: &mut Frame<'_>, area: Rect, app: &ChatApp) { .add_modifier(Modifier::BOLD), ), Span::styled( - format!(" │ {}", focus_label), + format!(" │ {} · {}", focus_label, focus_hint), Style::default() .fg(theme.pane_header_active) .add_modifier(Modifier::BOLD | Modifier::ITALIC), @@ -2440,7 +2446,7 @@ fn render_code_pane( if is_active { title_spans.push(Span::raw(" ")); title_spans.push(Span::styled( - "Ctrl+W split · :w save", + "Ctrl+W split · :w save · Ctrl+3 focus", panel_hint_style(has_focus && is_active, theme), )); } @@ -2605,6 +2611,11 @@ fn render_provider_selector(frame: &mut Frame<'_>, app: &ChatApp) { }) .collect(); + let highlight_style = Style::default() + .bg(theme.selection_bg) + .fg(theme.selection_fg) + .add_modifier(Modifier::BOLD); + let list = List::new(items) .block( Block::default() @@ -2618,11 +2629,7 @@ fn render_provider_selector(frame: &mut Frame<'_>, app: &ChatApp) { .border_style(Style::default().fg(theme.unfocused_panel_border)) .style(Style::default().bg(theme.background).fg(theme.text)), ) - .highlight_style( - Style::default() - .fg(theme.focused_panel_border) - .add_modifier(Modifier::BOLD), - ) + .highlight_style(highlight_style) .highlight_symbol("▶ "); let mut state = ListState::default();