Add game logo files and enhance home view layout with grid and button components

This commit is contained in:
mscrnt 2025-05-25 09:50:33 -07:00
parent 9154f8ed3e
commit d62820dd80
14 changed files with 292 additions and 216 deletions

View File

@ -0,0 +1,10 @@
██████╗ ███████╗ █████╗ ██████╗ ██████╗ ██████╗ █████╗ ██╗ ██╗██╗ ██╗███████╗
██╔══██╗██╔════╝██╔══██╗██╔══██╗ ██╔═══██╗██╔══██╗ ██╔══██╗██║ ██║██║ ██║██╔════╝
██║ ██║█████╗ ███████║██║ ██║ ██║ ██║██████╔╝ ███████║██║ ██║██║ ██║█████╗
██║ ██║██╔══╝ ██╔══██║██║ ██║ ██║ ██║██╔══██╗ ██╔══██║██║ ██║╚██╗ ██╔╝██╔══╝
██████╔╝███████╗██║ ██║██████╔╝ ╚██████╔╝██║ ██║ ██║ ██║███████╗██║ ╚████╔╝ ███████╗
╚═════╝ ╚══════╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝ ╚══════╝

View File

@ -0,0 +1,10 @@
████████╗██╗ ██╗███████╗ ██╗ ██╗██╗███╗ ██╗ ██████╗ ██████╗ ███████╗ ███████╗██╗ ██████╗ ██╗ ██╗████████╗███████╗██████╗ ███████╗
╚══██╔══╝██║ ██║██╔════╝ ██║ ██╔╝██║████╗ ██║██╔════╝ ██╔═══██╗██╔════╝ ██╔════╝██║██╔════╝ ██║ ██║╚══██╔══╝██╔════╝██╔══██╗██╔════╝
██║ ███████║█████╗ █████╔╝ ██║██╔██╗ ██║██║ ███╗ ██║ ██║█████╗ █████╗ ██║██║ ███╗███████║ ██║ █████╗ ██████╔╝███████╗
██║ ██╔══██║██╔══╝ ██╔═██╗ ██║██║╚██╗██║██║ ██║ ██║ ██║██╔══╝ ██╔══╝ ██║██║ ██║██╔══██║ ██║ ██╔══╝ ██╔══██╗╚════██║
██║ ██║ ██║███████╗ ██║ ██╗██║██║ ╚████║╚██████╔╝ ╚██████╔╝██║ ██║ ██║╚██████╔╝██║ ██║ ██║ ███████╗██║ ██║███████║
╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝

View File

@ -0,0 +1,10 @@
███╗ ███╗ █████╗ ██████╗ ██╗ ██╗███████╗██╗ ██╗ ██╗███████╗ ██████╗ █████╗ ██████╗ ██████╗ ██████╗ ███╗ ███╗
████╗ ████║██╔══██╗██╔══██╗██║ ██║██╔════╝██║ ██║ ██║██╔════╝ ██╔════╝██╔══██╗██╔══██╗██╔════╝██╔═══██╗████╗ ████║
██╔████╔██║███████║██████╔╝██║ ██║█████╗ ██║ ██║ ██║███████╗ ██║ ███████║██████╔╝██║ ██║ ██║██╔████╔██║
██║╚██╔╝██║██╔══██║██╔══██╗╚██╗ ██╔╝██╔══╝ ██║ ╚██╗ ██╔╝╚════██║ ██║ ██╔══██║██╔═══╝ ██║ ██║ ██║██║╚██╔╝██║
██║ ╚═╝ ██║██║ ██║██║ ██║ ╚████╔╝ ███████╗███████╗ ╚████╔╝ ███████║██╗ ╚██████╗██║ ██║██║ ╚██████╗╚██████╔╝██║ ╚═╝ ██║
╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝

View File

@ -0,0 +1,10 @@
███████╗ █████╗ ███╗ ███╗██╗ ██╗██████╗ █████╗ ██╗ ███████╗██╗ ██╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗███╗ ██╗ ██╗ ██╗
██╔════╝██╔══██╗████╗ ████║██║ ██║██╔══██╗██╔══██╗██║ ██╔════╝██║ ██║██╔═══██╗██╔══██╗██╔═══██╗██║ ██║████╗ ██║ ██║ ██║
███████╗███████║██╔████╔██║██║ ██║██████╔╝███████║██║ ███████╗███████║██║ ██║██║ ██║██║ ██║██║ █╗ ██║██╔██╗ ██║ ██║ ██║
╚════██║██╔══██║██║╚██╔╝██║██║ ██║██╔══██╗██╔══██║██║ ╚════██║██╔══██║██║ ██║██║ ██║██║ ██║██║███╗██║██║╚██╗██║ ╚██╗ ██╔╝
███████║██║ ██║██║ ╚═╝ ██║╚██████╔╝██║ ██║██║ ██║██║ ███████║██║ ██║╚██████╔╝██████╔╝╚██████╔╝╚███╔███╔╝██║ ╚████║ ╚████╔╝
╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══╝╚══╝ ╚═╝ ╚═══╝ ╚═══╝

View File

@ -0,0 +1,10 @@
███████╗████████╗██████╗ ███████╗███████╗████████╗ ███████╗██╗ ██████╗ ██╗ ██╗████████╗███████╗██████╗ ██╗██╗██╗
██╔════╝╚══██╔══╝██╔══██╗██╔════╝██╔════╝╚══██╔══╝ ██╔════╝██║██╔════╝ ██║ ██║╚══██╔══╝██╔════╝██╔══██╗ ██║██║██║
███████╗ ██║ ██████╔╝█████╗ █████╗ ██║ █████╗ ██║██║ ███╗███████║ ██║ █████╗ ██████╔╝ ██║██║██║
╚════██║ ██║ ██╔══██╗██╔══╝ ██╔══╝ ██║ ██╔══╝ ██║██║ ██║██╔══██║ ██║ ██╔══╝ ██╔══██╗ ██║██║██║
███████║ ██║ ██║ ██║███████╗███████╗ ██║ ██║ ██║╚██████╔╝██║ ██║ ██║ ███████╗██║ ██║ ██║██║██║
╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝╚═╝╚═╝

View File

@ -0,0 +1,10 @@
███████╗ ██████╗ ██╗ ██╗██╗ ██████╗ █████╗ ██╗ ██╗██████╗ ██╗ ██╗██████╗
██╔════╝██╔═══██╗██║ ██║██║ ██╔════╝██╔══██╗██║ ██║██╔══██╗██║ ██║██╔══██╗
███████╗██║ ██║██║ ██║██║ ██║ ███████║██║ ██║██████╔╝██║ ██║██████╔╝
╚════██║██║ ██║██║ ██║██║ ██║ ██╔══██║██║ ██║██╔══██╗██║ ██║██╔══██╗
███████║╚██████╔╝╚██████╔╝███████╗ ╚██████╗██║ ██║███████╗██║██████╔╝╚██████╔╝██║ ██║
╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝╚═════╝ ╚═════╝ ╚═╝ ╚═╝

View File

@ -0,0 +1,10 @@
████████╗███████╗██╗ ██╗██╗ ██╗███████╗███╗ ██╗ ████████╗ █████╗ ██████╗ ████████╗ ██████╗ ██╗ ██╗██████╗ ███╗ ██╗ █████╗ ███╗ ███╗███████╗███╗ ██╗████████╗
╚══██╔══╝██╔════╝██║ ██╔╝██║ ██╔╝██╔════╝████╗ ██║ ╚══██╔══╝██╔══██╗██╔════╝ ╚══██╔══╝██╔═══██╗██║ ██║██╔══██╗████╗ ██║██╔══██╗████╗ ████║██╔════╝████╗ ██║╚══██╔══╝
██║ █████╗ █████╔╝ █████╔╝ █████╗ ██╔██╗ ██║ ██║ ███████║██║ ███╗ ██║ ██║ ██║██║ ██║██████╔╝██╔██╗ ██║███████║██╔████╔██║█████╗ ██╔██╗ ██║ ██║
██║ ██╔══╝ ██╔═██╗ ██╔═██╗ ██╔══╝ ██║╚██╗██║ ██║ ██╔══██║██║ ██║ ██║ ██║ ██║██║ ██║██╔══██╗██║╚██╗██║██╔══██║██║╚██╔╝██║██╔══╝ ██║╚██╗██║ ██║
██║ ███████╗██║ ██╗██║ ██╗███████╗██║ ╚████║ ██║ ██║ ██║╚██████╔╝ ██║ ╚██████╔╝╚██████╔╝██║ ██║██║ ╚████║██║ ██║██║ ╚═╝ ██║███████╗██║ ╚████║ ██║
╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝ ╚═╝

View File

@ -0,0 +1,10 @@
███╗ ███╗ ██████╗ ██████╗ ████████╗ █████╗ ██╗ ██╗ ██╗ ██████╗ ███╗ ███╗██████╗ █████╗ ████████╗ ██████╗
████╗ ████║██╔═══██╗██╔══██╗╚══██╔══╝██╔══██╗██║ ██║ ██╔╝██╔═══██╗████╗ ████║██╔══██╗██╔══██╗╚══██╔══╝ ╚════██╗
██╔████╔██║██║ ██║██████╔╝ ██║ ███████║██║ █████╔╝ ██║ ██║██╔████╔██║██████╔╝███████║ ██║ █████╔╝
██║╚██╔╝██║██║ ██║██╔══██╗ ██║ ██╔══██║██║ ██╔═██╗ ██║ ██║██║╚██╔╝██║██╔══██╗██╔══██║ ██║ ╚═══██╗
██║ ╚═╝ ██║╚██████╔╝██║ ██║ ██║ ██║ ██║███████╗ ██║ ██╗╚██████╔╝██║ ╚═╝ ██║██████╔╝██║ ██║ ██║ ██████╔╝
╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝

View File

@ -0,0 +1,10 @@
██╗ ██╗ ███╗ ███╗███████╗███╗ ██╗ ██╗ ██╗███████╗ ███████╗████████╗██████╗ ███████╗███████╗████████╗ ███████╗██╗ ██████╗ ██╗ ██╗████████╗███████╗██████╗
╚██╗██╔╝ ████╗ ████║██╔════╝████╗ ██║ ██║ ██║██╔════╝ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██╔════╝╚══██╔══╝ ██╔════╝██║██╔════╝ ██║ ██║╚══██╔══╝██╔════╝██╔══██╗
╚███╔╝█████╗██╔████╔██║█████╗ ██╔██╗ ██║ ██║ ██║███████╗ ███████╗ ██║ ██████╔╝█████╗ █████╗ ██║ █████╗ ██║██║ ███╗███████║ ██║ █████╗ ██████╔╝
██╔██╗╚════╝██║╚██╔╝██║██╔══╝ ██║╚██╗██║ ╚██╗ ██╔╝╚════██║ ╚════██║ ██║ ██╔══██╗██╔══╝ ██╔══╝ ██║ ██╔══╝ ██║██║ ██║██╔══██║ ██║ ██╔══╝ ██╔══██╗
██╔╝ ██╗ ██║ ╚═╝ ██║███████╗██║ ╚████║ ╚████╔╝ ███████║ ███████║ ██║ ██║ ██║███████╗███████╗ ██║ ██║ ██║╚██████╔╝██║ ██║ ██║ ███████╗██║ ██║
╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝ ╚═══╝ ╚══════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝

View File

@ -1,7 +1,13 @@
# agentm/main.py import subprocess
from agentm.app import AgentMApp from agentm.app import AgentMApp
if __name__ == "__main__": if __name__ == "__main__":
# Run the generate_theme_css module silently
subprocess.run(
["python", "-m", "agentm.theme.generate_theme_css", "--silent"],
check=True
)
# Launch the app
app = AgentMApp() app = AgentMApp()
app.run() app.run()

View File

@ -1,6 +1,10 @@
from agentm.theme.palette import get_theme import sys
from pathlib import Path
import re import re
from pathlib import Path
from agentm.theme.palette import get_theme
# Check for --silent in command-line arguments
silent = "--silent" in sys.argv
# Load dark theme instance # Load dark theme instance
theme = get_theme("dark") theme = get_theme("dark")
@ -16,14 +20,16 @@ template = template_path.read_text()
# Find all placeholders like {{FOREGROUND}}, {{BACKGROUND}}, etc. # Find all placeholders like {{FOREGROUND}}, {{BACKGROUND}}, etc.
tokens = set(re.findall(r"{{\s*([A-Z0-9_]+)\s*}}", template)) tokens = set(re.findall(r"{{\s*([A-Z0-9_]+)\s*}}", template))
# Replace them with actual values from theme # Replace tokens
for token in tokens: for token in tokens:
value = getattr(theme, token, None) value = getattr(theme, token, None)
if value is not None: if value is not None:
template = template.replace(f"{{{{{token}}}}}", value) template = template.replace(f"{{{{{token}}}}}", value)
else: elif not silent:
print(f"⚠️ Warning: Theme token '{token}' not found in ThemeManager") print(f"⚠️ Warning: Theme token '{token}' not found in ThemeManager")
# Write final output # Write final output
output_path.write_text(template) output_path.write_text(template)
print(f"✅ Synced themed CSS written to: {output_path}")
if not silent:
print(f"✅ Synced themed CSS written to: {output_path}")

View File

@ -14,7 +14,7 @@ Screen {
/* === Headers === */ /* === Headers === */
# Header, .header { # .header {
# dock: top; # dock: top;
# height: 3; # height: 3;
# content-align: center middle; # content-align: center middle;
@ -31,10 +31,13 @@ Button {
background: {{SURFACE_10}}; background: {{SURFACE_10}};
color: {{ACCENT}}; color: {{ACCENT}};
border: solid {{ACCENT}}; border: solid {{ACCENT}};
padding: 1 2; padding: 1;
margin: 1; margin: 0;
content-align: center middle; content-align: center middle;
text-style: bold; text-style: bold;
width: 100%;
min-height: 3;
text-align: center;
} }
Button:hover { Button:hover {
@ -47,6 +50,42 @@ Button:focus {
border: solid {{ACCENT}}; border: solid {{ACCENT}};
} }
Button:disabled {
background: {{DISABLED_BG}};
color: {{DISABLED}};
border: solid {{DISABLED_BORDER}};
text-style: dim;
}
/* === Grid Layout === */
.rom_grid {
grid-size: 5;
grid-gutter: 1;
padding: 0 1; /* Was: 1 2 */
margin: 0;
width: 100%;
align-horizontal: center;
align-vertical: middle;
height: auto; /* Ensure it can grow */
}
#rom_grid_scroll {
max-height: 30vh; /* was: height: 40vh */
overflow-y: auto;
scrollbar-gutter: stable;
padding: 0;
margin: 0;
border-bottom: solid {{BORDER}};
}
/* Optionally ensure the parent container scrolls properly */
#game_content_layout {
height: auto;
width: 100%;
padding: 1 2;
}
/* === Inputs === */ /* === Inputs === */
Input { Input {
@ -115,7 +154,7 @@ Input:focus {
content-align: center middle; content-align: center middle;
} }
/* === Game Layout === */ /* === Layout === */
.centered_layout { .centered_layout {
layout: vertical; layout: vertical;
@ -125,30 +164,6 @@ Input:focus {
width: 100%; 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 { Static {
text-align: center; text-align: center;
} }
@ -157,47 +172,16 @@ Horizontal {
content-align: center middle; 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 { .info_confirm_row {
layout: horizontal;
width: 100%;
height: auto; height: auto;
padding: 1 2; padding: 0 0;
align-vertical: top; margin: 0;
} }
#confirm_button { #confirm_button {
width: 20%; width: 100%; /* Take full width of its container */
height: auto; min-width: 20; /* Increase minimum width if needed */
content-align: center middle; max-width: 100%; /* Optional: avoid oversizing */
} }
.confirm_button:hover { .confirm_button:hover {
@ -205,21 +189,30 @@ Horizontal {
color: {{BACKGROUND}}; color: {{BACKGROUND}};
} }
Button:disabled {
background: {{DISABLED_BG}};
color: {{DISABLED}};
border: solid {{DISABLED_BORDER}};
text-style: dim;
}
.game_info { .game_info {
padding: 1 2; padding: 0 1;
margin: 0;
width: 100%; width: 100%;
margin-right: 1;
color: {{ACCENT}}; color: {{ACCENT}};
} }
#game_info_box { #game_info_box {
width: 80%; width: 80%;
height: auto; height: auto;
max-height: 50vh; /* Adjusted for better visibility */
padding: 1;
overflow-y: auto;
}
Button.game_button {
min-height: 3;
}
#confirm_button_container {
width: 20%;
min-width: 20;
align-vertical: middle;
align-horizontal: center;
height: 100%;
padding-left: 1;
} }

View File

@ -14,7 +14,7 @@ Screen {
/* === Headers === */ /* === Headers === */
# Header, .header { # .header {
# dock: top; # dock: top;
# height: 3; # height: 3;
# content-align: center middle; # content-align: center middle;
@ -31,10 +31,13 @@ Button {
background: #282828; background: #282828;
color: #ed7d3a; color: #ed7d3a;
border: solid #ed7d3a; border: solid #ed7d3a;
padding: 1 2; padding: 1;
margin: 1; margin: 0;
content-align: center middle; content-align: center middle;
text-style: bold; text-style: bold;
width: 100%;
min-height: 3;
text-align: center;
} }
Button:hover { Button:hover {
@ -47,6 +50,42 @@ Button:focus {
border: solid #ed7d3a; border: solid #ed7d3a;
} }
Button:disabled {
background: #444444;
color: #999999;
border: solid #666666;
text-style: dim;
}
/* === Grid Layout === */
.rom_grid {
grid-size: 5;
grid-gutter: 1;
padding: 0 1; /* Was: 1 2 */
margin: 0;
width: 100%;
align-horizontal: center;
align-vertical: middle;
height: auto; /* Ensure it can grow */
}
#rom_grid_scroll {
max-height: 30vh; /* was: height: 40vh */
overflow-y: auto;
scrollbar-gutter: stable;
padding: 0;
margin: 0;
border-bottom: solid #3a9bed;
}
/* Optionally ensure the parent container scrolls properly */
#game_content_layout {
height: auto;
width: 100%;
padding: 1 2;
}
/* === Inputs === */ /* === Inputs === */
Input { Input {
@ -115,7 +154,7 @@ Input:focus {
content-align: center middle; content-align: center middle;
} }
/* === Game Layout === */ /* === Layout === */
.centered_layout { .centered_layout {
layout: vertical; layout: vertical;
@ -125,30 +164,6 @@ Input:focus {
width: 100%; width: 100%;
} }
.rom_rows_container {
layout: vertical;
align-horizontal: center;
}
.game_card {
width: auto;
height: auto;
max-width: 60;
margin: 1;
padding: 1;
background: #282828;
color: #ed7d3a;
border: solid #3a9bed;
content-align: center middle;
text-style: bold;
text-align: center;
}
.game_card:hover {
background: #3f3f3f;
color: rgb(236, 194, 169);
}
Static { Static {
text-align: center; text-align: center;
} }
@ -157,47 +172,16 @@ Horizontal {
content-align: center middle; content-align: center middle;
} }
.game_card:focus {
background: #3f3f3f;
border: solid #ed7d3a;
}
.game_card:disabled {
background: #282828;
color: #3a9bed;
}
.game_card_clicked {
background: #ed7d3a;
color: #0e0e0e;
}
.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 { .info_confirm_row {
layout: horizontal;
width: 100%;
height: auto; height: auto;
padding: 1 2; padding: 0 0;
align-vertical: top; margin: 0;
} }
#confirm_button { #confirm_button {
width: 20%; width: 100%; /* Take full width of its container */
height: auto; min-width: 20; /* Increase minimum width if needed */
content-align: center middle; max-width: 100%; /* Optional: avoid oversizing */
} }
.confirm_button:hover { .confirm_button:hover {
@ -205,21 +189,30 @@ Horizontal {
color: #0e0e0e; color: #0e0e0e;
} }
Button:disabled {
background: #444444;
color: #999999;
border: solid #666666;
text-style: dim;
}
.game_info { .game_info {
padding: 1 2; padding: 0 1;
margin: 0;
width: 100%; width: 100%;
margin-right: 1;
color: #ed7d3a; color: #ed7d3a;
} }
#game_info_box { #game_info_box {
width: 80%; width: 80%;
height: auto; height: auto;
max-height: 50vh; /* Adjusted for better visibility */
padding: 1;
overflow-y: auto;
}
Button.game_button {
min-height: 3;
}
#confirm_button_container {
width: 20%;
min-width: 20;
align-vertical: middle;
align-horizontal: center;
height: 100%;
padding-left: 1;
} }

View File

@ -1,6 +1,6 @@
from textual.screen import Screen from textual.screen import Screen
from textual.widgets import Static, Button from textual.widgets import Static, Button
from textual.containers import Vertical, Horizontal, HorizontalScroll from textual.containers import Vertical, Horizontal, VerticalScroll, Grid
from textual.message import Message from textual.message import Message
from textual.reactive import reactive from textual.reactive import reactive
from textual.widget import Widget from textual.widget import Widget
@ -8,18 +8,12 @@ from rich.panel import Panel
from rich.console import Group from rich.console import Group
from rich.table import Table from rich.table import Table
from rich.rule import Rule from rich.rule import Rule
from rich.markup import escape from rich.text import Text
from PIL import Image, ImageFilter
import os
from rich_pixels import Pixels
from rich_pixels._renderer import HalfcellRenderer
from agentm.utils.logger import log_with_caller from agentm.utils.logger import log_with_caller
from agentm.logic.roms import get_verified_roms, GAME_FILES from agentm.logic.roms import get_verified_roms, GAME_FILES
from agentm.theme.palette import get_theme from agentm.theme.palette import get_theme
from agentm.components.footer import AgentMFooter from agentm.components.footer import AgentMFooter
from agentm.components.game_image_preview import GameImagePreview
palette = get_theme() palette = get_theme()
@ -37,30 +31,19 @@ class ProgressWidget(Widget):
return f"[bold {palette.ACCENT}]{self.message}[/]" return f"[bold {palette.ACCENT}]{self.message}[/]"
class GameCardButton(Button):
class GameAccordion(Static): def __init__(self, metadata: dict, parent_view):
def __init__(self, title: str, rom_file: str, metadata: dict, parent_view):
super().__init__(
id=f"accordion_{rom_file.replace('.', '_').replace('-', '_')}",
classes="game_card"
)
self.title = title
self.rom_file = rom_file
self.metadata = metadata self.metadata = metadata
self.parent_view = parent_view self.parent_view = parent_view
safe_id = metadata["rom_file"].replace(".", "_").replace("-", "_")
label = Text(metadata["title"], style=f"bold {palette.ACCENT}")
self.title_label = Static( super().__init__(
f"[b {palette.ACCENT}]{escape(self.title.upper())}[/]\n", label=label,
classes="game_title", id=f"game_btn_{safe_id}",
markup=True classes="game_button"
) )
self.styles.min_height = 3 # Ensures buttons stay visible even in constrained space
self.image_path = os.path.abspath(self.metadata.get("image_path", ""))
def compose(self):
yield self.title_label
yield GameImagePreview(image_path=self.image_path)
async def on_click(self): async def on_click(self):
await self.display_info() await self.display_info()
@ -68,19 +51,19 @@ class GameAccordion(Static):
async def display_info(self): async def display_info(self):
meta = self.metadata meta = self.metadata
log_with_caller("debug", f"Showing shared info for {self.rom_file}")
table = Table.grid(expand=True) table = Table.grid(padding=(0, 1))
table.add_column(ratio=1) table.add_column("Key", style="bold underline", no_wrap=True)
table.add_column() table.add_column("Value", style=palette.ACCENT, overflow="fold")
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( table.add_row("Title", meta["title"])
table.add_row("Game ID", meta["game_id"])
table.add_row("Difficulty", f"{meta.get('difficulty_min')} - {meta.get('difficulty_max')}")
table.add_row("Characters", ", ".join(meta.get("characters", [])))
table.add_row("Keywords", ", ".join(meta.get("keywords", [])))
table.add_row("SHA256", meta["sha256"])
self.parent_view.shared_info_content.update(
Panel(Group(table, Rule(style="dim")), title="Game Info", border_style=palette.BORDER, expand=True) 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.label = f"✅ Confirm {meta['title']}"
@ -88,13 +71,12 @@ class GameAccordion(Static):
self.parent_view.selected_game = meta self.parent_view.selected_game = meta
class HomeView(Screen): class HomeView(Screen):
BINDINGS = [("escape", "app.quit", "Quit")] BINDINGS = [("escape", "app.quit", "Quit")]
def highlight_selected(self, selected_widget: GameAccordion): def highlight_selected(self, selected_widget: GameCardButton):
for card in self.rom_scroll_row.children: for card in self.rom_grid.children:
if isinstance(card, GameAccordion): if isinstance(card, GameCardButton):
card.remove_class("game_card_clicked") card.remove_class("game_card_clicked")
selected_widget.add_class("game_card_clicked") selected_widget.add_class("game_card_clicked")
@ -125,7 +107,6 @@ class HomeView(Screen):
id="loading_container" id="loading_container"
) )
# This will be the main container we later modify
self.dynamic_container = Vertical(self.loading_container, id="dynamic_content") self.dynamic_container = Vertical(self.loading_container, id="dynamic_content")
yield Vertical( yield Vertical(
@ -148,7 +129,7 @@ class HomeView(Screen):
for idx, rom in enumerate(verified_roms, start=1): for idx, rom in enumerate(verified_roms, start=1):
self.app.call_from_thread( self.app.call_from_thread(
lambda title=rom['title'], idx=idx: setattr( lambda title=rom["title"], idx=idx: setattr(
self.progress_text, "message", self.progress_text, "message",
f"Processing {title} ({idx}/{total})" f"Processing {title} ({idx}/{total})"
) )
@ -161,49 +142,56 @@ class HomeView(Screen):
async def display_verified_roms(self, verified_roms): async def display_verified_roms(self, verified_roms):
log_with_caller("info", f"ROM verification complete. Total: {len(verified_roms)}") log_with_caller("info", f"ROM verification complete. Total: {len(verified_roms)}")
self.shared_info_box = Static( self.shared_info_content = Static(
Panel( Panel(
"[dim]Select a Game From Above to Start[/dim]", "[dim]Select a Game From the Grid Below to Start[/dim]",
title="Game Info", title="Game Info",
border_style=palette.BORDER, border_style=palette.BORDER,
expand=True expand=True
), ),
id="game_info_box", id="info_panel_static"
classes="game_info",
expand=True
) )
self.shared_info_box = VerticalScroll(
self.shared_info_content,
id="game_info_box",
classes="game_info"
)
self.shared_info_box.styles.height = 7 # Around 5 visible rows
self.shared_confirm_button = Button( self.shared_confirm_button = Button(
"✅ Confirm", "✅ Confirm",
id="confirm_button", id="confirm_button",
classes="confirm_button", classes="confirm_button",
disabled=True disabled=True
) )
self.rom_scroll_row = HorizontalScroll(id="rom_scroll_row", classes="rom_row")
self.rom_grid = Grid(id="rom_grid", classes="rom_grid")
self.rom_grid.styles.grid_columns = ["1fr"] * 5
self.rom_grid.styles.grid_gap = (0, 1)
self.rom_grid.styles.width = "100%"
rom_grid_scroll = VerticalScroll(self.rom_grid, id="rom_grid_scroll")
rom_grid_scroll.styles.max_height = "30vh"
new_content = Vertical( new_content = Vertical(
self.rom_scroll_row, rom_grid_scroll,
Horizontal( Horizontal(
self.shared_info_box, self.shared_info_box,
self.shared_confirm_button, Vertical(self.shared_confirm_button, id="confirm_button_container"),
id="info_row", id="info_row",
classes="info_confirm_row" classes="info_confirm_row"
) ),
id="game_content_layout",
classes="game_content_layout"
) )
# Replace loading content with new UI below logo and welcome
dynamic_container = self.query_one("#dynamic_content") dynamic_container = self.query_one("#dynamic_content")
await dynamic_container.remove_children() await dynamic_container.remove_children()
await dynamic_container.mount(new_content) await dynamic_container.mount(new_content)
# Populate games
for rom in verified_roms: for rom in verified_roms:
await self.rom_scroll_row.mount(GameAccordion( await self.rom_grid.mount(GameCardButton(metadata=rom, parent_view=self))
title=rom["title"],
rom_file=rom["rom_file"],
metadata=rom,
parent_view=self
))
async def on_button_pressed(self, event: Button.Pressed) -> None: async def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "confirm_button" and self.selected_game: if event.button.id == "confirm_button" and self.selected_game: