Skip to content

Commit aa6f3a5

Browse files
committed
feat(tui): implement live theme preview system
- Add preview_theme field to AppState for temporary theme previews - Add set_preview_theme, get_effective_theme_colors, start_theme_preview, cancel_theme_preview, and confirm_theme_preview methods - Add ModalResult::ActionContinue for live preview without closing modal - Add ModalAction variants: PreviewTheme, RevertTheme, ConfirmTheme - Update ThemeSelectorModal to emit preview actions on navigation - Add color swatch display in theme selector rows - Handle new theme actions in event loop - Update ThemePicker to use ThemeSelectorModal instead of interactive builder - Add comprehensive tests for new preview functionality
1 parent e6658f8 commit aa6f3a5

File tree

7 files changed

+242
-46
lines changed

7 files changed

+242
-46
lines changed

src/cortex-tui/src/app/methods.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,43 @@ impl AppState {
631631
}
632632
}
633633

634+
// ============================================================================
635+
// APPSTATE METHODS - Theme Preview
636+
// ============================================================================
637+
638+
impl AppState {
639+
/// Start previewing a theme.
640+
///
641+
/// This updates the cached theme colors to show the preview theme,
642+
/// without changing the active theme.
643+
pub fn start_theme_preview(&mut self, theme_name: &str) {
644+
self.set_preview_theme(Some(theme_name.to_string()));
645+
}
646+
647+
/// Cancel theme preview and revert to the original (active) theme.
648+
///
649+
/// Restores the cached colors to the active theme.
650+
pub fn cancel_theme_preview(&mut self) {
651+
self.set_preview_theme(None);
652+
}
653+
654+
/// Confirm the previewed theme as the active theme.
655+
///
656+
/// Makes the preview theme the new active theme and clears the preview state.
657+
pub fn confirm_theme_preview(&mut self) {
658+
if let Some(preview) = self.preview_theme.take() {
659+
self.active_theme = preview.clone();
660+
// Colors are already set to the preview theme, just need to clear preview state
661+
self.preview_theme = None;
662+
}
663+
}
664+
665+
/// Check if a theme preview is active.
666+
pub fn has_theme_preview(&self) -> bool {
667+
self.preview_theme.is_some()
668+
}
669+
}
670+
634671
// ============================================================================
635672
// APPSTATE METHODS - Operation Mode
636673
// ============================================================================

src/cortex-tui/src/app/state.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ pub struct AppState {
106106
pub input_mode: crate::interactive::InputMode,
107107
/// Active theme name
108108
pub active_theme: String,
109+
/// Preview theme name (for live theme preview in selector)
110+
pub preview_theme: Option<String>,
109111
/// Cached theme colors for quick access
110112
pub theme_colors: ThemeColors,
111113
/// Cached markdown theme for quick access
@@ -238,6 +240,7 @@ impl AppState {
238240
diff_scroll: 0,
239241
input_mode: crate::interactive::InputMode::Normal,
240242
active_theme: "dark".to_string(),
243+
preview_theme: None,
241244
theme_colors: ThemeColors::dark(),
242245
markdown_theme: MarkdownTheme::default(),
243246
compact_mode: false,
@@ -372,10 +375,32 @@ impl AppState {
372375
/// Change the active theme
373376
pub fn set_theme(&mut self, name: &str) {
374377
self.active_theme = name.to_string();
378+
self.preview_theme = None; // Clear any preview when setting the theme
375379
self.theme_colors = ThemeColors::from_name(name);
376380
self.markdown_theme = MarkdownTheme::from_name(name);
377381
}
378382

383+
/// Set a preview theme for live preview functionality
384+
///
385+
/// Updates the cached theme_colors to the preview theme colors.
386+
pub fn set_preview_theme(&mut self, theme: Option<String>) {
387+
self.preview_theme = theme.clone();
388+
// Update cached colors based on preview or active theme
389+
let effective_theme = theme.as_deref().unwrap_or(&self.active_theme);
390+
self.theme_colors = ThemeColors::from_name(effective_theme);
391+
self.markdown_theme = MarkdownTheme::from_name(effective_theme);
392+
}
393+
394+
/// Get the effective theme colors (preview if set, otherwise active)
395+
pub fn get_effective_theme_colors(&self) -> &ThemeColors {
396+
&self.theme_colors
397+
}
398+
399+
/// Get the name of the effective theme (preview if set, otherwise active)
400+
pub fn get_effective_theme_name(&self) -> &str {
401+
self.preview_theme.as_deref().unwrap_or(&self.active_theme)
402+
}
403+
379404
/// Get AdaptiveColors from the current theme
380405
pub fn adaptive_colors(&self) -> crate::ui::AdaptiveColors {
381406
crate::ui::AdaptiveColors::from_theme_colors(&self.theme_colors)

src/cortex-tui/src/modal/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ pub enum ModalResult {
9797
Close,
9898
/// Perform an action and close
9999
Action(ModalAction),
100+
/// Perform an action but keep the modal open (for live preview)
101+
ActionContinue(ModalAction),
100102
/// Push a new modal on top of this one
101103
Push(Box<dyn Modal>),
102104
/// Replace this modal with another
@@ -172,6 +174,14 @@ pub enum ModalAction {
172174
api_key: String,
173175
},
174176

177+
// Theme Preview Actions
178+
/// Preview a theme without applying it permanently
179+
PreviewTheme(String),
180+
/// Revert to the original theme (cancel preview)
181+
RevertTheme,
182+
/// Confirm and apply the previewed theme
183+
ConfirmTheme(String),
184+
175185
// Generic/Custom
176186
Custom(String),
177187
}
@@ -253,6 +263,10 @@ impl ModalStack {
253263
self.pop();
254264
ModalResult::Action(action)
255265
}
266+
ModalResult::ActionContinue(action) => {
267+
// Return the action but keep the modal open (for live preview)
268+
ModalResult::ActionContinue(action)
269+
}
256270
other => other,
257271
}
258272
} else {

0 commit comments

Comments
 (0)