diff --git a/poetry.lock b/poetry.lock index c1d1edef8..ae30eda4b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -500,7 +500,7 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "2.19.0" +version = "2.20.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false @@ -658,16 +658,21 @@ version = "12.4.4" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" category = "main" optional = false -python-versions = ">=3.6.3,<4.0.0" +python-versions = "^3.6.3" +develop = true [package.dependencies] -commonmark = ">=0.9.0,<0.10.0" -pygments = ">=2.6.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} +commonmark = "^0.9.0" +pygments = "^2.6.0" +typing-extensions = {version = ">=4.0.0, <5.0", markers = "python_version < \"3.9\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] +[package.source] +type = "directory" +url = "../rich" + [[package]] name = "six" version = "1.16.0" @@ -780,7 +785,7 @@ dev = ["aiohttp", "click", "msgpack"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "8ce8d66466dad1b984673595ebd0cc7bc0d28c7a672269e9b5620c242d87d9ad" +content-hash = "08c432accbe56db11ca9b2d69c2b4e15967e1a0142d4508f245715cae6f3d239" [metadata.files] aiohttp = [ @@ -1295,10 +1300,7 @@ pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] -pre-commit = [ - {file = "pre_commit-2.19.0-py2.py3-none-any.whl", hash = "sha256:10c62741aa5704faea2ad69cb550ca78082efe5697d6f04e5710c3c229afdd10"}, - {file = "pre_commit-2.19.0.tar.gz", hash = "sha256:4233a1e38621c87d9dda9808c6606d7e7ba0e087cd56d3fe03202a01d2919615"}, -] +pre-commit = [] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, @@ -1375,10 +1377,7 @@ pyyaml-env-tag = [ {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, ] -rich = [ - {file = "rich-12.4.4-py3-none-any.whl", hash = "sha256:d2bbd99c320a2532ac71ff6a3164867884357da3e3301f0240090c5d2fdac7ec"}, - {file = "rich-12.4.4.tar.gz", hash = "sha256:4c586de507202505346f3e32d1363eb9ed6932f0c2f63184dea88983ff4971e2"}, -] +rich = [] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, diff --git a/pyproject.toml b/pyproject.toml index 5b058e02a..0b932972b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,8 +22,8 @@ textual = "textual.cli.cli:run" [tool.poetry.dependencies] python = "^3.7" -rich = "^12.4.3" -#rich = {path="../rich", develop=true} +#rich = "^12.4.3" +rich = {path="../rich", develop=true} importlib-metadata = "^4.11.3" typing-extensions = { version = "^4.0.0", python = "<3.8" } diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index d1a93576b..fd15b7fd3 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -594,7 +594,6 @@ class Compositor: ] return segment_lines - @timer("render") def render(self, full: bool = False) -> RenderableType | None: """Render a layout. diff --git a/src/textual/app.py b/src/textual/app.py index 156b2a16b..35af680bc 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -645,6 +645,7 @@ class App(Generic[ReturnType], DOMNode): # Change focus self.focused = widget # Send focus event + widget.parent.scroll_to_widget(widget) widget.post_message_no_wait(events.Focus(self)) widget.emit_no_wait(events.DescendantFocus(self)) @@ -926,7 +927,6 @@ class App(Generic[ReturnType], DOMNode): stylesheet.update(self.app, animate=animate) self.screen._refresh_layout(self.size, full=True) - @timer("_display") def _display(self, renderable: RenderableType | None) -> None: """Display a renderable within a sync. diff --git a/src/textual/geometry.py b/src/textual/geometry.py index 11ca38254..73498d8ee 100644 --- a/src/textual/geometry.py +++ b/src/textual/geometry.py @@ -569,8 +569,27 @@ class Region(NamedTuple): ) return new_region + def grow(self, margin: tuple[int, int, int, int]) -> Region: + """Grow a region by adding spacing. + + Args: + margin (Spacing): Defines how many cells to grow the Region by at each edge. + + Returns: + Region: New region. + """ + + top, right, bottom, left = margin + x, y, width, height = self + return Region( + x=x - left, + y=y - top, + width=max(0, width + left + right), + height=max(0, height + top + bottom), + ) + def shrink(self, margin: tuple[int, int, int, int]) -> Region: - """Shrink a region by pushing each edge inwards. + """Shrink a region by subtracting spacing. Args: margin (Spacing): Defines how many cells to shrink the Region by at each edge. diff --git a/src/textual/widget.py b/src/textual/widget.py index e3d2dac5f..7f53daf19 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -688,9 +688,7 @@ class Widget(DOMNode): ) def scroll_to_widget(self, widget: Widget, *, animate: bool = True) -> bool: - """Starting from `widget`, travel up the DOM to this node, scrolling all containers such that - every widget is visible within its parent container. This will, in the majority of cases, - bring the target widget into + """Scroll scrolling to bring a widget in to view. Args: widget (Widget): A descendant widget. @@ -705,7 +703,8 @@ class Widget(DOMNode): node = widget.parent child = widget - while node: + while isinstance(node, Widget): + assert isinstance(child, Widget) try: widget_region = child.region container_region = node.region @@ -718,12 +717,14 @@ class Widget(DOMNode): node = node.parent continue + widget_region = widget_region.grow(widget.styles.margin) + # We can either scroll so the widget is at the top of the container, or so that # it is at the bottom. We want to pick which has the shortest distance - top_delta = widget_region.offset - container_region.origin + top_delta = widget_region.offset - container_region.offset bottom_delta = widget_region.offset - ( - container_region.origin + container_region.offset + Offset(0, container_region.height - widget_region.height) ) diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py index e9856d949..085c8630e 100644 --- a/src/textual/widgets/_button.py +++ b/src/textual/widgets/_button.py @@ -43,7 +43,7 @@ class Button(Widget, can_focus=True): } Button:focus { - text-style: bold underline; + text-style: bold reverse; } Button:hover { @@ -183,6 +183,7 @@ class Button(Widget, can_focus=True): def render(self) -> RenderableType: label = self.label.copy() + label = Text.assemble(" ", label, " ") label.stylize(self.text_style) return label diff --git a/tests/test_geometry.py b/tests/test_geometry.py index eb9e9a16a..df345158a 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -251,6 +251,12 @@ def test_region_shrink(): assert region.shrink(margin) == Region(x=14, y=11, width=44, height=46) +def test_region_grow(): + margin = Spacing(top=1, right=2, bottom=3, left=4) + region = Region(x=10, y=10, width=50, height=50) + assert region.grow(margin) == Region(x=6, y=9, width=56, height=54) + + def test_region_intersection(): assert Region(0, 0, 100, 50).intersection(Region(10, 10, 10, 10)) == Region( 10, 10, 10, 10