mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge branch 'main' into no-container-scroll
This commit is contained in:
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
### Changed
|
||||
|
||||
- `textual run` execs apps in a new context.
|
||||
- Textual console no longer parses console markup.
|
||||
- Breaking change: `Container` no longer shows required scrollbars by default https://github.com/Textualize/textual/issues/2361
|
||||
- Breaking change: `VerticalScroll` no longer shows a required horizontal scrollbar by default
|
||||
- Breaking change: `HorizontalScroll` no longer shows a required vertical scrollbar by default
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
"""
|
||||
Simulates a screenshot of the Textual devtools
|
||||
|
||||
"""
|
||||
|
||||
from textual.app import App
|
||||
|
||||
from textual.devtools.renderables import DevConsoleHeader
|
||||
from textual.widgets import Static
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Static, Header
|
||||
from textual.widgets import Header, Static
|
||||
|
||||
TEXT = """\
|
||||
Docking a widget removes it from the layout and fixes its position, aligned to either the top, right, bottom, or left edges of a container.
|
||||
|
||||
@@ -45,8 +45,6 @@ class ListViewExample(App):
|
||||
)
|
||||
yield Footer()
|
||||
```
|
||||
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ Code browser example.
|
||||
Run with:
|
||||
|
||||
python code_browser.py PATH
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
@@ -131,7 +131,6 @@ class GameCell(Button):
|
||||
Args:
|
||||
row (int): The row of the cell.
|
||||
col (int): The column of the cell.
|
||||
|
||||
"""
|
||||
super().__init__("", id=self.at(row, col))
|
||||
self.row = row
|
||||
|
||||
@@ -144,7 +144,6 @@ class BoundAnimator:
|
||||
delay: A delay (in seconds) before the animation starts.
|
||||
easing: An easing method.
|
||||
on_complete: A callable to invoke when the animation is finished.
|
||||
|
||||
"""
|
||||
start_value = getattr(self._obj, attribute)
|
||||
if isinstance(value, str) and hasattr(start_value, "parse"):
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""
|
||||
Compatibility layer for asyncio.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -9,7 +9,6 @@ Note that stdlib's @lru_cache is implemented in C and faster! It's best to use
|
||||
@lru_cache where you are caching things that are fairly quick and called many times.
|
||||
Use LRUCache where you want increased flexibility and you are caching slow operations
|
||||
where the overhead of the cache is a small fraction of the total processing time.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -35,7 +34,6 @@ class LRUCache(Generic[CacheKey, CacheValue]):
|
||||
|
||||
Each entry is stored as [PREV, NEXT, KEY, VALUE] where PREV is a reference
|
||||
to the previous entry, and NEXT is a reference to the next value.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = [
|
||||
|
||||
@@ -8,7 +8,6 @@ queries regarding the widget under an offset, or the style under an offset.
|
||||
|
||||
Additionally, the compositor can render portions of the screen which may have updated,
|
||||
without having to render the entire screen.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -410,7 +409,6 @@ class Compositor:
|
||||
|
||||
Returns:
|
||||
Set of widgets that were exposed by the scroll.
|
||||
|
||||
"""
|
||||
self._cuts = None
|
||||
self._layers = None
|
||||
@@ -790,7 +788,6 @@ class Compositor:
|
||||
|
||||
Returns:
|
||||
Widget's composition information.
|
||||
|
||||
"""
|
||||
if self.root is None:
|
||||
raise errors.NoWidget("Widget is not in layout")
|
||||
@@ -932,7 +929,6 @@ class Compositor:
|
||||
|
||||
Returns:
|
||||
A ChopsUpdate if there is anything to update, otherwise `None`.
|
||||
|
||||
"""
|
||||
screen_region = self.size.region
|
||||
update_regions = self._dirty_regions.copy()
|
||||
@@ -1019,7 +1015,6 @@ class Compositor:
|
||||
|
||||
Args:
|
||||
widgets: Set of Widgets to update.
|
||||
|
||||
"""
|
||||
# If there are any *new* widgets we need to invalidate the full map
|
||||
if not self._full_map_invalidated and not widgets.issubset(
|
||||
|
||||
@@ -25,7 +25,6 @@ def _duration_as_seconds(duration: str) -> float:
|
||||
DurationParseError: If the argument ``duration`` is not a valid duration string.
|
||||
Returns:
|
||||
The duration in seconds.
|
||||
|
||||
"""
|
||||
match = _match_duration(duration)
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ class NodeList(Sequence["Widget"]):
|
||||
A container for widgets that forms one level of hierarchy.
|
||||
|
||||
Although named a list, widgets may appear only once, making them more like a set.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""
|
||||
Timer context manager, only used in debug.
|
||||
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
|
||||
@@ -21,7 +21,6 @@ class SpatialMap(Generic[ValueType]):
|
||||
|
||||
The SpatialMap is able to quickly retrieve the values under a given "window" region
|
||||
by combining the values in the grid squares under the visible area.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, grid_width: int = 100, grid_height: int = 20) -> None:
|
||||
|
||||
@@ -65,7 +65,6 @@ class StylesCache:
|
||||
┃ ┃
|
||||
┗━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
```
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
A version of `time.sleep` that is more accurate than the standard library (even on Python 3.11).
|
||||
|
||||
This should only be imported on Windows.
|
||||
|
||||
"""
|
||||
|
||||
from time import sleep as time_sleep
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""
|
||||
|
||||
A decorator used to create [workers](/guide/workers).
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -68,7 +67,6 @@ def work(
|
||||
group: A short string to identify a group of workers.
|
||||
exit_on_error: Exit the app if the worker raises an error. Set to `False` to suppress exceptions.
|
||||
exclusive: Cancel all workers in the same group.
|
||||
|
||||
"""
|
||||
|
||||
def decorator(
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
A class to manage [workers](/guide/workers) for an app.
|
||||
|
||||
You access this object via [App.workers][textual.app.App.workers] or [Widget.workers][textual.dom.DOMNode.workers].
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -28,7 +27,6 @@ class WorkerManager:
|
||||
|
||||
You will not have to construct this class manually, as widgets, screens, and apps
|
||||
have a worker manager accessibly via a `workers` attribute.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, app: App) -> None:
|
||||
@@ -163,7 +161,6 @@ class WorkerManager:
|
||||
|
||||
Returns:
|
||||
List of cancelled workers.
|
||||
|
||||
"""
|
||||
workers = [worker for worker in self._workers if worker.node == node]
|
||||
for worker in workers:
|
||||
|
||||
@@ -231,7 +231,6 @@ class XTermParser(Parser[events.Event]):
|
||||
|
||||
Returns:
|
||||
Keys
|
||||
|
||||
"""
|
||||
keys = ANSI_SEQUENCES_KEYS.get(sequence)
|
||||
if keys is not None:
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
Here you will find the [App][textual.app.App] class, which is the base class for Textual apps.
|
||||
|
||||
See [app basics](/guide/app) for how to build Textual apps.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -248,7 +247,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
```python
|
||||
self.app.dark = not self.app.dark # Toggle dark mode
|
||||
```
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -403,7 +401,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
Returns:
|
||||
An object to manage workers.
|
||||
|
||||
"""
|
||||
return self._workers
|
||||
|
||||
@@ -412,7 +409,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
"""The return value of the app, or `None` if it as not yet been set.
|
||||
|
||||
The return value is set when calling [exit][textual.app.App.exit].
|
||||
|
||||
"""
|
||||
return self._return_value
|
||||
|
||||
@@ -425,7 +421,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
Returns:
|
||||
A sequence of widgets.
|
||||
|
||||
"""
|
||||
try:
|
||||
return (self.screen,)
|
||||
@@ -477,7 +472,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
delay: A delay (in seconds) before the animation starts.
|
||||
easing: An easing method.
|
||||
on_complete: A callable to invoke when the animation is finished.
|
||||
|
||||
"""
|
||||
self._animate(
|
||||
attribute,
|
||||
@@ -500,7 +494,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
"""Is the driver running in 'headless' mode?
|
||||
|
||||
Headless mode is used when running tests with [run_test][textual.app.App.run_test].
|
||||
|
||||
"""
|
||||
return False if self._driver is None else self._driver.is_headless
|
||||
|
||||
@@ -510,7 +503,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
Returns:
|
||||
A snapshot of the current state of the screen stack.
|
||||
|
||||
"""
|
||||
return self._screen_stack.copy()
|
||||
|
||||
@@ -537,7 +529,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
Returns:
|
||||
The currently focused widget, or `None` if nothing is focused.
|
||||
|
||||
"""
|
||||
return self.screen.focused
|
||||
|
||||
@@ -553,7 +544,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
Returns:
|
||||
|
||||
A mapping of keys on to node + binding.
|
||||
|
||||
"""
|
||||
|
||||
namespace_binding_map: dict[str, tuple[DOMNode, Binding]] = {}
|
||||
@@ -571,7 +561,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
"""Yield child widgets for a container.
|
||||
|
||||
This method should be implemented in a subclass.
|
||||
|
||||
"""
|
||||
yield from ()
|
||||
|
||||
@@ -591,7 +580,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
This method handles the transition between light and dark mode when you
|
||||
change the [dark][textual.app.App.dark] attribute.
|
||||
|
||||
"""
|
||||
self.set_class(dark, "-dark-mode")
|
||||
self.set_class(not dark, "-light-mode")
|
||||
@@ -692,7 +680,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
Returns:
|
||||
Size of the terminal.
|
||||
|
||||
"""
|
||||
if self._driver is not None and self._driver._size is not None:
|
||||
width, height = self._driver._size
|
||||
@@ -712,7 +699,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
Returns:
|
||||
A Textual logger.
|
||||
|
||||
"""
|
||||
return self._logger
|
||||
|
||||
@@ -837,7 +823,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
Args:
|
||||
title: The title of the exported screenshot or None
|
||||
to use app title.
|
||||
|
||||
"""
|
||||
assert self._driver is not None, "App must be running"
|
||||
width, height = self.size
|
||||
@@ -985,8 +970,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
headless: Run in headless mode (no output or input).
|
||||
size: Force terminal size to `(WIDTH, HEIGHT)`,
|
||||
or None to auto-detect.
|
||||
|
||||
|
||||
"""
|
||||
from .pilot import Pilot
|
||||
|
||||
@@ -1364,7 +1347,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
Returns:
|
||||
A screen instance and an awaitable that awaits the children mounting.
|
||||
|
||||
"""
|
||||
_screen = self.get_screen(screen)
|
||||
if not _screen.is_running:
|
||||
@@ -1385,7 +1367,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
Returns:
|
||||
The screen that was replaced.
|
||||
|
||||
"""
|
||||
if self._screen_stack:
|
||||
self.screen.refresh()
|
||||
@@ -1432,7 +1413,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
Args:
|
||||
screen: Either a Screen object or screen name (the `name` argument when installed).
|
||||
|
||||
"""
|
||||
if not isinstance(screen, (Screen, str)):
|
||||
raise TypeError(
|
||||
@@ -1770,7 +1750,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
"""Called immediately prior to processing messages.
|
||||
|
||||
May be used as a hook for any operations that should run first.
|
||||
|
||||
"""
|
||||
|
||||
async def take_screenshot() -> None:
|
||||
@@ -1859,7 +1838,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
after: A location to mount after.
|
||||
Returns:
|
||||
List of modified widgets.
|
||||
|
||||
"""
|
||||
|
||||
if not widgets:
|
||||
@@ -2061,8 +2039,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
For terminals that support a bell, this typically makes a notification or error sound.
|
||||
Some terminals may make no sound or display a visual bell indicator, depending on configuration.
|
||||
|
||||
|
||||
"""
|
||||
if not self.is_headless and self._driver is not None:
|
||||
self._driver.write("\07")
|
||||
@@ -2345,7 +2321,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
Returns:
|
||||
The child widgets of root.
|
||||
|
||||
"""
|
||||
stack: list[Widget] = [root]
|
||||
pop = stack.pop
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
"""
|
||||
|
||||
An *optionally* awaitable object returned by methods that remove widgets.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from asyncio import Event, Task
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
A binding maps a key press on to an action.
|
||||
|
||||
See [bindings](/guide/input#bindings) in the guide for details.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ Functions to run Textual apps with an updated environment.
|
||||
|
||||
Note that these methods will execute apps in a new process, and abandon the current process.
|
||||
This means that (if they succeed) they will never return.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -98,7 +97,6 @@ def exec_python(args: list[str], environment: dict[str, str]) -> NoReturn:
|
||||
Args:
|
||||
args: Additional arguments.
|
||||
environment: Environment variables.
|
||||
|
||||
"""
|
||||
_flush()
|
||||
os.execvpe(sys.executable, ["python", *args], environment)
|
||||
@@ -148,7 +146,6 @@ def exec_import(
|
||||
import_name: The Python import.
|
||||
args: Command line arguments.
|
||||
environment: Environment variables.
|
||||
|
||||
"""
|
||||
module, _colon, app = import_name.partition(":")
|
||||
app = app or "app"
|
||||
|
||||
@@ -38,7 +38,6 @@ class Bar(Widget):
|
||||
background: $surface;
|
||||
color: $success;
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
def watch_animation_running(self, running: bool) -> None:
|
||||
|
||||
@@ -26,8 +26,6 @@ for name, triplet in sorted(COLOR_NAME_TO_RGB.items()):
|
||||
)
|
||||
output = table
|
||||
```
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -158,7 +156,6 @@ class Color(NamedTuple):
|
||||
>>> color + color_with_alpha
|
||||
Color(177, 25, 12)
|
||||
```
|
||||
|
||||
"""
|
||||
|
||||
r: int
|
||||
@@ -204,7 +201,6 @@ class Color(NamedTuple):
|
||||
|
||||
Returns:
|
||||
Inverse color.
|
||||
|
||||
"""
|
||||
r, g, b, a = self
|
||||
return Color(255 - r, 255 - g, 255 - b, a)
|
||||
@@ -245,7 +241,6 @@ class Color(NamedTuple):
|
||||
|
||||
Returns:
|
||||
Normalized components.
|
||||
|
||||
"""
|
||||
r, g, b, _a = self
|
||||
return (r / 255, g / 255, b / 255)
|
||||
@@ -264,7 +259,6 @@ class Color(NamedTuple):
|
||||
|
||||
Returns:
|
||||
Color encoded in HSL format.
|
||||
|
||||
"""
|
||||
r, g, b = self.normalized
|
||||
h, l, s = rgb_to_hls(r, g, b)
|
||||
@@ -276,7 +270,6 @@ class Color(NamedTuple):
|
||||
|
||||
A value of 1 is returned for pure white, and 0 for pure black.
|
||||
Other colors lie on a gradient between the two extremes.
|
||||
|
||||
"""
|
||||
r, g, b = self.normalized
|
||||
brightness = (299 * r + 587 * g + 114 * b) / 1000
|
||||
@@ -287,7 +280,6 @@ class Color(NamedTuple):
|
||||
"""The color in CSS hex form, with 6 digits for RGB, and 8 digits for RGBA.
|
||||
|
||||
For example, `"#46b3de"` for an RGB color, or `"#3342457f"` for a color with alpha.
|
||||
|
||||
"""
|
||||
r, g, b, a = self.clamped
|
||||
return (
|
||||
@@ -301,7 +293,6 @@ class Color(NamedTuple):
|
||||
"""The color in CSS hex form, with 6 digits for RGB. Alpha is ignored.
|
||||
|
||||
For example, `"#46b3de"`.
|
||||
|
||||
"""
|
||||
r, g, b, _a = self.clamped
|
||||
return f"#{r:02X}{g:02X}{b:02X}"
|
||||
@@ -311,7 +302,6 @@ class Color(NamedTuple):
|
||||
"""The color in CSS RGB or RGBA form.
|
||||
|
||||
For example, `"rgb(10,20,30)"` for an RGB color, or `"rgb(50,70,80,0.5)"` for an RGBA color.
|
||||
|
||||
"""
|
||||
r, g, b, a = self
|
||||
return f"rgb({r},{g},{b})" if a == 1 else f"rgba({r},{g},{b},{a})"
|
||||
@@ -322,7 +312,6 @@ class Color(NamedTuple):
|
||||
|
||||
Returns:
|
||||
The monochrome (black and white) version of this color.
|
||||
|
||||
"""
|
||||
r, g, b, a = self
|
||||
gray = round(r * 0.2126 + g * 0.7152 + b * 0.0722)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""
|
||||
Constants that we might want to expose via the public API.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""
|
||||
Container widgets for quick styling.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""
|
||||
A class to store a coordinate, used by the [DataTable][textual.widgets.DataTable].
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -4,7 +4,6 @@ setting attributes. This gives the developer more freedom in how to express styl
|
||||
|
||||
Descriptors also play nicely with Mypy, which is aware that attributes can have different types
|
||||
when setting and getting.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -383,7 +382,6 @@ class BorderProperty:
|
||||
|
||||
Args:
|
||||
layout: True if the layout should be refreshed after setting, False otherwise.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, layout: bool) -> None:
|
||||
|
||||
@@ -227,7 +227,6 @@ def _unresolved(variable_name: str, variables: Iterable[str], token: Token) -> N
|
||||
|
||||
Raises:
|
||||
UnresolvedVariableError: Always raises a TokenError.
|
||||
|
||||
"""
|
||||
message = f"reference to undefined variable '${variable_name}'"
|
||||
suggested_variable = get_suggestion(variable_name, list(variables))
|
||||
|
||||
@@ -770,7 +770,6 @@ class Styles(StylesBase):
|
||||
|
||||
Returns:
|
||||
An iterable of CSS declarations.
|
||||
|
||||
"""
|
||||
|
||||
has_rule = rules.__contains__
|
||||
@@ -1119,7 +1118,6 @@ class RenderStyles(StylesBase):
|
||||
delay: A delay (in seconds) before the animation starts.
|
||||
easing: An easing method.
|
||||
on_complete: A callable to invoke when the animation is finished.
|
||||
|
||||
"""
|
||||
if self._animate is None:
|
||||
assert self.node is not None
|
||||
|
||||
@@ -333,7 +333,6 @@ class Stylesheet:
|
||||
Raises:
|
||||
StylesheetError: If the CSS could not be read.
|
||||
StylesheetParseError: If the CSS is invalid.
|
||||
|
||||
"""
|
||||
# Do this in a fresh Stylesheet so if there are errors we don't break self.
|
||||
stylesheet = Stylesheet(variables=self._variables)
|
||||
|
||||
@@ -72,7 +72,6 @@ WELCOME_MD = """
|
||||
## Textual Demo
|
||||
|
||||
**Welcome**! Textual is a framework for creating sophisticated applications with the terminal.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -83,8 +82,6 @@ Textual is built on **Rich**, the popular Python library for advanced terminal o
|
||||
Add content to your Textual App with Rich *renderables* (this text is written in Markdown and formatted with Rich's Markdown class).
|
||||
|
||||
Here are some examples:
|
||||
|
||||
|
||||
"""
|
||||
|
||||
CSS_MD = """
|
||||
@@ -95,7 +92,6 @@ Textual uses Cascading Stylesheets (CSS) to create Rich interactive User Interfa
|
||||
- **Live editing** - see your changes without restarting the app!
|
||||
|
||||
Here's an example of some CSS used in this app:
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -143,7 +139,6 @@ Build your own or use the builtin widgets.
|
||||
- **Tree** An generic tree with expandable nodes.
|
||||
- **DirectoryTree** A tree of file and folders.
|
||||
- *... many more planned ...*
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -160,7 +155,6 @@ Here are some links. You can click these!
|
||||
|
||||
|
||||
Built with ♥ by [@click="app.open_link('https://www.textualize.io')"]Textualize.io[/]
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@ class ColorSystem:
|
||||
|
||||
Primary is the main theme color
|
||||
Secondary is a second theme color
|
||||
|
||||
|
||||
"""
|
||||
|
||||
COLOR_NAMES = [
|
||||
@@ -96,7 +94,6 @@ class ColorSystem:
|
||||
|
||||
Returns:
|
||||
A mapping of color name on to a CSS-style encoded color
|
||||
|
||||
"""
|
||||
|
||||
primary = self.primary
|
||||
@@ -134,7 +131,6 @@ class ColorSystem:
|
||||
|
||||
Returns:
|
||||
Iterable of tuples (<SHADE SUFFIX, LUMINOSITY DELTA>)
|
||||
|
||||
"""
|
||||
luminosity_step = spread / 2
|
||||
for n in range(-NUMBER_OF_SHADES, +NUMBER_OF_SHADES + 1):
|
||||
@@ -196,7 +192,6 @@ def show_design(light: ColorSystem, dark: ColorSystem) -> Table:
|
||||
|
||||
Returns:
|
||||
Table showing all colors.
|
||||
|
||||
"""
|
||||
|
||||
@group()
|
||||
|
||||
@@ -211,9 +211,9 @@ class DevtoolsClient:
|
||||
log: The log to write to devtools
|
||||
"""
|
||||
if isinstance(log.objects_or_string, str):
|
||||
self.console.print(log.objects_or_string)
|
||||
self.console.print(log.objects_or_string, markup=False)
|
||||
else:
|
||||
self.console.print(*log.objects_or_string)
|
||||
self.console.print(*log.objects_or_string, markup=False)
|
||||
|
||||
segments = self.console.export_segments()
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
A DOMNode is a base class for any object within the Textual Document Object Model,
|
||||
which includes all Widgets, Screens, and Apps.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -74,7 +72,6 @@ def check_identifiers(description: str, *names: str) -> None:
|
||||
Args:
|
||||
description: Description of where identifier is used for error message.
|
||||
*names: Identifiers to check.
|
||||
|
||||
"""
|
||||
match = _re_identifier.match
|
||||
for name in names:
|
||||
@@ -203,7 +200,6 @@ class DOMNode(MessagePump):
|
||||
|
||||
Returns:
|
||||
The node's children.
|
||||
|
||||
"""
|
||||
return self._nodes
|
||||
|
||||
@@ -328,7 +324,6 @@ class DOMNode(MessagePump):
|
||||
"""Called after styles are updated.
|
||||
|
||||
Implement this in a subclass if you want to clear any cached data when the CSS is reloaded.
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
@@ -450,7 +445,6 @@ class DOMNode(MessagePump):
|
||||
"""The parent node.
|
||||
|
||||
All nodes have parent once added to the DOM, with the exception of the App which is the *root* node.
|
||||
|
||||
"""
|
||||
return cast("DOMNode | None", self._parent)
|
||||
|
||||
@@ -463,7 +457,6 @@ class DOMNode(MessagePump):
|
||||
|
||||
Raises:
|
||||
NoScreen: If this node isn't mounted (and has no screen).
|
||||
|
||||
"""
|
||||
# Get the node by looking up a chain of parents
|
||||
# Note that self.screen may not be the same as self.app.screen
|
||||
@@ -490,7 +483,6 @@ class DOMNode(MessagePump):
|
||||
|
||||
Raises:
|
||||
ValueError: If the ID has already been set.
|
||||
|
||||
"""
|
||||
check_identifiers("id", new_id)
|
||||
|
||||
@@ -520,7 +512,6 @@ class DOMNode(MessagePump):
|
||||
|
||||
Returns:
|
||||
A Rich Text object.
|
||||
|
||||
"""
|
||||
tokens = Text.styled(self.__class__.__name__)
|
||||
if self.id is not None:
|
||||
@@ -584,7 +575,6 @@ class DOMNode(MessagePump):
|
||||
```python
|
||||
my_widget.display = False # Hide my_widget
|
||||
```
|
||||
|
||||
"""
|
||||
return self.styles.display != "none" and not (self._closing or self._closed)
|
||||
|
||||
@@ -616,7 +606,6 @@ class DOMNode(MessagePump):
|
||||
May be set to a boolean to make the node visible (`True`) or invisible (`False`), or to any valid value for the `visibility` rule.
|
||||
|
||||
When a node is invisible, Textual will reserve space for it, but won't display anything there.
|
||||
|
||||
"""
|
||||
return self.styles.visibility != "hidden"
|
||||
|
||||
@@ -645,7 +634,6 @@ class DOMNode(MessagePump):
|
||||
|
||||
Returns:
|
||||
A Tree renderable.
|
||||
|
||||
"""
|
||||
|
||||
def render_info(node: DOMNode) -> Pretty:
|
||||
@@ -745,7 +733,6 @@ class DOMNode(MessagePump):
|
||||
|
||||
Returns:
|
||||
A Rich style.
|
||||
|
||||
"""
|
||||
background = Color(0, 0, 0, 0)
|
||||
color = Color(255, 255, 255, 0)
|
||||
@@ -775,7 +762,6 @@ class DOMNode(MessagePump):
|
||||
|
||||
Returns:
|
||||
A Rich style.
|
||||
|
||||
"""
|
||||
styles = self.styles
|
||||
if styles.auto_border_title_color:
|
||||
@@ -798,7 +784,6 @@ class DOMNode(MessagePump):
|
||||
|
||||
Returns:
|
||||
A Rich style.
|
||||
|
||||
"""
|
||||
styles = self.styles
|
||||
if styles.auto_border_subtitle_color:
|
||||
@@ -817,7 +802,6 @@ class DOMNode(MessagePump):
|
||||
|
||||
Returns:
|
||||
`(<background color>, <color>)`
|
||||
|
||||
"""
|
||||
base_background = background = BLACK
|
||||
for node in reversed(self.ancestors_with_self):
|
||||
@@ -832,7 +816,6 @@ class DOMNode(MessagePump):
|
||||
|
||||
Returns:
|
||||
`(<parent background>, <parent color>, <background>, <color>)`
|
||||
|
||||
"""
|
||||
base_background = background = WHITE
|
||||
base_color = color = BLACK
|
||||
@@ -873,7 +856,6 @@ class DOMNode(MessagePump):
|
||||
|
||||
Returns:
|
||||
A list of nodes.
|
||||
|
||||
"""
|
||||
return self.ancestors_with_self[1:]
|
||||
|
||||
@@ -883,7 +865,6 @@ class DOMNode(MessagePump):
|
||||
|
||||
Returns:
|
||||
A list of nodes.
|
||||
|
||||
"""
|
||||
return [child for child in self._nodes if child.display]
|
||||
|
||||
@@ -995,7 +976,6 @@ class DOMNode(MessagePump):
|
||||
|
||||
Returns:
|
||||
A list of nodes.
|
||||
|
||||
"""
|
||||
check_type = filter_type or DOMNode
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ Verbose events are excluded from the textual console, unless you explicit reques
|
||||
```
|
||||
textual console -v
|
||||
```
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -50,15 +49,6 @@ class Callback(Event, bubble=False, verbose=True):
|
||||
yield "callback", self.callback
|
||||
|
||||
|
||||
class InvokeCallbacks(Event, bubble=False, verbose=True):
|
||||
"""An internal event, sent to the screen to run callbacks.
|
||||
|
||||
- [ ] Bubbles
|
||||
- [X] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class ShutdownRequest(Event):
|
||||
pass
|
||||
|
||||
@@ -76,7 +66,6 @@ class Load(Event, bubble=False):
|
||||
|
||||
- [ ] Bubbles
|
||||
- [ ] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -88,7 +77,6 @@ class Idle(Event, bubble=False):
|
||||
|
||||
- [ ] Bubbles
|
||||
- [ ] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -113,7 +101,6 @@ class Resize(Event, bubble=False):
|
||||
size: The new size of the Widget.
|
||||
virtual_size: The virtual size (scrollable size) of the Widget.
|
||||
container_size: The size of the Widget's container widget.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ["size", "virtual_size", "container_size"]
|
||||
@@ -143,7 +130,6 @@ class Compose(Event, bubble=False, verbose=True):
|
||||
|
||||
- [ ] Bubbles
|
||||
- [X] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -152,7 +138,6 @@ class Mount(Event, bubble=False, verbose=False):
|
||||
|
||||
- [ ] Bubbles
|
||||
- [ ] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -161,7 +146,6 @@ class Unmount(Mount, bubble=False, verbose=False):
|
||||
|
||||
- [ ] Bubbles
|
||||
- [ ] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -170,7 +154,6 @@ class Show(Event, bubble=False):
|
||||
|
||||
- [ ] Bubbles
|
||||
- [ ] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -182,7 +165,6 @@ class Hide(Event, bubble=False):
|
||||
|
||||
A widget may be hidden by setting its `visible` flag to `False`, if it is no longer in a layout,
|
||||
or if it has been offset beyond the edges of the terminal.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -191,7 +173,6 @@ class Ready(Event, bubble=False):
|
||||
|
||||
- [ ] Bubbles
|
||||
- [ ] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -206,7 +187,6 @@ class MouseCapture(Event, bubble=False):
|
||||
|
||||
Args:
|
||||
mouse_position: The position of the mouse when captured.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, mouse_position: Offset) -> None:
|
||||
@@ -318,7 +298,6 @@ class MouseEvent(InputEvent, bubble=True):
|
||||
screen_x: The absolute x coordinate.
|
||||
screen_y: The absolute y coordinate.
|
||||
style: The Rich Style under the mouse cursor.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = [
|
||||
@@ -399,7 +378,6 @@ class MouseEvent(InputEvent, bubble=True):
|
||||
|
||||
Returns:
|
||||
Mouse coordinate.
|
||||
|
||||
"""
|
||||
return Offset(self.x, self.y)
|
||||
|
||||
@@ -418,7 +396,6 @@ class MouseEvent(InputEvent, bubble=True):
|
||||
|
||||
Returns:
|
||||
Mouse coordinate.
|
||||
|
||||
"""
|
||||
return Offset(self.delta_x, self.delta_y)
|
||||
|
||||
@@ -466,7 +443,6 @@ class MouseMove(MouseEvent, bubble=False, verbose=True):
|
||||
|
||||
- [ ] Bubbles
|
||||
- [X] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -476,7 +452,6 @@ class MouseDown(MouseEvent, bubble=True, verbose=True):
|
||||
|
||||
- [X] Bubbles
|
||||
- [X] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -486,7 +461,6 @@ class MouseUp(MouseEvent, bubble=True, verbose=True):
|
||||
|
||||
- [X] Bubbles
|
||||
- [X] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -496,7 +470,6 @@ class MouseScrollDown(MouseEvent, bubble=True):
|
||||
|
||||
- [X] Bubbles
|
||||
- [ ] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -506,7 +479,6 @@ class MouseScrollUp(MouseEvent, bubble=True):
|
||||
|
||||
- [X] Bubbles
|
||||
- [ ] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -515,7 +487,6 @@ class Click(MouseEvent, bubble=True):
|
||||
|
||||
- [X] Bubbles
|
||||
- [ ] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -525,8 +496,6 @@ class Timer(Event, bubble=False, verbose=True):
|
||||
|
||||
- [ ] Bubbles
|
||||
- [X] Verbose
|
||||
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ["time", "count", "callback"]
|
||||
@@ -554,7 +523,6 @@ class Enter(Event, bubble=False, verbose=True):
|
||||
|
||||
- [ ] Bubbles
|
||||
- [X] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -563,7 +531,6 @@ class Leave(Event, bubble=False, verbose=True):
|
||||
|
||||
- [ ] Bubbles
|
||||
- [X] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -572,7 +539,6 @@ class Focus(Event, bubble=False):
|
||||
|
||||
- [X] Bubbles
|
||||
- [ ] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -581,7 +547,6 @@ class Blur(Event, bubble=False):
|
||||
|
||||
- [X] Bubbles
|
||||
- [ ] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -590,7 +555,6 @@ class DescendantFocus(Event, bubble=True, verbose=True):
|
||||
|
||||
- [X] Bubbles
|
||||
- [X] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -599,7 +563,6 @@ class DescendantBlur(Event, bubble=True, verbose=True):
|
||||
|
||||
- [X] Bubbles
|
||||
- [X] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -631,7 +594,6 @@ class ScreenResume(Event, bubble=False):
|
||||
|
||||
- [ ] Bubbles
|
||||
- [ ] Verbose
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -640,5 +602,4 @@ class ScreenSuspend(Event, bubble=False):
|
||||
|
||||
- [ ] Bubbles
|
||||
- [ ] Verbose
|
||||
|
||||
"""
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""
|
||||
|
||||
Functions and classes to manage terminal geometry (anything involving coordinates or dimensions).
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -75,7 +74,6 @@ class Offset(NamedTuple):
|
||||
>>> -offset
|
||||
Offset(x=-13, y=-2)
|
||||
```
|
||||
|
||||
"""
|
||||
|
||||
x: int = 0
|
||||
@@ -167,7 +165,6 @@ class Size(NamedTuple):
|
||||
>>> size + Size(10, 20)
|
||||
Size(width=12, height=23)
|
||||
```
|
||||
|
||||
"""
|
||||
|
||||
width: int = 0
|
||||
@@ -283,7 +280,6 @@ class Region(NamedTuple):
|
||||
>>> region.contains(10, 8)
|
||||
True
|
||||
```
|
||||
|
||||
"""
|
||||
|
||||
x: int = 0
|
||||
@@ -407,7 +403,6 @@ class Region(NamedTuple):
|
||||
"""A pair of integers for the start and end columns (x coordinates) in this region.
|
||||
|
||||
The end value is *exclusive*.
|
||||
|
||||
"""
|
||||
return (self.x, self.x + self.width)
|
||||
|
||||
@@ -416,7 +411,6 @@ class Region(NamedTuple):
|
||||
"""A pair of integers for the start and end lines (y coordinates) in this region.
|
||||
|
||||
The end value is *exclusive*.
|
||||
|
||||
"""
|
||||
return (self.y, self.y + self.height)
|
||||
|
||||
@@ -441,7 +435,6 @@ class Region(NamedTuple):
|
||||
|
||||
Returns:
|
||||
An offset.
|
||||
|
||||
"""
|
||||
return Offset(*self[:2])
|
||||
|
||||
@@ -461,7 +454,6 @@ class Region(NamedTuple):
|
||||
|
||||
Returns:
|
||||
An offset.
|
||||
|
||||
"""
|
||||
x, y, width, _height = self
|
||||
return Offset(x + width, y)
|
||||
@@ -472,7 +464,6 @@ class Region(NamedTuple):
|
||||
|
||||
Returns:
|
||||
An offset.
|
||||
|
||||
"""
|
||||
x, y, width, height = self
|
||||
return Offset(x + width, y + height)
|
||||
@@ -504,7 +495,6 @@ class Region(NamedTuple):
|
||||
|
||||
Returns:
|
||||
A region at the origin.
|
||||
|
||||
"""
|
||||
_, _, width, height = self
|
||||
return Region(0, 0, width, height)
|
||||
@@ -894,7 +884,6 @@ class Spacing(NamedTuple):
|
||||
>>> spacing.css
|
||||
'1 2 3 4'
|
||||
```
|
||||
|
||||
"""
|
||||
|
||||
top: int = 0
|
||||
|
||||
@@ -34,7 +34,6 @@ class GridLayout(Layout):
|
||||
|
||||
Args:
|
||||
column_count: Number of columns
|
||||
|
||||
"""
|
||||
row = 0
|
||||
while True:
|
||||
|
||||
@@ -4,7 +4,6 @@ A Textual Logging handler.
|
||||
If there is an active Textual app, then log messages will go via the app (and logged via textual console).
|
||||
|
||||
If there is *no* active app, then log messages will go to stderr or stdout, depending on configuration.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
"""
|
||||
|
||||
The base class for all messages (including events).
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""
|
||||
|
||||
A message pump is a base class for any object which processes messages, which includes Widget, Screen, and App.
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -47,7 +46,6 @@ class MessagePumpClosed(Exception):
|
||||
class _MessagePumpMeta(type):
|
||||
"""Metaclass for message pump. This exists to populate a Message inner class of a Widget with the
|
||||
parent classes' name.
|
||||
|
||||
"""
|
||||
|
||||
def __new__(
|
||||
@@ -122,7 +120,6 @@ class MessagePump(metaclass=_MessagePumpMeta):
|
||||
with self.prevent(Input.Changed):
|
||||
input.value = "foo"
|
||||
```
|
||||
|
||||
"""
|
||||
if message_types:
|
||||
prevent_stack = self._prevent_message_types_stack
|
||||
@@ -374,6 +371,7 @@ class MessagePump(metaclass=_MessagePumpMeta):
|
||||
**kwargs: Keyword arguments to pass to the callable.
|
||||
"""
|
||||
self._next_callbacks.append(partial(callback, *args, **kwargs))
|
||||
self.check_idle()
|
||||
|
||||
def _on_invoke_later(self, message: messages.InvokeLater) -> None:
|
||||
# Forward InvokeLater message to the Screen
|
||||
@@ -506,6 +504,7 @@ class MessagePump(metaclass=_MessagePumpMeta):
|
||||
except Exception as error:
|
||||
self.app._handle_exception(error)
|
||||
break
|
||||
await self._flush_next_callbacks()
|
||||
|
||||
async def _flush_next_callbacks(self) -> None:
|
||||
"""Invoke pending callbacks in next callbacks queue."""
|
||||
@@ -545,7 +544,6 @@ class MessagePump(metaclass=_MessagePumpMeta):
|
||||
Args:
|
||||
method_name: Handler method name.
|
||||
message: Message object.
|
||||
|
||||
"""
|
||||
private_method = f"_{method_name}"
|
||||
for cls in self.__class__.__mro__:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""
|
||||
|
||||
The pilot object is used by [App.run_test][textual.app.App.run_test] to programmatically operate an app.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -63,7 +62,6 @@ class Pilot(Generic[ReturnType]):
|
||||
|
||||
Args:
|
||||
*keys: Keys to press.
|
||||
|
||||
"""
|
||||
if keys:
|
||||
await self._app._press_keys(keys)
|
||||
@@ -100,11 +98,11 @@ class Pilot(Generic[ReturnType]):
|
||||
target_widget, offset, button=1, shift=shift, meta=meta, control=control
|
||||
)
|
||||
app.post_message(MouseDown(**message_arguments))
|
||||
await self.pause()
|
||||
await self.pause(0.1)
|
||||
app.post_message(MouseUp(**message_arguments))
|
||||
await self.pause()
|
||||
await self.pause(0.1)
|
||||
app.post_message(Click(**message_arguments))
|
||||
await self.pause()
|
||||
await self.pause(0.1)
|
||||
|
||||
async def hover(
|
||||
self,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""
|
||||
|
||||
The `Reactive` class implements [reactivity](/guide/reactivity/).
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -227,7 +226,6 @@ class Reactive(Generic[ReactiveType]):
|
||||
watch_function: A watch function, which may be sync or async.
|
||||
old_value: The old value of the attribute.
|
||||
value: The new value of the attribute.
|
||||
|
||||
"""
|
||||
_rich_traceback_omit = True
|
||||
param_count = count_parameters(watch_function)
|
||||
|
||||
@@ -57,7 +57,6 @@ class TextOpacity:
|
||||
|
||||
Returns:
|
||||
Segments with applied opacity.
|
||||
|
||||
"""
|
||||
_Segment = Segment
|
||||
_from_color = Style.from_color
|
||||
|
||||
@@ -38,7 +38,6 @@ class Tint:
|
||||
|
||||
Returns:
|
||||
Segments with applied tint.
|
||||
|
||||
"""
|
||||
from_rich_color = Color.from_rich_color
|
||||
style_from_color = Style.from_color
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""
|
||||
|
||||
The `Screen` class is a special widget which represents the content in the terminal. See [Screens](/guide/screens/) for details.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -444,15 +443,12 @@ class Screen(Generic[ScreenResultType], Widget):
|
||||
or self._dirty_widgets
|
||||
):
|
||||
self._update_timer.resume()
|
||||
return
|
||||
|
||||
# The Screen is idle - a good opportunity to invoke the scheduled callbacks
|
||||
|
||||
if self._callbacks:
|
||||
self._on_timer_update()
|
||||
await self._invoke_and_clear_callbacks()
|
||||
|
||||
def _on_timer_update(self) -> None:
|
||||
"""Called by the _update_timer."""
|
||||
|
||||
self._update_timer.pause()
|
||||
if self.is_current:
|
||||
if self._layout_required:
|
||||
@@ -465,12 +461,11 @@ class Screen(Generic[ScreenResultType], Widget):
|
||||
self._scroll_required = False
|
||||
|
||||
if self._repaint_required:
|
||||
self._update_timer.resume()
|
||||
self._dirty_widgets.clear()
|
||||
self._dirty_widgets.add(self)
|
||||
self._repaint_required = False
|
||||
|
||||
if self._dirty_widgets and self.is_current:
|
||||
if self._dirty_widgets:
|
||||
self._compositor.update_widgets(self._dirty_widgets)
|
||||
update = self._compositor.render_update(
|
||||
screen_stack=self.app._background_screens
|
||||
@@ -479,20 +474,12 @@ class Screen(Generic[ScreenResultType], Widget):
|
||||
self._dirty_widgets.clear()
|
||||
|
||||
if self._callbacks:
|
||||
self.post_message(events.InvokeCallbacks())
|
||||
|
||||
async def _on_invoke_callbacks(self, event: events.InvokeCallbacks) -> None:
|
||||
"""Handle PostScreenUpdate events, which are sent after the screen is updated"""
|
||||
await self._invoke_and_clear_callbacks()
|
||||
self.call_next(self._invoke_and_clear_callbacks)
|
||||
|
||||
async def _invoke_and_clear_callbacks(self) -> None:
|
||||
"""If there are scheduled callbacks to run, call them and clear
|
||||
the callback queue."""
|
||||
if self._callbacks:
|
||||
display_update = self._compositor.render_update(
|
||||
screen_stack=self.app._background_screens
|
||||
)
|
||||
self.app._display(self, display_update)
|
||||
callbacks = self._callbacks[:]
|
||||
self._callbacks.clear()
|
||||
for callback in callbacks:
|
||||
@@ -757,8 +744,6 @@ class ModalScreen(Screen[ScreenResultType]):
|
||||
"""A screen with bindings that take precedence over the App's key bindings.
|
||||
|
||||
The default styling of a modal screen will dim the screen underneath.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
"""
|
||||
`ScrollView` is a base class for [line api](/guide/widgets#line-api) widgets.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from rich.console import RenderableType
|
||||
|
||||
from ._animator import EasingFunction
|
||||
from .containers import ScrollableContainer
|
||||
from .geometry import Size
|
||||
|
||||
@@ -15,7 +15,6 @@ class ScrollView(ScrollableContainer):
|
||||
"""
|
||||
A base class for a Widget that handles its own scrolling (i.e. doesn't rely
|
||||
on the compositor to render children).
|
||||
|
||||
"""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
@@ -111,3 +110,37 @@ class ScrollView(ScrollableContainer):
|
||||
from rich.panel import Panel
|
||||
|
||||
return Panel(f"{self.scroll_offset} {self.show_vertical_scrollbar}")
|
||||
|
||||
# Custom scroll to which doesn't require call_after_refresh
|
||||
def scroll_to(
|
||||
self,
|
||||
x: float | None = None,
|
||||
y: float | None = None,
|
||||
*,
|
||||
animate: bool = True,
|
||||
speed: float | None = None,
|
||||
duration: float | None = None,
|
||||
easing: EasingFunction | str | None = None,
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
"""Scroll to a given (absolute) coordinate, optionally animating.
|
||||
|
||||
Args:
|
||||
x: X coordinate (column) to scroll to, or `None` for no change.
|
||||
y: Y coordinate (row) to scroll to, or `None` for no change.
|
||||
animate: Animate to new scroll position.
|
||||
speed: Speed of scroll if `animate` is `True`; or `None` to use `duration`.
|
||||
duration: Duration of animation, if `animate` is `True` and `speed` is `None`.
|
||||
easing: An easing method for the scrolling animation.
|
||||
force: Force scrolling even when prohibited by overflow styling.
|
||||
"""
|
||||
|
||||
self._scroll_to(
|
||||
x,
|
||||
y,
|
||||
animate=animate,
|
||||
speed=speed,
|
||||
duration=duration,
|
||||
easing=easing,
|
||||
force=force,
|
||||
)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
Timer objects are created by [set_interval][textual.message_pump.MessagePump.set_interval] or
|
||||
[set_timer][textual.message_pump.MessagePump.set_timer].
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -95,7 +94,6 @@ class Timer:
|
||||
"""Pause the timer.
|
||||
|
||||
A paused timer will not send events until it is resumed.
|
||||
|
||||
"""
|
||||
self._active.clear()
|
||||
|
||||
@@ -131,20 +129,18 @@ class Timer:
|
||||
continue
|
||||
now = _time.get_time()
|
||||
wait_time = max(0, next_timer - now)
|
||||
if wait_time > 1 / 1000:
|
||||
await sleep(wait_time)
|
||||
|
||||
count += 1
|
||||
try:
|
||||
await self._tick(next_timer=next_timer, count=count)
|
||||
except EventTargetGone:
|
||||
break
|
||||
await self._active.wait()
|
||||
if self._reset:
|
||||
start = _time.get_time()
|
||||
count = 0
|
||||
self._reset = False
|
||||
continue
|
||||
try:
|
||||
await self._tick(next_timer=next_timer, count=count)
|
||||
except EventTargetGone:
|
||||
break
|
||||
|
||||
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"""
|
||||
|
||||
@@ -4,7 +4,6 @@ Functions for *walking* the DOM.
|
||||
!!! note
|
||||
|
||||
For most purposes you would be better off using [query][textual.dom.DOMNode.query], which uses these functions internally.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -57,7 +56,6 @@ def walk_depth_first(
|
||||
|
||||
Returns:
|
||||
An iterable of DOMNodes, or the type specified in ``filter_type``.
|
||||
|
||||
"""
|
||||
from textual.dom import DOMNode
|
||||
|
||||
@@ -118,7 +116,6 @@ def walk_breadth_first(
|
||||
|
||||
Returns:
|
||||
An iterable of DOMNodes, or the type specified in ``filter_type``.
|
||||
|
||||
"""
|
||||
from textual.dom import DOMNode
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""
|
||||
The base class for widgets.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -94,7 +93,6 @@ class AwaitMount:
|
||||
```python
|
||||
await self.mount(Static("foo"))
|
||||
```
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, parent: Widget, widgets: Sequence[Widget]) -> None:
|
||||
@@ -233,7 +231,6 @@ class Widget(DOMNode):
|
||||
A Widget is the base class for Textual widgets.
|
||||
|
||||
See also [static][textual.widgets._static.Static] for starting point for your own widgets.
|
||||
|
||||
"""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
@@ -409,7 +406,6 @@ class Widget(DOMNode):
|
||||
"""Check if vertical scroll is permitted.
|
||||
|
||||
May be overridden if you want different logic regarding allowing scrolling.
|
||||
|
||||
"""
|
||||
return self.is_scrollable and self.show_vertical_scrollbar
|
||||
|
||||
@@ -418,7 +414,6 @@ class Widget(DOMNode):
|
||||
"""Check if horizontal scroll is permitted.
|
||||
|
||||
May be overridden if you want different logic regarding allowing scrolling.
|
||||
|
||||
"""
|
||||
return self.is_scrollable and self.show_horizontal_scrollbar
|
||||
|
||||
@@ -635,7 +630,6 @@ class Widget(DOMNode):
|
||||
|
||||
Returns:
|
||||
An iterable of Widgets.
|
||||
|
||||
"""
|
||||
if self._horizontal_scrollbar is not None:
|
||||
yield self._horizontal_scrollbar
|
||||
@@ -874,7 +868,6 @@ class Widget(DOMNode):
|
||||
)
|
||||
yield Footer()
|
||||
```
|
||||
|
||||
"""
|
||||
yield from ()
|
||||
|
||||
@@ -1134,7 +1127,6 @@ class Widget(DOMNode):
|
||||
|
||||
Returns:
|
||||
ScrollBarCorner Widget.
|
||||
|
||||
"""
|
||||
from .scrollbar import ScrollBarCorner
|
||||
|
||||
@@ -1248,7 +1240,6 @@ class Widget(DOMNode):
|
||||
|
||||
Returns:
|
||||
A tuple of (<vertical scrollbar enabled>, <horizontal scrollbar enabled>)
|
||||
|
||||
"""
|
||||
if not self.is_scrollable:
|
||||
return False, False
|
||||
@@ -1300,7 +1291,6 @@ class Widget(DOMNode):
|
||||
|
||||
Returns:
|
||||
Additional spacing around content area.
|
||||
|
||||
"""
|
||||
return self.styles.gutter + self.scrollbar_gutter
|
||||
|
||||
@@ -1359,7 +1349,6 @@ class Widget(DOMNode):
|
||||
|
||||
Returns:
|
||||
Offset from widget's origin.
|
||||
|
||||
"""
|
||||
x, y = self.gutter.top_left
|
||||
return Offset(x, y)
|
||||
@@ -1370,7 +1359,6 @@ class Widget(DOMNode):
|
||||
|
||||
Returns:
|
||||
Content area size.
|
||||
|
||||
"""
|
||||
return self.region.shrink(self.styles.gutter).size
|
||||
|
||||
@@ -1412,7 +1400,6 @@ class Widget(DOMNode):
|
||||
|
||||
Returns:
|
||||
The virtual region.
|
||||
|
||||
"""
|
||||
try:
|
||||
return self.screen.find_widget(self).virtual_region
|
||||
@@ -1461,7 +1448,6 @@ class Widget(DOMNode):
|
||||
|
||||
Returns:
|
||||
List of widgets that can receive focus.
|
||||
|
||||
"""
|
||||
focusable = [child for child in self._nodes if child.display and child.visible]
|
||||
return sorted(focusable, key=attrgetter("_focus_sort_key"))
|
||||
@@ -1493,7 +1479,6 @@ class Widget(DOMNode):
|
||||
|
||||
Returns:
|
||||
A Rich console object.
|
||||
|
||||
"""
|
||||
return active_app.get().console
|
||||
|
||||
@@ -1534,7 +1519,6 @@ class Widget(DOMNode):
|
||||
delay: A delay (in seconds) before the animation starts.
|
||||
easing: An easing method.
|
||||
on_complete: A callable to invoke when the animation is finished.
|
||||
|
||||
"""
|
||||
if self._animate is None:
|
||||
self._animate = self.app.animator.bind(self)
|
||||
@@ -1556,7 +1540,6 @@ class Widget(DOMNode):
|
||||
|
||||
Returns:
|
||||
A layout object.
|
||||
|
||||
"""
|
||||
return self.styles.layout or self._default_layout
|
||||
|
||||
@@ -1576,7 +1559,6 @@ class Widget(DOMNode):
|
||||
|
||||
Returns:
|
||||
Name of layer.
|
||||
|
||||
"""
|
||||
return self.styles.layer or "default"
|
||||
|
||||
@@ -1600,7 +1582,6 @@ class Widget(DOMNode):
|
||||
|
||||
Returns:
|
||||
Rich style.
|
||||
|
||||
"""
|
||||
styles = self.styles
|
||||
_, background = self.background_colors
|
||||
@@ -1622,7 +1603,6 @@ class Widget(DOMNode):
|
||||
|
||||
Returns:
|
||||
Rich Style.
|
||||
|
||||
"""
|
||||
styles = self.styles
|
||||
_, background = self.background_colors
|
||||
@@ -1646,7 +1626,6 @@ class Widget(DOMNode):
|
||||
|
||||
Args:
|
||||
*regions: Regions which require a repaint.
|
||||
|
||||
"""
|
||||
if regions:
|
||||
content_offset = self.content_offset
|
||||
@@ -2559,7 +2538,6 @@ class Widget(DOMNode):
|
||||
|
||||
Returns:
|
||||
Tuples of scrollbar Widget and region.
|
||||
|
||||
"""
|
||||
show_vertical_scrollbar, show_horizontal_scrollbar = self.scrollbars_enabled
|
||||
|
||||
@@ -2613,7 +2591,6 @@ class Widget(DOMNode):
|
||||
|
||||
Returns:
|
||||
Names of the pseudo classes.
|
||||
|
||||
"""
|
||||
node: MessagePump | None = self
|
||||
while isinstance(node, Widget):
|
||||
|
||||
@@ -143,7 +143,6 @@ class Button(Static, can_focus=True):
|
||||
border-bottom: tall $error-lighten-2;
|
||||
border-top: tall $error-darken-2;
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
BINDINGS = [Binding("enter", "press", "Press Button", show=False)]
|
||||
|
||||
@@ -130,7 +130,6 @@ class MarkdownH1(MarkdownHeader):
|
||||
text-style: bold;
|
||||
color: $text;
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -148,7 +147,6 @@ class MarkdownH2(MarkdownHeader):
|
||||
padding: 1;
|
||||
text-style: bold;
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -378,7 +376,6 @@ class MarkdownTable(MarkdownBlock):
|
||||
background: $panel;
|
||||
border: wide $background;
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
@@ -830,7 +827,6 @@ class MarkdownViewer(VerticalScroll, can_focus=True, can_focus_children=True):
|
||||
MarkdownViewer.-show-table-of-contents > MarkdownTableOfContents {
|
||||
display: block;
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
show_table_of_contents = reactive(True)
|
||||
|
||||
@@ -69,7 +69,6 @@ class Placeholder(Widget):
|
||||
Placeholder.-text {
|
||||
padding: 1;
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
# Consecutive placeholders get assigned consecutive colors.
|
||||
|
||||
@@ -36,7 +36,6 @@ class TabPane(Widget):
|
||||
"""A container for switchable content, with additional title.
|
||||
|
||||
This widget is intended to be used with [TabbedContent][textual.widgets.TabbedContent].
|
||||
|
||||
"""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
|
||||
@@ -42,7 +42,6 @@ class Underline(Widget):
|
||||
| Class | Description |
|
||||
| :- | :- |
|
||||
| `underline-bar` | Style of the bar (may be used to change the color). |
|
||||
|
||||
"""
|
||||
|
||||
highlight_start = reactive(0)
|
||||
|
||||
@@ -418,7 +418,6 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||
Tree > .tree--highlight-line {
|
||||
background: $boost;
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
show_root = reactive(True)
|
||||
|
||||
@@ -21,7 +21,6 @@ I will face my fear.
|
||||
I will permit it to pass over me and through me.
|
||||
And when it has gone past, I will turn the inner eye to see its path.
|
||||
Where the fear has gone there will be nothing. Only I will remain."
|
||||
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""
|
||||
A class to manage concurrent [work](/guide/workers).
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -204,7 +203,6 @@ class Worker(Generic[ResultType]):
|
||||
"""Has the work been cancelled?
|
||||
|
||||
Note that cancelled work may still be running.
|
||||
|
||||
"""
|
||||
return self._cancelled
|
||||
|
||||
@@ -237,7 +235,6 @@ class Worker(Generic[ResultType]):
|
||||
"""Progress as a percentage.
|
||||
|
||||
If the total steps is None, then this will return 0. The percentage will be clamped between 0 and 100.
|
||||
|
||||
"""
|
||||
if not self._total_steps:
|
||||
return 0.0
|
||||
@@ -267,7 +264,6 @@ class Worker(Generic[ResultType]):
|
||||
|
||||
Args:
|
||||
steps: Number of steps to advance.
|
||||
|
||||
"""
|
||||
self._completed_steps += steps
|
||||
|
||||
@@ -278,7 +274,6 @@ class Worker(Generic[ResultType]):
|
||||
|
||||
Returns:
|
||||
Return value of work.
|
||||
|
||||
"""
|
||||
|
||||
if (
|
||||
|
||||
@@ -29,7 +29,6 @@ class ScrollViewTester(App[None]):
|
||||
background: $primary-darken-2;
|
||||
border: round red;
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
|
||||
@@ -9,7 +9,6 @@ class AnimApp(App):
|
||||
#foo {
|
||||
height: 1;
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
|
||||
@@ -39,4 +39,4 @@ async def test_call_after_refresh() -> None:
|
||||
app.call_after_refresh(callback)
|
||||
await asyncio.wait_for(called_event.wait(), 1)
|
||||
app_display_count = app.display_count
|
||||
assert app_display_count > display_count
|
||||
assert app_display_count == display_count
|
||||
|
||||
@@ -12,7 +12,6 @@ re_link_ids = re.compile(r"id=[\d\.\-]*?;.*?\x1b")
|
||||
def replace_link_ids(render: str) -> str:
|
||||
"""Link IDs have a random ID and system path which is a problem for
|
||||
reproducible tests.
|
||||
|
||||
"""
|
||||
return re_link_ids.sub("id=0;foo\x1b", render)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user