added press to run

This commit is contained in:
Will McGugan
2022-08-20 21:23:26 +01:00
parent bda9790a2a
commit 4e4d0b1bb9
5 changed files with 44 additions and 18 deletions

View File

@@ -248,7 +248,7 @@ You may have noticed that the stop button (`#stop` in the CSS) has `display: non
We want our Stopwatch widget to have two states. An _unstarted_ state with a Start and Reset button, and a _started_ state with a Stop button.
There are other differences between the two states. It would be nice if the stopwatch turns green when it is started. And we could make the time text bold, so it is clear it is running. It's possible to do this in code, but
There are other visual differences between the two states. When a stopwatch is running it should have a green background and bold text.
```{.textual path="docs/examples/introduction/stopwatch04.py" title="stopwatch04.py" press="tab,enter"}

View File

@@ -57,6 +57,7 @@ from .drivers.headless_driver import HeadlessDriver
from .features import FeatureFlag, parse_features
from .file_monitor import FileMonitor
from .geometry import Offset, Region, Size
from .messages import CallbackType
from .reactive import Reactive
from .renderables.blank import Blank
from .screen import Screen
@@ -560,6 +561,7 @@ class App(Generic[ReturnType], DOMNode):
quit_after (float | None, optional): Quit after a given number of seconds, or None
to run forever. Defaults to None.
headless (bool, optional): Run in "headless" mode (don't write to stdout).
press (str, optional): An iterable of keys to simulate being pressed.
Returns:
ReturnType | None: The return value specified in `App.exit` or None if exit wasn't called.
@@ -574,18 +576,25 @@ class App(Generic[ReturnType], DOMNode):
if quit_after is not None:
self.set_timer(quit_after, self.shutdown)
if press is not None:
app = self
async def press_keys(app: App):
async def press_keys() -> None:
"""A task to send key events."""
assert press
await asyncio.sleep(0.05)
driver = app._driver
assert driver is not None
for key in press:
print(f"press {key!r}")
await app.post_message(events.Key(self, key))
await asyncio.sleep(0.01)
driver.send_event(events.Key(self, key))
await asyncio.sleep(0.02)
self.call_later(lambda: asyncio.create_task(press_keys(self)))
async def press_keys_task():
"""Press some keys in the background."""
asyncio.create_task(press_keys())
await self.process_messages()
await self.process_messages(ready_callback=press_keys_task)
else:
await self.process_messages()
if _ASYNCIO_GET_EVENT_LOOP_IS_DEPRECATED:
# N.B. This doesn't work with Python<3.10, as we end up with 2 event loops:
@@ -933,7 +942,9 @@ class App(Generic[ReturnType], DOMNode):
self.error_console.print(renderable)
self._exit_renderables.clear()
async def process_messages(self) -> None:
async def process_messages(
self, ready_callback: CallbackType | None = None
) -> None:
self._set_active()
if self.devtools_enabled:
@@ -975,6 +986,8 @@ class App(Generic[ReturnType], DOMNode):
self.refresh()
await self.animator.start()
await self._ready()
if ready_callback is not None:
await ready_callback()
await process_messages()
await self.animator.stop()
await self.close_all()
@@ -1210,11 +1223,14 @@ class App(Generic[ReturnType], DOMNode):
Returns:
bool: True if the key was handled by a binding, otherwise False
"""
print("press", key)
try:
binding = self.bindings.get_key(key)
except NoBinding:
print("no binding")
return False
else:
print(binding)
await self.action(binding.action)
return True

View File

@@ -116,7 +116,8 @@ def import_app(import_name: str) -> App:
@run.command("run")
@click.argument("import_name", metavar="FILE or FILE:APP")
@click.option("--dev", "dev", help="Enable development mode", is_flag=True)
def run_app(import_name: str, dev: bool) -> None:
@click.option("--press", "press", help="Comma separated keys to simulate press")
def run_app(import_name: str, dev: bool, press: str) -> None:
"""Run a Textual app.
The code to run may be given as a path (ending with .py) or as a Python
@@ -156,7 +157,8 @@ def run_app(import_name: str, dev: bool) -> None:
console.print(str(error))
sys.exit(1)
app.run()
press_keys = press.split(",") if press else None
app.run(press=press_keys)
@run.command("borders")

View File

@@ -228,7 +228,7 @@ class DOMNode(MessagePump):
while node and not isinstance(node, Screen):
node = node._parent
if not isinstance(node, Screen):
raise NoScreen("{self} has no screen")
raise NoScreen(f"{self} has no screen")
return node
@property

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
from asyncio import Lock
from itertools import islice
from fractions import Fraction
from operator import attrgetter
@@ -124,6 +125,8 @@ class Widget(DOMNode):
self._styles_cache = StylesCache()
self._lock = Lock()
super().__init__(
name=name,
id=id,
@@ -1159,13 +1162,18 @@ class Widget(DOMNode):
Args:
event (events.Idle): Idle event.
"""
if self._parent is not None:
if self._repaint_required:
self._repaint_required = False
self.screen.post_message_no_wait(messages.Update(self, self))
if self._layout_required:
self._layout_required = False
self.screen.post_message_no_wait(messages.Layout(self))
if self._parent is not None and not self._closing:
try:
screen = self.screen
except NoScreen:
pass
else:
if self._repaint_required:
self._repaint_required = False
screen.post_message_no_wait(messages.Update(self, self))
if self._layout_required:
self._layout_required = False
screen.post_message_no_wait(messages.Layout(self))
def focus(self) -> None:
"""Give input focus to this widget."""