moving the state from general to each page owning its own state of button or canvas focus

This commit is contained in:
Priec
2025-09-04 17:36:13 +02:00
parent 6891631b8d
commit d9d8562539
24 changed files with 286 additions and 168 deletions

View File

@@ -19,7 +19,6 @@ impl AppState {
self.ui.dialog.purpose = Some(purpose); self.ui.dialog.purpose = Some(purpose);
self.ui.dialog.is_loading = false; self.ui.dialog.is_loading = false;
self.ui.dialog.dialog_show = true; self.ui.dialog.dialog_show = true;
self.ui.focus_outside_canvas = true;
} }
pub fn show_loading_dialog(&mut self, title: &str, message: &str) { pub fn show_loading_dialog(&mut self, title: &str, message: &str) {
@@ -30,7 +29,6 @@ impl AppState {
self.ui.dialog.purpose = None; self.ui.dialog.purpose = None;
self.ui.dialog.is_loading = true; self.ui.dialog.is_loading = true;
self.ui.dialog.dialog_show = true; self.ui.dialog.dialog_show = true;
self.ui.focus_outside_canvas = true;
} }
pub fn update_dialog_content( pub fn update_dialog_content(
@@ -55,7 +53,6 @@ impl AppState {
self.ui.dialog.dialog_buttons.clear(); self.ui.dialog.dialog_buttons.clear();
self.ui.dialog.dialog_active_button_index = 0; self.ui.dialog.dialog_active_button_index = 0;
self.ui.dialog.purpose = None; self.ui.dialog.purpose = None;
self.ui.focus_outside_canvas = false;
self.ui.dialog.is_loading = false; self.ui.dialog.is_loading = false;
} }

View File

@@ -67,11 +67,11 @@ pub async fn handle_navigation_event(
"select" => { "select" => {
let (context, index) = match &router.current { let (context, index) = match &router.current {
Page::Intro(state) => (UiContext::Intro, state.selected_option), Page::Intro(state) => (UiContext::Intro, state.selected_option),
Page::Login(_) if app_state.ui.focus_outside_canvas => { Page::Login(state) if state.focus_outside_canvas => {
(UiContext::Login, app_state.focused_button_index) (UiContext::Login, state.focused_button_index)
} }
Page::Register(_) if app_state.ui.focus_outside_canvas => { Page::Register(state) if state.focus_outside_canvas => {
(UiContext::Register, app_state.focused_button_index) (UiContext::Register, state.focused_button_index)
} }
Page::Admin(state) => { Page::Admin(state) => {
(UiContext::Admin, state.get_selected_index().unwrap_or(0)) (UiContext::Admin, state.get_selected_index().unwrap_or(0))
@@ -91,24 +91,24 @@ pub async fn handle_navigation_event(
pub fn up(app_state: &mut AppState, router: &mut Router) { pub fn up(app_state: &mut AppState, router: &mut Router) {
match &mut router.current { match &mut router.current {
Page::Login(page) if app_state.ui.focus_outside_canvas => { Page::Login(page) if page.focus_outside_canvas => {
if app_state.focused_button_index == 0 { if page.focused_button_index == 0 {
app_state.ui.focus_outside_canvas = false; page.focus_outside_canvas = false;
let last_field_index = page.state.field_count().saturating_sub(1); let last_field_index = page.state.field_count().saturating_sub(1);
page.state.set_current_field(last_field_index); page.state.set_current_field(last_field_index);
} else { } else {
app_state.focused_button_index = page.focused_button_index =
app_state.focused_button_index.saturating_sub(1); page.focused_button_index.saturating_sub(1);
} }
} }
Page::Register(state) if app_state.ui.focus_outside_canvas => { Page::Register(state) if state.focus_outside_canvas => {
if app_state.focused_button_index == 0 { if state.focused_button_index == 0 {
app_state.ui.focus_outside_canvas = false; state.focus_outside_canvas = false;
let last_field_index = state.state.field_count().saturating_sub(1); let last_field_index = state.state.field_count().saturating_sub(1);
state.set_current_field(last_field_index); state.set_current_field(last_field_index);
} else { } else {
app_state.focused_button_index = state.focused_button_index =
app_state.focused_button_index.saturating_sub(1); state.focused_button_index.saturating_sub(1);
} }
} }
Page::Intro(state) => state.previous_option(), Page::Intro(state) => state.previous_option(),
@@ -119,10 +119,16 @@ pub fn up(app_state: &mut AppState, router: &mut Router) {
pub fn down(app_state: &mut AppState, router: &mut Router) { pub fn down(app_state: &mut AppState, router: &mut Router) {
match &mut router.current { match &mut router.current {
Page::Login(_) | Page::Register(_) if app_state.ui.focus_outside_canvas => { Page::Login(state) if state.focus_outside_canvas => {
let num_general_elements = 2; let num_general_elements = 2;
if app_state.focused_button_index < num_general_elements - 1 { if state.focused_button_index < num_general_elements - 1 {
app_state.focused_button_index += 1; state.focused_button_index += 1;
}
}
Page::Register(state) if state.focus_outside_canvas => {
let num_general_elements = 2;
if state.focused_button_index < num_general_elements - 1 {
state.focused_button_index += 1;
} }
} }
Page::Intro(state) => state.next_option(), Page::Intro(state) => state.next_option(),
@@ -134,11 +140,11 @@ pub fn down(app_state: &mut AppState, router: &mut Router) {
pub fn next_option(app_state: &mut AppState, router: &mut Router) { pub fn next_option(app_state: &mut AppState, router: &mut Router) {
match &mut router.current { match &mut router.current {
Page::Intro(state) => state.next_option(), Page::Intro(state) => state.next_option(),
Page::Admin(_) => { Page::Admin(state) => {
let option_count = app_state.profile_tree.profiles.len(); let option_count = app_state.profile_tree.profiles.len();
if option_count > 0 { if option_count > 0 {
app_state.focused_button_index = state.focused_button_index =
(app_state.focused_button_index + 1) % option_count; (state.focused_button_index + 1) % option_count;
} }
} }
_ => {} _ => {}
@@ -148,13 +154,13 @@ pub fn next_option(app_state: &mut AppState, router: &mut Router) {
pub fn previous_option(app_state: &mut AppState, router: &mut Router) { pub fn previous_option(app_state: &mut AppState, router: &mut Router) {
match &mut router.current { match &mut router.current {
Page::Intro(state) => state.previous_option(), Page::Intro(state) => state.previous_option(),
Page::Admin(_) => { Page::Admin(state) => {
let option_count = app_state.profile_tree.profiles.len(); let option_count = app_state.profile_tree.profiles.len();
if option_count > 0 { if option_count > 0 {
app_state.focused_button_index = if app_state.focused_button_index == 0 { state.focused_button_index = if state.focused_button_index == 0 {
option_count.saturating_sub(1) option_count.saturating_sub(1)
} else { } else {
app_state.focused_button_index - 1 state.focused_button_index - 1
}; };
} }
} }

View File

@@ -346,7 +346,7 @@ impl EventHandler {
self.command_input.clear(); self.command_input.clear();
self.command_message.clear(); self.command_message.clear();
self.key_sequence_tracker.reset(); self.key_sequence_tracker.reset();
app_state.ui.focus_outside_canvas = true; self.set_focus_outside(router, true);
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
} }
@@ -392,7 +392,7 @@ impl EventHandler {
self.command_input.clear(); self.command_input.clear();
self.command_message.clear(); self.command_message.clear();
self.key_sequence_tracker.reset(); self.key_sequence_tracker.reset();
app_state.ui.focus_outside_canvas = true; self.set_focus_outside(router, true);
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
} }
@@ -523,7 +523,7 @@ impl EventHandler {
app_state.ui.show_search_palette = true; app_state.ui.show_search_palette = true;
app_state.search_state = app_state.search_state =
Some(SearchState::new(table_name)); Some(SearchState::new(table_name));
app_state.ui.focus_outside_canvas = true; self.set_focus_outside(router, true);
return Ok(EventOutcome::Ok( return Ok(EventOutcome::Ok(
"Search palette opened".to_string(), "Search palette opened".to_string(),
)); ));
@@ -532,7 +532,7 @@ impl EventHandler {
} }
// Allow ":" / ctrl+; to enter command mode only when outside canvas. // Allow ":" / ctrl+; to enter command mode only when outside canvas.
if action == "enter_command_mode" { if action == "enter_command_mode" {
if app_state.ui.focus_outside_canvas if self.is_focus_outside(router)
&& !self.command_mode && !self.command_mode
&& !app_state.ui.show_search_palette && !app_state.ui.show_search_palette
&& !self.navigation_state.active && !self.navigation_state.active
@@ -542,7 +542,7 @@ impl EventHandler {
self.command_message.clear(); self.command_message.clear();
self.key_sequence_tracker.reset(); self.key_sequence_tracker.reset();
// Keep focus outside so canvas won't receive keys // Keep focus outside so canvas won't receive keys
app_state.ui.focus_outside_canvas = true; self.set_focus_outside(router, true);
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
} }
@@ -955,4 +955,52 @@ impl EventHandler {
"find_file_palette_toggle" "find_file_palette_toggle"
) )
} }
fn set_focus_outside(&mut self, router: &mut Router, outside: bool) {
match &mut router.current {
Page::Login(state) => state.focus_outside_canvas = outside,
Page::Register(state) => state.focus_outside_canvas = outside,
Page::Intro(state) => state.focus_outside_canvas = outside,
Page::Admin(state) => state.focus_outside_canvas = outside,
Page::AddLogic(state) => state.focus_outside_canvas = outside,
Page::AddTable(state) => state.focus_outside_canvas = outside,
_ => {}
}
}
fn set_focused_button(&mut self, router: &mut Router, index: usize) {
match &mut router.current {
Page::Login(state) => state.focused_button_index = index,
Page::Register(state) => state.focused_button_index = index,
Page::Intro(state) => state.focused_button_index = index,
Page::Admin(state) => state.focused_button_index = index,
Page::AddLogic(state) => state.focused_button_index = index,
Page::AddTable(state) => state.focused_button_index = index,
_ => {}
}
}
fn is_focus_outside(&self, router: &Router) -> bool {
match &router.current {
Page::Login(state) => state.focus_outside_canvas,
Page::Register(state) => state.focus_outside_canvas,
Page::Intro(state) => state.focus_outside_canvas,
Page::Admin(state) => state.focus_outside_canvas,
Page::AddLogic(state) => state.focus_outside_canvas,
Page::AddTable(state) => state.focus_outside_canvas,
_ => false,
}
}
fn focused_button(&self, router: &Router) -> usize {
match &router.current {
Page::Login(state) => state.focused_button_index,
Page::Register(state) => state.focused_button_index,
Page::Intro(state) => state.focused_button_index,
Page::Admin(state) => state.focused_button_index,
Page::AddLogic(state) => state.focused_button_index,
Page::AddTable(state) => state.focused_button_index,
_ => 0,
}
}
} }

View File

@@ -40,14 +40,11 @@ impl ModeManager {
// If focus is inside a canvas, we don't duplicate canvas modes here. // If focus is inside a canvas, we don't duplicate canvas modes here.
// Canvas crate owns ReadOnly/Edit/Highlight internally. // Canvas crate owns ReadOnly/Edit/Highlight internally.
match &router.current { match &router.current {
Page::Form(_) Page::Form(_) => AppMode::General, // Form always has its own canvas
| Page::Login(_) Page::Login(state) if !state.focus_outside_canvas => AppMode::General,
| Page::Register(_) Page::Register(state) if !state.focus_outside_canvas => AppMode::General,
| Page::AddTable(_) Page::AddTable(state) if !state.focus_outside_canvas => AppMode::General,
| Page::AddLogic(_) if !app_state.ui.focus_outside_canvas => { Page::AddLogic(state) if !state.focus_outside_canvas => AppMode::General,
// Canvas active → let canvas handle its own AppMode
AppMode::General
}
_ => AppMode::General, _ => AppMode::General,
} }
} }

View File

@@ -25,6 +25,8 @@ pub struct AdminState {
pub selected_profile_index: Option<usize>, pub selected_profile_index: Option<usize>,
pub selected_table_index: Option<usize>, pub selected_table_index: Option<usize>,
pub current_focus: AdminFocus, pub current_focus: AdminFocus,
pub focus_outside_canvas: bool,
pub focused_button_index: usize,
} }
impl AdminState { impl AdminState {

View File

@@ -43,15 +43,29 @@ pub fn handle_admin_navigation(
) -> bool { ) -> bool {
let action = config.get_general_action(key.code, key.modifiers).map(String::from); let action = config.get_general_action(key.code, key.modifiers).map(String::from);
let Page::Admin(admin_state) = &mut router.current else { // Check if we're in admin page, but don't borrow mutably yet
let is_admin = matches!(&router.current, Page::Admin(_));
if !is_admin {
return false;
}
// Get the current focus without borrowing mutably
let current_focus = if let Page::Admin(admin_state) = &router.current {
admin_state.current_focus
} else {
return false; return false;
}; };
let current_focus = admin_state.current_focus;
let profile_count = app_state.profile_tree.profiles.len(); let profile_count = app_state.profile_tree.profiles.len();
let mut handled = false; let mut handled = false;
match current_focus { match current_focus {
AdminFocus::ProfilesPane => { AdminFocus::ProfilesPane => {
// Now we can borrow mutably since we're not reassigning router.current
let Page::Admin(admin_state) = &mut router.current else {
return false;
};
match action.as_deref() { match action.as_deref() {
Some("select") => { Some("select") => {
admin_state.current_focus = AdminFocus::InsideProfilesList; admin_state.current_focus = AdminFocus::InsideProfilesList;
@@ -69,7 +83,6 @@ pub fn handle_admin_navigation(
handled = true; handled = true;
} }
Some("previous_option") | Some("move_up") => { Some("previous_option") | Some("move_up") => {
// No wrap-around: Stay on ProfilesPane if trying to go "before" it
*command_message = "At first focusable pane.".to_string(); *command_message = "At first focusable pane.".to_string();
handled = true; handled = true;
} }
@@ -78,6 +91,10 @@ pub fn handle_admin_navigation(
} }
AdminFocus::InsideProfilesList => { AdminFocus::InsideProfilesList => {
let Page::Admin(admin_state) = &mut router.current else {
return false;
};
match action.as_deref() { match action.as_deref() {
Some("move_up") => { Some("move_up") => {
if profile_count > 0 { if profile_count > 0 {
@@ -95,11 +112,11 @@ pub fn handle_admin_navigation(
} }
Some("select") => { Some("select") => {
admin_state.selected_profile_index = admin_state.profile_list_state.selected(); admin_state.selected_profile_index = admin_state.profile_list_state.selected();
admin_state.selected_table_index = None; // Deselect table when profile changes admin_state.selected_table_index = None;
if let Some(profile_idx) = admin_state.selected_profile_index { if let Some(profile_idx) = admin_state.selected_profile_index {
if let Some(profile) = app_state.profile_tree.profiles.get(profile_idx) { if let Some(profile) = app_state.profile_tree.profiles.get(profile_idx) {
if !profile.tables.is_empty() { if !profile.tables.is_empty() {
admin_state.table_list_state.select(Some(0)); // Auto-select first table for nav admin_state.table_list_state.select(Some(0));
} else { } else {
admin_state.table_list_state.select(None); admin_state.table_list_state.select(None);
} }
@@ -123,6 +140,10 @@ pub fn handle_admin_navigation(
} }
AdminFocus::Tables => { AdminFocus::Tables => {
let Page::Admin(admin_state) = &mut router.current else {
return false;
};
match action.as_deref() { match action.as_deref() {
Some("select") => { Some("select") => {
admin_state.current_focus = AdminFocus::InsideTablesList; admin_state.current_focus = AdminFocus::InsideTablesList;
@@ -152,7 +173,7 @@ pub fn handle_admin_navigation(
} else { } else {
*command_message = "No tables in selected profile.".to_string(); *command_message = "No tables in selected profile.".to_string();
} }
admin_state.current_focus = AdminFocus::Tables; // Stay in Tables pane if no tables to enter admin_state.current_focus = AdminFocus::Tables;
} }
handled = true; handled = true;
} }
@@ -171,6 +192,10 @@ pub fn handle_admin_navigation(
} }
AdminFocus::InsideTablesList => { AdminFocus::InsideTablesList => {
let Page::Admin(admin_state) = &mut router.current else {
return false;
};
match action.as_deref() { match action.as_deref() {
Some("move_up") => { Some("move_up") => {
let current_profile_idx = admin_state.selected_profile_index let current_profile_idx = admin_state.selected_profile_index
@@ -210,7 +235,7 @@ pub fn handle_admin_navigation(
handled = true; handled = true;
} }
} }
Some("select") => { // This is for persistently selecting a table with [*] Some("select") => {
admin_state.selected_table_index = admin_state.table_list_state.selected(); admin_state.selected_table_index = admin_state.table_list_state.selected();
let table_name = admin_state.selected_profile_index let table_name = admin_state.selected_profile_index
.and_then(|p_idx| app_state.profile_tree.profiles.get(p_idx)) .and_then(|p_idx| app_state.profile_tree.profiles.get(p_idx))
@@ -230,10 +255,17 @@ pub fn handle_admin_navigation(
AdminFocus::Button1 => { // Add Logic Button AdminFocus::Button1 => { // Add Logic Button
match action.as_deref() { match action.as_deref() {
Some("select") => { // Typically "Enter" key Some("select") => {
if let Some(p_idx) = admin_state.selected_profile_index { // Extract needed data first, before any router reassignment
let (selected_profile_idx, selected_table_idx) = if let Page::Admin(admin_state) = &router.current {
(admin_state.selected_profile_index, admin_state.selected_table_index)
} else {
return false;
};
if let Some(p_idx) = selected_profile_idx {
if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) { if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) {
if let Some(t_idx) = admin_state.selected_table_index { if let Some(t_idx) = selected_table_idx {
if let Some(table) = profile.tables.get(t_idx) { if let Some(table) = profile.tables.get(t_idx) {
// Create AddLogic page with selected profile & table // Create AddLogic page with selected profile & table
let add_logic_form = AddLogicFormState::new_with_table( let add_logic_form = AddLogicFormState::new_with_table(
@@ -243,16 +275,16 @@ pub fn handle_admin_navigation(
table.name.clone(), table.name.clone(),
); );
// Route to AddLogic
router.current = Page::AddLogic(add_logic_form);
// Store table info for later fetching // Store table info for later fetching
app_state.pending_table_structure_fetch = Some(( app_state.pending_table_structure_fetch = Some((
profile.name.clone(), profile.name.clone(),
table.name.clone(), table.name.clone(),
)); ));
// Now it's safe to reassign router.current
router.current = Page::AddLogic(add_logic_form);
buffer_state.update_history(AppView::AddLogic); buffer_state.update_history(AppView::AddLogic);
app_state.ui.focus_outside_canvas = false;
*command_message = format!( *command_message = format!(
"Opening Add Logic for table '{}' in profile '{}'...", "Opening Add Logic for table '{}' in profile '{}'...",
table.name, profile.name table.name, profile.name
@@ -272,11 +304,17 @@ pub fn handle_admin_navigation(
handled = true; handled = true;
} }
Some("previous_option") | Some("move_up") => { Some("previous_option") | Some("move_up") => {
let Page::Admin(admin_state) = &mut router.current else {
return false;
};
admin_state.current_focus = AdminFocus::Tables; admin_state.current_focus = AdminFocus::Tables;
*command_message = "Focus: Tables Pane".to_string(); *command_message = "Focus: Tables Pane".to_string();
handled = true; handled = true;
} }
Some("next_option") | Some("move_down") => { Some("next_option") | Some("move_down") => {
let Page::Admin(admin_state) = &mut router.current else {
return false;
};
admin_state.current_focus = AdminFocus::Button2; admin_state.current_focus = AdminFocus::Button2;
*command_message = "Focus: Add Table Button".to_string(); *command_message = "Focus: Add Table Button".to_string();
handled = true; handled = true;
@@ -288,23 +326,32 @@ pub fn handle_admin_navigation(
AdminFocus::Button2 => { // Add Table Button AdminFocus::Button2 => { // Add Table Button
match action.as_deref() { match action.as_deref() {
Some("select") => { Some("select") => {
if let Some(p_idx) = admin_state.selected_profile_index { // Extract needed data first
let selected_profile_idx = if let Page::Admin(admin_state) = &router.current {
admin_state.selected_profile_index
} else {
return false;
};
if let Some(p_idx) = selected_profile_idx {
if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) { if let Some(profile) = app_state.profile_tree.profiles.get(p_idx) {
let selected_profile_name = profile.name.clone(); let selected_profile_name = profile.name.clone();
// Prepare links from the selected profile's existing tables // Prepare links from the selected profile's existing tables
let available_links: Vec<LinkDefinition> = profile.tables.iter() let available_links: Vec<LinkDefinition> = profile.tables.iter()
.map(|table| LinkDefinition { .map(|table| LinkDefinition {
linked_table_name: table.name.clone(), linked_table_name: table.name.clone(),
is_required: false, // Default, can be changed in AddTable screen is_required: false,
selected: false, selected: false,
}).collect(); }).collect();
// Build decoupled AddTable page and route into it // Build decoupled AddTable page and route into it
let mut page = AddTableFormState::new(selected_profile_name.clone()); let mut page = AddTableFormState::new(selected_profile_name.clone());
page.state.links = available_links; page.state.links = available_links;
// Now safe to reassign router.current
router.current = Page::AddTable(page); router.current = Page::AddTable(page);
buffer_state.update_history(AppView::AddTable); buffer_state.update_history(AppView::AddTable);
app_state.ui.focus_outside_canvas = false;
*command_message = format!( *command_message = format!(
"Opening Add Table for profile '{}'...", "Opening Add Table for profile '{}'...",
selected_profile_name selected_profile_name
@@ -320,11 +367,17 @@ pub fn handle_admin_navigation(
} }
} }
Some("previous_option") | Some("move_up") => { Some("previous_option") | Some("move_up") => {
let Page::Admin(admin_state) = &mut router.current else {
return false;
};
admin_state.current_focus = AdminFocus::Button1; admin_state.current_focus = AdminFocus::Button1;
*command_message = "Focus: Add Logic Button".to_string(); *command_message = "Focus: Add Logic Button".to_string();
handled = true; handled = true;
} }
Some("next_option") | Some("move_down") => { Some("next_option") | Some("move_down") => {
let Page::Admin(admin_state) = &mut router.current else {
return false;
};
admin_state.current_focus = AdminFocus::Button3; admin_state.current_focus = AdminFocus::Button3;
*command_message = "Focus: Change Table Button".to_string(); *command_message = "Focus: Change Table Button".to_string();
handled = true; handled = true;
@@ -336,17 +389,18 @@ pub fn handle_admin_navigation(
AdminFocus::Button3 => { // Change Table Button AdminFocus::Button3 => { // Change Table Button
match action.as_deref() { match action.as_deref() {
Some("select") => { Some("select") => {
// Future: Logic to load selected table into AddTableState for editing
*command_message = "Action: Change Table (Not Implemented)".to_string(); *command_message = "Action: Change Table (Not Implemented)".to_string();
handled = true; handled = true;
} }
Some("previous_option") | Some("move_up") => { Some("previous_option") | Some("move_up") => {
let Page::Admin(admin_state) = &mut router.current else {
return false;
};
admin_state.current_focus = AdminFocus::Button2; admin_state.current_focus = AdminFocus::Button2;
*command_message = "Focus: Add Table Button".to_string(); *command_message = "Focus: Add Table Button".to_string();
handled = true; handled = true;
} }
Some("next_option") | Some("move_down") => { Some("next_option") | Some("move_down") => {
// No wrap-around: Stay on Button3 if trying to go "after" it
*command_message = "At last focusable button.".to_string(); *command_message = "At last focusable button.".to_string();
handled = true; handled = true;
} }

View File

@@ -39,7 +39,7 @@ pub fn handle_add_logic_event(
match key_event.code { match key_event.code {
crossterm::event::KeyCode::Esc => { crossterm::event::KeyCode::Esc => {
add_logic_page.state.current_focus = AddLogicFocus::ScriptContentPreview; add_logic_page.state.current_focus = AddLogicFocus::ScriptContentPreview;
app_state.ui.focus_outside_canvas = true; add_logic_page.focus_outside_canvas = true;
return Ok(EventOutcome::Ok("Exited script editing.".to_string())); return Ok(EventOutcome::Ok("Exited script editing.".to_string()));
} }
_ => { _ => {
@@ -85,7 +85,7 @@ pub fn handle_add_logic_event(
if at_last && matches!(ma, MovementAction::Down | MovementAction::Next) { if at_last && matches!(ma, MovementAction::Down | MovementAction::Next) {
add_logic_page.state.last_canvas_field = last_idx; add_logic_page.state.last_canvas_field = last_idx;
add_logic_page.state.current_focus = AddLogicFocus::ScriptContentPreview; add_logic_page.state.current_focus = AddLogicFocus::ScriptContentPreview;
app_state.ui.focus_outside_canvas = true; add_logic_page.focus_outside_canvas = true;
return Ok(EventOutcome::Ok("Moved to Script Preview".to_string())); return Ok(EventOutcome::Ok("Moved to Script Preview".to_string()));
} }
} }
@@ -114,7 +114,7 @@ pub fn handle_add_logic_event(
let mut current = add_logic_page.state.current_focus; let mut current = add_logic_page.state.current_focus;
if move_focus(&ADD_LOGIC_FOCUS_ORDER, &mut current, ma) { if move_focus(&ADD_LOGIC_FOCUS_ORDER, &mut current, ma) {
add_logic_page.state.current_focus = current; add_logic_page.state.current_focus = current;
app_state.ui.focus_outside_canvas = !matches!( add_logic_page.focus_outside_canvas = !matches!(
add_logic_page.state.current_focus, add_logic_page.state.current_focus,
AddLogicFocus::InputLogicName AddLogicFocus::InputLogicName
| AddLogicFocus::InputTargetColumn | AddLogicFocus::InputTargetColumn
@@ -127,7 +127,7 @@ pub fn handle_add_logic_event(
MovementAction::Select => match add_logic_page.state.current_focus { MovementAction::Select => match add_logic_page.state.current_focus {
AddLogicFocus::ScriptContentPreview => { AddLogicFocus::ScriptContentPreview => {
add_logic_page.state.current_focus = AddLogicFocus::InsideScriptContent; add_logic_page.state.current_focus = AddLogicFocus::InsideScriptContent;
app_state.ui.focus_outside_canvas = false; add_logic_page.focus_outside_canvas = false;
return Ok(EventOutcome::Ok( return Ok(EventOutcome::Ok(
"Fullscreen script editing. Esc to exit.".to_string(), "Fullscreen script editing. Esc to exit.".to_string(),
)); ));
@@ -147,7 +147,7 @@ pub fn handle_add_logic_event(
MovementAction::Esc => { MovementAction::Esc => {
if add_logic_page.state.current_focus == AddLogicFocus::ScriptContentPreview { if add_logic_page.state.current_focus == AddLogicFocus::ScriptContentPreview {
add_logic_page.state.current_focus = AddLogicFocus::InputDescription; add_logic_page.state.current_focus = AddLogicFocus::InputDescription;
app_state.ui.focus_outside_canvas = false; add_logic_page.focus_outside_canvas = false;
return Ok(EventOutcome::Ok("Back to Description".to_string())); return Ok(EventOutcome::Ok("Back to Description".to_string()));
} }
} }

View File

@@ -335,6 +335,7 @@ pub struct AddLogicFormState {
pub state: AddLogicState, pub state: AddLogicState,
pub editor: FormEditor<AddLogicState>, pub editor: FormEditor<AddLogicState>,
pub focus_outside_canvas: bool, pub focus_outside_canvas: bool,
pub focused_button_index: usize,
} }
// manual Debug because FormEditor may not implement Debug // manual Debug because FormEditor may not implement Debug
@@ -343,6 +344,7 @@ impl std::fmt::Debug for AddLogicFormState {
f.debug_struct("AddLogicFormState") f.debug_struct("AddLogicFormState")
.field("state", &self.state) .field("state", &self.state)
.field("focus_outside_canvas", &self.focus_outside_canvas) .field("focus_outside_canvas", &self.focus_outside_canvas)
.field("focused_button_index", &self.focused_button_index)
.finish() .finish()
} }
} }
@@ -355,6 +357,7 @@ impl AddLogicFormState {
state, state,
editor, editor,
focus_outside_canvas: false, focus_outside_canvas: false,
focused_button_index: 0,
} }
} }
@@ -373,6 +376,7 @@ impl AddLogicFormState {
state, state,
editor, editor,
focus_outside_canvas: false, focus_outside_canvas: false,
focused_button_index: 0,
} }
} }
@@ -382,6 +386,7 @@ impl AddLogicFormState {
state, state,
editor, editor,
focus_outside_canvas: false, focus_outside_canvas: false,
focused_button_index: 0,
} }
} }

View File

@@ -252,11 +252,14 @@ pub fn render_add_logic(
]) ])
.split(buttons_area); .split(buttons_area);
let save_active = add_logic_state.focus_outside_canvas
&& add_logic_state.focused_button_index == 0;
let save_button = Paragraph::new(" Save Logic ") let save_button = Paragraph::new(" Save Logic ")
.style(get_button_style( .style(if save_active {
AddLogicFocus::SaveButton, Style::default().fg(theme.highlight).add_modifier(Modifier::BOLD)
add_logic_state.current_focus(), } else {
)) Style::default().fg(theme.secondary)
})
.alignment(Alignment::Center) .alignment(Alignment::Center)
.block( .block(
Block::default() Block::default()
@@ -269,11 +272,14 @@ pub fn render_add_logic(
); );
f.render_widget(save_button, button_chunks[0]); f.render_widget(save_button, button_chunks[0]);
let cancel_active = add_logic_state.focus_outside_canvas
&& add_logic_state.focused_button_index == 1;
let cancel_button = Paragraph::new(" Cancel ") let cancel_button = Paragraph::new(" Cancel ")
.style(get_button_style( .style(if cancel_active {
AddLogicFocus::CancelButton, Style::default().fg(theme.highlight).add_modifier(Modifier::BOLD)
add_logic_state.current_focus(), } else {
)) Style::default().fg(theme.secondary)
})
.alignment(Alignment::Center) .alignment(Alignment::Center)
.block( .block(
Block::default() Block::default()

View File

@@ -51,7 +51,7 @@ pub fn handle_add_table_event(
if inside_canvas_inputs { if inside_canvas_inputs {
// Disable global shortcuts while typing // Disable global shortcuts while typing
app_state.ui.focus_outside_canvas = false; page.focus_outside_canvas = false;
// Only allow leaving the canvas with Down/Next when in ReadOnly mode // Only allow leaving the canvas with Down/Next when in ReadOnly mode
let in_edit_mode = page.editor.mode() == CanvasMode::Edit; let in_edit_mode = page.editor.mode() == CanvasMode::Edit;
@@ -62,7 +62,7 @@ pub fn handle_add_table_event(
if at_last && matches!(ma, MovementAction::Down | MovementAction::Next) { if at_last && matches!(ma, MovementAction::Down | MovementAction::Next) {
page.state.last_canvas_field = last_idx; page.state.last_canvas_field = last_idx;
page.set_current_focus(AddTableFocus::AddColumnButton); page.set_current_focus(AddTableFocus::AddColumnButton);
app_state.ui.focus_outside_canvas = true; page.focus_outside_canvas = true;
return Ok(EventOutcome::Ok("Moved to Add button".to_string())); return Ok(EventOutcome::Ok("Moved to Add button".to_string()));
} }
} }
@@ -100,7 +100,7 @@ pub fn handle_add_table_event(
} else if !page.state.columns.is_empty() { } else if !page.state.columns.is_empty() {
page.state.column_table_state.select(Some(0)); page.state.column_table_state.select(Some(0));
} }
app_state.ui.focus_outside_canvas = true; page.focus_outside_canvas = true;
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
MovementAction::Down => { MovementAction::Down => {
@@ -111,7 +111,7 @@ pub fn handle_add_table_event(
} else if !page.state.columns.is_empty() { } else if !page.state.columns.is_empty() {
page.state.column_table_state.select(Some(0)); page.state.column_table_state.select(Some(0));
} }
app_state.ui.focus_outside_canvas = true; page.focus_outside_canvas = true;
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
MovementAction::Select => { MovementAction::Select => {
@@ -121,13 +121,13 @@ pub fn handle_add_table_event(
page.state.has_unsaved_changes = true; page.state.has_unsaved_changes = true;
} }
} }
app_state.ui.focus_outside_canvas = true; page.focus_outside_canvas = true;
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
MovementAction::Esc => { MovementAction::Esc => {
page.state.column_table_state.select(None); page.state.column_table_state.select(None);
page.set_current_focus(AddTableFocus::ColumnsTable); page.set_current_focus(AddTableFocus::ColumnsTable);
app_state.ui.focus_outside_canvas = true; page.focus_outside_canvas = true;
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
MovementAction::Next | MovementAction::Previous => { MovementAction::Next | MovementAction::Previous => {
@@ -146,7 +146,7 @@ pub fn handle_add_table_event(
} else if !page.state.indexes.is_empty() { } else if !page.state.indexes.is_empty() {
page.state.index_table_state.select(Some(0)); page.state.index_table_state.select(Some(0));
} }
app_state.ui.focus_outside_canvas = true; page.focus_outside_canvas = true;
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
MovementAction::Down => { MovementAction::Down => {
@@ -157,7 +157,7 @@ pub fn handle_add_table_event(
} else if !page.state.indexes.is_empty() { } else if !page.state.indexes.is_empty() {
page.state.index_table_state.select(Some(0)); page.state.index_table_state.select(Some(0));
} }
app_state.ui.focus_outside_canvas = true; page.focus_outside_canvas = true;
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
MovementAction::Select => { MovementAction::Select => {
@@ -167,13 +167,13 @@ pub fn handle_add_table_event(
page.state.has_unsaved_changes = true; page.state.has_unsaved_changes = true;
} }
} }
app_state.ui.focus_outside_canvas = true; page.focus_outside_canvas = true;
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
MovementAction::Esc => { MovementAction::Esc => {
page.state.index_table_state.select(None); page.state.index_table_state.select(None);
page.set_current_focus(AddTableFocus::IndexesTable); page.set_current_focus(AddTableFocus::IndexesTable);
app_state.ui.focus_outside_canvas = true; page.focus_outside_canvas = true;
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
MovementAction::Next | MovementAction::Previous => { MovementAction::Next | MovementAction::Previous => {
@@ -191,7 +191,7 @@ pub fn handle_add_table_event(
} else if !page.state.links.is_empty() { } else if !page.state.links.is_empty() {
page.state.link_table_state.select(Some(0)); page.state.link_table_state.select(Some(0));
} }
app_state.ui.focus_outside_canvas = true; page.focus_outside_canvas = true;
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
MovementAction::Down => { MovementAction::Down => {
@@ -202,7 +202,7 @@ pub fn handle_add_table_event(
} else if !page.state.links.is_empty() { } else if !page.state.links.is_empty() {
page.state.link_table_state.select(Some(0)); page.state.link_table_state.select(Some(0));
} }
app_state.ui.focus_outside_canvas = true; page.focus_outside_canvas = true;
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
MovementAction::Select => { MovementAction::Select => {
@@ -212,13 +212,13 @@ pub fn handle_add_table_event(
page.state.has_unsaved_changes = true; page.state.has_unsaved_changes = true;
} }
} }
app_state.ui.focus_outside_canvas = true; page.focus_outside_canvas = true;
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
MovementAction::Esc => { MovementAction::Esc => {
page.state.link_table_state.select(None); page.state.link_table_state.select(None);
page.set_current_focus(AddTableFocus::LinksTable); page.set_current_focus(AddTableFocus::LinksTable);
app_state.ui.focus_outside_canvas = true; page.focus_outside_canvas = true;
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
MovementAction::Next | MovementAction::Previous => { MovementAction::Next | MovementAction::Previous => {
@@ -233,7 +233,7 @@ pub fn handle_add_table_event(
let mut current = page.current_focus(); let mut current = page.current_focus();
if move_focus(&ADD_TABLE_FOCUS_ORDER, &mut current, ma) { if move_focus(&ADD_TABLE_FOCUS_ORDER, &mut current, ma) {
page.set_current_focus(current); page.set_current_focus(current);
app_state.ui.focus_outside_canvas = !matches!( page.focus_outside_canvas = !matches!(
page.current_focus(), page.current_focus(),
AddTableFocus::InputTableName AddTableFocus::InputTableName
| AddTableFocus::InputColumnName | AddTableFocus::InputColumnName

View File

@@ -251,6 +251,7 @@ pub struct AddTableFormState {
pub state: AddTableState, pub state: AddTableState,
pub editor: FormEditor<AddTableState>, pub editor: FormEditor<AddTableState>,
pub focus_outside_canvas: bool, pub focus_outside_canvas: bool,
pub focused_button_index: usize,
} }
impl std::fmt::Debug for AddTableFormState { impl std::fmt::Debug for AddTableFormState {
@@ -258,6 +259,7 @@ impl std::fmt::Debug for AddTableFormState {
f.debug_struct("AddTableFormState") f.debug_struct("AddTableFormState")
.field("state", &self.state) .field("state", &self.state)
.field("focus_outside_canvas", &self.focus_outside_canvas) .field("focus_outside_canvas", &self.focus_outside_canvas)
.field("focused_button_index", &self.focused_button_index)
.finish() .finish()
} }
} }
@@ -271,6 +273,7 @@ impl AddTableFormState {
state, state,
editor, editor,
focus_outside_canvas: false, focus_outside_canvas: false,
focused_button_index: 0,
} }
} }
@@ -280,6 +283,7 @@ impl AddTableFormState {
state, state,
editor, editor,
focus_outside_canvas: false, focus_outside_canvas: false,
focused_button_index: 0,
} }
} }
@@ -319,4 +323,10 @@ impl AddTableFormState {
pub fn link_table_state(&mut self) -> &mut TableState { pub fn link_table_state(&mut self) -> &mut TableState {
&mut self.state.link_table_state &mut self.state.link_table_state
} }
pub fn set_focused_button(&mut self, index: usize) {
self.focused_button_index = index;
}
pub fn focused_button(&self) -> usize {
self.focused_button_index
}
} }

View File

@@ -489,11 +489,14 @@ pub fn render_add_table(
]) ])
.split(bottom_buttons_area); .split(bottom_buttons_area);
let save_active = add_table_state.focus_outside_canvas
&& add_table_state.focused_button_index == 0;
let save_button = Paragraph::new(" Save table ") let save_button = Paragraph::new(" Save table ")
.style(get_button_style( .style(if save_active {
AddTableFocus::SaveButton, Style::default().fg(theme.highlight).add_modifier(Modifier::BOLD)
add_table_state.current_focus(), } else {
)) Style::default().fg(theme.secondary)
})
.alignment(Alignment::Center) .alignment(Alignment::Center)
.block( .block(
Block::default() Block::default()
@@ -506,37 +509,37 @@ pub fn render_add_table(
); );
f.render_widget(save_button, bottom_button_chunks[0]); f.render_widget(save_button, bottom_button_chunks[0]);
let delete_active = add_table_state.focus_outside_canvas
&& add_table_state.focused_button_index == 1;
let delete_button = Paragraph::new(" Delete Selected ") let delete_button = Paragraph::new(" Delete Selected ")
.style(get_button_style( .style(if delete_active {
AddTableFocus::DeleteSelectedButton, Style::default().fg(theme.highlight).add_modifier(Modifier::BOLD)
add_table_state.current_focus(), } else {
)) Style::default().fg(theme.secondary)
})
.alignment(Alignment::Center) .alignment(Alignment::Center)
.block( .block(
Block::default() Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
.border_type(BorderType::Rounded) .border_type(BorderType::Rounded)
.border_style(get_button_border_style( .border_style(get_button_border_style(delete_active, theme)),
add_table_state.current_focus() == AddTableFocus::DeleteSelectedButton, // Pass bool
theme,
)),
); );
f.render_widget(delete_button, bottom_button_chunks[1]); f.render_widget(delete_button, bottom_button_chunks[1]);
let cancel_active = add_table_state.focus_outside_canvas
&& add_table_state.focused_button_index == 2;
let cancel_button = Paragraph::new(" Cancel ") let cancel_button = Paragraph::new(" Cancel ")
.style(get_button_style( .style(if cancel_active {
AddTableFocus::CancelButton, Style::default().fg(theme.highlight).add_modifier(Modifier::BOLD)
add_table_state.current_focus(), } else {
)) Style::default().fg(theme.secondary)
})
.alignment(Alignment::Center) .alignment(Alignment::Center)
.block( .block(
Block::default() Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
.border_type(BorderType::Rounded) .border_type(BorderType::Rounded)
.border_style(get_button_border_style( .border_style(get_button_border_style(cancel_active, theme)),
add_table_state.current_focus() == AddTableFocus::CancelButton, // Pass bool
theme,
)),
); );
f.render_widget(cancel_button, bottom_button_chunks[2]); f.render_widget(cancel_button, bottom_button_chunks[2]);

View File

@@ -67,9 +67,6 @@ pub fn handle_intro_selection(
} }
3 => { 3 => {
buffer_state.update_history(AppView::Register); buffer_state.update_history(AppView::Register);
// Register view requires focus reset
app_state.ui.focus_outside_canvas = false;
app_state.focused_button_index = 0;
} }
_ => return, _ => return,
} }

View File

@@ -3,23 +3,27 @@ use crate::movement::MovementAction;
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
pub struct IntroState { pub struct IntroState {
pub selected_option: usize, pub focus_outside_canvas: bool,
pub focused_button_index: usize,
} }
impl IntroState { impl IntroState {
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self {
focus_outside_canvas: true,
focused_button_index: 0,
}
} }
pub fn next_option(&mut self) { pub fn next_option(&mut self) {
if self.selected_option < 3 { if self.focused_button_index < 3 {
self.selected_option += 1; self.focused_button_index += 1;
} }
} }
pub fn previous_option(&mut self) { pub fn previous_option(&mut self) {
if self.selected_option > 0 { if self.focused_button_index > 0 {
self.selected_option -= 1 self.focused_button_index -= 1;
} }
} }
} }

View File

@@ -56,7 +56,8 @@ pub fn render_intro(f: &mut Frame, intro_state: &IntroState, area: Rect, theme:
let buttons = ["Continue", "Admin", "Login", "Register"]; let buttons = ["Continue", "Admin", "Login", "Register"];
for (i, &text) in buttons.iter().enumerate() { for (i, &text) in buttons.iter().enumerate() {
render_button(f, button_area[i], text, intro_state.selected_option == i, theme); let active = intro_state.focus_outside_canvas && intro_state.focused_button_index == i;
render_button(f, button_area[i], text, active, theme);
} }
} }

View File

@@ -26,7 +26,6 @@ pub fn handle_login_event(
&& modifiers.is_empty() && modifiers.is_empty()
{ {
login_page.focus_outside_canvas = false; login_page.focus_outside_canvas = false;
app_state.ui.focus_outside_canvas = false; // 🔑 keep global in sync
login_page.editor.set_mode(CanvasMode::ReadOnly); login_page.editor.set_mode(CanvasMode::ReadOnly);
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
@@ -43,9 +42,7 @@ pub fn handle_login_event(
) )
{ {
login_page.focus_outside_canvas = true; login_page.focus_outside_canvas = true;
login_page.focused_button_index = 0; // focus "Login" button login_page.focused_button_index = 0;
app_state.ui.focus_outside_canvas = true;
app_state.focused_button_index = 0;
login_page.editor.set_mode(CanvasMode::ReadOnly); login_page.editor.set_mode(CanvasMode::ReadOnly);
return Ok(EventOutcome::Ok("Focus moved to buttons".into())); return Ok(EventOutcome::Ok("Focus moved to buttons".into()));
} }

View File

@@ -125,9 +125,6 @@ pub async fn back_to_main(
buffer_state.close_active_buffer(); buffer_state.close_active_buffer();
buffer_state.update_history(AppView::Intro); buffer_state.update_history(AppView::Intro);
app_state.ui.focus_outside_canvas = false;
app_state.focused_button_index = 0;
"Returned to main menu".to_string() "Returned to main menu".to_string()
} }

View File

@@ -80,11 +80,8 @@ pub fn render_login(
// Login Button // Login Button
let login_button_index = 0; let login_button_index = 0;
let login_active = if login_page.focus_outside_canvas { let login_active = login_page.focus_outside_canvas
app_state.focused_button_index == login_button_index && login_page.focused_button_index == login_button_index;
} else {
false
};
let mut login_style = Style::default().fg(theme.fg); let mut login_style = Style::default().fg(theme.fg);
let mut login_border = Style::default().fg(theme.border); let mut login_border = Style::default().fg(theme.border);
if login_active { if login_active {
@@ -107,11 +104,8 @@ pub fn render_login(
// Return Button // Return Button
let return_button_index = 1; let return_button_index = 1;
let return_active = if app_state.ui.focus_outside_canvas { let return_active = login_page.focus_outside_canvas
app_state.focused_button_index == return_button_index && login_page.focused_button_index == return_button_index;
} else {
false
};
let mut return_style = Style::default().fg(theme.fg); let mut return_style = Style::default().fg(theme.fg);
let mut return_border = Style::default().fg(theme.border); let mut return_border = Style::default().fg(theme.border);
if return_active { if return_active {

View File

@@ -28,8 +28,6 @@ pub fn handle_register_event(
&& modifiers.is_empty() && modifiers.is_empty()
{ {
register_page.focus_outside_canvas = false; register_page.focus_outside_canvas = false;
// Keep global in sync for now (cursor styling elsewhere still reads it)
app_state.ui.focus_outside_canvas = false;
register_page.editor.set_mode(CanvasMode::ReadOnly); register_page.editor.set_mode(CanvasMode::ReadOnly);
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
@@ -47,9 +45,6 @@ pub fn handle_register_event(
{ {
register_page.focus_outside_canvas = true; register_page.focus_outside_canvas = true;
register_page.focused_button_index = 0; // focus "Register" button register_page.focused_button_index = 0; // focus "Register" button
// Keep global in sync for now
app_state.ui.focus_outside_canvas = true;
app_state.focused_button_index = 0;
register_page.editor.set_mode(CanvasMode::ReadOnly); register_page.editor.set_mode(CanvasMode::ReadOnly);
return Ok(EventOutcome::Ok("Focus moved to buttons".into())); return Ok(EventOutcome::Ok("Focus moved to buttons".into()));
} }

View File

@@ -56,8 +56,6 @@ pub async fn back_to_login(
// Reset focus state // Reset focus state
register_state.focus_outside_canvas = false; register_state.focus_outside_canvas = false;
register_state.focused_button_index = 0; register_state.focused_button_index = 0;
app_state.ui.focus_outside_canvas = false;
app_state.focused_button_index = 0;
"Returned to main menu".to_string() "Returned to main menu".to_string()
} }

View File

@@ -106,7 +106,6 @@ pub async fn handle_search_palette_event(
if should_close { if should_close {
app_state.search_state = None; app_state.search_state = None;
app_state.ui.show_search_palette = false; app_state.ui.show_search_palette = false;
app_state.ui.focus_outside_canvas = false;
} }
Ok(outcome_message) Ok(outcome_message)

View File

@@ -28,7 +28,6 @@ pub struct UiState {
pub show_login: bool, pub show_login: bool,
pub show_register: bool, pub show_register: bool,
pub show_search_palette: bool, pub show_search_palette: bool,
pub focus_outside_canvas: bool,
pub dialog: DialogState, pub dialog: DialogState,
} }
@@ -52,7 +51,6 @@ pub struct AppState {
// NEW: The "Rulebook" cache. We use Arc for efficient sharing. // NEW: The "Rulebook" cache. We use Arc for efficient sharing.
pub schema_cache: HashMap<String, Arc<TableStructureResponse>>, pub schema_cache: HashMap<String, Arc<TableStructureResponse>>,
pub focused_button_index: usize,
pub pending_table_structure_fetch: Option<(String, String)>, pub pending_table_structure_fetch: Option<(String, String)>,
pub search_state: Option<SearchState>, pub search_state: Option<SearchState>,
@@ -77,7 +75,6 @@ impl AppState {
current_view_table_name: None, current_view_table_name: None,
current_mode: AppMode::General, current_mode: AppMode::General,
schema_cache: HashMap::new(), // NEW: Initialize the cache schema_cache: HashMap::new(), // NEW: Initialize the cache
focused_button_index: 0,
pending_table_structure_fetch: None, pending_table_structure_fetch: None,
search_state: None, search_state: None,
ui: UiState::default(), ui: UiState::default(),
@@ -166,8 +163,7 @@ impl Default for UiState {
show_login: false, show_login: false,
show_register: false, show_register: false,
show_buffer_list: true, show_buffer_list: true,
show_search_palette: false, // ADDED show_search_palette: false,
focus_outside_canvas: false,
dialog: DialogState::default(), dialog: DialogState::default(),
} }
} }

View File

@@ -20,17 +20,12 @@ pub fn logout(
// Delete stored auth data // Delete stored auth data
if let Err(e) = delete_auth_data() { if let Err(e) = delete_auth_data() {
error!("Failed to delete stored auth data: {}", e); error!("Failed to delete stored auth data: {}", e);
// Continue anyway - user is logged out in memory
} }
// Navigate to intro screen // Navigate to intro screen
buffer_state.history = vec![AppView::Intro]; buffer_state.history = vec![AppView::Intro];
buffer_state.active_index = 0; buffer_state.active_index = 0;
// Reset UI state
app_state.ui.focus_outside_canvas = false;
app_state.focused_button_index = 0;
// Hide any open dialogs // Hide any open dialogs
app_state.hide_dialog(); app_state.hide_dialog();
@@ -39,7 +34,7 @@ pub fn logout(
"Logged Out", "Logged Out",
"You have been successfully logged out.", "You have been successfully logged out.",
vec!["OK".to_string()], vec!["OK".to_string()],
DialogPurpose::LoginSuccess, // Reuse or create a new purpose DialogPurpose::LoginSuccess,
); );
info!("User logged out successfully."); info!("User logged out successfully.");

View File

@@ -227,8 +227,17 @@ pub async fn run_ui() -> Result<()> {
|| app_state.ui.show_search_palette || app_state.ui.show_search_palette
|| event_handler.navigation_state.active; || event_handler.navigation_state.active;
if !overlay_active { if !overlay_active {
let inside_canvas = match &router.current {
Page::Form(_) => true,
Page::Login(state) => !state.focus_outside_canvas,
Page::Register(state) => !state.focus_outside_canvas,
Page::AddTable(state) => !state.focus_outside_canvas,
Page::AddLogic(state) => !state.focus_outside_canvas,
_ => false,
};
if inside_canvas {
if let Page::Form(path) = &router.current { if let Page::Form(path) = &router.current {
if !app_state.ui.focus_outside_canvas {
if let Some(editor) = app_state.editor_for_path(path) { if let Some(editor) = app_state.editor_for_path(path) {
match editor.handle_key_event(*key_event) { match editor.handle_key_event(*key_event) {
KeyEventOutcome::Consumed(Some(msg)) => { KeyEventOutcome::Consumed(Some(msg)) => {
@@ -633,7 +642,15 @@ pub async fn run_ui() -> Result<()> {
match current_mode { match current_mode {
AppMode::General => { AppMode::General => {
if app_state.ui.focus_outside_canvas { let outside_canvas = match &router.current {
Page::Login(state) => state.focus_outside_canvas,
Page::Register(state) => state.focus_outside_canvas,
Page::AddTable(state) => state.focus_outside_canvas,
Page::AddLogic(state) => state.focus_outside_canvas,
_ => false, // Form and Admin dont use this flag
};
if outside_canvas {
// Outside canvas → app decides // Outside canvas → app decides
terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?;
terminal.show_cursor()?; terminal.show_cursor()?;