mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
newline
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
32
poetry.lock
generated
@@ -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"},
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user