diff --git a/sandbox/darren/just_a_box.css b/sandbox/darren/just_a_box.css index 765fd5651..062dff0ec 100644 --- a/sandbox/darren/just_a_box.css +++ b/sandbox/darren/just_a_box.css @@ -1,6 +1,61 @@ -#box { - height: 50%; - width: 50%; - align: center middle; +Screen { + height: 100vh; + width: 100%; + background: red; +} + +#horizontal { + width: 100%; +} + +.box { + height: 5; + width: 5; + margin: 1 10; +} + +#left_pane { + width: 1fr; + background: $background; +} + +#middle_pane { + margin-top: 4; + width: 1fr; + background: #173f5f; +} + +#middle_pane:focus { + tint: cyan 40%; +} + +#right_pane { + width: 1fr; + background: #f6d55c; +} + +.box:focus { + tint: cyan 40%; +} + +#box1 { background: green; } + +#box2 { + offset-y: 3; + background: hotpink; +} + +#box3 { + background: red; +} + + +#box4 { + background: blue; +} + +#box5 { + background: darkviolet; +} diff --git a/sandbox/darren/just_a_box.py b/sandbox/darren/just_a_box.py index 0750afc43..8e6fc3ae7 100644 --- a/sandbox/darren/just_a_box.py +++ b/sandbox/darren/just_a_box.py @@ -4,10 +4,11 @@ from rich.console import RenderableType from rich.panel import Panel from textual.app import App, ComposeResult +from textual.layout import Horizontal, Vertical from textual.widget import Widget -class Box(Widget): +class Box(Widget, can_focus=True): CSS = "#box {background: blue;}" def __init__( @@ -20,8 +21,25 @@ class Box(Widget): class JustABox(App): + dark = True + def compose(self) -> ComposeResult: - yield Box(id="box") + yield Horizontal( + Vertical( + Box(id="box1", classes="box"), + Box(id="box2", classes="box"), + Box(id="box3", classes="box"), + id="left_pane", + ), + Box(id="middle_pane"), + Vertical( + Box(id="box", classes="box"), + Box(id="box4", classes="box"), + Box(id="box5", classes="box"), + id="right_pane", + ), + id="horizontal", + ) if __name__ == "__main__": diff --git a/src/textual/dom.py b/src/textual/dom.py index ccccd19e2..78f90e306 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -1,6 +1,7 @@ from __future__ import annotations from inspect import getfile +from operator import attrgetter from typing import ClassVar, Iterable, Iterator, Type, TYPE_CHECKING import rich.repr @@ -26,6 +27,7 @@ if TYPE_CHECKING: from .css.styles import StylesBase from .css.query import DOMQuery from .screen import Screen + from .widget import Widget class NoParent(Exception): @@ -425,12 +427,6 @@ class DOMNode(MessagePump): """The children which don't have display: none set.""" return [child for child in self.children if child.display] - @property - def focusable_children(self) -> list[DOMNode]: - """Get the children which may be focused.""" - # TODO: This may be the place to define order, other focus related rules - return [child for child in self.children if child.display and child.visible] - def get_pseudo_classes(self) -> Iterable[str]: """Get any pseudo classes applicable to this Node, e.g. hover, focus. diff --git a/src/textual/widget.py b/src/textual/widget.py index 2555f1121..d1f218bc2 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -1,6 +1,7 @@ from __future__ import annotations from fractions import Fraction +from operator import attrgetter from typing import ( TYPE_CHECKING, Any, @@ -494,6 +495,30 @@ class Widget(DOMNode): window_region = self.region.at_offset(self.scroll_offset) return window_region + @property + def virtual_region_with_margin(self) -> Region: + """The widget region relative to its container (*including margin*), which may not be visible, + depending on the scroll offset. + + Returns: + Region: The virtual region of the Widget, inclusive of its margin. + """ + return self.virtual_region.grow(self.styles.margin) + + @property + def focusable_children(self) -> list[Widget]: + """Get the children which may be focused.""" + focusable = [ + child for child in self.children if child.display and child.visible + ] + return sorted(focusable, key=attrgetter("_focus_sort_key")) + + @property + def _focus_sort_key(self) -> tuple[int, int]: + x, y, _, _ = self.virtual_region + top, _, _, left = self.styles.margin + return y - top, x - left + @property def scroll_offset(self) -> Offset: return Offset(int(self.scroll_x), int(self.scroll_y)) @@ -736,7 +761,7 @@ class Widget(DOMNode): """ # Grow the region by the margin so to keep the margin in view. - region = widget.virtual_region.grow(widget.styles.margin) + region = widget.virtual_region_with_margin scrolled = False while isinstance(widget.parent, Widget) and widget is not self: