diff --git a/src/toad/native_layout.py b/src/toad/native_layout.py new file mode 100644 index 000000000..cd72c9061 --- /dev/null +++ b/src/toad/native_layout.py @@ -0,0 +1,32 @@ +"""Native widgets declare their layout before their first stylesheet application.""" + +from typing import ClassVar + +from textual.app import App +from textual.css.native import NativeDeclaration, NativeStyleResolver +from textual.widget import Widget + + +class NativeLayout(Widget): + """The shared registration point for a widget's own layout declarations.""" + + def _post_register(self, app: App) -> None: + super()._post_register(app) + self.apply_native_layout() + + def apply_native_layout(self) -> None: + """A widget without additional layout keeps its inherited native defaults.""" + + +class NativeTheme(Widget): + """Native property declarations follow the app's current theme value owner.""" + + native_theme: ClassVar[tuple[NativeDeclaration, ...]] = () + + def native_declarations(self) -> tuple[NativeDeclaration, ...]: + """Providers may extend this cooperatively when their declarations overlap.""" + return self.native_theme + + def apply_native_styles(self, resolver: NativeStyleResolver) -> None: + super().apply_native_styles(resolver) + self.styles.merge(resolver.native_styles(*self.native_declarations())) diff --git a/src/toad/screens/session_resume_modal.py b/src/toad/screens/session_resume_modal.py index 0cffcda7c..648fa86b4 100644 --- a/src/toad/screens/session_resume_modal.py +++ b/src/toad/screens/session_resume_modal.py @@ -3,6 +3,12 @@ from textual import containers, getters, on, widgets from textual.app import ComposeResult +from textual.css.native import Keyword, NativeDeclaration, Percent, TextVariable, Variable +from textual.css.styles import Styles +from textual.widgets._markdown import MarkdownH1 +from ui_theme.scheme import PrimaryColour, WarningColour + +from toad.native_layout import NativeLayout, NativeTheme from toad.screens.modal import DismissibleScreen from toad.db import DB, Session @@ -14,23 +20,73 @@ """ -class SessionResumeModal(DismissibleScreen[Session]): +class ResumeHeading(NativeLayout, MarkdownH1): + def apply_native_layout(self) -> None: + self.styles.margin = (1, 0) + + +class ResumeHelp(widgets.Markdown): + BLOCKS = widgets.Markdown.BLOCKS | {f"h{ResumeHeading.LEVEL}": ResumeHeading} + + +class ResumeWarning(NativeTheme, NativeLayout, widgets.Static): + native_theme = ( + NativeDeclaration.of(Styles.border, Keyword("round"), Variable(WarningColour.name)), + NativeDeclaration.of(Styles.color, TextVariable(WarningColour.name)), + ) + + def apply_native_layout(self) -> None: + self.styles.padding = (0, 1) + self.styles.margin = (0, 0, 1, 1) + + +class ResumeFrame(NativeTheme, NativeLayout, containers.VerticalGroup): + native_theme = ( + NativeDeclaration.of(Styles.border, Keyword("thick"), Variable(PrimaryColour.name), Percent(20)), + ) + + def apply_native_layout(self) -> None: + self.styles.margin = (2, 4, 1, 4) + self.styles.padding = (0, 1, 0, 0) + self.styles.max_width = 100 + self.styles.height = "auto" + + +class ResumeTableContainer(NativeLayout, containers.Center): + def apply_native_layout(self) -> None: + self.styles.height = "auto" + self.styles.padding = (0, 0, 0, 1) + + +class ResumeTable(NativeLayout, widgets.DataTable): + def apply_native_layout(self) -> None: + self.styles.width = "1fr" + self.styles.height = 12 + + +class ResumeButtons(NativeLayout, containers.HorizontalGroup): + def apply_native_layout(self) -> None: + self.styles.margin = (1, 0, 0, 0) + self.styles.align = ("right", "top") + + +class SessionResumeModal(NativeLayout, DismissibleScreen[Session]): """Dialog to select a session to resume.""" - CSS_PATH = "session_resume_modal.tcss" + session_table = getters.query_one("#sessions", ResumeTable) - session_table = getters.query_one("#sessions", widgets.DataTable) + def apply_native_layout(self) -> None: + self.styles.align = ("center", "middle") def compose(self) -> ComposeResult: - with containers.VerticalGroup(id="container"): - yield widgets.Markdown(HELP) - yield widgets.Static( + with ResumeFrame(id="container"): + yield ResumeHelp(HELP) + yield ResumeWarning( "⚠ Not all ACP agents currently support resume", - classes="warning", ) - with containers.Center(id="table-container"): - yield widgets.DataTable(id="sessions", cursor_type="row") - with containers.HorizontalGroup(id="buttons"): + with ResumeTableContainer(id="table-container"): + yield ResumeTable(id="sessions", cursor_type="row") + with ResumeButtons(id="buttons"): yield widgets.Button( "Resume", id="resume", variant="primary", disabled=True ) diff --git a/src/toad/screens/session_resume_modal.tcss b/src/toad/screens/session_resume_modal.tcss deleted file mode 100644 index 30e1a25c0..000000000 --- a/src/toad/screens/session_resume_modal.tcss +++ /dev/null @@ -1,35 +0,0 @@ -SessionResumeModal { - align: center middle; - .warning { - border: round $warning; - padding: 0 1; - color: $text-warning; - margin: 0 0 1 1; - } - #container { - margin: 2 4 1 4; - padding: 0 1 0 0; - max-width: 100; - height: auto; - border: thick $primary 20%; - - Markdown { - MarkdownH1 { - margin: 1 0; - } - } - - #table-container { - height: auto; - padding: 0 0 0 1; - DataTable { - width: 1fr; - height: 12; - } - } - #buttons { - margin-top: 1; - align: right top; - } - } -} \ No newline at end of file