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