Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions tame/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -613,18 +613,22 @@ def action_pause_all(self) -> None:

def action_toggle_theme(self) -> None:
new_theme = self._theme_manager.cycle()
css_path = self._theme_manager.get_css_path()
if css_path:
self.stylesheet.source.clear()
# Re-add the app's structural layout CSS first
self.stylesheet.add_source(self.CSS, read_from=("TAMEApp", ""))
# Then add the new theme CSS (Screen colors, widget colors)
self.stylesheet.add_source(
css_path.read_text(), read_from=(str(css_path), "")
)
self.stylesheet.set_variables(self.get_css_variables())
self.stylesheet.reparse()
self.stylesheet.update(self)
colors = self._theme_manager.get_colors()
bg, fg = colors["screen"]
self.screen.styles.background = bg
self.screen.styles.color = fg
for widget_key, widget_id in (
("header", "header-bar"),
("viewer", "session-viewer"),
("status", "status-bar"),
):
wbg, wfg = colors[widget_key]
try:
widget = self.query_one(f"#{widget_id}")
widget.styles.background = wbg
widget.styles.color = wfg
except Exception:
pass
log.info("Switched theme to '%s'", new_theme)

def action_delete_session(self) -> None:
Expand Down
58 changes: 58 additions & 0 deletions tame/ui/themes/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,59 @@
"solarized_light",
]

# Colors for widgets that actually exist in the app.
# Each entry: (background, foreground) per widget selector.
THEME_COLORS: dict[str, dict[str, tuple[str, str]]] = {
"dark": {
"screen": ("#1e1e1e", "#d4d4d4"),
"header": ("#007acc", "#ffffff"),
"viewer": ("#1e1e1e", "#d4d4d4"),
"status": ("#007acc", "#ffffff"),
},
"light": {
"screen": ("#ffffff", "#333333"),
"header": ("#007acc", "#ffffff"),
"viewer": ("#ffffff", "#333333"),
"status": ("#007acc", "#ffffff"),
},
"dracula": {
"screen": ("#282a36", "#f8f8f2"),
"header": ("#6272a4", "#f8f8f2"),
"viewer": ("#282a36", "#f8f8f2"),
"status": ("#bd93f9", "#282a36"),
},
"nord": {
"screen": ("#2e3440", "#d8dee9"),
"header": ("#5e81ac", "#eceff4"),
"viewer": ("#2e3440", "#d8dee9"),
"status": ("#5e81ac", "#eceff4"),
},
"monokai": {
"screen": ("#272822", "#f8f8f2"),
"header": ("#75715e", "#f8f8f2"),
"viewer": ("#272822", "#f8f8f2"),
"status": ("#66d9ef", "#272822"),
},
"gruvbox": {
"screen": ("#282828", "#ebdbb2"),
"header": ("#458588", "#ebdbb2"),
"viewer": ("#282828", "#ebdbb2"),
"status": ("#458588", "#ebdbb2"),
},
"solarized_dark": {
"screen": ("#002b36", "#839496"),
"header": ("#268bd2", "#fdf6e3"),
"viewer": ("#002b36", "#839496"),
"status": ("#268bd2", "#fdf6e3"),
},
"solarized_light": {
"screen": ("#fdf6e3", "#657b83"),
"header": ("#268bd2", "#fdf6e3"),
"viewer": ("#fdf6e3", "#657b83"),
"status": ("#268bd2", "#fdf6e3"),
},
}


class ThemeManager:
def __init__(self, current: str = "dark", custom_css_path: str = "") -> None:
Expand Down Expand Up @@ -70,6 +123,11 @@ def cycle(self) -> str:
self._current = self._available[(idx + 1) % len(self._available)]
return self._current

def get_colors(self, theme_name: str | None = None) -> dict[str, tuple[str, str]]:
"""Return (background, foreground) pairs for the given theme."""
name = theme_name or self._current
return THEME_COLORS.get(name, THEME_COLORS["dark"])

def set_theme(self, name: str) -> bool:
if name in self._available:
self._current = name
Expand Down