From da88ae71fe03b86cf399102023798a6b49355c86 Mon Sep 17 00:00:00 2001 From: mscrnt Date: Sat, 24 May 2025 19:57:59 -0700 Subject: [PATCH] Refactor theming and styles: implement dynamic theme management, replace static styles with theme variables, and enhance game card UI --- agentm/app.py | 13 +- agentm/assets/game_titles.bak/doapp.txt | 3 + agentm/assets/game_titles.bak/kof98umh.txt | 3 + agentm/assets/game_titles.bak/mvsc.txt | 3 + agentm/assets/game_titles.bak/samsh5sp.txt | 3 + agentm/assets/game_titles.bak/sfiii3n.txt | 3 + agentm/assets/game_titles.bak/soulclbr.txt | 3 + agentm/assets/game_titles.bak/tektagt.txt | 3 + agentm/assets/game_titles.bak/umk3.txt | 3 + agentm/assets/game_titles.bak/xmvsf.txt | 3 + agentm/theme/generate_theme_css.py | 29 +++ agentm/theme/palette.py | 94 +++++++++ agentm/theme/styles.base.tcss | 225 +++++++++++++++++++++ agentm/{assets => theme}/styles.tcss | 83 ++++---- agentm/views/home.py | 139 ++++++++----- 15 files changed, 514 insertions(+), 96 deletions(-) create mode 100644 agentm/assets/game_titles.bak/doapp.txt create mode 100644 agentm/assets/game_titles.bak/kof98umh.txt create mode 100644 agentm/assets/game_titles.bak/mvsc.txt create mode 100644 agentm/assets/game_titles.bak/samsh5sp.txt create mode 100644 agentm/assets/game_titles.bak/sfiii3n.txt create mode 100644 agentm/assets/game_titles.bak/soulclbr.txt create mode 100644 agentm/assets/game_titles.bak/tektagt.txt create mode 100644 agentm/assets/game_titles.bak/umk3.txt create mode 100644 agentm/assets/game_titles.bak/xmvsf.txt create mode 100644 agentm/theme/generate_theme_css.py create mode 100644 agentm/theme/palette.py create mode 100644 agentm/theme/styles.base.tcss rename agentm/{assets => theme}/styles.tcss (79%) diff --git a/agentm/app.py b/agentm/app.py index 19e0e73..0b9df45 100644 --- a/agentm/app.py +++ b/agentm/app.py @@ -3,21 +3,26 @@ from agentm.views.home import HomeView from agentm.views.login import LoginView from agentm import DIAMBRA_CREDENTIALS_PATH from agentm.utils.logger import log_with_caller -from agentm.logic.db import initialize_database +from agentm.logic.db import initialize_database +from agentm.theme.palette import get_theme # ← Add this import + class AgentMApp(App): - CSS_PATH = "assets/styles.tcss" + CSS_PATH = "theme/styles.tcss" def on_mount(self) -> None: """Called when the app starts.""" log_with_caller("debug", "App mounted. Initializing database...") - initialize_database() + initialize_database() + + # Initialize global theme instance (dark mode for now) + log_with_caller("debug", "Initializing theme: dark") + get_theme("dark") log_with_caller("debug", "Checking for credentials...") if DIAMBRA_CREDENTIALS_PATH.exists(): token = DIAMBRA_CREDENTIALS_PATH.read_text().strip() - if token and len(token) > 10: log_with_caller("info", "Found populated DIAMBRA credentials. Launching Home.") self.push_screen(HomeView()) diff --git a/agentm/assets/game_titles.bak/doapp.txt b/agentm/assets/game_titles.bak/doapp.txt new file mode 100644 index 0000000..7f0d09e --- /dev/null +++ b/agentm/assets/game_titles.bak/doapp.txt @@ -0,0 +1,3 @@ + +-+-+-+-+ +-+-+ +-+-+-+-+-+ +-+-+ + |D|E|A|D| |O|R| |A|L|I|V|E| |+|+| + +-+-+-+-+ +-+-+ +-+-+-+-+-+ +-+-+ \ No newline at end of file diff --git a/agentm/assets/game_titles.bak/kof98umh.txt b/agentm/assets/game_titles.bak/kof98umh.txt new file mode 100644 index 0000000..f6b8d66 --- /dev/null +++ b/agentm/assets/game_titles.bak/kof98umh.txt @@ -0,0 +1,3 @@ + +-+-+-+-+ +-+-+ +-+-+-+-+-+-+-+-+ + |K|i|n|g| |o|f| |F|i|g|h|t|e|r|s| + +-+-+-+-+ +-+-+ +-+-+-+-+-+-+-+-+ \ No newline at end of file diff --git a/agentm/assets/game_titles.bak/mvsc.txt b/agentm/assets/game_titles.bak/mvsc.txt new file mode 100644 index 0000000..2dc7fbb --- /dev/null +++ b/agentm/assets/game_titles.bak/mvsc.txt @@ -0,0 +1,3 @@ + +-+-+-+-+-+-+ +-+-+ +-+-+-+-+-+-+ + |M|a|r|v|e|l| |v|s| |C|a|p|c|o|m| + +-+-+-+-+-+-+ +-+-+ +-+-+-+-+-+-+ \ No newline at end of file diff --git a/agentm/assets/game_titles.bak/samsh5sp.txt b/agentm/assets/game_titles.bak/samsh5sp.txt new file mode 100644 index 0000000..7e6b642 --- /dev/null +++ b/agentm/assets/game_titles.bak/samsh5sp.txt @@ -0,0 +1,3 @@ + +-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ + |S|a|m|u|r|a|i| |S|h|o|w|d|o|w|n| + +-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ \ No newline at end of file diff --git a/agentm/assets/game_titles.bak/sfiii3n.txt b/agentm/assets/game_titles.bak/sfiii3n.txt new file mode 100644 index 0000000..c1e45dd --- /dev/null +++ b/agentm/assets/game_titles.bak/sfiii3n.txt @@ -0,0 +1,3 @@ + +-+-+-+-+-+-+ +-+-+-+-+-+-+-+ +-+-+-+ + |S|t|r|e|e|t| |F|i|g|h|t|e|r| |I|I|I| + +-+-+-+-+-+-+ +-+-+-+-+-+-+-+ +-+-+-+ \ No newline at end of file diff --git a/agentm/assets/game_titles.bak/soulclbr.txt b/agentm/assets/game_titles.bak/soulclbr.txt new file mode 100644 index 0000000..87d6212 --- /dev/null +++ b/agentm/assets/game_titles.bak/soulclbr.txt @@ -0,0 +1,3 @@ + +-+-+-+-+ +-+-+-+-+-+-+-+ + |S|o|u|l| |C|a|l|i|b|u|r| + +-+-+-+-+ +-+-+-+-+-+-+-+ \ No newline at end of file diff --git a/agentm/assets/game_titles.bak/tektagt.txt b/agentm/assets/game_titles.bak/tektagt.txt new file mode 100644 index 0000000..fae2775 --- /dev/null +++ b/agentm/assets/game_titles.bak/tektagt.txt @@ -0,0 +1,3 @@ + +-+-+-+-+-+-+ +-+-+-+ + |T|e|k|k|e|n| |T|a|g| + +-+-+-+-+-+-+ +-+-+-+ \ No newline at end of file diff --git a/agentm/assets/game_titles.bak/umk3.txt b/agentm/assets/game_titles.bak/umk3.txt new file mode 100644 index 0000000..4c044c1 --- /dev/null +++ b/agentm/assets/game_titles.bak/umk3.txt @@ -0,0 +1,3 @@ + +-+-+-+-+-+-+ +-+-+-+-+-+-+ +-+ + |M|o|r|t|a|l| |K|o|m|b|a|t| |3| + +-+-+-+-+-+-+ +-+-+-+-+-+-+ +-+ \ No newline at end of file diff --git a/agentm/assets/game_titles.bak/xmvsf.txt b/agentm/assets/game_titles.bak/xmvsf.txt new file mode 100644 index 0000000..4c044c1 --- /dev/null +++ b/agentm/assets/game_titles.bak/xmvsf.txt @@ -0,0 +1,3 @@ + +-+-+-+-+-+-+ +-+-+-+-+-+-+ +-+ + |M|o|r|t|a|l| |K|o|m|b|a|t| |3| + +-+-+-+-+-+-+ +-+-+-+-+-+-+ +-+ \ No newline at end of file diff --git a/agentm/theme/generate_theme_css.py b/agentm/theme/generate_theme_css.py new file mode 100644 index 0000000..6855311 --- /dev/null +++ b/agentm/theme/generate_theme_css.py @@ -0,0 +1,29 @@ +from agentm.theme.palette import get_theme +from pathlib import Path +import re + +# Load dark theme instance +theme = get_theme("dark") + +# Path setup +base_path = Path(__file__).parent +template_path = base_path / "styles.base.tcss" +output_path = base_path / "styles.tcss" + +# Load template +template = template_path.read_text() + +# Find all placeholders like {{FOREGROUND}}, {{BACKGROUND}}, etc. +tokens = set(re.findall(r"{{\s*([A-Z0-9_]+)\s*}}", template)) + +# Replace them with actual values from theme +for token in tokens: + value = getattr(theme, token, None) + if value is not None: + template = template.replace(f"{{{{{token}}}}}", value) + else: + print(f"⚠️ Warning: Theme token '{token}' not found in ThemeManager") + +# Write final output +output_path.write_text(template) +print(f"✅ Synced themed CSS written to: {output_path}") diff --git a/agentm/theme/palette.py b/agentm/theme/palette.py new file mode 100644 index 0000000..1536e0d --- /dev/null +++ b/agentm/theme/palette.py @@ -0,0 +1,94 @@ +from typing import Literal, Optional + +class ThemeManager: + def __init__(self, theme: Literal["dark", "light"] = "dark"): + self.use_theme(theme) + + def use_theme(self, theme: str): + if theme == "dark": + self.ACCENT = "#ed7d3a" + self.ACCENT_HOVER = "rgb(236, 194, 169)" + self.BACKGROUND = "#0e0e0e" + self.FOREGROUND = "#f0f0f0" + self.BORDER = "#3a9bed" + self.SURFACE_MUTED = "#8b8b8b" + self.SUCCESS = "#4cd964" + self.ERROR = "red" + self.DISABLED = "#999999" + self.DISABLED_BG = "#444444" + self.DISABLED_BORDER = "#666666" + else: + self.ACCENT = "#004488" + self.ACCENT_HOVER = "#0077cc" + self.BACKGROUND = "#ffffff" + self.FOREGROUND = "#000000" + self.BORDER = "#003366" + self.SURFACE_MUTED = "#dddddd" + self.SUCCESS = "#007f00" + self.ERROR = "#cc0000" + self.DISABLED = "#cccccc" + self.DISABLED_BG = "#f0f0f0" + self.DISABLED_BORDER = "#aaaaaa" + + # Shared base + primary + tonal + self.DARK = "#000000" + self.LIGHT = "#ffffff" + + self.PRIMARY_0 = "#6e1106" + self.PRIMARY_10 = "#812f1f" + self.PRIMARY_20 = "#934937" + self.PRIMARY_30 = "#a46251" + self.PRIMARY_40 = "#b57b6b" + self.PRIMARY_50 = "#c59487" + + self.SURFACE_0 = "#121212" + self.SURFACE_10 = "#282828" + self.SURFACE_20 = "#3f3f3f" + self.SURFACE_30 = "#575757" + self.SURFACE_40 = "#717171" + self.SURFACE_50 = "#8b8b8b" + + self.TONAL_0 = "#1d1411" + self.TONAL_10 = "#322927" + self.TONAL_20 = "#48403e" + self.TONAL_30 = "#605856" + self.TONAL_40 = "#787270" + self.TONAL_50 = "#928c8b" + + self.COMPONENT_CLASSES = { + "palette--foreground", + "palette--background", + "palette--accent", + "palette--accent-hover", + "palette--border", + "palette--surface-muted", + "palette--primary-0", + "palette--primary-10", + "palette--primary-20", + "palette--primary-30", + "palette--primary-40", + "palette--primary-50", + "palette--surface-0", + "palette--surface-10", + "palette--surface-20", + "palette--surface-30", + "palette--surface-40", + "palette--surface-50", + "palette--success", + "palette--error", + "palette--disabled", + "palette-bg--accent", + "palette-bg--background", + "palette-bg--surface", + } + + +# === Singleton-style global instance === +_theme_instance: Optional[ThemeManager] = None + + +def get_theme(theme: Literal["dark", "light"] = "dark") -> ThemeManager: + global _theme_instance + if _theme_instance is None: + _theme_instance = ThemeManager(theme) + return _theme_instance diff --git a/agentm/theme/styles.base.tcss b/agentm/theme/styles.base.tcss new file mode 100644 index 0000000..2c938b0 --- /dev/null +++ b/agentm/theme/styles.base.tcss @@ -0,0 +1,225 @@ +/* === Global App Styles === */ + +Screen { + background: {{BACKGROUND}}; + color: {{FOREGROUND}}; +} + +/* === Resets === */ + +* { + padding: 0 1; + border: none; +} + +/* === Headers === */ + +# Header, .header { +# dock: top; +# height: 3; +# content-align: center middle; +# background: {{SURFACE_10}}; +# color: {{ACCENT}}; +# text-style: bold; +# padding: 1 2; +# border: solid {{BORDER}}; +# } + +/* === Buttons === */ + +Button { + background: {{SURFACE_10}}; + color: {{ACCENT}}; + border: solid {{ACCENT}}; + padding: 1 2; + margin: 1; + content-align: center middle; + text-style: bold; +} + +Button:hover { + background: {{ACCENT}}; + color: {{BACKGROUND}}; +} + +Button:focus { + background: {{SURFACE_20}}; + border: solid {{ACCENT}}; +} + +/* === Inputs === */ + +Input { + background: {{SURFACE_10}}; + color: {{FOREGROUND}}; + border: solid {{BORDER}}; +} + +Input:focus { + background: {{SURFACE_20}}; + border: solid {{ACCENT}}; +} + +/* === Alerts === */ + +.success { + color: {{SUCCESS}}; +} + +.warning { + color: {{ACCENT}}; +} + +.error { + color: {{ERROR}}; +} + +/* === Login Form === */ + +#login_form { + layout: vertical; + align-horizontal: center; + align-vertical: middle; + width: 60%; + height: auto; + padding: 2 4; + border: solid {{BORDER}}; +} + +#status_message { + color: {{ACCENT}}; + padding: 1; +} + +#pw_row { + layout: horizontal; + padding: 0; +} + +#pw_row > * { + margin-right: 1; +} + +#pw_row > *:last-child { + margin-right: 0; +} + +/* === Loading Overlay === */ + +#loading_overlay { + dock: top; + background: {{SURFACE_10}}; + color: {{FOREGROUND}}; + padding: 1 2; + text-style: bold; + content-align: center middle; +} + +/* === Game Layout === */ + +.centered_layout { + layout: vertical; + align-horizontal: center; + align-vertical: middle; + padding: 2; + width: 100%; +} + +.rom_rows_container { + layout: vertical; + align-horizontal: center; +} + +.game_card { + width: auto; + height: auto; + max-width: 60; + margin: 1; + padding: 1; + background: {{SURFACE_10}}; + color: {{ACCENT}}; + border: solid {{BORDER}}; + content-align: center middle; + text-style: bold; + text-align: center; +} + +.game_card:hover { + background: {{SURFACE_20}}; + color: {{ACCENT_HOVER}}; +} + +Static { + text-align: center; +} + +Horizontal { + content-align: center middle; +} + +.game_card:focus { + background: {{SURFACE_20}}; + border: solid {{ACCENT}}; +} + +.game_card:disabled { + background: {{SURFACE_10}}; + color: {{BORDER}}; +} + +.game_card_clicked { + background: {{ACCENT}}; + color: {{BACKGROUND}}; +} + +.offset_card { + width: 11; + height: auto; + visibility: hidden; +} + +.rom_row { + layout: horizontal; + align-horizontal: center; + align-vertical: middle; + padding: 1 2; + width: 100%; +} + +.info_confirm_row { + layout: horizontal; + width: 100%; + height: auto; + padding: 1 2; + align-vertical: top; +} + +#confirm_button { + width: 20%; + height: auto; + content-align: center middle; +} + +.confirm_button:hover { + background: {{ACCENT}}; + color: {{BACKGROUND}}; +} + +Button:disabled { + background: {{DISABLED_BG}}; + color: {{DISABLED}}; + border: solid {{DISABLED_BORDER}}; + text-style: dim; +} + +.game_info { + padding: 1 2; + width: 100%; + margin-right: 1; + color: {{ACCENT}}; +} + +#game_info_box { + width: 80%; + height: auto; +} diff --git a/agentm/assets/styles.tcss b/agentm/theme/styles.tcss similarity index 79% rename from agentm/assets/styles.tcss rename to agentm/theme/styles.tcss index c29fa34..c29051e 100644 --- a/agentm/assets/styles.tcss +++ b/agentm/theme/styles.tcss @@ -14,21 +14,21 @@ Screen { /* === Headers === */ -Header, .header { - dock: top; - height: 3; - content-align: center middle; - background: #1f1f1f; - color: #ed7d3a; - text-style: bold; - padding: 1 2; - border: solid #3a9bed; -} +# Header, .header { +# dock: top; +# height: 3; +# content-align: center middle; +# background: #282828; +# color: #ed7d3a; +# text-style: bold; +# padding: 1 2; +# border: solid #3a9bed; +# } /* === Buttons === */ Button { - background: #1f1f1f; + background: #282828; color: #ed7d3a; border: solid #ed7d3a; padding: 1 2; @@ -43,20 +43,20 @@ Button:hover { } Button:focus { - background: #2a2a2a; + background: #3f3f3f; border: solid #ed7d3a; } /* === Inputs === */ Input { - background: #1f1f1f; + background: #282828; color: #f0f0f0; border: solid #3a9bed; } Input:focus { - background: #2a2a2a; + background: #3f3f3f; border: solid #ed7d3a; } @@ -108,7 +108,7 @@ Input:focus { #loading_overlay { dock: top; - background: #1f1f1f; + background: #282828; color: #f0f0f0; padding: 1 2; text-style: bold; @@ -130,42 +130,40 @@ Input:focus { align-horizontal: center; } -.rom_row { - layout: horizontal; - align-horizontal: center; - align-vertical: middle; - padding: 1 2; - width: 100%; -} - - .game_card { width: auto; height: auto; max-width: 60; margin: 1; padding: 1; - background: #1f1f1f; + background: #282828; color: #ed7d3a; border: solid #3a9bed; content-align: center middle; text-style: bold; text-align: center; - } .game_card:hover { - background: #2a2a2a; - color: #ed7d3a; + background: #3f3f3f; + color: rgb(236, 194, 169); +} + +Static { + text-align: center; +} + +Horizontal { + content-align: center middle; } .game_card:focus { - background: #2a2a2a; + background: #3f3f3f; border: solid #ed7d3a; } .game_card:disabled { - background: #1f1f1f; + background: #282828; color: #3a9bed; } @@ -174,27 +172,20 @@ Input:focus { color: #0e0e0e; } - .offset_card { width: 11; height: auto; visibility: hidden; } -/* ✅ Keep this for consistent row layout */ .rom_row { layout: horizontal; align-horizontal: center; - padding: 1 1; + align-vertical: middle; + padding: 1 2; + width: 100%; } -.rom_row { - layout: horizontal; - align-horizontal: center; - padding: 1 2; /* maybe increase vertical padding */ -} - - .info_confirm_row { layout: horizontal; width: 100%; @@ -203,7 +194,6 @@ Input:focus { align-vertical: top; } - #confirm_button { width: 20%; height: auto; @@ -215,12 +205,21 @@ Input:focus { color: #0e0e0e; } +Button:disabled { + background: #444444; + color: #999999; + border: solid #666666; + text-style: dim; +} + .game_info { padding: 1 2; width: 100%; + margin-right: 1; color: #ed7d3a; } #game_info_box { width: 80%; -} \ No newline at end of file + height: auto; +} diff --git a/agentm/views/home.py b/agentm/views/home.py index f2bf2b0..c69d3f6 100644 --- a/agentm/views/home.py +++ b/agentm/views/home.py @@ -6,7 +6,10 @@ from textual.reactive import reactive from textual.widget import Widget from rich.panel import Panel from rich.console import Group -from PIL import Image, ImageEnhance, ImageFilter +from rich.table import Table +from rich.rule import Rule +from rich.markup import escape +from PIL import Image, ImageFilter import os from rich_pixels import Pixels @@ -14,6 +17,9 @@ from rich_pixels._renderer import HalfcellRenderer from agentm.utils.logger import log_with_caller from agentm.logic.roms import get_verified_roms, GAME_FILES +from agentm.theme.palette import get_theme + +palette = get_theme() class GameSelected(Message): @@ -26,7 +32,8 @@ class ProgressWidget(Widget): message = reactive("Loading...") def render(self) -> str: - return f"[bold cyan]{self.message}[/bold cyan]" + return f"[bold {palette.ACCENT}]{self.message}[/]" + class GameAccordion(Static): @@ -39,15 +46,25 @@ class GameAccordion(Static): image_path = os.path.abspath(self.metadata.get("image_path", "")) self.image_renderable = self.load_image_scaled(image_path) + self.ascii_title = self.load_ascii_art() super().__init__(id=f"accordion_{self.safe_id}", classes="game_card") + def load_ascii_art(self) -> Static: + game_id = self.metadata.get("game_id", "").lower() + ascii_path = os.path.join("agentm", "assets", "game_titles", f"{game_id}.txt") + try: + with open(ascii_path, "r", encoding="utf-8") as f: + return Static(f"[bold {palette.ACCENT}]{f.read()}[/]", markup=True) + except FileNotFoundError: + return Static(f"[bold {palette.ACCENT}]{self.title.upper()}[/]", markup=True) + def load_image_scaled(self, path: str): try: with Image.open(path) as img: if img.mode != "RGBA": img = img.convert("RGBA") - scale_factor = 0.10 + scale_factor = 0.098 target_width = int(img.width * scale_factor) target_height = int(img.height * scale_factor) resized = img.resize( @@ -66,17 +83,9 @@ class GameAccordion(Static): self._render_height = 16 return f"[red]Failed to load image[/red]\n[dim]{e}]" - def render(self): - return Panel( - Group( - self.image_renderable, - "", # spacer - f"[center][bold orange1][u]{self.title.upper()}[/u][/bold orange1][/center]\n", - ), - border_style="bright_magenta", - padding=(1, 2), - expand=False - ) + def compose(self): + yield Static(self.image_renderable) + yield self.ascii_title async def on_click(self): await self.display_info() @@ -85,16 +94,22 @@ class GameAccordion(Static): async def display_info(self): meta = self.metadata log_with_caller("debug", f"Showing shared info for {self.rom_file}") - info_lines = [ - f"[b]Title:[/b] {meta['title']}", - f"[b]Game ID:[/b] {meta['game_id']}", - f"[b]Difficulty:[/b] {meta.get('difficulty_min')} - {meta.get('difficulty_max')}", - f"[b]Characters:[/b] {', '.join(meta.get('characters', []))}", - f"[b]Keywords:[/b] {', '.join(meta.get('keywords', []))}", - f"[b]SHA256:[/b] {meta['sha256']}", - ] - self.parent_view.shared_info_box.update("\n".join(info_lines)) + + table = Table.grid(expand=True) + table.add_column(ratio=1) + table.add_column() + table.add_row("[b]Title:[/b]", meta['title']) + table.add_row("[b]Game ID:[/b]", meta['game_id']) + table.add_row("[b]Difficulty:[/b]", f"{meta.get('difficulty_min')} - {meta.get('difficulty_max')}") + table.add_row("[b]Characters:[/b]", ", ".join(meta.get("characters", []))) + table.add_row("[b]Keywords:[/b]", ", ".join(meta.get("keywords", []))) + table.add_row("[b]SHA256:[/b]", meta["sha256"]) + + self.parent_view.shared_info_box.update( + Panel(Group(table, Rule(style="dim")), title="Game Info", border_style=palette.BORDER, expand=True) + ) self.parent_view.shared_confirm_button.label = f"✅ Confirm {meta['title']}" + self.parent_view.shared_confirm_button.disabled = False self.parent_view.selected_game = meta @@ -109,7 +124,7 @@ class HomeView(Screen): def compose(self): self.logo = Static( - "[b bright_magenta]\n\n" + f"[bold {palette.ACCENT}]\n\n" " █████╗ ██████╗ ███████╗ ███╗ ██╗ ████████╗ ███╗ ███╗\n" "██╔══██╗ ██╔════╝ ██╔════╝ ████╗ ██║ ╚══██╔══╝ ████╗ ████║\n" "███████║ ██║ ███╗ █████╗ ██╔██╗██║ ██║ ██╔████╔██║\n" @@ -119,13 +134,30 @@ class HomeView(Screen): classes="header", expand=False, ) + + self.welcome_text = Static( + "This is an unofficial DIAMBRA launcher to help you easily train, evaluate, and submit RL agents for fighting games.", + classes="body", + expand=False + ) + self.progress_text = ProgressWidget() + self.loading_container = Vertical( + Static(f"[bold {palette.SUCCESS}]LOADING...[/]", expand=True), + self.progress_text, + id="loading_container" + ) + + # This will be the main container we later modify + self.dynamic_container = Vertical(self.loading_container, id="dynamic_content") + yield Vertical( self.logo, - Static("[bold green]LOADING...[/bold green]", expand=True), - self.progress_text, - id="loading_container" + self.welcome_text, + self.dynamic_container, + id="home_screen_container", + classes="centered_layout" ) async def on_mount(self): @@ -151,36 +183,42 @@ class HomeView(Screen): async def display_verified_roms(self, verified_roms): log_with_caller("info", f"ROM verification complete. Total: {len(verified_roms)}") - self.query_one("#loading_container").remove() - - self.shared_info_box = Static("", id="game_info_box", classes="game_info") - self.shared_confirm_button = Button("✅ Confirm", id="confirm_button", classes="confirm_button") + self.shared_info_box = Static( + Panel( + "[dim]Select a Game From Above to Start[/dim]", + title="Game Info", + border_style=palette.BORDER, + expand=True + ), + id="game_info_box", + classes="game_info", + expand=True + ) + self.shared_confirm_button = Button( + "✅ Confirm", + id="confirm_button", + classes="confirm_button", + disabled=True + ) self.rom_scroll_row = HorizontalScroll(id="rom_scroll_row", classes="rom_row") - await self.mount( - Vertical( - self.logo, - Static("🎮 Welcome to Agent M", classes="header", expand=False), - Static( - "This is an unofficial DIAMBRA launcher to help you easily train, evaluate, and submit RL agents for fighting games.\n\n" - "Verified ROMs are shown below. Click one to view game info and confirm selection.", - classes="body", - expand=False - ), - self.rom_scroll_row, - Horizontal( - self.shared_info_box, - self.shared_confirm_button, - id="info_row", - classes="info_confirm_row", - ), - id="home_screen_container", - classes="centered_layout" + new_content = Vertical( + self.rom_scroll_row, + Horizontal( + self.shared_info_box, + self.shared_confirm_button, + id="info_row", + classes="info_confirm_row" ) ) + # Replace loading content with new UI below logo and welcome + dynamic_container = self.query_one("#dynamic_content") + await dynamic_container.remove_children() + await dynamic_container.mount(new_content) + # Populate games for rom in verified_roms: await self.rom_scroll_row.mount(GameAccordion( title=rom["title"], @@ -189,6 +227,7 @@ class HomeView(Screen): parent_view=self )) + async def on_button_pressed(self, event: Button.Pressed) -> None: if event.button.id == "confirm_button" and self.selected_game: await self.app.push_screen("training", self.selected_game)