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

View File

@@ -91,6 +91,6 @@ class TimerApp(App):
timers.last().remove() timers.last().remove()
app = TimerApp(title="TimerApp", css_path="timers.css") app = TimerApp(css_path="timers.css")
if __name__ == "__main__": if __name__ == "__main__":
app.run() 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. - Installed `textual` from Pypi.
- Basic Python skills. - Basic Python skills.
```{.textual path="docs/examples/introduction/timers.py"}
```
## A Simple App ## A Simple App
Let's looks at the simplest possible Textual 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") os.environ["LINES"] = attrs.get("lines", "24")
path = attrs.get("path") path = attrs.get("path")
print(f"screenshotting {path!r}")
if path: if path:
cwd = os.getcwd() cwd = os.getcwd()
examples_path, filename = os.path.split(path) 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 = app_vars["app"]
app.run() app.run()
svg = app._screenshot svg = app._screenshot
return svg return svg

View File

@@ -188,6 +188,6 @@ def align_lines(
get_line_length = Segment.get_line_length get_line_length = Segment.get_line_length
for line in lines: for line in lines:
left_space = width - get_line_length(line) 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) yield from blank_lines(bottom_blank_lines)

View File

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

View File

@@ -144,7 +144,7 @@ class App(Generic[ReturnType], DOMNode):
log_color_system: Literal[ log_color_system: Literal[
"auto", "standard", "256", "truecolor", "windows" "auto", "standard", "256", "truecolor", "windows"
] = "auto", ] = "auto",
title: str = "Textual Application", title: str | None = None,
css_path: str | PurePath | None = None, css_path: str | PurePath | None = None,
watch_css: bool = False, watch_css: bool = False,
): ):
@@ -189,7 +189,10 @@ class App(Generic[ReturnType], DOMNode):
self.animate = self._animator.bind(self) self.animate = self._animator.bind(self)
self.mouse_position = Offset(0, 0) self.mouse_position = Offset(0, 0)
self.bindings = Bindings() 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_console: Console | None = None
self._log_file: TextIO | None = None self._log_file: TextIO | None = None
@@ -1015,7 +1018,7 @@ class App(Generic[ReturnType], DOMNode):
self._screenshot = svg # type: ignore self._screenshot = svg # type: ignore
await self.shutdown() 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: def on_mount(self) -> None:
widgets = self.compose() widgets = self.compose()

View File

@@ -105,7 +105,7 @@ class DOMNode(MessagePump):
self._auto_refresh_timer = None self._auto_refresh_timer = None
if interval is not None: if interval is not None:
self._auto_refresh_timer = self.set_interval( 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 self._auto_refresh = interval

View File

@@ -1,14 +1,44 @@
from __future__ import annotations from __future__ import annotations
import asyncio
from ..driver import Driver from ..driver import Driver
from ..geometry import Size
from .. import events
class HeadlessDriver(Driver): class HeadlessDriver(Driver):
"""A do-nothing driver for testing.""" """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: 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: def disable_input(self) -> None:
pass pass

View File

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

View File

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

View File

@@ -41,7 +41,7 @@ class HeaderClock(Widget):
""" """
def on_mount(self) -> None: 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): def render(self):
return Text(datetime.now().time().strftime("%X")) return Text(datetime.now().time().strftime("%X"))