mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
calculator example
This commit is contained in:
10
CHANGELOG.md
10
CHANGELOG.md
@@ -5,7 +5,11 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [Unreleased] - yyyy-mm-dd
|
||||
## [0.1.7] - 2021-07-14
|
||||
|
||||
Here we write upgrading notes for brands. It's a team effort to make them as
|
||||
straightforward as possible.
|
||||
### Changed
|
||||
|
||||
- Added functionality to calculator example.
|
||||
- Scrollview now shows scrollbars automatically
|
||||
- New handler system for messages that doesn't require inheritance
|
||||
- Improved traceback handling
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from rich.align import Align
|
||||
from rich.console import Console, ConsoleOptions, RenderResult
|
||||
from rich.console import Console, ConsoleOptions, RenderResult, RenderableType
|
||||
from rich.padding import Padding
|
||||
from rich.text import Text
|
||||
|
||||
|
||||
from textual.app import App
|
||||
from textual import events
|
||||
from textual.message import Message
|
||||
from textual.reactive import Reactive
|
||||
from textual.view import View
|
||||
from textual.widgets import Button, Static
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Button
|
||||
from textual.layouts.grid import GridLayout
|
||||
|
||||
try:
|
||||
from pyfiglet import Figlet
|
||||
except ImportError:
|
||||
print("Please install pyfiglet to run this example")
|
||||
import sys
|
||||
|
||||
sys.exit()
|
||||
|
||||
|
||||
class FigletText:
|
||||
@@ -24,10 +34,8 @@ class FigletText:
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> RenderResult:
|
||||
size = min(options.max_width / 2, options.max_height)
|
||||
|
||||
text = self.text
|
||||
if size < 4:
|
||||
yield Text(text, style="bold")
|
||||
yield Text(self.text, style="bold")
|
||||
else:
|
||||
if size < 7:
|
||||
font_name = "mini"
|
||||
@@ -37,42 +45,148 @@ class FigletText:
|
||||
font_name = "standard"
|
||||
else:
|
||||
font_name = "big"
|
||||
font = Figlet(font=font_name)
|
||||
yield Text(font.renderText(text).rstrip("\n"), style="bold")
|
||||
font = Figlet(font=font_name, width=options.max_width)
|
||||
yield Text(font.renderText(self.text).rstrip("\n"), style="bold")
|
||||
|
||||
|
||||
class Numbers(Widget):
|
||||
"""The digital display of the calculator."""
|
||||
|
||||
value: Reactive[str] = Reactive("0")
|
||||
|
||||
def render(self) -> RenderableType:
|
||||
return Padding(
|
||||
Align.right(FigletText(self.value), vertical="middle"),
|
||||
(0, 1),
|
||||
style="white on rgb(51,51,51)",
|
||||
)
|
||||
|
||||
|
||||
class CalculatorApp(App):
|
||||
async def on_startup(self, event: events.Startup) -> None:
|
||||
"""A working calculator app."""
|
||||
|
||||
async def on_load(self, event: events.Load) -> None:
|
||||
"""Sent when the app starts, but before displaying anything."""
|
||||
|
||||
self.left = Decimal("0")
|
||||
self.right = Decimal("0")
|
||||
self.value = ""
|
||||
self.operator = "+"
|
||||
self.numbers = Numbers()
|
||||
|
||||
def make_button(text: str, style: str) -> Button:
|
||||
"""Create a button with the given Figlet label."""
|
||||
return Button(FigletText(text), style=style, name=text)
|
||||
|
||||
dark = "white on rgb(51,51,51)"
|
||||
light = "black on rgb(165,165,165)"
|
||||
yellow = "white on rgb(255,159,7)"
|
||||
|
||||
button_styles = {
|
||||
"AC": light,
|
||||
"C": light,
|
||||
"+/-": light,
|
||||
"%": light,
|
||||
"/": yellow,
|
||||
"X": yellow,
|
||||
"-": yellow,
|
||||
"+": yellow,
|
||||
"=": yellow,
|
||||
}
|
||||
|
||||
# Make all the buttons
|
||||
self.buttons = {
|
||||
name: make_button(name, button_styles.get(name, dark))
|
||||
for name in "+/-,%,/,7,8,9,X,4,5,6,-,1,2,3,+,.,=".split(",")
|
||||
}
|
||||
|
||||
self.zero = make_button("0", dark)
|
||||
self.ac = make_button("AC", light)
|
||||
self.c = make_button("C", light)
|
||||
self.c.visible = False
|
||||
|
||||
async def on_startup(self, event: events.Startup) -> None:
|
||||
"""Sent when the app has gone full screen."""
|
||||
|
||||
# Create the layout which defines where our widgets will go
|
||||
layout = GridLayout(gap=(2, 1), gutter=1, align=("center", "center"))
|
||||
await self.push_view(View(layout=layout))
|
||||
|
||||
# Create rows / columns / areas
|
||||
layout.add_column("col", max_size=30, repeat=4)
|
||||
layout.add_row("numbers")
|
||||
layout.add_row("numbers", max_size=15)
|
||||
layout.add_row("row", max_size=15, repeat=5)
|
||||
layout.add_areas(
|
||||
numbers="col1-start|col4-end,numbers", zero="col1-start|col2-end,row5"
|
||||
clear="col1,row1",
|
||||
numbers="col1-start|col4-end,numbers",
|
||||
zero="col1-start|col2-end,row5",
|
||||
)
|
||||
|
||||
def make_button(text: str) -> Button:
|
||||
return Button(FigletText(text), style="white on rgb(51,51,51)")
|
||||
|
||||
buttons = {
|
||||
name: make_button(name)
|
||||
for name in "AC,+/-,%,/,7,8,9,X,4,5,6,-,1,2,3,+,.,=".split(",")
|
||||
}
|
||||
for name in ("AC", "+/-", "%"):
|
||||
buttons[name].style = "black on rgb(165,165,165)"
|
||||
for name in "/X-+=":
|
||||
buttons[name].style = "white on rgb(255,159,7)"
|
||||
|
||||
numbers = Align.right(FigletText("0"), vertical="middle")
|
||||
|
||||
# Place out widgets in to the layout
|
||||
layout.place(clear=self.c)
|
||||
layout.place(
|
||||
*buttons.values(),
|
||||
numbers=Static(numbers, padding=(0, 1), style="white on rgb(51,51,51)"),
|
||||
zero=make_button("0"),
|
||||
*self.buttons.values(), clear=self.ac, numbers=self.numbers, zero=self.zero
|
||||
)
|
||||
|
||||
async def message_button_pressed(self, message: Message) -> None:
|
||||
"""A message sent by the button widget"""
|
||||
|
||||
assert isinstance(message.sender, Button)
|
||||
button_name = message.sender.name
|
||||
|
||||
def do_math() -> bool:
|
||||
operator = self.operator
|
||||
right = self.right
|
||||
if operator == "+":
|
||||
self.left += right
|
||||
elif operator == "-":
|
||||
self.left -= right
|
||||
elif operator == "/":
|
||||
try:
|
||||
self.left /= right
|
||||
except ZeroDivisionError:
|
||||
self.numbers.value = "Error"
|
||||
return False
|
||||
elif operator == "X":
|
||||
self.left *= right
|
||||
return True
|
||||
|
||||
if button_name.isdigit():
|
||||
self.value = self.value.lstrip("0") + button_name
|
||||
self.numbers.value = self.value
|
||||
elif button_name == "+/-":
|
||||
self.value = str(Decimal(self.value or "0") * -1)
|
||||
self.numbers.value = self.value
|
||||
elif button_name == "%":
|
||||
self.value = str(Decimal(self.value or "0") / Decimal(100))
|
||||
self.numbers.value = self.value
|
||||
elif button_name == ".":
|
||||
if "." not in self.value:
|
||||
self.value += "."
|
||||
self.numbers.value = self.value
|
||||
elif button_name == "AC":
|
||||
self.value = ""
|
||||
self.left = self.right = Decimal(0)
|
||||
self.operator = "+"
|
||||
self.numbers.value = "0"
|
||||
elif button_name == "C":
|
||||
self.value = ""
|
||||
self.numbers.value = "0"
|
||||
elif button_name in ("+", "-", "/", "X"):
|
||||
self.right = Decimal(self.value or "0")
|
||||
if do_math():
|
||||
self.numbers.value = str(self.left)
|
||||
self.value = ""
|
||||
self.operator = button_name
|
||||
elif button_name == "=":
|
||||
if self.value:
|
||||
self.right = Decimal(self.value or "0")
|
||||
if do_math():
|
||||
self.numbers.value = str(self.left)
|
||||
self.value = ""
|
||||
|
||||
show_ac = self.value in ("", "0") and self.numbers.value == "0"
|
||||
self.c.visible = not show_ac
|
||||
self.ac.visible = show_ac
|
||||
|
||||
|
||||
CalculatorApp.run(title="Calculator Test")
|
||||
|
||||
386
poetry.lock
generated
386
poetry.lock
generated
@@ -6,6 +6,17 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "astunparse"
|
||||
version = "1.6.3"
|
||||
description = "An AST unparser for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.6.1,<2.0"
|
||||
|
||||
[[package]]
|
||||
name = "atomicwrites"
|
||||
version = "1.4.0"
|
||||
@@ -50,6 +61,14 @@ typing-extensions = ">=3.7.4"
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
|
||||
|
||||
[[package]]
|
||||
name = "cached-property"
|
||||
version = "1.5.2"
|
||||
description = "A decorator for caching properties in classes."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.0.1"
|
||||
@@ -92,6 +111,20 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
|
||||
[package.extras]
|
||||
toml = ["toml"]
|
||||
|
||||
[[package]]
|
||||
name = "ghp-import"
|
||||
version = "2.0.1"
|
||||
description = "Copy your docs directly to the gh-pages branch."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
python-dateutil = ">=2.8.1"
|
||||
|
||||
[package.extras]
|
||||
dev = ["twine", "markdown", "flake8"]
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "4.6.1"
|
||||
@@ -117,6 +150,128 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.0.1"
|
||||
description = "A very fast and expressive template engine."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "markdown"
|
||||
version = "3.3.4"
|
||||
description = "Python implementation of Markdown."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
||||
|
||||
[package.extras]
|
||||
testing = ["coverage", "pyyaml"]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.0.1"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "mergedeep"
|
||||
version = "1.3.4"
|
||||
description = "A deep merge function for 🐍."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs"
|
||||
version = "1.2.1"
|
||||
description = "Project documentation with Markdown."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=3.3"
|
||||
ghp-import = ">=1.0"
|
||||
importlib-metadata = ">=3.10"
|
||||
Jinja2 = ">=2.10.1"
|
||||
Markdown = ">=3.2.1"
|
||||
mergedeep = ">=1.3.4"
|
||||
packaging = ">=20.5"
|
||||
PyYAML = ">=3.10"
|
||||
pyyaml-env-tag = ">=0.1"
|
||||
watchdog = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["babel (>=2.9.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-autorefs"
|
||||
version = "0.2.1"
|
||||
description = "Automatically link across pages in MkDocs."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6,<4.0"
|
||||
|
||||
[package.dependencies]
|
||||
Markdown = ">=3.3,<4.0"
|
||||
mkdocs = ">=1.1,<2.0"
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-material"
|
||||
version = "7.1.10"
|
||||
description = "A Material Design theme for MkDocs"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
markdown = ">=3.2"
|
||||
mkdocs = ">=1.1"
|
||||
mkdocs-material-extensions = ">=1.0"
|
||||
Pygments = ">=2.4"
|
||||
pymdown-extensions = ">=7.0"
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-material-extensions"
|
||||
version = "1.0.1"
|
||||
description = "Extension pack for Python Markdown."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[package.dependencies]
|
||||
mkdocs-material = ">=5.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "mkdocstrings"
|
||||
version = "0.15.2"
|
||||
description = "Automatic documentation from sources, for MkDocs."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6,<4.0"
|
||||
|
||||
[package.dependencies]
|
||||
Jinja2 = ">=2.11.1,<4.0"
|
||||
Markdown = ">=3.3,<4.0"
|
||||
MarkupSafe = ">=1.1,<3.0"
|
||||
mkdocs = ">=1.1.1,<2.0.0"
|
||||
mkdocs-autorefs = ">=0.1,<0.3"
|
||||
pymdown-extensions = ">=6.3,<9.0"
|
||||
pytkdocs = ">=0.2.0,<0.12.0"
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "0.910"
|
||||
@@ -192,6 +347,17 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "pymdown-extensions"
|
||||
version = "8.2"
|
||||
description = "Extension pack for Python Markdown."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
Markdown = ">=3.2"
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "2.4.7"
|
||||
@@ -238,6 +404,52 @@ toml = "*"
|
||||
[package.extras]
|
||||
testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.8.2"
|
||||
description = "Extensions to the standard Python datetime module"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.5"
|
||||
|
||||
[[package]]
|
||||
name = "pytkdocs"
|
||||
version = "0.11.1"
|
||||
description = "Load Python objects documentation."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.1,<4.0.0"
|
||||
|
||||
[package.dependencies]
|
||||
astunparse = {version = ">=1.6.3,<2.0.0", markers = "python_version < \"3.9\""}
|
||||
cached-property = {version = ">=1.5.2,<2.0.0", markers = "python_version < \"3.8\""}
|
||||
typing-extensions = {version = ">=3.7.4.3,<4.0.0.0", markers = "python_version < \"3.8\""}
|
||||
|
||||
[package.extras]
|
||||
numpy-style = ["docstring_parser (>=0.7.3,<0.8.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "5.4.1"
|
||||
description = "YAML parser and emitter for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml-env-tag"
|
||||
version = "0.1"
|
||||
description = "A custom YAML tag for referencing environment variables in YAML files. "
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
pyyaml = "*"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2021.7.6"
|
||||
@@ -263,6 +475,14 @@ typing-extensions = {version = ">=3.7.4,<4.0.0", markers = "python_version < \"3
|
||||
[package.extras]
|
||||
jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
@@ -287,6 +507,17 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "watchdog"
|
||||
version = "2.1.3"
|
||||
description = "Filesystem events monitoring"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
watchmedo = ["PyYAML (>=3.10)", "argh (>=0.24.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.5.0"
|
||||
@@ -302,13 +533,17 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.7"
|
||||
content-hash = "137e746aef38f8e8eac2674219503221c2705c80b9ee4b463129b1c47e459460"
|
||||
content-hash = "ac64f9d88e11df57c6fabe30fb9f39aee4ea9925809a8c850a61e0b4e92bf64f"
|
||||
|
||||
[metadata.files]
|
||||
appdirs = [
|
||||
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
|
||||
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
|
||||
]
|
||||
astunparse = [
|
||||
{file = "astunparse-1.6.3-py2.py3-none-any.whl", hash = "sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8"},
|
||||
{file = "astunparse-1.6.3.tar.gz", hash = "sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872"},
|
||||
]
|
||||
atomicwrites = [
|
||||
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
|
||||
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
|
||||
@@ -320,6 +555,10 @@ attrs = [
|
||||
black = [
|
||||
{file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"},
|
||||
]
|
||||
cached-property = [
|
||||
{file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"},
|
||||
{file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
|
||||
{file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
|
||||
@@ -386,6 +625,9 @@ coverage = [
|
||||
{file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"},
|
||||
{file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"},
|
||||
]
|
||||
ghp-import = [
|
||||
{file = "ghp-import-2.0.1.tar.gz", hash = "sha256:753de2eace6e0f7d4edfb3cce5e3c3b98cd52aadb80163303d1d036bda7b4483"},
|
||||
]
|
||||
importlib-metadata = [
|
||||
{file = "importlib_metadata-4.6.1-py3-none-any.whl", hash = "sha256:9f55f560e116f8643ecf2922d9cd3e1c7e8d52e683178fecd9d08f6aa357e11e"},
|
||||
{file = "importlib_metadata-4.6.1.tar.gz", hash = "sha256:079ada16b7fc30dfbb5d13399a5113110dab1aa7c2bc62f66af75f0b717c8cac"},
|
||||
@@ -394,6 +636,74 @@ iniconfig = [
|
||||
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
|
||||
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
|
||||
]
|
||||
jinja2 = [
|
||||
{file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"},
|
||||
{file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"},
|
||||
]
|
||||
markdown = [
|
||||
{file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"},
|
||||
{file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49"},
|
||||
]
|
||||
markupsafe = [
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
|
||||
{file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
|
||||
]
|
||||
mergedeep = [
|
||||
{file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
|
||||
{file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
|
||||
]
|
||||
mkdocs = [
|
||||
{file = "mkdocs-1.2.1-py3-none-any.whl", hash = "sha256:11141126e5896dd9d279b3e4814eb488e409a0990fb638856255020406a8e2e7"},
|
||||
{file = "mkdocs-1.2.1.tar.gz", hash = "sha256:6e0ea175366e3a50d334597b0bc042b8cebd512398cdd3f6f34842d0ef524905"},
|
||||
]
|
||||
mkdocs-autorefs = [
|
||||
{file = "mkdocs-autorefs-0.2.1.tar.gz", hash = "sha256:b8156d653ed91356e71675ce1fa1186d2b2c2085050012522895c9aa98fca3e5"},
|
||||
{file = "mkdocs_autorefs-0.2.1-py3-none-any.whl", hash = "sha256:f301b983a34259df90b3fcf7edc234b5e6c7065bd578781e66fd90b8cfbe76be"},
|
||||
]
|
||||
mkdocs-material = [
|
||||
{file = "mkdocs-material-7.1.10.tar.gz", hash = "sha256:890e9be00bfbe4d22ccccbcde1bf9bad67a3ba495f2a7d2422ea4acb5099f014"},
|
||||
{file = "mkdocs_material-7.1.10-py2.py3-none-any.whl", hash = "sha256:92ff8c4a8e78555ef7b7ed0ba3043421d18971b48d066ea2cefb50e889fc66db"},
|
||||
]
|
||||
mkdocs-material-extensions = [
|
||||
{file = "mkdocs-material-extensions-1.0.1.tar.gz", hash = "sha256:6947fb7f5e4291e3c61405bad3539d81e0b3cd62ae0d66ced018128af509c68f"},
|
||||
{file = "mkdocs_material_extensions-1.0.1-py3-none-any.whl", hash = "sha256:d90c807a88348aa6d1805657ec5c0b2d8d609c110e62b9dce4daf7fa981fa338"},
|
||||
]
|
||||
mkdocstrings = [
|
||||
{file = "mkdocstrings-0.15.2-py3-none-any.whl", hash = "sha256:8d6cbe64c07ae66739010979ca01d49dd2f64d1a45009f089d217b9cd2a65e36"},
|
||||
{file = "mkdocstrings-0.15.2.tar.gz", hash = "sha256:c2fee9a3a644647c06eb2044fdfede1073adfd1a55bf6752005d3db10705fe73"},
|
||||
]
|
||||
mypy = [
|
||||
{file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"},
|
||||
{file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"},
|
||||
@@ -443,6 +753,10 @@ pygments = [
|
||||
{file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"},
|
||||
{file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"},
|
||||
]
|
||||
pymdown-extensions = [
|
||||
{file = "pymdown-extensions-8.2.tar.gz", hash = "sha256:b6daa94aad9e1310f9c64c8b1f01e4ce82937ab7eb53bfc92876a97aca02a6f4"},
|
||||
{file = "pymdown_extensions-8.2-py3-none-any.whl", hash = "sha256:141452d8ed61165518f2c923454bf054866b85cf466feedb0eb68f04acdc2560"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
||||
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
|
||||
@@ -455,6 +769,49 @@ pytest-cov = [
|
||||
{file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"},
|
||||
{file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"},
|
||||
]
|
||||
python-dateutil = [
|
||||
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
|
||||
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
|
||||
]
|
||||
pytkdocs = [
|
||||
{file = "pytkdocs-0.11.1-py3-none-any.whl", hash = "sha256:89ca4926d0acc266235beb24cb0b0591aa6bf7adedfae54bf9421d529d782c8d"},
|
||||
{file = "pytkdocs-0.11.1.tar.gz", hash = "sha256:1ec7e028fe8361acc1ce909ada4e6beabec28ef31e629618549109e1d58549f0"},
|
||||
]
|
||||
pyyaml = [
|
||||
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
|
||||
{file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
|
||||
{file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"},
|
||||
{file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"},
|
||||
{file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"},
|
||||
{file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"},
|
||||
{file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"},
|
||||
{file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"},
|
||||
{file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"},
|
||||
{file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"},
|
||||
{file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"},
|
||||
{file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"},
|
||||
{file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"},
|
||||
{file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"},
|
||||
{file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"},
|
||||
{file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"},
|
||||
{file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"},
|
||||
{file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"},
|
||||
{file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"},
|
||||
{file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"},
|
||||
{file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"},
|
||||
{file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"},
|
||||
{file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"},
|
||||
{file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"},
|
||||
{file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"},
|
||||
{file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"},
|
||||
{file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"},
|
||||
{file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
|
||||
{file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
|
||||
]
|
||||
pyyaml-env-tag = [
|
||||
{file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
|
||||
{file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
|
||||
]
|
||||
regex = [
|
||||
{file = "regex-2021.7.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e6a1e5ca97d411a461041d057348e578dc344ecd2add3555aedba3b408c9f874"},
|
||||
{file = "regex-2021.7.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:6afe6a627888c9a6cfbb603d1d017ce204cebd589d66e0703309b8048c3b0854"},
|
||||
@@ -502,6 +859,10 @@ rich = [
|
||||
{file = "rich-10.6.0-py3-none-any.whl", hash = "sha256:d3f72827cd5df13b2ef7f1a97f81ec65548d4fdeb92cef653234f227580bbb2a"},
|
||||
{file = "rich-10.6.0.tar.gz", hash = "sha256:128261b3e2419a4ef9c97066ccc2abbfb49fa7c5e89c3fe4056d00aa5e9c1e65"},
|
||||
]
|
||||
six = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
]
|
||||
toml = [
|
||||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
@@ -543,6 +904,29 @@ typing-extensions = [
|
||||
{file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"},
|
||||
{file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"},
|
||||
]
|
||||
watchdog = [
|
||||
{file = "watchdog-2.1.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9628f3f85375a17614a2ab5eac7665f7f7be8b6b0a2a228e6f6a2e91dd4bfe26"},
|
||||
{file = "watchdog-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:acc4e2d5be6f140f02ee8590e51c002829e2c33ee199036fcd61311d558d89f4"},
|
||||
{file = "watchdog-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:85b851237cf3533fabbc034ffcd84d0fa52014b3121454e5f8b86974b531560c"},
|
||||
{file = "watchdog-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a12539ecf2478a94e4ba4d13476bb2c7a2e0a2080af2bb37df84d88b1b01358a"},
|
||||
{file = "watchdog-2.1.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6fe9c8533e955c6589cfea6f3f0a1a95fb16867a211125236c82e1815932b5d7"},
|
||||
{file = "watchdog-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d9456f0433845e7153b102fffeb767bde2406b76042f2216838af3b21707894e"},
|
||||
{file = "watchdog-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fd8c595d5a93abd441ee7c5bb3ff0d7170e79031520d113d6f401d0cf49d7c8f"},
|
||||
{file = "watchdog-2.1.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0bcfe904c7d404eb6905f7106c54873503b442e8e918cc226e1828f498bdc0ca"},
|
||||
{file = "watchdog-2.1.3-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bf84bd94cbaad8f6b9cbaeef43080920f4cb0e61ad90af7106b3de402f5fe127"},
|
||||
{file = "watchdog-2.1.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b8ddb2c9f92e0c686ea77341dcb58216fa5ff7d5f992c7278ee8a392a06e86bb"},
|
||||
{file = "watchdog-2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8805a5f468862daf1e4f4447b0ccf3acaff626eaa57fbb46d7960d1cf09f2e6d"},
|
||||
{file = "watchdog-2.1.3-py3-none-manylinux2014_armv7l.whl", hash = "sha256:3e305ea2757f81d8ebd8559d1a944ed83e3ab1bdf68bcf16ec851b97c08dc035"},
|
||||
{file = "watchdog-2.1.3-py3-none-manylinux2014_i686.whl", hash = "sha256:431a3ea70b20962e6dee65f0eeecd768cd3085ea613ccb9b53c8969de9f6ebd2"},
|
||||
{file = "watchdog-2.1.3-py3-none-manylinux2014_ppc64.whl", hash = "sha256:e4929ac2aaa2e4f1a30a36751160be391911da463a8799460340901517298b13"},
|
||||
{file = "watchdog-2.1.3-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:201cadf0b8c11922f54ec97482f95b2aafca429c4c3a4bb869a14f3c20c32686"},
|
||||
{file = "watchdog-2.1.3-py3-none-manylinux2014_s390x.whl", hash = "sha256:3a7d242a7963174684206093846537220ee37ba9986b824a326a8bb4ef329a33"},
|
||||
{file = "watchdog-2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:54e057727dd18bd01a3060dbf5104eb5a495ca26316487e0f32a394fd5fe725a"},
|
||||
{file = "watchdog-2.1.3-py3-none-win32.whl", hash = "sha256:b5fc5c127bad6983eecf1ad117ab3418949f18af9c8758bd10158be3647298a9"},
|
||||
{file = "watchdog-2.1.3-py3-none-win_amd64.whl", hash = "sha256:44acad6f642996a2b50bb9ce4fb3730dde08f23e79e20cd3d8e2a2076b730381"},
|
||||
{file = "watchdog-2.1.3-py3-none-win_ia64.whl", hash = "sha256:0bcdf7b99b56a3ae069866c33d247c9994ffde91b620eaf0306b27e099bd1ae0"},
|
||||
{file = "watchdog-2.1.3.tar.gz", hash = "sha256:e5236a8e8602ab6db4b873664c2d356c365ab3cac96fbdec4970ad616415dd45"},
|
||||
]
|
||||
zipp = [
|
||||
{file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"},
|
||||
{file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "textual"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
homepage = "https://github.com/willmcgugan/textual"
|
||||
description = "Text User Interface using Rich"
|
||||
authors = ["Will McGugan <willmcgugan@gmail.com>"]
|
||||
@@ -31,6 +31,9 @@ pytest = "^6.2.3"
|
||||
black = "^20.8b1"
|
||||
mypy = "^0.910"
|
||||
pytest-cov = "^2.12.1"
|
||||
mkdocs = "^1.2.1"
|
||||
mkdocstrings = "^0.15.2"
|
||||
mkdocs-material = "^7.1.10"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
|
||||
@@ -23,7 +23,7 @@ from .binding import Bindings, NoBinding
|
||||
from .geometry import Point, Region
|
||||
from ._context import active_app
|
||||
from ._event_broker import extract_handler_actions, NoHandler
|
||||
from .keys import Binding
|
||||
from ._types import MessageTarget
|
||||
from .driver import Driver
|
||||
from .layouts.dock import DockLayout, Dock
|
||||
from ._linux_driver import LinuxDriver
|
||||
@@ -56,6 +56,12 @@ ViewType = TypeVar("ViewType", bound=View)
|
||||
# uvloop.install()
|
||||
|
||||
|
||||
class PanicMessage(Message):
|
||||
def __init__(self, sender: MessageTarget, traceback: Traceback) -> None:
|
||||
self.traceback = traceback
|
||||
super().__init__(sender)
|
||||
|
||||
|
||||
class ActionError(Exception):
|
||||
pass
|
||||
|
||||
@@ -71,11 +77,19 @@ class App(MessagePump):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
console: Console = None,
|
||||
console: Console | None = None,
|
||||
screen: bool = True,
|
||||
driver_class: Type[Driver] = None,
|
||||
driver_class: Type[Driver] | None = None,
|
||||
title: str = "Textual Application",
|
||||
):
|
||||
"""The Textual Application base class
|
||||
|
||||
Args:
|
||||
console (Console, optional): A Rich Console. Defaults to None.
|
||||
screen (bool, optional): Enable full-screen application mode. Defaults to True.
|
||||
driver_class (Type[Driver], optional): Driver class, or None to use default. Defaults to None.
|
||||
title (str, optional): Title of the application. Defaults to "Textual Application".
|
||||
"""
|
||||
self.console = console or get_console()
|
||||
self._screen = screen
|
||||
self.driver_class = driver_class or LinuxDriver
|
||||
@@ -88,6 +102,7 @@ class App(MessagePump):
|
||||
self.mouse_over: Widget | None = None
|
||||
self.mouse_captured: Widget | None = None
|
||||
self._driver: Driver | None = None
|
||||
self._tracebacks: list[Traceback] = []
|
||||
|
||||
self._docks: list[Dock] = []
|
||||
self._action_targets = {"app", "view"}
|
||||
@@ -202,10 +217,12 @@ class App(MessagePump):
|
||||
if widget is not None:
|
||||
await widget.post_message(events.MouseCaptured(self, self.mouse_position))
|
||||
|
||||
def panic(self, traceback: Traceback) -> None:
|
||||
self._tracebacks.append(traceback)
|
||||
self.close_messages_no_wait()
|
||||
|
||||
async def process_messages(self) -> None:
|
||||
log.debug("driver=%r", self.driver_class)
|
||||
# loop = asyncio.get_event_loop()
|
||||
# loop.add_signal_handler(signal.SIGINT, self.on_keyboard_interupt)
|
||||
driver = self._driver = self.driver_class(self.console, self)
|
||||
active_app.set(self)
|
||||
|
||||
@@ -242,10 +259,10 @@ class App(MessagePump):
|
||||
view = self._view_stack.pop()
|
||||
await view.close_messages()
|
||||
except Exception:
|
||||
traceback = Traceback(show_locals=True)
|
||||
self._tracebacks.append(Traceback(show_locals=True))
|
||||
finally:
|
||||
driver.stop_application_mode()
|
||||
if traceback is not None:
|
||||
for traceback in self._tracebacks:
|
||||
self.console.print(traceback)
|
||||
|
||||
def require_repaint(self) -> None:
|
||||
@@ -289,7 +306,7 @@ class App(MessagePump):
|
||||
if sync_available:
|
||||
console.file.write("\x1bP=2s\x1b\\")
|
||||
except Exception:
|
||||
log.exception("refresh failed")
|
||||
self.panic(Traceback(show_locals=True))
|
||||
|
||||
def display(self, renderable: RenderableType) -> None:
|
||||
if not self._closed:
|
||||
|
||||
@@ -79,6 +79,16 @@ class Layout(ABC):
|
||||
self.height = 0
|
||||
self.renders: dict[Widget, tuple[Region, Lines]] = {}
|
||||
self._cuts: list[list[int]] | None = None
|
||||
self._require_update: bool = True
|
||||
|
||||
def check_update(self) -> bool:
|
||||
return self._require_update
|
||||
|
||||
def require_update(self) -> None:
|
||||
self._require_update = True
|
||||
|
||||
def reset_update(self) -> None:
|
||||
self._require_update = False
|
||||
|
||||
def reset(self) -> None:
|
||||
self._cuts = None
|
||||
@@ -265,10 +275,12 @@ class Layout(ABC):
|
||||
) -> Iterable[list[Segment]]:
|
||||
|
||||
for bucket in chops:
|
||||
yield sum(
|
||||
(segments for _, segments in sorted(bucket.items()) if segments),
|
||||
start=[],
|
||||
)
|
||||
line: list[Segment] = []
|
||||
extend = line.extend
|
||||
for _, segments in sorted(bucket.items()):
|
||||
if segments:
|
||||
extend(segments)
|
||||
yield line
|
||||
|
||||
def render(
|
||||
self,
|
||||
|
||||
@@ -9,7 +9,7 @@ import sys
|
||||
from typing import Iterable, NamedTuple
|
||||
|
||||
from .._layout_resolve import layout_resolve
|
||||
from .._loop import loop_last
|
||||
|
||||
from ..geometry import Dimensions, Point, Region
|
||||
from ..layout import Layout, OrderedRegion
|
||||
from ..view import View
|
||||
@@ -80,17 +80,39 @@ class GridLayout(Layout):
|
||||
|
||||
super().__init__()
|
||||
|
||||
def hide_row(self, row_name: str) -> None:
|
||||
self.hidden_rows.add(row_name)
|
||||
def is_row_visible(self, row_name: str) -> bool:
|
||||
return row_name not in self.hidden_rows
|
||||
|
||||
def show_row(self, row_name: str) -> None:
|
||||
self.hidden_rows.discard(row_name)
|
||||
def is_column_visible(self, column_name: str) -> bool:
|
||||
return column_name not in self.hidden_columns
|
||||
|
||||
def hide_column(self, column_name: str) -> None:
|
||||
self.hidden_rows.add(column_name)
|
||||
def show_row(self, row_name: str, visible: bool = True) -> bool:
|
||||
changed = False
|
||||
if visible:
|
||||
if not self.is_row_visible(row_name):
|
||||
self.require_update()
|
||||
changed = True
|
||||
self.hidden_rows.discard(row_name)
|
||||
else:
|
||||
if self.is_row_visible(row_name):
|
||||
self.require_update()
|
||||
changed = True
|
||||
self.hidden_rows.add(row_name)
|
||||
return changed
|
||||
|
||||
def show_column(self, column_name: str) -> None:
|
||||
self.hidden_rows.discard(column_name)
|
||||
def show_column(self, column_name: str, visible: bool = True) -> bool:
|
||||
changed = False
|
||||
if visible:
|
||||
if not self.is_column_visible(column_name):
|
||||
self.require_update()
|
||||
changed = True
|
||||
self.hidden_rows.discard(column_name)
|
||||
else:
|
||||
if self.is_column_visible(column_name):
|
||||
self.require_update()
|
||||
changed = True
|
||||
self.hidden_rows.add(column_name)
|
||||
return changed
|
||||
|
||||
def add_column(
|
||||
self,
|
||||
|
||||
@@ -27,7 +27,7 @@ class Message:
|
||||
self.name = camel_to_snake(self.__class__.__name__.replace("Message", ""))
|
||||
self.time = monotonic()
|
||||
self._no_default_action = False
|
||||
self._stop_propagaton = False
|
||||
self._stop_propagation = False
|
||||
super().__init__()
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.RichReprResult:
|
||||
@@ -57,5 +57,5 @@ class Message:
|
||||
"""
|
||||
self._no_default_action = prevent
|
||||
|
||||
def stop_propagation(self, stop: bool = True) -> None:
|
||||
self._stop_propagaton = stop
|
||||
def stop(self, stop: bool = True) -> None:
|
||||
self._stop_propagation = stop
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from asyncio import CancelledError
|
||||
from functools import partial
|
||||
import logging
|
||||
from asyncio import Event, Queue, QueueEmpty, Task
|
||||
from typing import Any, Awaitable, Coroutine, NamedTuple
|
||||
from asyncio import Queue, QueueEmpty, Task
|
||||
from typing import TYPE_CHECKING, Awaitable, Iterable, Callable
|
||||
from weakref import WeakSet
|
||||
|
||||
from rich.traceback import Traceback
|
||||
|
||||
from . import events
|
||||
from ._timer import Timer, TimerCallback
|
||||
from ._types import MessageHandler
|
||||
from ._context import active_app
|
||||
from .message import Message
|
||||
|
||||
log = logging.getLogger("rich")
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .app import App
|
||||
|
||||
|
||||
class NoParent(Exception):
|
||||
pass
|
||||
|
||||
@@ -44,6 +53,15 @@ class MessagePump:
|
||||
raise NoParent(f"{self._parent} has no parent")
|
||||
return self._parent
|
||||
|
||||
@property
|
||||
def app(self) -> "App":
|
||||
"""Get the current app."""
|
||||
return active_app.get()
|
||||
|
||||
@property
|
||||
def is_parent_active(self):
|
||||
return self._parent and not self._parent._closed and not self._parent._closing
|
||||
|
||||
def set_parent(self, parent: MessagePump) -> None:
|
||||
self._parent = parent
|
||||
|
||||
@@ -120,6 +138,9 @@ class MessagePump:
|
||||
self._child_tasks.add(asyncio.get_event_loop().create_task(timer.run()))
|
||||
return timer
|
||||
|
||||
def close_messages_no_wait(self) -> None:
|
||||
self._message_queue.put_nowait(None)
|
||||
|
||||
async def close_messages(self, wait: bool = True) -> None:
|
||||
"""Close message queue, and optionally wait for queue to finish processing."""
|
||||
if self._closed:
|
||||
@@ -138,12 +159,20 @@ class MessagePump:
|
||||
self._task = asyncio.create_task(self.process_messages())
|
||||
|
||||
async def process_messages(self) -> None:
|
||||
try:
|
||||
return await self._process_messages()
|
||||
except CancelledError:
|
||||
pass
|
||||
|
||||
async def _process_messages(self) -> None:
|
||||
"""Process messages until the queue is closed."""
|
||||
while not self._closed:
|
||||
try:
|
||||
message = await self.get_message()
|
||||
except MessagePumpClosed:
|
||||
break
|
||||
except CancelledError:
|
||||
raise
|
||||
except Exception as error:
|
||||
log.exception("error in get_message()")
|
||||
raise error from None
|
||||
@@ -161,9 +190,11 @@ class MessagePump:
|
||||
|
||||
try:
|
||||
await self.dispatch_message(message)
|
||||
except Exception as error:
|
||||
log.exception("error in dispatch_message")
|
||||
except CancelledError:
|
||||
raise
|
||||
except Exception as error:
|
||||
self.app.panic(Traceback(show_locals=True))
|
||||
break
|
||||
finally:
|
||||
if isinstance(message, events.Event) and self._message_queue.empty():
|
||||
if not self._closed:
|
||||
@@ -182,25 +213,39 @@ class MessagePump:
|
||||
return await self.on_message(message)
|
||||
return False
|
||||
|
||||
async def on_event(self, event: events.Event) -> None:
|
||||
method_name = f"on_{event.name}"
|
||||
def _get_dispatch_methods(
|
||||
self, method_name: str, message: Message
|
||||
) -> Iterable[Callable[[Message], Awaitable]]:
|
||||
for cls in self.__class__.__mro__:
|
||||
if message._no_default_action:
|
||||
break
|
||||
method = getattr(cls, method_name, None)
|
||||
if method is not None:
|
||||
yield method.__get__(self, cls)
|
||||
|
||||
dispatch_function: MessageHandler = getattr(self, method_name, None)
|
||||
if dispatch_function is not None:
|
||||
await dispatch_function(event)
|
||||
if event.bubble and self._parent and not event._stop_propagaton:
|
||||
if event.sender == self._parent:
|
||||
pass
|
||||
# log.debug("bubbled event abandoned; %r", event)
|
||||
elif not self._parent._closed and not self._parent._closing:
|
||||
async def on_event(self, event: events.Event) -> None:
|
||||
_rich_traceback_guard = True
|
||||
|
||||
for method in self._get_dispatch_methods(f"on_{event.name}", event):
|
||||
await method(event)
|
||||
|
||||
if event.bubble and self._parent and not event._stop_propagation:
|
||||
if event.sender != self._parent and self.is_parent_active:
|
||||
await self._parent.post_message(event)
|
||||
|
||||
async def on_message(self, message: Message) -> None:
|
||||
_rich_traceback_guard = True
|
||||
method_name = f"message_{message.name}"
|
||||
method = getattr(self, method_name, None)
|
||||
if method is not None:
|
||||
|
||||
for method in self._get_dispatch_methods(method_name, message):
|
||||
await method(message)
|
||||
|
||||
if message.bubble and self._parent and not message._stop_propagation:
|
||||
if message.sender == self._parent:
|
||||
pass
|
||||
elif not self._parent._closed and not self._parent._closing:
|
||||
await self._parent.post_message(message)
|
||||
|
||||
def post_message_no_wait(self, message: Message) -> bool:
|
||||
if self._closing or self._closed:
|
||||
return False
|
||||
|
||||
@@ -7,7 +7,6 @@ from rich.padding import Padding, PaddingDimensions
|
||||
from rich.segment import Segment
|
||||
from rich.style import StyleType
|
||||
|
||||
from . import events
|
||||
from .geometry import Dimensions, Point
|
||||
from .message import Message
|
||||
from .widget import Widget, Reactive
|
||||
@@ -56,7 +55,6 @@ class PageRender:
|
||||
|
||||
def render(self, console: Console, options: ConsoleOptions) -> None:
|
||||
width = self.width or options.max_width or console.width
|
||||
width *= 2
|
||||
options = options.update_dimensions(width, None)
|
||||
style = console.get_style(self.style)
|
||||
renderable = self.renderable
|
||||
|
||||
@@ -152,7 +152,6 @@ class ScrollBarRender:
|
||||
def __rich_console__(
|
||||
self, console: Console, options: ConsoleOptions
|
||||
) -> RenderResult:
|
||||
log.debug("SCROLLBAR RENDER")
|
||||
size = (
|
||||
(options.height or console.height)
|
||||
if self.vertical
|
||||
@@ -231,7 +230,6 @@ class ScrollBar(Widget):
|
||||
async def on_mouse_up(self, event: events.MouseUp) -> None:
|
||||
if self.grabbed:
|
||||
await self.release_mouse()
|
||||
await super().on_mouse_up(event)
|
||||
|
||||
async def on_mouse_captured(self, event: events.MouseCaptured) -> None:
|
||||
self.grabbed = event.mouse_position
|
||||
|
||||
@@ -44,6 +44,9 @@ class View(Widget):
|
||||
def __rich_repr__(self) -> rich.repr.RichReprResult:
|
||||
yield "name", self.name
|
||||
|
||||
def __getitem__(self, widget_name: str) -> Widget:
|
||||
return self.named_widgets[widget_name]
|
||||
|
||||
@property
|
||||
def is_visual(self) -> bool:
|
||||
return False
|
||||
|
||||
@@ -87,11 +87,6 @@ class Widget(MessagePump):
|
||||
def is_visual(self) -> bool:
|
||||
return True
|
||||
|
||||
@property
|
||||
def app(self) -> "App":
|
||||
"""Get the current app."""
|
||||
return active_app.get()
|
||||
|
||||
@property
|
||||
def console(self) -> Console:
|
||||
"""Get the current console."""
|
||||
|
||||
@@ -7,10 +7,16 @@ from rich.panel import Panel
|
||||
import rich.repr
|
||||
from rich.style import StyleType
|
||||
|
||||
from .. import events
|
||||
from ..message import Message
|
||||
from ..reactive import Reactive
|
||||
from ..widget import Widget
|
||||
|
||||
|
||||
class ButtonPressed(Message, bubble=True):
|
||||
pass
|
||||
|
||||
|
||||
class Expand:
|
||||
def __init__(self, renderable: RenderableType) -> None:
|
||||
self.renderable = renderable
|
||||
@@ -48,11 +54,15 @@ class Button(Widget):
|
||||
name: str | None = None,
|
||||
style: StyleType = "white on dark_blue",
|
||||
):
|
||||
self.label = label
|
||||
self.name = name or str(label)
|
||||
self.style = style
|
||||
super().__init__()
|
||||
super().__init__(name=name)
|
||||
self.label = label
|
||||
|
||||
label: Reactive[RenderableType] = Reactive("")
|
||||
|
||||
def render(self) -> RenderableType:
|
||||
return ButtonRenderable(self.label, style=self.style)
|
||||
return Align.center(self.label, vertical="middle", style=self.style)
|
||||
|
||||
async def on_click(self, event: events.Click) -> None:
|
||||
await self.emit(ButtonPressed(self))
|
||||
|
||||
@@ -29,18 +29,19 @@ class ScrollView(View):
|
||||
fluid: bool = True,
|
||||
) -> None:
|
||||
self.fluid = fluid
|
||||
self._vertical_scrollbar = ScrollBar(vertical=True)
|
||||
self._horizontal_scrollbar = ScrollBar(vertical=False)
|
||||
self._page = Page(renderable or "", style=style)
|
||||
self.vscroll = ScrollBar(vertical=True)
|
||||
self.hscroll = ScrollBar(vertical=False)
|
||||
self.page = Page(renderable or "", style=style)
|
||||
layout = GridLayout()
|
||||
layout.add_column("main")
|
||||
layout.add_column("vertical", size=1)
|
||||
layout.add_column("vscroll", size=1)
|
||||
layout.add_row("main")
|
||||
layout.add_row("horizontal", size=1)
|
||||
layout.add_row("hscroll", size=1)
|
||||
layout.add_areas(
|
||||
content="main,main", vertical="vertical,main", horizontal="main,horizontal"
|
||||
content="main,main", vscroll="vscroll,main", hscroll="main,hscroll"
|
||||
)
|
||||
# layout.hide_row("horizontal")
|
||||
layout.show_row("hscroll", False)
|
||||
layout.show_row("vscroll", False)
|
||||
super().__init__(name=name, layout=layout)
|
||||
|
||||
x: Reactive[float] = Reactive(0)
|
||||
@@ -50,35 +51,35 @@ class ScrollView(View):
|
||||
target_y: Reactive[float] = Reactive(0)
|
||||
|
||||
def validate_x(self, value: float) -> float:
|
||||
return clamp(value, 0, self._page.contents_size.width - self.size.width)
|
||||
return clamp(value, 0, self.page.contents_size.width - self.size.width)
|
||||
|
||||
def validate_target_x(self, value: float) -> float:
|
||||
return clamp(value, 0, self._page.contents_size.width - self.size.width)
|
||||
return clamp(value, 0, self.page.contents_size.width - self.size.width)
|
||||
|
||||
def validate_y(self, value: float) -> float:
|
||||
return clamp(value, 0, self._page.contents_size.height - self.size.height)
|
||||
return clamp(value, 0, self.page.contents_size.height - self.size.height)
|
||||
|
||||
def validate_target_y(self, value: float) -> float:
|
||||
return clamp(value, 0, self._page.contents_size.height - self.size.height)
|
||||
return clamp(value, 0, self.page.contents_size.height - self.size.height)
|
||||
|
||||
async def watch_x(self, new_value: float) -> None:
|
||||
self._page.x = round(new_value)
|
||||
self._horizontal_scrollbar.position = round(new_value)
|
||||
self.page.x = round(new_value)
|
||||
self.hscroll.position = round(new_value)
|
||||
|
||||
async def watch_y(self, new_value: float) -> None:
|
||||
self._page.y = round(new_value)
|
||||
self._vertical_scrollbar.position = round(new_value)
|
||||
self.page.y = round(new_value)
|
||||
self.vscroll.position = round(new_value)
|
||||
|
||||
async def update(self, renderabe: RenderableType) -> None:
|
||||
self._page.update(renderabe)
|
||||
self.page.update(renderabe)
|
||||
self.require_repaint()
|
||||
|
||||
async def on_mount(self, event: events.Mount) -> None:
|
||||
assert isinstance(self.layout, GridLayout)
|
||||
self.layout.place(
|
||||
content=self._page,
|
||||
vertical=self._vertical_scrollbar,
|
||||
horizontal=self._horizontal_scrollbar,
|
||||
content=self.page,
|
||||
vscroll=self.vscroll,
|
||||
hscroll=self.hscroll,
|
||||
)
|
||||
await self.layout.mount_all(self)
|
||||
|
||||
@@ -133,7 +134,7 @@ class ScrollView(View):
|
||||
|
||||
async def key_end(self) -> None:
|
||||
self.target_x = 0
|
||||
self.target_y = self._page.contents_size.height - self.size.height
|
||||
self.target_y = self.page.contents_size.height - self.size.height
|
||||
self.animate("x", self.target_x, duration=1, easing="out_cubic")
|
||||
self.animate("y", self.target_y, duration=1, easing="out_cubic")
|
||||
|
||||
@@ -146,7 +147,7 @@ class ScrollView(View):
|
||||
async def on_resize(self, event: events.Resize) -> None:
|
||||
await super().on_resize(event)
|
||||
if self.fluid:
|
||||
self._page.update()
|
||||
self.page.update()
|
||||
|
||||
async def message_scroll_up(self, message: Message) -> None:
|
||||
self.page_up()
|
||||
@@ -171,7 +172,17 @@ class ScrollView(View):
|
||||
async def message_page_update(self, message: Message) -> None:
|
||||
self.x = self.validate_x(self.x)
|
||||
self.y = self.validate_y(self.y)
|
||||
self._horizontal_scrollbar.virtual_size = self._page.virtual_size.width
|
||||
self._horizontal_scrollbar.window_size = self.size.width
|
||||
self._vertical_scrollbar.virtual_size = self._page.virtual_size.height
|
||||
self._vertical_scrollbar.window_size = self.size.height
|
||||
self.vscroll.virtual_size = self.page.virtual_size.height
|
||||
self.vscroll.window_size = self.size.height
|
||||
if self.layout.show_column(
|
||||
"vscroll", self.page.virtual_size.height > self.size.height
|
||||
):
|
||||
self.page.update()
|
||||
|
||||
self.hscroll.virtual_size = self.page.virtual_size.width
|
||||
self.hscroll.window_size = self.size.width
|
||||
|
||||
if self.layout.show_row(
|
||||
"hscroll", self.page.virtual_size.width > self.size.width
|
||||
):
|
||||
self.page.update()
|
||||
|
||||
Reference in New Issue
Block a user