bottom_panel decoupled
This commit is contained in:
@@ -21,6 +21,7 @@ use crate::modes::{
|
|||||||
use crate::services::auth::AuthClient;
|
use crate::services::auth::AuthClient;
|
||||||
use crate::services::grpc_client::GrpcClient;
|
use crate::services::grpc_client::GrpcClient;
|
||||||
use canvas::AppMode as CanvasMode;
|
use canvas::AppMode as CanvasMode;
|
||||||
|
use canvas::DataProvider;
|
||||||
use crate::state::app::state::AppState;
|
use crate::state::app::state::AppState;
|
||||||
use crate::pages::admin::AdminState;
|
use crate::pages::admin::AdminState;
|
||||||
use crate::state::pages::auth::AuthState;
|
use crate::state::pages::auth::AuthState;
|
||||||
@@ -279,20 +280,55 @@ impl EventHandler {
|
|||||||
let key_code = key_event.code;
|
let key_code = key_event.code;
|
||||||
let modifiers = key_event.modifiers;
|
let modifiers = key_event.modifiers;
|
||||||
|
|
||||||
if let Page::Login(login_page) = &mut router.current {
|
// LOGIN: canvas <-> buttons focus handoff
|
||||||
match login_page.handle_key_event(key_event) {
|
// Do not let Login canvas receive keys when overlays/palettes are active
|
||||||
KeyEventOutcome::Consumed(Some(msg)) => {
|
let overlay_active = self.command_mode
|
||||||
self.command_message = msg;
|
|| app_state.ui.show_search_palette
|
||||||
return Ok(EventOutcome::Ok("Login input updated".to_string()));
|
|| self.navigation_state.active;
|
||||||
|
|
||||||
|
if !overlay_active {
|
||||||
|
if let Page::Login(login_page) = &mut router.current {
|
||||||
|
use crossterm::event::{KeyCode, KeyModifiers};
|
||||||
|
|
||||||
|
// Inside canvas: at the last field, 'j' or Down moves focus to buttons
|
||||||
|
if !app_state.ui.focus_outside_canvas {
|
||||||
|
let last_idx = login_page
|
||||||
|
.editor
|
||||||
|
.data_provider()
|
||||||
|
.field_count()
|
||||||
|
.saturating_sub(1);
|
||||||
|
let at_last = login_page.editor.current_field() >= last_idx;
|
||||||
|
if at_last
|
||||||
|
&& matches!(
|
||||||
|
(key_code, modifiers),
|
||||||
|
(KeyCode::Char('j'), KeyModifiers::NONE) | (KeyCode::Down, _)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
app_state.ui.focus_outside_canvas = true;
|
||||||
|
app_state.focused_button_index = 0; // focus "Login" button
|
||||||
|
// Ensure canvas mode is ReadOnly when leaving
|
||||||
|
login_page.editor.set_mode(CanvasMode::ReadOnly);
|
||||||
|
return Ok(EventOutcome::Ok("Focus moved to buttons".to_string()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
KeyEventOutcome::Consumed(None) => {
|
|
||||||
return Ok(EventOutcome::Ok("Login input updated".to_string()));
|
// Only forward to the canvas while focus is inside it
|
||||||
}
|
if !app_state.ui.focus_outside_canvas {
|
||||||
KeyEventOutcome::Pending => {
|
match login_page.handle_key_event(key_event) {
|
||||||
return Ok(EventOutcome::Ok("Waiting for next key...".to_string()));
|
KeyEventOutcome::Consumed(Some(msg)) => {
|
||||||
}
|
self.command_message = msg;
|
||||||
KeyEventOutcome::NotMatched => {
|
return Ok(EventOutcome::Ok("Login input updated".to_string()));
|
||||||
// fall through to other handlers (buttons, etc.)
|
}
|
||||||
|
KeyEventOutcome::Consumed(None) => {
|
||||||
|
return Ok(EventOutcome::Ok("Login input updated".to_string()));
|
||||||
|
}
|
||||||
|
KeyEventOutcome::Pending => {
|
||||||
|
return Ok(EventOutcome::Ok("Waiting for next key...".to_string()));
|
||||||
|
}
|
||||||
|
KeyEventOutcome::NotMatched => {
|
||||||
|
// fall through to other handlers (buttons, etc.)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -407,6 +443,21 @@ impl EventHandler {
|
|||||||
// Let the current page handle decoupled movement first
|
// Let the current page handle decoupled movement first
|
||||||
if let Some(ma) = movement_action {
|
if let Some(ma) = movement_action {
|
||||||
match &mut router.current {
|
match &mut router.current {
|
||||||
|
// LOGIN: From buttons (general) back into the canvas with 'k' (Up),
|
||||||
|
// but ONLY from the left-most "Login" button.
|
||||||
|
Page::Login(page) => {
|
||||||
|
if app_state.ui.focus_outside_canvas {
|
||||||
|
if app_state.focused_button_index == 0
|
||||||
|
&& matches!(ma, crate::movement::MovementAction::Up)
|
||||||
|
{
|
||||||
|
app_state.ui.focus_outside_canvas = false;
|
||||||
|
// Enter canvas in ReadOnly mode (never jump straight to Edit)
|
||||||
|
page.editor.set_mode(CanvasMode::ReadOnly);
|
||||||
|
// Optional: keep current field (usually 0 initially)
|
||||||
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Page::AddTable(state) => {
|
Page::AddTable(state) => {
|
||||||
if state.handle_movement(ma) {
|
if state.handle_movement(ma) {
|
||||||
// Keep UI focus consistent with inputs vs. outer elements
|
// Keep UI focus consistent with inputs vs. outer elements
|
||||||
@@ -414,8 +465,8 @@ impl EventHandler {
|
|||||||
let is_canvas_input = matches!(
|
let is_canvas_input = matches!(
|
||||||
state.current_focus,
|
state.current_focus,
|
||||||
AddTableFocus::InputTableName
|
AddTableFocus::InputTableName
|
||||||
| AddTableFocus::InputColumnName
|
| AddTableFocus::InputColumnName
|
||||||
| AddTableFocus::InputColumnType
|
| AddTableFocus::InputColumnType
|
||||||
);
|
);
|
||||||
app_state.ui.focus_outside_canvas = !is_canvas_input;
|
app_state.ui.focus_outside_canvas = !is_canvas_input;
|
||||||
return Ok(EventOutcome::Ok(String::new()));
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
|
|||||||
@@ -202,25 +202,37 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
let event = event_reader.read_event().context("Failed to read terminal event")?;
|
let event = event_reader.read_event().context("Failed to read terminal event")?;
|
||||||
event_processed = true;
|
event_processed = true;
|
||||||
|
|
||||||
|
// Decouple Command Line and palettes from canvas:
|
||||||
|
// Only forward keys to Form canvas when:
|
||||||
|
// - not in command mode
|
||||||
|
// - no search/palette active
|
||||||
|
// - focus is inside the canvas
|
||||||
if let crossterm_event::Event::Key(key_event) = &event {
|
if let crossterm_event::Event::Key(key_event) = &event {
|
||||||
if let Page::Form(_) = &router.current {
|
let overlay_active = event_handler.command_mode
|
||||||
if let Some(editor) = app_state.form_editor.as_mut() {
|
|| app_state.ui.show_search_palette
|
||||||
match editor.handle_key_event(*key_event) {
|
|| event_handler.navigation_state.active;
|
||||||
KeyEventOutcome::Consumed(Some(msg)) => {
|
if !overlay_active {
|
||||||
event_handler.command_message = msg;
|
if let Page::Form(_) = &router.current {
|
||||||
needs_redraw = true;
|
if !app_state.ui.focus_outside_canvas {
|
||||||
continue;
|
if let Some(editor) = app_state.form_editor.as_mut() {
|
||||||
}
|
match editor.handle_key_event(*key_event) {
|
||||||
KeyEventOutcome::Consumed(None) => {
|
KeyEventOutcome::Consumed(Some(msg)) => {
|
||||||
needs_redraw = true;
|
event_handler.command_message = msg;
|
||||||
continue;
|
needs_redraw = true;
|
||||||
}
|
continue;
|
||||||
KeyEventOutcome::Pending => {
|
}
|
||||||
needs_redraw = true;
|
KeyEventOutcome::Consumed(None) => {
|
||||||
continue;
|
needs_redraw = true;
|
||||||
}
|
continue;
|
||||||
KeyEventOutcome::NotMatched => {
|
}
|
||||||
// fall through to client-level handling
|
KeyEventOutcome::Pending => {
|
||||||
|
needs_redraw = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
KeyEventOutcome::NotMatched => {
|
||||||
|
// fall through to client-level handling
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -706,7 +718,15 @@ pub async fn run_ui() -> Result<()> {
|
|||||||
terminal
|
terminal
|
||||||
.show_cursor()
|
.show_cursor()
|
||||||
.context("Failed to show cursor in Command mode")?;
|
.context("Failed to show cursor in Command mode")?;
|
||||||
|
|
||||||
|
// Enforce ReadOnly on any active canvases while in command mode
|
||||||
|
if let Some(editor) = &mut app_state.form_editor {
|
||||||
|
editor.set_mode(canvas::AppMode::ReadOnly);
|
||||||
}
|
}
|
||||||
|
if let Page::Login(page) = &mut router.current {
|
||||||
|
page.editor.set_mode(canvas::AppMode::ReadOnly);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workaround for borrow checker
|
// Workaround for borrow checker
|
||||||
|
|||||||
Reference in New Issue
Block a user