diff --git a/docs/examples/guide/structure.py b/docs/examples/guide/structure.py index d1766420a..11f285a74 100644 --- a/docs/examples/guide/structure.py +++ b/docs/examples/guide/structure.py @@ -7,7 +7,7 @@ from textual.widget import Widget class Clock(Widget): """A clock app.""" - CSS = """ + DEFAULT_CSS = """ Clock { content-align: center middle; } diff --git a/docs/examples/light_dark.py b/docs/examples/light_dark.py index 5e2e9229f..be4351258 100644 --- a/docs/examples/light_dark.py +++ b/docs/examples/light_dark.py @@ -4,7 +4,7 @@ from textual.widgets import Button class ButtonApp(App): - CSS = """ + DEFAULT_CSS = """ Button { width: 100%; } diff --git a/sandbox/borders.py b/sandbox/borders.py index 04161dd62..e67456ffa 100644 --- a/sandbox/borders.py +++ b/sandbox/borders.py @@ -8,7 +8,7 @@ from textual.widgets import Placeholder class VerticalContainer(Widget): - CSS = """ + DEFAULT_CSS = """ VerticalContainer { layout: vertical; overflow: hidden auto; @@ -24,7 +24,7 @@ class VerticalContainer(Widget): class Introduction(Widget): - CSS = """ + DEFAULT_CSS = """ Introduction { background: indigo; color: white; diff --git a/sandbox/color_names.py b/sandbox/color_names.py index 6e0d904e9..464b65c0c 100644 --- a/sandbox/color_names.py +++ b/sandbox/color_names.py @@ -23,7 +23,7 @@ class ColorDisplay(Widget, can_focus=True): class ColorNames(App): - CSS = """ + DEFAULT_CSS = """ ColorDisplay { height: 1; } diff --git a/sandbox/fifty.py b/sandbox/fifty.py index 0a1f91027..c946e47ae 100644 --- a/sandbox/fifty.py +++ b/sandbox/fifty.py @@ -5,7 +5,7 @@ from textual.widget import Widget class FiftyApp(App): - CSS = """ + DEFAULT_CSS = """ Screen { layout: vertical; } @@ -24,6 +24,7 @@ class FiftyApp(App): yield layout.Horizontal(Widget(), Widget()) yield layout.Horizontal(Widget(), Widget()) + app = FiftyApp() if __name__ == "__main__": app.run() diff --git a/sandbox/scroll_to_widget.py b/sandbox/scroll_to_widget.py index 81b0bf83c..209439c16 100644 --- a/sandbox/scroll_to_widget.py +++ b/sandbox/scroll_to_widget.py @@ -9,7 +9,7 @@ placeholders_count = 12 class VerticalContainer(Widget): - CSS = """ + DEFAULT_CSS = """ VerticalContainer { layout: vertical; overflow: hidden auto; @@ -26,7 +26,7 @@ class VerticalContainer(Widget): class Introduction(Widget): - CSS = """ + DEFAULT_CSS = """ Introduction { background: indigo; color: white; diff --git a/sandbox/vertical_container.py b/sandbox/vertical_container.py index bb1fca46f..a06e1cd94 100644 --- a/sandbox/vertical_container.py +++ b/sandbox/vertical_container.py @@ -10,7 +10,7 @@ initial_placeholders_count = 4 class VerticalContainer(Widget): - CSS = """ + DEFAULT_CSS = """ VerticalContainer { layout: vertical; overflow: hidden auto; @@ -30,7 +30,7 @@ class VerticalContainer(Widget): class Introduction(Widget): - CSS = """ + DEFAULT_CSS = """ Introduction { background: indigo; color: white; diff --git a/sandbox/will/add_remove.py b/sandbox/will/add_remove.py index 5d95bd4da..a34ddd617 100644 --- a/sandbox/will/add_remove.py +++ b/sandbox/will/add_remove.py @@ -11,7 +11,7 @@ class Thing(Static): class AddRemoveApp(App): - CSS = """ + DEFAULT_CSS = """ #buttons { dock: top; height: auto; diff --git a/sandbox/will/center.py b/sandbox/will/center.py index b5f5a08b2..dddaf086c 100644 --- a/sandbox/will/center.py +++ b/sandbox/will/center.py @@ -3,7 +3,7 @@ from textual.widgets import Static class CenterApp(App): - CSS = """ + DEFAULT_CSS = """ CenterApp Screen { layout: center; diff --git a/sandbox/will/center2.py b/sandbox/will/center2.py index 1711fd09a..7cfd0b7ea 100644 --- a/sandbox/will/center2.py +++ b/sandbox/will/center2.py @@ -4,7 +4,7 @@ from textual.widgets import Static class CenterApp(App): - CSS = """ + DEFAULT_CSS = """ #sidebar { dock: left; diff --git a/sandbox/will/just_a_box.py b/sandbox/will/just_a_box.py index 11c46589f..ea35fa128 100644 --- a/sandbox/will/just_a_box.py +++ b/sandbox/will/just_a_box.py @@ -10,7 +10,7 @@ from textual.widget import Widget class Box(Widget, can_focus=True): - CSS = "#box {background: blue;}" + DEFAULT_CSS = "#box {background: blue;}" def render(self) -> RenderableType: return Panel("Box") diff --git a/sandbox/will/screens.py b/sandbox/will/screens.py index c3f655fee..3a9f34dc2 100644 --- a/sandbox/will/screens.py +++ b/sandbox/will/screens.py @@ -21,7 +21,7 @@ class NewScreen(Screen): class ScreenApp(App): - CSS = """ + DEFAULT_CSS = """ ScreenApp Screen { background: #111144; color: white; diff --git a/sandbox/will/tree.py b/sandbox/will/tree.py index 158009232..75b1ef283 100644 --- a/sandbox/will/tree.py +++ b/sandbox/will/tree.py @@ -5,7 +5,7 @@ from textual.widgets import DirectoryTree class TreeApp(App): - CSS = """ + DEFAULT_CSS = """ Screen { overflow: auto; diff --git a/src/textual/app.py b/src/textual/app.py index 207d480cb..410c526d0 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -142,7 +142,11 @@ class App(Generic[ReturnType], DOMNode): """ - CSS = """ + # Inline CSS for quick scripts (generally css_path should be preferred.) + CSS = "" + + # Default (lowest priority) CSS + DEFAULT_CSS = """ App { background: $background; color: $text-background; @@ -683,7 +687,6 @@ class App(Generic[ReturnType], DOMNode): async def _on_css_change(self) -> None: """Called when the CSS changes (if watch_css is True).""" if self.css_path is not None: - try: time = perf_counter() stylesheet = self.stylesheet.copy() @@ -1060,6 +1063,16 @@ class App(Generic[ReturnType], DOMNode): self.stylesheet.add_source( css, path=path, is_default_css=True, tie_breaker=tie_breaker ) + if self.CSS: + try: + app_css_path = ( + f"{inspect.getfile(self.__class__)}:{self.__class__.__name__}" + ) + except TypeError: + app_css_path = f"{self.__class__.__name__}" + self.stylesheet.add_source( + self.CSS, path=app_css_path, is_default_css=False + ) except Exception as error: self.on_exception(error) self._print_error_renderables() diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index 8dec85133..a476460a9 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -571,7 +571,7 @@ class Styles(StylesBase): Args: specificity (Specificity3): A node specificity. is_default_rules (bool): True if the rules we're extracting are - default (i.e. in Widget.CSS) rules. False if they're from user defined CSS. + default (i.e. in Widget.DEFAULT_CSS) rules. False if they're from user defined CSS. Returns: list[tuple[str, Specificity5, Any]]]: A list containing a tuple of , . diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index 388c44501..6bba42357 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -555,7 +555,7 @@ if __name__ == "__main__": print(app.tree) print() - CSS = """ + DEFAULT_CSS = """ App > View { layout: dock; docks: sidebar=left | widgets=top; diff --git a/src/textual/devtools/borders.py b/src/textual/devtools/borders.py index 27813cdfa..376bd79d4 100644 --- a/src/textual/devtools/borders.py +++ b/src/textual/devtools/borders.py @@ -14,7 +14,7 @@ Where the fear has gone there will be nothing. Only I will remain.""" class BorderButtons(layout.Vertical): - CSS = """ + DEFAULT_CSS = """ BorderButtons { dock: left; width: 24; @@ -34,7 +34,7 @@ class BorderButtons(layout.Vertical): class BorderApp(App): """Demonstrates the border styles.""" - CSS = """ + DEFAULT_CSS = """ Static { margin: 2 4; padding: 2 4; diff --git a/src/textual/dom.py b/src/textual/dom.py index a79058e78..ba53f0775 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -54,8 +54,8 @@ class NoParent(Exception): class DOMNode(MessagePump): """The base class for object that can be in the Textual DOM (App and Widget)""" - # Custom CSS - CSS: ClassVar[str] = "" + # CSS defaults + DEFAULT_CSS: ClassVar[str] = "" # Default classes argument if not supplied DEFAULT_CLASSES: str = "" @@ -198,7 +198,7 @@ class DOMNode(MessagePump): return f"{base.__name__}" for tie_breaker, base in enumerate(self._node_bases): - css = base.CSS.strip() + css = base.DEFAULT_CSS.strip() if css: css_stack.append((get_path(base), css, -tie_breaker)) diff --git a/src/textual/layout.py b/src/textual/layout.py index b1cc7a008..b06c347c3 100644 --- a/src/textual/layout.py +++ b/src/textual/layout.py @@ -4,7 +4,7 @@ from .widget import Widget class Container(Widget): """Simple container widget, with vertical layout.""" - CSS = """ + DEFAULT_CSS = """ Container { layout: vertical; overflow: auto; @@ -16,13 +16,13 @@ class Vertical(Container): """A container widget to align children vertically.""" # Blank CSS is important, otherwise you get a clone of Container - CSS = "" + DEFAULT_CSS = "" class Horizontal(Container): """A container widget to align children horizontally.""" - CSS = """ + DEFAULT_CSS = """ Horizontal { layout: horizontal; } @@ -32,7 +32,7 @@ class Horizontal(Container): class Center(Container): """A container widget to align children in the center.""" - CSS = """ + DEFAULT_CSS = """ Center { layout: center; } diff --git a/src/textual/screen.py b/src/textual/screen.py index 303c76ea0..afeffdc46 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -29,7 +29,7 @@ UPDATE_PERIOD: Final = 1 / 60 class Screen(Widget): """A widget for the root of the app.""" - CSS = """ + DEFAULT_CSS = """ Screen { layout: vertical; overflow-y: auto; diff --git a/src/textual/scroll_view.py b/src/textual/scroll_view.py index 6561905db..6397c9b04 100644 --- a/src/textual/scroll_view.py +++ b/src/textual/scroll_view.py @@ -16,7 +16,7 @@ class ScrollView(Widget): """ - CSS = """ + DEFAULT_CSS = """ ScrollView { overflow-y: auto; diff --git a/src/textual/widget.py b/src/textual/widget.py index a1da87848..db9affb9a 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -72,7 +72,7 @@ class Widget(DOMNode): """ - CSS = """ + DEFAULT_CSS = """ Widget{ scrollbar-background: $panel-darken-1; scrollbar-background-hover: $panel-darken-2; diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py index 3121ef9c4..68c50839a 100644 --- a/src/textual/widgets/_button.py +++ b/src/textual/widgets/_button.py @@ -29,7 +29,7 @@ class InvalidButtonVariant(Exception): class Button(Widget, can_focus=True): """A simple clickable button.""" - CSS = """ + DEFAULT_CSS = """ Button { width: auto; min-width: 10; diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index c4059c000..71a667c08 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -106,7 +106,7 @@ class Coord(NamedTuple): class DataTable(ScrollView, Generic[CellType], can_focus=True): - CSS = """ + DEFAULT_CSS = """ DataTable { background: $surface; color: $text-surface; diff --git a/src/textual/widgets/_footer.py b/src/textual/widgets/_footer.py index 66211a450..987193087 100644 --- a/src/textual/widgets/_footer.py +++ b/src/textual/widgets/_footer.py @@ -13,7 +13,7 @@ from ..widget import Widget @rich.repr.auto class Footer(Widget): - CSS = """ + DEFAULT_CSS = """ Footer { background: $accent; color: $text-accent; diff --git a/src/textual/widgets/_header.py b/src/textual/widgets/_header.py index e54c6ce6d..26cb4bea0 100644 --- a/src/textual/widgets/_header.py +++ b/src/textual/widgets/_header.py @@ -11,7 +11,7 @@ from ..reactive import Reactive, watch class HeaderIcon(Widget): """Display an 'icon' on the left of the header.""" - CSS = """ + DEFAULT_CSS = """ HeaderIcon { dock: left; padding: 0 1; @@ -28,7 +28,7 @@ class HeaderIcon(Widget): class HeaderClock(Widget): """Display a clock on the right of the header.""" - CSS = """ + DEFAULT_CSS = """ HeaderClock { dock: right; width: auto; @@ -50,7 +50,7 @@ class HeaderClock(Widget): class HeaderTitle(Widget): """Display the title / subtitle in the header.""" - CSS = """ + DEFAULT_CSS = """ HeaderTitle { content-align: center middle; width: 100%; @@ -70,7 +70,7 @@ class HeaderTitle(Widget): class Header(Widget): """A header widget with icon and clock.""" - CSS = """ + DEFAULT_CSS = """ Header { dock: top; width: 100%; diff --git a/src/textual/widgets/_pretty.py b/src/textual/widgets/_pretty.py index 3d4369e52..ff43350f5 100644 --- a/src/textual/widgets/_pretty.py +++ b/src/textual/widgets/_pretty.py @@ -7,7 +7,7 @@ from ..widget import Widget class Pretty(Widget): - CSS = """ + DEFAULT_CSS = """ Static { height: auto; } diff --git a/src/textual/widgets/_static.py b/src/textual/widgets/_static.py index 1813d530f..824248ce7 100644 --- a/src/textual/widgets/_static.py +++ b/src/textual/widgets/_static.py @@ -25,7 +25,7 @@ def _check_renderable(renderable: object): class Static(Widget): - CSS = """ + DEFAULT_CSS = """ Static { height: auto; } diff --git a/src/textual/widgets/_tree_control.py b/src/textual/widgets/_tree_control.py index 86d2b0e9e..b3200c071 100644 --- a/src/textual/widgets/_tree_control.py +++ b/src/textual/widgets/_tree_control.py @@ -169,7 +169,7 @@ class TreeClick(Generic[NodeDataType], Message, bubble=True): class TreeControl(Generic[NodeDataType], Widget, can_focus=True): - CSS = """ + DEFAULT_CSS = """ TreeControl { background: $panel; color: $text-panel; diff --git a/src/textual/widgets/text_input.py b/src/textual/widgets/text_input.py index 4e9c71f62..966faf2a1 100644 --- a/src/textual/widgets/text_input.py +++ b/src/textual/widgets/text_input.py @@ -112,7 +112,7 @@ class TextInput(TextWidgetBase, can_focus=True): suggestion will be displayed as dim text similar to suggestion text in the zsh or fish shells. """ - CSS = """ + DEFAULT_CSS = """ TextInput { width: auto; background: $surface; @@ -417,7 +417,7 @@ class TextInput(TextWidgetBase, can_focus=True): class TextArea(Widget): - CSS = """ + DEFAULT_CSS = """ TextArea { overflow: auto auto; height: 5; background: $primary-darken-1; } """ @@ -428,7 +428,7 @@ class TextArea(Widget): class TextAreaChild(TextWidgetBase, can_focus=True): # TODO: Not nearly ready for prime-time, but it exists to help # model the superclass. - CSS = "TextAreaChild { height: auto; background: $primary-darken-1; }" + DEFAULT_CSS = "TextAreaChild { height: auto; background: $primary-darken-1; }" STOP_PROPAGATE = {"tab", "shift+tab"} def render(self) -> RenderableType: diff --git a/tests/css/test_stylesheet.py b/tests/css/test_stylesheet.py index ac732f6f7..7713a604b 100644 --- a/tests/css/test_stylesheet.py +++ b/tests/css/test_stylesheet.py @@ -106,7 +106,7 @@ def test_stylesheet_apply_user_css_over_widget_css(): user_css = ".a {color: red; tint: yellow;}" class MyWidget(Widget): - CSS = ".a {color: blue !important; background: lime;}" + DEFAULT_CSS = ".a {color: blue !important; background: lime;}" node = MyWidget() node.add_class("a") diff --git a/tests/test_integration_layout.py b/tests/test_integration_layout.py index d60112393..46106dd5f 100644 --- a/tests/test_integration_layout.py +++ b/tests/test_integration_layout.py @@ -110,7 +110,7 @@ async def test_composition_of_vertical_container_with_children( expected_placeholders_offset_x: int, ): class VerticalContainer(Widget): - CSS = ( + DEFAULT_CSS = ( """ VerticalContainer { layout: vertical; @@ -304,7 +304,7 @@ async def test_scrollbar_size_impact_on_the_layout( class LargeWidgetContainer(Widget): # TODO: Once textual#581 ("Default versus User CSS") is solved the following CSS should just use the # "LargeWidgetContainer" selector, without having to use a more specific one to be able to override Widget's CSS: - CSS = """ + DEFAULT_CSS = """ #large-widget-container { width: 20; height: 20; diff --git a/tests/test_integration_scrolling.py b/tests/test_integration_scrolling.py index b3059b146..8c4175c62 100644 --- a/tests/test_integration_scrolling.py +++ b/tests/test_integration_scrolling.py @@ -48,7 +48,7 @@ async def test_scroll_to_widget( last_screen_expected_placeholder_ids: Sequence[int], ): class VerticalContainer(Widget): - CSS = """ + DEFAULT_CSS = """ VerticalContainer { layout: vertical; overflow: hidden auto; @@ -60,7 +60,7 @@ async def test_scroll_to_widget( """ class MyTestApp(AppTest): - CSS = """ + DEFAULT_CSS = """ Placeholder { height: 5; /* minimal height to see the name of a Placeholder */ }