From 6ee4d41bb7a39238a18949f5648773562c6a1c9b Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 24 Aug 2022 11:19:29 +0100 Subject: [PATCH] docs --- docs/guide/CSS.md | 49 +++++++++------ docs/guide/devtools.md | 6 +- docs/reference/color.md | 1 + docs/reference/dom_node.md | 1 + mkdocs.yml | 18 +++--- src/textual/actions.py | 13 +++- src/textual/color.py | 56 +++++++++++++++--- src/textual/dom.py | 24 +++++--- src/textual/geometry.py | 78 ++++++++++++++++++++---- src/textual/scroll_view.py | 118 ------------------------------------- src/textual/widget.py | 71 +++++++++++++++++++--- 11 files changed, 253 insertions(+), 182 deletions(-) create mode 100644 docs/reference/color.md create mode 100644 docs/reference/dom_node.md delete mode 100644 src/textual/scroll_view.py diff --git a/docs/guide/CSS.md b/docs/guide/CSS.md index 790a4235a..3f25742de 100644 --- a/docs/guide/CSS.md +++ b/docs/guide/CSS.md @@ -16,7 +16,7 @@ CSS is typically stored in an external file with the extension `.css` alongside Let's look at some Textual CSS. -```css +```sass Header { dock: top; height: 3; @@ -28,7 +28,7 @@ Header { This is an example of a CSS _rule set_. There may be many such sections in any given CSS file. -The first line is a _selector_, which tells Textual which Widget(s) to modify. In the above example, the styles will be applied to a widget defined in the Python class `Header`. +Let's break this CSS code down a bit. ```css hl_lines="1" Header { @@ -40,7 +40,7 @@ Header { } ``` -The lines inside the curly braces contains CSS _rules_, which consist of a rule name and rule value separated by a colon and ending in a semi-colon. Such rules are typically written one per line, but you could add additional rules as long as they are separated by semi-colons. +The first line is a _selector_ which tells Textual which Widget(s) to modify. In the above example, the styles will be applied to a widget defined in the Python class `Header`. ```css hl_lines="2 3 4 5 6" Header { @@ -52,6 +52,8 @@ Header { } ``` +The lines inside the curly braces contains CSS _rules_, which consist of a rule name and rule value separated by a colon and ending in a semi-colon. Such rules are typically written one per line, but you could add additional rules as long as they are separated by semi-colons. + The first rule in the above example reads `"dock: top;"`. The rule name is `dock` which tells Textual to place the widget on a edge of the screen. The text after the colon is `top` which tells Textual to dock to the _top_ of the screen. Other valid values for dock are "right", "bottom", or "left"; but `top` is naturally appropriate for a header. You may be able to guess what some of the the other rules do. We will cover those later. @@ -75,7 +77,7 @@ Let's look at a trivial Textual app. ```{.textual path="docs/examples/guide/dom1.py"} ``` -When you run this code you will have an instance of an app (ExampleApp) in memory. This app class will also create a Screen object. In DOM terms, the Screen is a _child_ of the app. +When you run this code you will have an instance of an `ExampleApp` in memory. This app class will also create a `Screen` object. In DOM terms, the Screen is a _child_ of the app. With the above example, the DOM will look like the following: @@ -121,7 +123,7 @@ To further explore the DOM, we're going to build a simple dialog with a question --8<-- "docs/examples/guide/dom3.py" ``` -We've added a Container to our DOM which (as the name suggests) is a container for other widgets. The container has a number of other widgets passed as positional arguments which will be added as the children of the container. Not all widgets accept child widgets in this way; for instance a Button widget doesn't need any children. +We've added a Container to our DOM which (as the name suggests) is a container for other widgets. The container has a number of other widgets passed as positional arguments which will be added as the children of the container. Not all widgets accept child widgets in this way. A Button widget doesn't require any children, for example. Here's the DOM created by the above code: @@ -149,7 +151,7 @@ You may have noticed that some of the constructors have additional keywords argu Here's the CSS file we are applying: -```python +```sass --8<-- "docs/examples/guide/dom4.css" ``` @@ -175,7 +177,7 @@ Finally, Textual CSS allows you to _live edit_ the styles in your app. If you ru textual run my_app.py --dev ``` -Being able to iterate on the design without restarting the Python code can make it much easier to design beautiful interfaces. +Being able to iterate on the design without restarting the Python code can make it easier and faster to design beautiful interfaces. ## Selectors @@ -198,7 +200,7 @@ class Button(Static): The following rule applies a border to this widget: -```css +```sass Button { border: solid blue; } @@ -206,7 +208,7 @@ Button { The type selector will also match a widget's base classes. Consequently a `Static` selector will also style the button because the `Button` Python class extends `Static`. -```css +```sass Static { background: blue; border: rounded white; @@ -231,15 +233,17 @@ yield Button(id="next") You can match an ID with a selector starting with a hash (`#`). Here is how you might draw a red outline around the above button: -```css +```sass #next { outline: red; } ``` +A Widget's `id` attribute can not be changed after the Widget has been constructed. + ### Class-name selector -Every widget can have a number of class names applied. The term "class" here is borrowed from web CSS, and has a different meaning to a Python class. You can think of a CSS class as a tag of sorts. Widgets with the same tag may share a particular style. +Every widget can have a number of class names applied. The term "class" here is borrowed from web CSS, and has a different meaning to a Python class. You can think of a CSS class as a tag of sorts. Widgets with the same tag will share styles. CSS classes are set via the widgets `classes` parameter in the constructor. Here's an example: @@ -257,7 +261,7 @@ yield Button(classes="error disabled") To match a Widget with a given class in CSS you can precede the class name with a dot (`.`). Here's a rule with a class selector to match the `"success"` class name: -```css +```sass .success { background: green; color: white; @@ -270,19 +274,28 @@ To match a Widget with a given class in CSS you can precede the class name with Class name selectors may be _chained_ together by appending another full stop and class name. The selector will match a widget that has _all_ of the class names set. For instance, the following sets a red background on widgets that have both `error` _and_ `disabled` class names. -```css +```sass .error.disabled { background: darkred; } ``` +Unlike the `id` attribute a Widget's classes can be changed after the Widget was created. Adding and removing CSS classes is the recommended way of changing the display while your app is running. There are a few methods you can use to manage CSS classes. + +- [add_class()][textual.dom.DOMNode.add_class] Adds one or more classes to a widget. +- [remove_class()][textual.dom.DOMNode.remove_class] Removes class name(s) from a widget. +- [toggle_class()][textual.dom.DOMNode.toggle_class] Removes a class name if it is present, or adds the name if its not already present. +- [has_class()][textual.dom.DOMNode.has_class] Checks if a class(es) is set on a widget. +- [classes][textual.dom.DOMNode.classes] Is a frozen set of the class(es) set on a widget. + + ### Universal selector The _universal_ selectors is denoted by an asterisk and will match _all_ widgets. For example, the following will draw a red outline around all widgets: -```css +```sass * { outline: solid red; } @@ -292,7 +305,7 @@ For example, the following will draw a red outline around all widgets: Pseudo classes can be used to match widgets in a particular state. Psuedo classes are set automatically by Textual. For instance, you might want a button to have a green background when the mouse cursor moves over it. We can do this with the `:hover` pseudo selector. -```css +```sass Button:hover { background: green; } @@ -321,7 +334,7 @@ Here's a section of DOM to illustrate this combinator: Let's say we want to make the text of the buttons in the dialog bold, but we _don't_ want to change the Button in the sidebar. We can do this with the following rule: -```css hl_lines="1" +```sass hl_lines="1" #dialog Button { text-style: bold; } @@ -349,7 +362,7 @@ Let's use this to match the Button in the sidebar given the following DOM: We can use the following CSS to style all buttons which have a parent with an ID of `sidebar`: -```css +```sass #sidebar > Button { text-style: underline; } @@ -375,7 +388,7 @@ The specificity rules are usually enough to fix any conflicts in your stylesheet Here's an example that makes buttons blue when hovered over with the mouse, regardless of any other selectors that match Buttons: -```css hl_lines="2" +```sass hl_lines="2" Button:hover { background: blue !important; } diff --git a/docs/guide/devtools.md b/docs/guide/devtools.md index 54237af87..8a3b9b2c5 100644 --- a/docs/guide/devtools.md +++ b/docs/guide/devtools.md @@ -16,7 +16,7 @@ You can run Textual apps with the `run` subcommand. If you supply a path to a Py textual run my_app.py ``` -The `run` sub-command assumes you have a Application instance called `app` in the global scope of your Python file. If the application is called something different, you can specify it with a colon following the filename: +The `run` sub-command assumes you have a App instance called `app` in the global scope of your Python file. If the application is called something different, you can specify it with a colon following the filename: ``` textual run my_app.py:alternative_app @@ -24,7 +24,7 @@ textual run my_app.py:alternative_app !!! note - If the Python file contains a call to app.run() then you can launch the file as you normally would any other Python program. Running your app via `textual run` will give you access to a few Textual features such as dev mode which auto (re) loads your CSS if you change it. + If the Python file contains a call to app.run() then you can launch the file as you normally would any other Python program. Running your app via `textual run` will give you access to a few Textual features such as live editing of CSS files. ## Console @@ -44,7 +44,7 @@ This should look something like the following: In the other console, run your application using `textual run` and the `--dev` switch: ```bash -textual run my_app.py --dev +textual run --dev my_app.py ``` Anything you `print` from your application will be displayed in the console window. You can also call the `log()` method on App and Widget objects for advanced formatting. Try it with `self.log(self.tree)`. diff --git a/docs/reference/color.md b/docs/reference/color.md new file mode 100644 index 000000000..0d1d71759 --- /dev/null +++ b/docs/reference/color.md @@ -0,0 +1 @@ +::: textual.color diff --git a/docs/reference/dom_node.md b/docs/reference/dom_node.md new file mode 100644 index 000000000..90e75c8d9 --- /dev/null +++ b/docs/reference/dom_node.md @@ -0,0 +1 @@ +::: textual.dom.DOMNode diff --git a/mkdocs.yml b/mkdocs.yml index f6c520f31..8cdfb1830 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -6,8 +6,8 @@ nav: - "getting_started.md" - "introduction.md" - Guide: - - "guide/guide.md" - "guide/devtools.md" + - "guide/guide.md" - "guide/CSS.md" - "guide/events.md" @@ -44,19 +44,19 @@ nav: - "styles/color.md" - "styles/content_align.md" - "styles/display.md" - - "styles/min_height.md" - - "styles/max_height.md" - - "styles/min_width.md" - - "styles/max_width.md" - "styles/height.md" - "styles/margin.md" + - "styles/max_height.md" + - "styles/max_width.md" + - "styles/min_height.md" + - "styles/min_width.md" - "styles/offset.md" - "styles/outline.md" - "styles/overflow.md" - "styles/padding.md" - - "styles/scrollbar.md" - - "styles/scrollbar_size.md" - "styles/scrollbar_gutter.md" + - "styles/scrollbar_size.md" + - "styles/scrollbar.md" - "styles/text_style.md" - "styles/tint.md" - "styles/visibility.md" @@ -64,10 +64,13 @@ nav: - Widgets: "/widgets/" - Reference: - "reference/app.md" + - "reference/color.md" + - "reference/dom_node.md" - "reference/events.md" - "reference/geometry.md" - "reference/widget.md" + markdown_extensions: - admonition - def_list @@ -115,6 +118,7 @@ theme: plugins: - search: +- autorefs: - mkdocstrings: default_handler: python handlers: diff --git a/src/textual/actions.py b/src/textual/actions.py index 41839834b..118ce2b51 100644 --- a/src/textual/actions.py +++ b/src/textual/actions.py @@ -12,7 +12,18 @@ class ActionError(Exception): re_action_params = re.compile(r"([\w\.]+)(\(.*?\))") -def parse(action: str) -> tuple[str, tuple[Any, ...]]: +def parse(action: str) -> tuple[str, tuple[object, ...]]: + """Parses an action string. + + Args: + action (str): String containing action. + + Raises: + ActionError: If the action has invalid syntax. + + Returns: + tuple[str, tuple[object, ...]]: Action name and parameters + """ params_match = re_action_params.match(action) if params_match is not None: action_name, action_params_str = params_match.groups() diff --git a/src/textual/color.py b/src/textual/color.py index 7b2be31bf..383ba21c3 100644 --- a/src/textual/color.py +++ b/src/textual/color.py @@ -39,16 +39,22 @@ class HLS(NamedTuple): """A color in HLS format.""" h: float + """Hue""" l: float + """Lightness""" s: float + """Saturation""" class HSV(NamedTuple): """A color in HSV format.""" h: float + """Hue""" s: float + """Saturation""" v: float + """Value""" class Lab(NamedTuple): @@ -103,9 +109,13 @@ class Color(NamedTuple): """A class to represent a single RGB color with alpha.""" r: int + """Red component (0-255)""" g: int + """Green component (0-255)""" b: int + """Blue component (0-255)""" a: float = 1.0 + """Alpha component (0-1)""" @classmethod def from_rich_color(cls, rich_color: RichColor) -> Color: @@ -146,12 +156,22 @@ class Color(NamedTuple): @property def is_transparent(self) -> bool: - """Check if the color is transparent, i.e. has 0 alpha.""" + """Check if the color is transparent, i.e. has 0 alpha. + + Returns: + bool: True if transparent, otherwise False. + + """ return self.a == 0 @property def clamped(self) -> Color: - """Get a color with all components saturated to maximum and minimum values.""" + """Get a color with all components saturated to maximum and minimum values. + + Returns: + Color: A color object. + + """ r, g, b, a = self _clamp = clamp color = Color( @@ -164,7 +184,11 @@ class Color(NamedTuple): @property def rich_color(self) -> RichColor: - """This color encoded in Rich's Color class.""" + """This color encoded in Rich's Color class. + + Returns: + RichColor: A color object as used by Rich. + """ r, g, b, _a = self return RichColor( f"#{r:02x}{g:02x}{b:02x}", _TRUECOLOR, None, ColorTriplet(r, g, b) @@ -172,25 +196,43 @@ class Color(NamedTuple): @property def normalized(self) -> tuple[float, float, float]: - """A tuple of the color components normalized to between 0 and 1.""" + """A tuple of the color components normalized to between 0 and 1. + + Returns: + tuple[float, float, float]: Normalized components. + + """ r, g, b, _a = self return (r / 255, g / 255, b / 255) @property def rgb(self) -> tuple[int, int, int]: - """Get just the red, green, and blue components.""" + """Get just the red, green, and blue components. + + Returns: + tuple[int, int, int]: Color components + """ r, g, b, _ = self return (r, g, b) @property def hls(self) -> HLS: - """Get the color as HLS.""" + """Get the color as HLS. + + Returns: + HLS: + """ r, g, b = self.normalized return HLS(*rgb_to_hls(r, g, b)) @property def brightness(self) -> float: - """Get the human perceptual brightness.""" + """Get the human perceptual brightness. + + Returns: + float: Brightness value (0-1). + + """ r, g, b = self.normalized brightness = (299 * r + 587 * g + 114 * b) / 1000 return brightness diff --git a/src/textual/dom.py b/src/textual/dom.py index d5b0d6679..05d4c7838 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -52,11 +52,7 @@ class NoParent(Exception): @rich.repr.auto class DOMNode(MessagePump): - """A node in a hierarchy of things forming the UI. - - Nodes are mountable and may be styled with CSS. - - """ + """The base class for object that can be in the Textual DOM (App and Widget)""" # Custom CSS CSS: ClassVar[str] = "" @@ -285,6 +281,12 @@ class DOMNode(MessagePump): @property def classes(self) -> frozenset[str]: + """A frozenset of the current classes set on the widget. + + Returns: + frozenset[str]: Set of class names. + + """ return frozenset(self._classes) @property @@ -312,7 +314,10 @@ class DOMNode(MessagePump): @property def display(self) -> bool: """ - Returns: ``True`` if this DOMNode is displayed (``display != "none"``), ``False`` otherwise. + Check if this widget should display or note. + + Returns: + bool: ``True`` if this DOMNode is displayed (``display != "none"``) otherwise ``False`` . """ return self.styles.display != "none" and not (self._closing or self._closed) @@ -484,7 +489,12 @@ class DOMNode(MessagePump): @property def displayed_children(self) -> list[DOMNode]: - """The children which don't have display: none set.""" + """The children which don't have display: none set. + + Returns: + list[DOMNode]: Children of this widget which will be displayed. + + """ return [child for child in self.children if child.display] def get_pseudo_classes(self) -> Iterable[str]: diff --git a/src/textual/geometry.py b/src/textual/geometry.py index 9a0da73fa..0e131f5b2 100644 --- a/src/textual/geometry.py +++ b/src/textual/geometry.py @@ -141,13 +141,22 @@ class Size(NamedTuple): @property def region(self) -> Region: - """Get a region of the same size.""" + """Get a region of the same size. + + Returns: + Region: A region with the same size at (0, 0) + + """ width, height = self return Region(0, 0, width, height) @property def line_range(self) -> range: - """Get a range covering lines.""" + """Get a range covering lines. + + Returns: + range: + """ return range(self.height) def __add__(self, other: object) -> Size: @@ -225,7 +234,7 @@ class Region(NamedTuple): y: int = 0 """Offset in the y-axis (vertical)""" width: int = 0 - """The widget of the region""" + """The width of the region""" height: int = 0 """The height of the region""" @@ -360,45 +369,85 @@ class Region(NamedTuple): @property def right(self) -> int: - """Maximum X value (non inclusive)""" + """Maximum X value (non inclusive). + + Returns: + int: x coordinate + + """ return self.x + self.width @property def bottom(self) -> int: - """Maximum Y value (non inclusive)""" + """Maximum Y value (non inclusive). + + Returns: + int: y coordinate + + """ return self.y + self.height @property def area(self) -> int: - """Get the area within the region.""" + """Get the area within the region. + + Returns: + int: area. + + """ return self.width * self.height @property def offset(self) -> Offset: - """Get the start point of the region.""" + """Get the start point of the region. + + Returns: + Offset: Top left offset. + + """ return Offset(self.x, self.y) @property def bottom_left(self) -> Offset: - """Bottom left offset of the region.""" + """Bottom left offset of the region. + + Returns: + Offset: Bottom left offset. + + """ x, y, _width, height = self return Offset(x, y + height) @property def top_right(self) -> Offset: - """Top right offset of the region.""" + """Top right offset of the region. + + Returns: + Offset: Top right. + + """ x, y, width, _height = self return Offset(x + width, y) @property def bottom_right(self) -> Offset: - """Bottom right of the region.""" + """Bottom right of the region. + + Returns: + Offset: Bottom right. + + """ x, y, width, height = self return Offset(x + width, y + height) @property def size(self) -> Size: - """Get the size of the region.""" + """Get the size of the region. + + Returns: + Size: Size of the region. + + """ return Size(self.width, self.height) @property @@ -423,7 +472,12 @@ class Region(NamedTuple): @property def reset_offset(self) -> Region: - """An region of the same size at (0, 0).""" + """An region of the same size at (0, 0). + + Returns: + Region: reset region. + + """ _, _, width, height = self return Region(0, 0, width, height) diff --git a/src/textual/scroll_view.py b/src/textual/scroll_view.py deleted file mode 100644 index 8312be117..000000000 --- a/src/textual/scroll_view.py +++ /dev/null @@ -1,118 +0,0 @@ -from __future__ import annotations - -from typing import Collection - -from rich.console import RenderableType - - -from .geometry import Region, Size -from .widget import Widget - - -class ScrollView(Widget): - """ - A base class for a Widget that handles it's own scrolling (i.e. doesn't rely - on the compositor to render children). - - """ - - CSS = """ - - ScrollView { - overflow-y: auto; - overflow-x: auto; - } - - """ - - def __init__( - self, name: str | None = None, id: str | None = None, classes: str | None = None - ) -> None: - super().__init__(name=name, id=id, classes=classes) - - @property - def is_scrollable(self) -> bool: - """Always scrollable.""" - return True - - @property - def is_transparent(self) -> bool: - """Not transparent, i.e. renders something.""" - return False - - def on_mount(self): - self._refresh_scrollbars() - - def get_content_width(self, container: Size, viewport: Size) -> int: - """Gets the width of the content area. - - Args: - container (Size): Size of the container (immediate parent) widget. - viewport (Size): Size of the viewport. - - Returns: - int: The optimal width of the content. - """ - return self.virtual_size.width - - def get_content_height(self, container: Size, viewport: Size, width: int) -> int: - """Gets the height (number of lines) in the content area. - - Args: - container (Size): Size of the container (immediate parent) widget. - viewport (Size): Size of the viewport. - width (int): Width of renderable. - - Returns: - int: The height of the content. - """ - return self.virtual_size.height - - def size_updated( - self, size: Size, virtual_size: Size, container_size: Size - ) -> None: - """Called when size is updated. - - Args: - size (Size): New size. - virtual_size (Size): New virtual size. - container_size (Size): New container size. - """ - virtual_size = self.virtual_size - if self._size != size: - self._size = size - self._container_size = container_size - - self._refresh_scrollbars() - width, height = self.container_size - if self.show_vertical_scrollbar: - self.vertical_scrollbar.window_virtual_size = virtual_size.height - self.vertical_scrollbar.window_size = height - if self.show_horizontal_scrollbar: - self.horizontal_scrollbar.window_virtual_size = virtual_size.width - self.horizontal_scrollbar.window_size = width - - self.scroll_x = self.validate_scroll_x(self.scroll_x) - self.scroll_y = self.validate_scroll_y(self.scroll_y) - self.refresh(layout=False) - self.call_later(self.scroll_to, self.scroll_x, self.scroll_y) - - def render(self) -> RenderableType: - """Render the scrollable region (if `render_lines` is not implemented). - - Returns: - RenderableType: Renderable object. - """ - from rich.panel import Panel - - return Panel(f"{self.scroll_offset} {self.show_vertical_scrollbar}") - - def watch_scroll_x(self, new_value: float) -> None: - """Called when horizontal bar is scrolled.""" - self.horizontal_scrollbar.position = int(new_value) - self.refresh(layout=False) - - def watch_scroll_y(self, new_value: float) -> None: - """Called when vertical bar is scrolled.""" - self.vertical_scrollbar.position = int(new_value) - self.refresh(layout=False) diff --git a/src/textual/widget.py b/src/textual/widget.py index 35f8c2b43..f53955e55 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -72,6 +72,10 @@ class RenderCache(NamedTuple): @rich.repr.auto class Widget(DOMNode): + """ + A Widget is the base class for Textual widgets. Extent this class (or a sub-class) when defining your own widgets. + + """ CSS = """ Widget{ @@ -488,6 +492,11 @@ class Widget(DOMNode): @property def scrollbar_gutter(self) -> Spacing: + """Spacing required to fit scrollbar(s) + + Returns: + Spacing: Scrollbar gutter spacing. + """ gutter = Spacing( 0, self.scrollbar_size_vertical, self.scrollbar_size_horizontal, 0 ) @@ -495,39 +504,73 @@ class Widget(DOMNode): @property def gutter(self) -> Spacing: - """Spacing for padding / border / scrollbars.""" + """Spacing for padding / border / scrollbars. + + Returns: + Spacing: Additional spacing around content area. + + """ return self.styles.gutter + self.scrollbar_gutter @property def size(self) -> Size: - """The size of the content area.""" + """The size of the content area. + + Returns: + Size: Content area size. + """ return self.content_region.size @property def outer_size(self) -> Size: - """The size of the widget (including padding and border).""" + """The size of the widget (including padding and border). + + Returns: + Size: Outer size. + """ return self._size @property def container_size(self) -> Size: - """The size of the container (parent widget).""" + """The size of the container (parent widget). + + Returns: + Size: Container size. + """ return self._container_size @property def content_region(self) -> Region: - """Gets an absolute region containing the content (minus padding and border).""" + """Gets an absolute region containing the content (minus padding and border). + + Returns: + Region: Screen region that contains a widget's content. + """ content_region = self.region.shrink(self.gutter) return content_region @property def content_offset(self) -> Offset: - """An offset from the Widget origin where the content begins.""" + """An offset from the Widget origin where the content begins. + + Returns: + Offset: Offset from widget's origin. + + """ x, y = self.gutter.top_left return Offset(x, y) @property def region(self) -> Region: - """The region occupied by this widget, relative to the Screen.""" + """The region occupied by this widget, relative to the Screen. + + Raises: + NoScreen: If there is no screen. + errors.NoWidget: If the widget is not on the screen. + + Returns: + Region: Region within screen occupied by widget. + """ try: return self.screen.find_widget(self).region except NoScreen: @@ -596,7 +639,12 @@ class Widget(DOMNode): @property def console(self) -> Console: - """Get the current console.""" + """Get the current console. + + Returns: + Console: A Rich console object. + + """ return active_app.get().console @property @@ -631,7 +679,12 @@ class Widget(DOMNode): @property def layer(self) -> str: - """Get the name of this widgets layer.""" + """Get the name of this widgets layer. + + Returns: + str: Name of layer. + + """ return self.styles.layer or "default" @property