fix screenshots

This commit is contained in:
Will McGugan
2022-08-19 14:13:57 +01:00
parent cccf0bd51b
commit ac24e77ecf
12 changed files with 93 additions and 46 deletions

View File

@@ -1,12 +1,12 @@
TimerWidget {
layout: horizontal;
height: 5;
background: $panel-darken-1;
border: tall $panel-darken-2;
height: 5;
min-width: 50;
margin: 1;
padding: 0 1;
transition: background 300ms linear;
min-width: 50;
}
TimerWidget.started {
@@ -35,6 +35,14 @@ Button {
dock: left;
}
#stop {
dock: left;
display: none;
}
#reset {
dock: right;
}
TimerWidget.started #start {
display: none
@@ -48,11 +56,3 @@ TimerWidget.started #reset {
visibility: hidden
}
#stop {
dock: left;
display: none;
}
#reset {
dock: right;
}

View File

@@ -91,6 +91,6 @@ class TimerApp(App):
timers.last().remove()
app = TimerApp(title="TimerApp", css_path="timers.css")
app = TimerApp(css_path="timers.css")
if __name__ == "__main__":
app.run()

View File

@@ -10,6 +10,11 @@ By the end of this page you should have a good idea of the steps involved in cre
- Installed `textual` from Pypi.
- Basic Python skills.
```{.textual path="docs/examples/introduction/timers.py"}
```
## A Simple App
Let's looks at the simplest possible Textual app.

View File

@@ -12,6 +12,7 @@ def format_svg(source, language, css_class, options, md, attrs, **kwargs):
os.environ["LINES"] = attrs.get("lines", "24")
path = attrs.get("path")
print(f"screenshotting {path!r}")
if path:
cwd = os.getcwd()
examples_path, filename = os.path.split(path)
@@ -34,4 +35,5 @@ def format_svg(source, language, css_class, options, md, attrs, **kwargs):
app = app_vars["app"]
app.run()
svg = app._screenshot
return svg

View File

@@ -188,6 +188,6 @@ def align_lines(
get_line_length = Segment.get_line_length
for line in lines:
left_space = width - get_line_length(line)
yield [*line, Segment(" " * left_space, style)]
yield [Segment(" " * left_space, style), *line]
yield from blank_lines(bottom_blank_lines)

View File

@@ -61,6 +61,7 @@ class Timer:
self._repeat = repeat
self._skip = skip
self._active = Event()
self._task: Task | None = None
if not pause:
self._active.set()
@@ -82,17 +83,21 @@ class Timer:
Returns:
Task: A Task instance for the timer.
"""
self._task = asyncio.create_task(self._run())
self._task = asyncio.create_task(self.run())
return self._task
def stop_no_wait(self) -> None:
"""Stop the timer."""
self._task.cancel()
if self._task is not None:
self._task.cancel()
self._task = None
async def stop(self) -> None:
"""Stop the timer, and block until it exits."""
self._task.cancel()
await self._task
if self._task is not None:
self._active.set()
self._task.cancel()
self._task = None
def pause(self) -> None:
"""Pause the timer."""
@@ -102,31 +107,35 @@ class Timer:
"""Result a paused timer."""
self._active.set()
async def run(self) -> None:
"""Run the timer task."""
try:
await self._run()
except CancelledError:
pass
async def _run(self) -> None:
"""Run the timer."""
count = 0
_repeat = self._repeat
_interval = self._interval
start = _clock.get_time_no_wait()
try:
while _repeat is None or count <= _repeat:
next_timer = start + ((count + 1) * _interval)
now = await _clock.get_time()
if self._skip and next_timer < now:
count += 1
continue
now = await _clock.get_time()
wait_time = max(0, next_timer - now)
if wait_time:
await _clock.sleep(wait_time)
while _repeat is None or count <= _repeat:
next_timer = start + ((count + 1) * _interval)
now = await _clock.get_time()
if self._skip and next_timer < now:
count += 1
try:
await self._tick(next_timer=next_timer, count=count)
except EventTargetGone:
break
await self._active.wait()
except CancelledError:
pass
continue
now = await _clock.get_time()
wait_time = max(0, next_timer - now)
if wait_time:
await _clock.sleep(wait_time)
count += 1
try:
await self._tick(next_timer=next_timer, count=count)
except EventTargetGone:
break
await self._active.wait()
async def _tick(self, *, next_timer: float, count: int) -> None:
"""Triggers the Timer's action: either call its callback, or sends an event to its target"""
@@ -140,5 +149,4 @@ class Timer:
count=count,
callback=self._callback,
)
await self.target.post_priority_message(event)

View File

@@ -144,7 +144,7 @@ class App(Generic[ReturnType], DOMNode):
log_color_system: Literal[
"auto", "standard", "256", "truecolor", "windows"
] = "auto",
title: str = "Textual Application",
title: str | None = None,
css_path: str | PurePath | None = None,
watch_css: bool = False,
):
@@ -189,7 +189,10 @@ class App(Generic[ReturnType], DOMNode):
self.animate = self._animator.bind(self)
self.mouse_position = Offset(0, 0)
self.bindings = Bindings()
self._title = title
if title is None:
self._title = f"{self.__class__.__name__}"
else:
self._title = title
self._log_console: Console | None = None
self._log_file: TextIO | None = None
@@ -1015,7 +1018,7 @@ class App(Generic[ReturnType], DOMNode):
self._screenshot = svg # type: ignore
await self.shutdown()
self.set_timer(screenshot_timer, on_screenshot)
self.set_timer(screenshot_timer, on_screenshot, name="screenshot timer")
def on_mount(self) -> None:
widgets = self.compose()

View File

@@ -105,7 +105,7 @@ class DOMNode(MessagePump):
self._auto_refresh_timer = None
if interval is not None:
self._auto_refresh_timer = self.set_interval(
interval, self._automatic_refresh
interval, self._automatic_refresh, name=f"auto refresh {self!r}"
)
self._auto_refresh = interval

View File

@@ -1,14 +1,44 @@
from __future__ import annotations
import asyncio
from ..driver import Driver
from ..geometry import Size
from .. import events
class HeadlessDriver(Driver):
"""A do-nothing driver for testing."""
def _get_terminal_size(self) -> tuple[int, int]:
width: int | None = 80
height: int | None = 25
import shutil
try:
width, height = shutil.get_terminal_size()
except (AttributeError, ValueError, OSError):
try:
width, height = shutil.get_terminal_size()
except (AttributeError, ValueError, OSError):
pass
width = width or 80
height = height or 25
return width, height
def start_application_mode(self) -> None:
pass
loop = asyncio.get_running_loop()
def send_size_event():
terminal_size = self._get_terminal_size()
width, height = terminal_size
textual_size = Size(width, height)
event = events.Resize(self._target, textual_size, textual_size)
asyncio.run_coroutine_threadsafe(
self._target.post_message(event),
loop=loop,
)
send_size_event()
def disable_input(self) -> None:
pass

View File

@@ -254,7 +254,8 @@ class MessagePump(metaclass=MessagePumpMeta):
if self._closed or self._closing:
return
self._closing = True
for timer in self._timers:
stop_timers = list(self._timers)
for timer in stop_timers:
await timer.stop()
self._timers.clear()
await self._message_queue.put(MessagePriority(None))
@@ -274,7 +275,7 @@ class MessagePump(metaclass=MessagePumpMeta):
pass
finally:
self._running = False
for timer in self._timers:
for timer in list(self._timers):
await timer.stop()
async def _process_messages(self) -> None:

View File

@@ -33,8 +33,6 @@ class Screen(Widget):
CSS = """
Screen {
color: $text-background;
background: $background;
layout: vertical;
overflow-y: auto;
}

View File

@@ -41,7 +41,7 @@ class HeaderClock(Widget):
"""
def on_mount(self) -> None:
self.set_interval(1, callback=self.refresh)
self.set_interval(1, callback=self.refresh, name=f"update header clock")
def render(self):
return Text(datetime.now().time().strftime("%X"))