131 lines
3.6 KiB
Python
131 lines
3.6 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from dataclasses import dataclass
|
||
|
|
|
||
|
|
from textual import events, on
|
||
|
|
from textual.app import ComposeResult
|
||
|
|
from textual.binding import Binding
|
||
|
|
from textual.containers import Center, Horizontal, ItemGrid, Vertical, VerticalScroll
|
||
|
|
from textual.demo.page import PageScreen
|
||
|
|
from textual.widgets import Footer, Label, Link, Markdown, Static
|
||
|
|
from textual.demo._project_stars import STARS
|
||
|
|
from textual.demo._project_data import PROJECTS, ProjectInfo
|
||
|
|
|
||
|
|
PROJECTS_MD = """\
|
||
|
|
# Projects
|
||
|
|
|
||
|
|
There are many amazing Open Source Textual apps available for download.
|
||
|
|
And many more still in development.
|
||
|
|
|
||
|
|
See below for a small selection!
|
||
|
|
"""
|
||
|
|
|
||
|
|
|
||
|
|
class Project(Vertical, can_focus=True, can_focus_children=False):
|
||
|
|
"""Display project information and open repo links."""
|
||
|
|
|
||
|
|
ALLOW_MAXIMIZE = True
|
||
|
|
DEFAULT_CSS = """
|
||
|
|
Project {
|
||
|
|
width: 1fr;
|
||
|
|
height: auto;
|
||
|
|
padding: 0 1;
|
||
|
|
border: tall transparent;
|
||
|
|
box-sizing: border-box;
|
||
|
|
&:focus {
|
||
|
|
border: tall $text-primary;
|
||
|
|
background: $primary 20%;
|
||
|
|
&.link {
|
||
|
|
color: red !important;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
#title { text-style: bold; width: 1fr; }
|
||
|
|
#author { text-style: italic; }
|
||
|
|
.stars {
|
||
|
|
color: $text-accent;
|
||
|
|
text-align: right;
|
||
|
|
text-style: bold;
|
||
|
|
width: auto;
|
||
|
|
}
|
||
|
|
.header { height: 1; }
|
||
|
|
.link {
|
||
|
|
color: $text-accent;
|
||
|
|
text-style: underline;
|
||
|
|
}
|
||
|
|
.description { color: $text-muted; }
|
||
|
|
&.-hover { opacity: 1; }
|
||
|
|
}
|
||
|
|
"""
|
||
|
|
|
||
|
|
BINDINGS = [
|
||
|
|
Binding(
|
||
|
|
"enter",
|
||
|
|
"open_repository",
|
||
|
|
"open repo",
|
||
|
|
tooltip="Open the GitHub repository in your browser",
|
||
|
|
)
|
||
|
|
]
|
||
|
|
|
||
|
|
def __init__(self, project_info: ProjectInfo) -> None:
|
||
|
|
self.project_info = project_info
|
||
|
|
super().__init__()
|
||
|
|
|
||
|
|
def compose(self) -> ComposeResult:
|
||
|
|
info = self.project_info
|
||
|
|
with Horizontal(classes="header"):
|
||
|
|
yield Label(info.title, id="title")
|
||
|
|
yield Label(f"★ {STARS[info.title]}", classes="stars")
|
||
|
|
yield Label(info.author, id="author")
|
||
|
|
yield Link(info.url, tooltip="Click to open project repository")
|
||
|
|
yield Static(info.description, classes="description")
|
||
|
|
|
||
|
|
@on(events.Enter)
|
||
|
|
@on(events.Leave)
|
||
|
|
def on_enter(self, event: events.Enter):
|
||
|
|
event.stop()
|
||
|
|
self.set_class(self.is_mouse_over, "-hover")
|
||
|
|
|
||
|
|
def action_open_repository(self) -> None:
|
||
|
|
self.app.open_url(self.project_info.url)
|
||
|
|
|
||
|
|
|
||
|
|
class ProjectsScreen(PageScreen):
|
||
|
|
AUTO_FOCUS = None
|
||
|
|
CSS = """
|
||
|
|
ProjectsScreen {
|
||
|
|
align-horizontal: center;
|
||
|
|
ItemGrid {
|
||
|
|
margin: 2 4;
|
||
|
|
padding: 1 2;
|
||
|
|
background: $boost;
|
||
|
|
width: 1fr;
|
||
|
|
height: auto;
|
||
|
|
grid-gutter: 1 1;
|
||
|
|
grid-rows: auto;
|
||
|
|
keyline:thin $foreground 30%;
|
||
|
|
}
|
||
|
|
Markdown { margin: 0; padding: 0 2; max-width: 100; background: transparent; }
|
||
|
|
}
|
||
|
|
"""
|
||
|
|
|
||
|
|
def compose(self) -> ComposeResult:
|
||
|
|
with VerticalScroll() as container:
|
||
|
|
container.can_focus = False
|
||
|
|
with Center():
|
||
|
|
yield Markdown(PROJECTS_MD)
|
||
|
|
with ItemGrid(min_column_width=40):
|
||
|
|
for project in PROJECTS:
|
||
|
|
yield Project(project)
|
||
|
|
yield Footer()
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
from textual.app import App
|
||
|
|
|
||
|
|
class GameApp(App):
|
||
|
|
def get_default_screen(self) -> Screen:
|
||
|
|
return ProjectsScreen()
|
||
|
|
|
||
|
|
app = GameApp()
|
||
|
|
app.run()
|