feat(theme-preview): add live preview toggle in Review (Now showing <Name> [on/off], Enter toggles); support saving without switching (persist but keep current theme) via set_custom_theme(set_active)

This commit is contained in:
James Peter
2025-09-11 07:54:57 +10:00
parent 47ecfbbdca
commit eb8ca975ca
@@ -782,11 +782,7 @@ impl<'a> BottomPaneView<'a> for ThemeSelectionView {
fn handle_key_event(&mut self, _pane: &mut BottomPane<'a>, key_event: KeyEvent) { fn handle_key_event(&mut self, _pane: &mut BottomPane<'a>, key_event: KeyEvent) {
match key_event { match key_event {
KeyEvent { KeyEvent { code: KeyCode::Up, modifiers: KeyModifiers::NONE, .. } => {
code: KeyCode::Up,
modifiers: KeyModifiers::NONE,
..
} => {
// In create form, Up navigates fields/buttons // In create form, Up navigates fields/buttons
if let Mode::CreateSpinner(ref mut s) = self.mode { if let Mode::CreateSpinner(ref mut s) = self.mode {
let new_step = match s.step.get() { let new_step = match s.step.get() {
@@ -806,22 +802,11 @@ impl<'a> BottomPaneView<'a> for ThemeSelectionView {
}; };
s.step.set(new_step); s.step.set(new_step);
} else if let Mode::CreateTheme(ref mut s) = self.mode { } else if let Mode::CreateTheme(ref mut s) = self.mode {
let new_step = match s.step.get() { match s.step.get() {
CreateStep::Prompt => CreateStep::Action, CreateStep::Prompt => s.step.set(CreateStep::Action),
CreateStep::Action => { CreateStep::Action => { if s.action_idx > 0 { s.action_idx -= 1; } },
if s.action_idx > 0 { CreateStep::Review => { s.review_focus_is_toggle.set(true); },
s.action_idx -= 1; }
}
CreateStep::Action
}
CreateStep::Review => {
if s.action_idx > 0 {
s.action_idx -= 1;
}
CreateStep::Review
}
};
s.step.set(new_step);
} else { } else {
match self.mode { match self.mode {
Mode::Overview => { Mode::Overview => {
@@ -832,11 +817,7 @@ impl<'a> BottomPaneView<'a> for ThemeSelectionView {
} }
} }
} }
KeyEvent { KeyEvent { code: KeyCode::Down, modifiers: KeyModifiers::NONE, .. } => {
code: KeyCode::Down,
modifiers: KeyModifiers::NONE,
..
} => {
// In create form, Down navigates fields/buttons // In create form, Down navigates fields/buttons
if let Mode::CreateSpinner(ref mut s) = self.mode { if let Mode::CreateSpinner(ref mut s) = self.mode {
let new_step = match s.step.get() { let new_step = match s.step.get() {
@@ -856,22 +837,11 @@ impl<'a> BottomPaneView<'a> for ThemeSelectionView {
}; };
s.step.set(new_step); s.step.set(new_step);
} else if let Mode::CreateTheme(ref mut s) = self.mode { } else if let Mode::CreateTheme(ref mut s) = self.mode {
let new_step = match s.step.get() { match s.step.get() {
CreateStep::Prompt => CreateStep::Action, CreateStep::Prompt => s.step.set(CreateStep::Action),
CreateStep::Action => { CreateStep::Action => { if s.action_idx < 1 { s.action_idx += 1; } },
if s.action_idx < 1 { CreateStep::Review => { s.review_focus_is_toggle.set(false); },
s.action_idx += 1; }
}
CreateStep::Action
}
CreateStep::Review => {
if s.action_idx < 1 {
s.action_idx += 1;
}
CreateStep::Review
}
};
s.step.set(new_step);
} else { } else {
match &self.mode { match &self.mode {
Mode::Overview => { Mode::Overview => {
@@ -930,6 +900,8 @@ impl<'a> BottomPaneView<'a> for ThemeSelectionView {
thinking_current: std::cell::RefCell::new(String::new()), thinking_current: std::cell::RefCell::new(String::new()),
proposed_name: std::cell::RefCell::new(None), proposed_name: std::cell::RefCell::new(None),
proposed_colors: std::cell::RefCell::new(None), proposed_colors: std::cell::RefCell::new(None),
preview_on: std::cell::Cell::new(true),
review_focus_is_toggle: std::cell::Cell::new(true),
}); });
} else { } else {
// confirm_theme sets self.mode back to Overview // confirm_theme sets self.mode back to Overview
@@ -1083,30 +1055,44 @@ impl<'a> BottomPaneView<'a> for ThemeSelectionView {
} }
} }
CreateStep::Review => { CreateStep::Review => {
if s.action_idx == 0 { if s.review_focus_is_toggle.get() {
// Toggle preview on/off
let now_on = !s.preview_on.get();
s.preview_on.set(now_on);
if now_on {
// Reapply preview
if let (Some(name), Some(colors)) = (
s.proposed_name.borrow().clone(),
s.proposed_colors.borrow().clone(),
) {
crate::theme::set_custom_theme_label(name.clone());
crate::theme::init_theme(&codex_core::config_types::ThemeConfig { name: ThemeName::Custom, colors, label: Some(name) });
}
} else {
// Revert to previous theme
self.app_event_tx.send(AppEvent::PreviewTheme(self.revert_theme_on_back));
}
self.app_event_tx.send(AppEvent::RequestRedraw);
} else if s.action_idx == 0 {
// Save // Save
if let (Some(name), Some(colors)) = ( if let (Some(name), Some(colors)) = (
s.proposed_name.borrow().clone(), s.proposed_name.borrow().clone(),
s.proposed_colors.borrow().clone(), s.proposed_colors.borrow().clone(),
) { ) {
if let Ok(home) = codex_core::config::find_codex_home() { if let Ok(home) = codex_core::config::find_codex_home() {
let _ = codex_core::config::set_custom_theme( let _ = codex_core::config::set_custom_theme(&home, &name, &colors, s.preview_on.get());
&home, &name, &colors,
);
} }
// Apply and record
crate::theme::set_custom_theme_label(name.clone()); crate::theme::set_custom_theme_label(name.clone());
crate::theme::init_theme( if s.preview_on.get() {
&codex_core::config_types::ThemeConfig { // Keep preview and set active in UI if chosen
name: ThemeName::Custom, crate::theme::init_theme(&codex_core::config_types::ThemeConfig { name: ThemeName::Custom, colors: colors.clone(), label: Some(name.clone()) });
colors: colors.clone(), self.revert_theme_on_back = ThemeName::Custom;
label: Some(name.clone()), self.current_theme = ThemeName::Custom;
}, self.app_event_tx.send(AppEvent::UpdateTheme(ThemeName::Custom));
); } else {
self.revert_theme_on_back = ThemeName::Custom; // Saved but not active: revert to previous theme visually
self.current_theme = ThemeName::Custom; self.app_event_tx.send(AppEvent::PreviewTheme(self.revert_theme_on_back));
self.app_event_tx }
.send(AppEvent::UpdateTheme(ThemeName::Custom));
self.app_event_tx.send( self.app_event_tx.send(
AppEvent::InsertBackgroundEventEarly( AppEvent::InsertBackgroundEventEarly(
"Custom theme saved".to_string(), "Custom theme saved".to_string(),
@@ -1581,6 +1567,26 @@ impl<'a> BottomPaneView<'a> for ThemeSelectionView {
.fg(theme.text_bright) .fg(theme.text_bright)
.add_modifier(Modifier::BOLD), .add_modifier(Modifier::BOLD),
))); )));
// Theme review header with preview toggle when in theme mode
if let Mode::CreateTheme(ref st) = self.mode {
let name = st
.proposed_name
.borrow()
.clone()
.unwrap_or_else(|| "Custom".to_string());
let onoff = if st.preview_on.get() { "on" } else { "off" };
let sel = st.review_focus_is_toggle.get();
let style = if sel {
Style::default().fg(theme.primary).add_modifier(Modifier::BOLD)
} else {
Style::default().fg(theme.text)
};
form_lines.push(Line::from(Span::styled(
format!("Now showing {} [{}]", name, onoff),
style,
)));
form_lines.push(Line::default());
}
// Blank line between title and preview row // Blank line between title and preview row
form_lines.push(Line::default()); form_lines.push(Line::default());
// Preview styled like selection rows: border rules + spinner + label // Preview styled like selection rows: border rules + spinner + label
@@ -1626,8 +1632,17 @@ impl<'a> BottomPaneView<'a> for ThemeSelectionView {
form_lines.push(Line::default()); form_lines.push(Line::default());
// Buttons row moved to bottom // Buttons row moved to bottom
let mut spans: Vec<Span> = Vec::new(); let mut spans: Vec<Span> = Vec::new();
let primary_selected = s.action_idx == 0; // In Theme review: allow focusing the toggle line; if focused there, do not style buttons as selected
let secondary_selected = s.action_idx == 1; let primary_selected = if let Mode::CreateTheme(ref st) = self.mode {
!st.review_focus_is_toggle.get() && s.action_idx == 0
} else {
s.action_idx == 0
};
let secondary_selected = if let Mode::CreateTheme(ref st) = self.mode {
!st.review_focus_is_toggle.get() && s.action_idx == 1
} else {
s.action_idx == 1
};
let sel = |b: bool| { let sel = |b: bool| {
if b { if b {
Style::default() Style::default()