Merge pull request #638 from Textualize/focus-order

Focus order
This commit is contained in:
Will McGugan
2022-08-05 15:06:46 +01:00
committed by GitHub
4 changed files with 107 additions and 13 deletions

View File

@@ -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;
}

View File

@@ -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__":

View File

@@ -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.

View File

@@ -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: