This commit is contained in:
Will McGugan
2021-07-18 10:31:00 +01:00
parent 4cb9fe1b01
commit f737252911
13 changed files with 154 additions and 99 deletions

View File

@@ -1,7 +1,6 @@
from textual import events from textual import events
from textual.app import App from textual.app import App
from textual.reactive import Reactive from textual.reactive import Reactive
from textual.views import DockView
from textual.widgets import Footer, Placeholder from textual.widgets import Footer, Placeholder
@@ -9,33 +8,29 @@ class SmoothApp(App):
"""Demonstrates smooth animation""" """Demonstrates smooth animation"""
async def on_load(self, event: events.Load) -> None: async def on_load(self, event: events.Load) -> None:
await self.bind("q,ctrl+c", "quit") """Bing keys here."""
await self.bind("x", "bang") await self.bind("b", "toggle_sidebar", "Toggle sidebar")
await self.bind("b", "toggle_sidebar") await self.bind("q", "quit", "Quit")
show_bar: Reactive[bool] = Reactive(False) show_bar: Reactive[bool] = Reactive(False)
async def watch_show_bar(self, show_bar: bool) -> None: async def watch_show_bar(self, show_bar: bool) -> None:
"""Called when show_bar changes."""
self.animator.animate(self.bar, "layout_offset_x", 0 if show_bar else -40) self.animator.animate(self.bar, "layout_offset_x", 0 if show_bar else -40)
async def action_toggle_sidebar(self) -> None: async def action_toggle_sidebar(self) -> None:
"""Called when user hits b key."""
self.show_bar = not self.show_bar self.show_bar = not self.show_bar
async def on_startup(self, event: events.Startup) -> None: async def on_startup(self, event: events.Startup) -> None:
"""Build layout here."""
view = await self.push_view(DockView())
footer = Footer() footer = Footer()
self.bar = Placeholder(name="left") self.bar = Placeholder(name="left")
self.bar.layout_offset_x = -40 self.bar.layout_offset_x = -40
footer.add_key("b", "Toggle sidebar") await self.view.dock(footer, edge="bottom")
footer.add_key("q", "Quit") await self.view.dock(self.bar, edge="left", size=40, z=1)
await self.view.dock(Placeholder(), Placeholder(), edge="top")
await view.dock(footer, edge="bottom")
await view.dock(self.bar, edge="left", size=40, z=1)
await view.dock(Placeholder(), Placeholder(), edge="top")
SmoothApp.run() SmoothApp.run()

View File

@@ -108,22 +108,23 @@ class CalculatorApp(App):
async def on_startup(self, event: events.Startup) -> None: async def on_startup(self, event: events.Startup) -> None:
"""Sent when the app has gone full screen.""" """Sent when the app has gone full screen."""
# Create the layout which defines where our widgets will go # Create a grid layout
layout = GridLayout(gap=(2, 1), gutter=1, align=("center", "center")) grid = await self.view.dock_grid(
await self.push_view(View(layout=layout)) gap=(2, 1), gutter=1, align=("center", "center")
)
# Create rows / columns / areas # Create rows / columns / areas
layout.add_column("col", max_size=30, repeat=4) grid.add_column("col", max_size=30, repeat=4)
layout.add_row("numbers", max_size=15) grid.add_row("numbers", max_size=15)
layout.add_row("row", max_size=15, repeat=5) grid.add_row("row", max_size=15, repeat=5)
layout.add_areas( grid.add_areas(
clear="col1,row1", clear="col1,row1",
numbers="col1-start|col4-end,numbers", numbers="col1-start|col4-end,numbers",
zero="col1-start|col2-end,row5", zero="col1-start|col2-end,row5",
) )
# Place out widgets in to the layout # Place out widgets in to the layout
layout.place(clear=self.c) grid.place(clear=self.c)
layout.place( grid.place(
*self.buttons.values(), clear=self.ac, numbers=self.numbers, zero=self.zero *self.buttons.values(), clear=self.ac, numbers=self.numbers, zero=self.zero
) )

View File

@@ -11,25 +11,24 @@ class GridTest(App):
async def on_startup(self, event: events.Startup) -> None: async def on_startup(self, event: events.Startup) -> None:
layout = GridLayout() grid = await self.view.dock_grid()
await self.push_view(View(layout=layout))
layout.add_column(fraction=1, name="left", min_size=20) grid.add_column(fraction=1, name="left", min_size=20)
layout.add_column(size=30, name="center") grid.add_column(size=30, name="center")
layout.add_column(fraction=1, name="right") grid.add_column(fraction=1, name="right")
layout.add_row(fraction=1, name="top", min_size=2) grid.add_row(fraction=1, name="top", min_size=2)
layout.add_row(fraction=2, name="middle") grid.add_row(fraction=2, name="middle")
layout.add_row(fraction=1, name="bottom") grid.add_row(fraction=1, name="bottom")
layout.add_areas( grid.add_areas(
area1="left,top", area1="left,top",
area2="center,middle", area2="center,middle",
area3="left-start|right-end,bottom", area3="left-start|right-end,bottom",
area4="right,top-start|middle-end", area4="right,top-start|middle-end",
) )
layout.place( grid.place(
area1=Placeholder(name="area1"), area1=Placeholder(name="area1"),
area2=Placeholder(name="area2"), area2=Placeholder(name="area2"),
area3=Placeholder(name="area3"), area3=Placeholder(name="area3"),

View File

@@ -11,16 +11,15 @@ class GridTest(App):
async def on_startup(self, event: events.Startup) -> None: async def on_startup(self, event: events.Startup) -> None:
layout = GridLayout() grid = await self.view.dock_grid()
await self.push_view(View(layout=layout))
layout.add_column("col", fraction=1, max_size=20) grid.add_column("col", fraction=1, max_size=20)
layout.add_row("row", fraction=1, max_size=10) grid.add_row("row", fraction=1, max_size=10)
layout.set_repeat(True, True) grid.set_repeat(True, True)
layout.add_areas(center="col-2-start|col-4-end,row-2-start|row-3-end") grid.add_areas(center="col-2-start|col-4-end,row-2-start|row-3-end")
layout.set_align("stretch", "center") grid.set_align("stretch", "center")
layout.place(*(Placeholder() for _ in range(20)), center=Placeholder()) grid.place(*(Placeholder() for _ in range(20)), center=Placeholder())
GridTest.run(title="Grid Test") GridTest.run(title="Grid Test")

View File

@@ -2,7 +2,6 @@ from rich.markdown import Markdown
from textual import events from textual import events
from textual.app import App from textual.app import App
from textual.views import DockView
from textual.widgets import Header, Footer, Placeholder, ScrollView from textual.widgets import Header, Footer, Placeholder, ScrollView
@@ -10,25 +9,17 @@ class MyApp(App):
"""An example of a very simple Textual App""" """An example of a very simple Textual App"""
async def on_load(self, event: events.Load) -> None: async def on_load(self, event: events.Load) -> None:
await self.bind("q,ctrl+c", "quit", "Quit")
await self.bind("b", "view.toggle('sidebar')", "Toggle sidebar") await self.bind("b", "view.toggle('sidebar')", "Toggle sidebar")
await self.bind("q", "quit", "Quit")
async def on_startup(self, event: events.Startup) -> None: async def on_startup(self, event: events.Startup) -> None:
view = await self.push_view(DockView())
footer = Footer()
header = Header()
body = ScrollView() body = ScrollView()
sidebar = Placeholder()
footer.add_key("b", "Toggle sidebar") await self.view.dock(Header(), edge="top")
footer.add_key("q", "Quit") await self.view.dock(Footer(), edge="bottom")
await self.view.dock(Placeholder(), edge="left", size=30, name="sidebar")
await view.dock(header, edge="top") await self.view.dock(body, edge="right")
await view.dock(footer, edge="bottom")
await view.dock(sidebar, edge="left", size=30, name="sidebar")
await view.dock(body, edge="right")
async def get_markdown(filename: str) -> None: async def get_markdown(filename: str) -> None:
with open(filename, "rt") as fh: with open(filename, "rt") as fh:

32
poetry.lock generated
View File

@@ -369,11 +369,11 @@ pyparsing = ">=2.0.2"
[[package]] [[package]]
name = "pathspec" name = "pathspec"
version = "0.8.1" version = "0.9.0"
description = "Utility library for gitignore style pattern matching of file paths." description = "Utility library for gitignore style pattern matching of file paths."
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[[package]] [[package]]
name = "platformdirs" name = "platformdirs"
@@ -547,17 +547,24 @@ version = "10.6.0"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.6,<4.0" python-versions = "^3.6"
develop = false
[package.dependencies] [package.dependencies]
colorama = ">=0.4.0,<0.5.0" colorama = "^0.4.0"
commonmark = ">=0.9.0,<0.10.0" commonmark = "^0.9.0"
pygments = ">=2.6.0,<3.0.0" pygments = "^2.6.0"
typing-extensions = {version = ">=3.7.4,<4.0.0", markers = "python_version < \"3.8\""} typing-extensions = {version = "^3.7.4", markers = "python_version < \"3.8\""}
[package.extras] [package.extras]
jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"]
[package.source]
type = "git"
url = "git@github.com:willmcgugan/rich"
reference = "link-id"
resolved_reference = "c4c00a2d0441519ced7ab2dead931341d9345eda"
[[package]] [[package]]
name = "six" name = "six"
version = "1.16.0" version = "1.16.0"
@@ -636,7 +643,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.7" python-versions = "^3.7"
content-hash = "b78b6843dbfa68dd86e0ec81c9f1980f57eb85c13d1df9b497c34762e8805699" content-hash = "89e70da124ff666d5f911585eb2032d523499bcfe3c0efad9b2f5367cc64183b"
[metadata.files] [metadata.files]
appdirs = [ appdirs = [
@@ -865,8 +872,8 @@ packaging = [
{file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"},
] ]
pathspec = [ pathspec = [
{file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
{file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
] ]
platformdirs = [ platformdirs = [
{file = "platformdirs-2.0.2-py2.py3-none-any.whl", hash = "sha256:0b9547541f599d3d242078ae60b927b3e453f0ad52f58b4d4bc3be86aed3ec41"}, {file = "platformdirs-2.0.2-py2.py3-none-any.whl", hash = "sha256:0b9547541f599d3d242078ae60b927b3e453f0ad52f58b4d4bc3be86aed3ec41"},
@@ -990,10 +997,7 @@ regex = [
{file = "regex-2021.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:4c9c3155fe74269f61e27617529b7f09552fbb12e44b1189cebbdb24294e6e1c"}, {file = "regex-2021.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:4c9c3155fe74269f61e27617529b7f09552fbb12e44b1189cebbdb24294e6e1c"},
{file = "regex-2021.7.6.tar.gz", hash = "sha256:8394e266005f2d8c6f0bc6780001f7afa3ef81a7a2111fa35058ded6fce79e4d"}, {file = "regex-2021.7.6.tar.gz", hash = "sha256:8394e266005f2d8c6f0bc6780001f7afa3ef81a7a2111fa35058ded6fce79e4d"},
] ]
rich = [ rich = []
{file = "rich-10.6.0-py3-none-any.whl", hash = "sha256:d3f72827cd5df13b2ef7f1a97f81ec65548d4fdeb92cef653234f227580bbb2a"},
{file = "rich-10.6.0.tar.gz", hash = "sha256:128261b3e2419a4ef9c97066ccc2abbfb49fa7c5e89c3fe4056d00aa5e9c1e65"},
]
six = [ six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},

View File

@@ -21,8 +21,8 @@ classifiers = [
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.7" python = "^3.7"
rich = "^10.6.0" #rich = "^10.6.0"
#rich = {git = "git@github.com:willmcgugan/rich", rev = "height-fixes"} rich = {git = "git@github.com:willmcgugan/rich", rev = "link-id"}
typing-extensions = { version = "^3.10.0", python = "<3.8" } typing-extensions = { version = "^3.10.0", python = "<3.8" }
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]

View File

View File

@@ -11,13 +11,11 @@ import rich.repr
from rich.screen import Screen from rich.screen import Screen
from rich import get_console from rich import get_console
from rich.console import Console, RenderableType from rich.console import Console, RenderableType
from rich.style import Style
from rich.traceback import Traceback from rich.traceback import Traceback
from . import events from . import events
from . import actions from . import actions
from ._animator import Animator from ._animator import Animator
from ._profile import timer
from .binding import Bindings, NoBinding from .binding import Bindings, NoBinding
from .geometry import Point, Region from .geometry import Point, Region
from . import log from . import log
@@ -92,7 +90,7 @@ class App(MessagePump):
self.driver_class = driver_class or LinuxDriver self.driver_class = driver_class or LinuxDriver
self._title = title self._title = title
self._layout = DockLayout() self._layout = DockLayout()
self._view_stack: list[View] = [] self._view_stack: list[DockView] = []
self.children: set[MessagePump] = set() self.children: set[MessagePump] = set()
self.focused: Widget | None = None self.focused: Widget | None = None
@@ -111,7 +109,7 @@ class App(MessagePump):
self.log_file = open(log, "wt") if log else None self.log_file = open(log, "wt") if log else None
self.bindings.bind("ctrl+c", "quit") self.bindings.bind("ctrl+c", "quit", show=False)
super().__init__() super().__init__()
@@ -130,7 +128,7 @@ class App(MessagePump):
return self._animator return self._animator
@property @property
def view(self) -> View: def view(self) -> DockView:
return self._view_stack[-1] return self._view_stack[-1]
def log(self, *args: Any, verbosity: int = 0) -> None: def log(self, *args: Any, verbosity: int = 0) -> None:
@@ -143,9 +141,16 @@ class App(MessagePump):
pass pass
async def bind( async def bind(
self, keys: str, action: str, description: str = "", show: bool = False self,
keys: str,
action: str,
description: str = "",
show: bool = True,
key_display: str | None = None,
) -> None: ) -> None:
self.bindings.bind(keys, action, description, show=show) self.bindings.bind(
keys, action, description, show=show, key_display=key_display
)
@classmethod @classmethod
def run( def run(
@@ -246,7 +251,7 @@ class App(MessagePump):
log(f"driver={self.driver_class}") log(f"driver={self.driver_class}")
await self.dispatch_message(events.Load(sender=self)) await self.dispatch_message(events.Load(sender=self))
await self.push_view(View()) await self.push_view(DockView())
try: try:
driver.start_application_mode() driver.start_application_mode()
@@ -345,14 +350,18 @@ class App(MessagePump):
def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]: def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]:
return self.view.get_widget_at(x, y) return self.view.get_widget_at(x, y)
async def on_event(self, event: events.Event) -> None: async def press(self, key: str) -> bool:
if isinstance(event, events.Key):
try: try:
binding = self.bindings.get_key(event.key) binding = self.bindings.get_key(key)
except NoBinding: except NoBinding:
pass return False
else: else:
await self.action(binding.action) await self.action(binding.action)
return True
async def on_event(self, event: events.Event) -> None:
if isinstance(event, events.Key):
if await self.press(event.key):
return return
await super().on_event(event) await super().on_event(event)
@@ -425,6 +434,9 @@ class App(MessagePump):
async def on_resize(self, event: events.Resize) -> None: async def on_resize(self, event: events.Resize) -> None:
await self.view.post_message(event) await self.view.post_message(event)
async def action_press(self, key: str) -> None:
await self.press(key)
async def action_quit(self) -> None: async def action_quit(self) -> None:
await self.shutdown() await self.shutdown()
@@ -467,9 +479,9 @@ if __name__ == "__main__":
"""Just a test app.""" """Just a test app."""
async def on_load(self, event: events.Load) -> None: async def on_load(self, event: events.Load) -> None:
await self.bind("q,ctrl+c", "quit") await self.bind("q,ctrl+c", "quit", "Exit app")
await self.bind("x", "bang") await self.bind("x", "bang", "Test error handling")
await self.bind("b", "toggle_sidebar") await self.bind("b", "toggle_sidebar", "Toggle sidebar")
show_bar: Reactive[bool] = Reactive(False) show_bar: Reactive[bool] = Reactive(False)
@@ -486,8 +498,6 @@ if __name__ == "__main__":
header = Header() header = Header()
footer = Footer() footer = Footer()
self.bar = Placeholder(name="left") self.bar = Placeholder(name="left")
footer.add_key("b", "Toggle sidebar")
footer.add_key("q", "Quit")
await view.dock(header, edge="top") await view.dock(header, edge="top")
await view.dock(footer, edge="bottom") await view.dock(footer, edge="bottom")

View File

@@ -12,6 +12,7 @@ class Binding:
action: str action: str
description: str description: str
show: bool = False show: bool = False
key_display: str | None = None
class Bindings: class Bindings:
@@ -20,16 +21,24 @@ class Bindings:
def __init__(self) -> None: def __init__(self) -> None:
self.keys: dict[str, Binding] = {} self.keys: dict[str, Binding] = {}
@property
def shown_keys(self) -> list[Binding]: def shown_keys(self) -> list[Binding]:
keys = [binding for binding in self.keys.values() if binding.show] keys = [binding for binding in self.keys.values() if binding.show]
return keys return keys
def bind( def bind(
self, keys: str, action: str, description: str = "", show: bool = False self,
keys: str,
action: str,
description: str = "",
show: bool = True,
key_display: str | None = None,
) -> None: ) -> None:
all_keys = [key.strip() for key in keys.split(",")] all_keys = [key.strip() for key in keys.split(",")]
for key in all_keys: for key in all_keys:
self.keys[key] = Binding(key, action, description, show=show) self.keys[key] = Binding(
key, action, description, show=show, key_display=key_display
)
def get_key(self, key: str) -> Binding: def get_key(self, key: str) -> Binding:
try: try:

View File

@@ -8,6 +8,7 @@ import rich.repr
from rich.style import Style from rich.style import Style
from . import events from . import events
from . import log
from .layout import Layout, NoWidget from .layout import Layout, NoWidget
from .layouts.dock import DockLayout from .layouts.dock import DockLayout
from .geometry import Dimensions, Point, Region from .geometry import Dimensions, Point, Region
@@ -30,6 +31,8 @@ class View(Widget):
self.size = Dimensions(0, 0) self.size = Dimensions(0, 0)
self.widgets: set[Widget] = set() self.widgets: set[Widget] = set()
self.named_widgets: dict[str, Widget] = {} self.named_widgets: dict[str, Widget] = {}
self._mouse_style: Style = Style()
self._mouse_widget: Widget | None = None
super().__init__(name) super().__init__(name)
background: Reactive[str] = Reactive("") background: Reactive[str] = Reactive("")
@@ -148,6 +151,7 @@ class View(Widget):
await self.refresh_layout() await self.refresh_layout()
async def _on_mouse_move(self, event: events.MouseMove) -> None: async def _on_mouse_move(self, event: events.MouseMove) -> None:
try: try:
if self.app.mouse_captured: if self.app.mouse_captured:
widget = self.app.mouse_captured widget = self.app.mouse_captured
@@ -157,8 +161,13 @@ class View(Widget):
except NoWidget: except NoWidget:
await self.app.set_mouse_over(None) await self.app.set_mouse_over(None)
else: else:
await self.app.set_mouse_over(widget) if event.style is not self._mouse_style and self._mouse_widget:
await self.app.broker_event("leave", event, self._mouse_widget)
await self.app.broker_event("enter", event, widget)
self._mouse_style = event.style
self._mouse_widget = widget
await self.app.set_mouse_over(widget)
await widget.forward_event( await widget.forward_event(
events.MouseMove( events.MouseMove(
self, self,

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
from typing import cast, Optional from typing import cast, Optional
from ..layouts.dock import DockLayout, Dock, DockEdge from ..layouts.dock import DockLayout, Dock, DockEdge
from ..layouts.grid import GridLayout, GridAlign
from ..view import View from ..view import View
from ..widget import Widget from ..widget import Widget
@@ -23,7 +24,7 @@ class DockView(View):
edge: DockEdge = "top", edge: DockEdge = "top",
z: int = 0, z: int = 0,
size: int | None | DoNotSet = do_not_set, size: int | None | DoNotSet = do_not_set,
name: str | None = None name: str | None = None,
) -> None: ) -> None:
dock = Dock(edge, widgets, z) dock = Dock(edge, widgets, z)
@@ -38,3 +39,29 @@ class DockView(View):
else: else:
await self.mount(**{name: widget}) await self.mount(**{name: widget})
await self.refresh_layout() await self.refresh_layout()
async def dock_grid(
self,
*,
edge: DockEdge = "top",
z: int = 0,
size: int | None | DoNotSet = do_not_set,
name: str | None = None,
gap: tuple[int, int] | int | None = None,
gutter: tuple[int, int] | int | None = None,
align: tuple[GridAlign, GridAlign] | None = None,
) -> GridLayout:
grid = GridLayout(gap=gap, gutter=gutter, align=align)
view = View(layout=grid)
dock = Dock(edge, (view,), z)
assert isinstance(self.layout, DockLayout)
self.layout.docks.append(dock)
if size is not do_not_set:
view.layout_size = cast(Optional[int], size)
if not self.is_mounted(view):
if name is None:
await self.mount(view)
else:
await self.mount(**{name: view})
return grid

View File

@@ -1,8 +1,8 @@
from rich.console import RenderableType from rich.console import RenderableType
from rich.style import Style
from rich.text import Text from rich.text import Text
import rich.repr import rich.repr
from .. import events
from ..widget import Widget from ..widget import Widget
@@ -20,7 +20,6 @@ class Footer(Widget):
self.keys.append((key, label)) self.keys.append((key, label))
def render(self) -> RenderableType: def render(self) -> RenderableType:
text = Text( text = Text(
style="white on dark_green", style="white on dark_green",
no_wrap=True, no_wrap=True,
@@ -28,7 +27,19 @@ class Footer(Widget):
justify="left", justify="left",
end="", end="",
) )
for key, label in self.keys: for binding in self.app.bindings.shown_keys:
text.append(f" {key.upper()} ", style="default on default") key_display = (
text.append(f" {label} ") binding.key.upper()
if binding.key_display is None
else binding.key_display
)
key_text = Text.assemble(
(f" {key_display} ", "default on default"), f" {binding.description} "
)
key_text.stylize(Style(meta={"@click": f"app.press('{binding.key}')"}))
text.append_text(key_text)
# text.append(f" {key_display} ", style="default on default")
# text.append(f" {binding.description} ")
# text.stylize(Style(meta={"@enter": "app.bell()"}))
return text return text