mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
key panel widget
This commit is contained in:
@@ -4,17 +4,23 @@ from pathlib import Path
|
||||
from sys import argv
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.binding import Binding
|
||||
from textual.reactive import var
|
||||
from textual.widgets import Footer, MarkdownViewer
|
||||
from textual.widgets import Footer, KeyPanel, MarkdownViewer
|
||||
|
||||
|
||||
class MarkdownApp(App):
|
||||
"""A simple Markdown viewer application."""
|
||||
|
||||
BINDINGS = [
|
||||
("t", "toggle_table_of_contents", "TOC"),
|
||||
("b", "back", "Back"),
|
||||
("f", "forward", "Forward"),
|
||||
Binding(
|
||||
"t",
|
||||
"toggle_table_of_contents",
|
||||
"TOC",
|
||||
tooltip="Toggle the Table of Contents Panel",
|
||||
),
|
||||
Binding("b", "back", "Back", tooltip="Navigate back"),
|
||||
Binding("f", "forward", "Forward", tooltip="Navigate forward"),
|
||||
]
|
||||
|
||||
path = var(Path(__file__).parent / "demo.md")
|
||||
@@ -27,6 +33,7 @@ class MarkdownApp(App):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Footer()
|
||||
yield MarkdownViewer()
|
||||
yield KeyPanel()
|
||||
|
||||
async def on_mount(self) -> None:
|
||||
"""Go to the first path when the app starts."""
|
||||
|
||||
@@ -647,7 +647,9 @@ class Compositor:
|
||||
|
||||
layers_to_index = {
|
||||
layer_name: index
|
||||
for index, layer_name in enumerate(widget.layers)
|
||||
for index, layer_name in enumerate(
|
||||
("textual-low", *widget.layers, "textual-high")
|
||||
)
|
||||
}
|
||||
|
||||
get_layer_index = layers_to_index.get
|
||||
|
||||
@@ -37,11 +37,6 @@ class SystemCommands(Provider):
|
||||
self.app.action_quit,
|
||||
"Quit the application as soon as possible",
|
||||
),
|
||||
(
|
||||
"Ring the bell",
|
||||
self.app.action_bell,
|
||||
"Ring the terminal's 'bell'",
|
||||
),
|
||||
)
|
||||
|
||||
async def discover(self) -> Hits:
|
||||
|
||||
@@ -21,6 +21,7 @@ if typing.TYPE_CHECKING:
|
||||
from ._footer import Footer
|
||||
from ._header import Header
|
||||
from ._input import Input
|
||||
from ._key_panel import KeyPanel
|
||||
from ._label import Label
|
||||
from ._list_item import ListItem
|
||||
from ._list_view import ListView
|
||||
@@ -59,6 +60,8 @@ __all__ = [
|
||||
"Footer",
|
||||
"Header",
|
||||
"Input",
|
||||
"KeyPanel",
|
||||
"KeyPanel",
|
||||
"Label",
|
||||
"ListItem",
|
||||
"ListView",
|
||||
|
||||
@@ -10,6 +10,7 @@ from ._directory_tree import DirectoryTree as DirectoryTree
|
||||
from ._footer import Footer as Footer
|
||||
from ._header import Header as Header
|
||||
from ._input import Input as Input
|
||||
from ._key_panel import KeyPanel
|
||||
from ._label import Label as Label
|
||||
from ._list_item import ListItem as ListItem
|
||||
from ._list_view import ListView as ListView
|
||||
|
||||
@@ -145,6 +145,7 @@ class Footer(ScrollableContainer, can_focus=False, can_focus_children=False):
|
||||
background: $panel;
|
||||
color: $text;
|
||||
dock: bottom;
|
||||
layer: textual-high;
|
||||
height: 1;
|
||||
scrollbar-size: 0 0;
|
||||
&.-compact {
|
||||
|
||||
106
src/textual/widgets/_key_panel.py
Normal file
106
src/textual/widgets/_key_panel.py
Normal file
@@ -0,0 +1,106 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from rich.table import Table
|
||||
from rich.text import Text
|
||||
|
||||
from ..binding import Binding
|
||||
from ..reactive import reactive
|
||||
from ..widgets import Static
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..screen import Screen
|
||||
|
||||
|
||||
class KeyPanel(Static):
|
||||
COMPONENT_CLASSES = {
|
||||
"footer-key--key",
|
||||
"footer-key--description",
|
||||
}
|
||||
|
||||
DEFAULT_CSS = """
|
||||
KeyPanel {
|
||||
layout: vertical;
|
||||
dock: right;
|
||||
# layer: textual-high;
|
||||
width: 20;
|
||||
# min-width: 20;
|
||||
max-width: 33%;
|
||||
# border-left: vkey $foreground 30%;
|
||||
|
||||
padding: 0 1;
|
||||
height: 1fr;
|
||||
|
||||
border-left: vkey $primary;
|
||||
|
||||
|
||||
padding-right: 1;
|
||||
|
||||
&>.footer-key--key {
|
||||
color: $secondary;
|
||||
|
||||
text-style: bold;
|
||||
padding: 0 1;
|
||||
}
|
||||
|
||||
&>.footer-key--description {
|
||||
color: $text;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
"""
|
||||
|
||||
_bindings_ready = reactive(False, repaint=False, recompose=True)
|
||||
|
||||
def update_bindings(self) -> None:
|
||||
bindings = [
|
||||
(binding, enabled, tooltip)
|
||||
for (_, binding, enabled, tooltip) in self.screen.active_bindings.values()
|
||||
]
|
||||
action_to_bindings: defaultdict[str, list[tuple[Binding, bool, str]]]
|
||||
action_to_bindings = defaultdict(list)
|
||||
for binding, enabled, tooltip in bindings:
|
||||
action_to_bindings[binding.action].append((binding, enabled, tooltip))
|
||||
|
||||
table = Table.grid(padding=(0, 1))
|
||||
|
||||
key_style = self.get_component_rich_style("footer-key--key")
|
||||
description_style = self.get_component_rich_style("footer-key--description")
|
||||
|
||||
def render_description(binding: Binding) -> Text:
|
||||
text = Text.from_markup(
|
||||
binding.description, end="", style=description_style
|
||||
)
|
||||
if binding.tooltip:
|
||||
text.append(" ")
|
||||
text.append(binding.tooltip, "dim")
|
||||
return text
|
||||
|
||||
table.add_column("", justify="right")
|
||||
for multi_bindings in action_to_bindings.values():
|
||||
binding, enabled, tooltip = multi_bindings[0]
|
||||
table.add_row(
|
||||
Text(
|
||||
binding.key_display or self.app.get_key_display(binding.key),
|
||||
style=key_style,
|
||||
),
|
||||
render_description(binding),
|
||||
)
|
||||
|
||||
self.update(table)
|
||||
|
||||
def on_mount(self) -> None:
|
||||
async def bindings_changed(screen: Screen) -> None:
|
||||
self._bindings_ready = True
|
||||
if self.is_attached and screen is self.screen:
|
||||
self.update_bindings()
|
||||
|
||||
self.screen.bindings_updated_signal.subscribe(self, bindings_changed)
|
||||
|
||||
def on_unmount(self) -> None:
|
||||
self.screen.bindings_updated_signal.unsubscribe(self)
|
||||
Reference in New Issue
Block a user