Merge branch 'main' of github.com:Textualize/textual into datatable-cell-keys

This commit is contained in:
Darren Burns
2023-02-09 15:55:45 +00:00
146 changed files with 840 additions and 658 deletions

View File

@@ -1,15 +1,22 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
args: ['--unsafe']
- repo: https://github.com/psf/black
rev: 22.10.0
args: [ '--unsafe' ]
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
name: isort (python)
language_version: '3.11'
args: ["--profile", "black", "--filter-files"]
- repo: https://github.com/psf/black
rev: 23.1.0
hooks:
- id: black
exclude: ^tests/snapshot_tests

View File

@@ -19,11 +19,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added Shift+scroll wheel and ctrl+scroll wheel to scroll horizontally
- Added `Tree.action_toggle_node` to toggle a node without selecting, and bound it to <kbd>Space</kbd> https://github.com/Textualize/textual/issues/1433
- Added `Tree.reset` to fully reset a `Tree` https://github.com/Textualize/textual/issues/1437
- Added DOMNode.watch and DOMNode.is_attached methods https://github.com/Textualize/textual/pull/1750
### Changed
- Breaking change: `TreeNode` can no longer be imported from `textual.widgets`; it is now available via `from textual.widgets.tree import TreeNode`. https://github.com/Textualize/textual/pull/1637
- `Tree` now shows a (subdued) cursor for a highlighted node when focus has moved elsewhere https://github.com/Textualize/textual/issues/1471
- Breaking change: renamed `Checkbox` to `Switch`.
### Fixed
@@ -42,6 +44,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Removed
- Methods `MessagePump.emit` and `MessagePump.emit_no_wait` https://github.com/Textualize/textual/pull/1738
- Removed `reactive.watch` in favor of DOMNode.watch.
## [0.10.1] - 2023-01-20

View File

@@ -1 +0,0 @@
::: textual.widgets.Checkbox

1
docs/api/switch.md Normal file
View File

@@ -0,0 +1 @@
::: textual.widgets.Switch

View File

@@ -7,7 +7,7 @@ Screen {
width: auto;
}
Checkbox {
Switch {
height: auto;
width: auto;
}
@@ -22,7 +22,7 @@ Checkbox {
background: darkslategrey;
}
#custom-design > .checkbox--switch {
#custom-design > .switch--switch {
color: dodgerblue;
background: darkslateblue;
}

View File

@@ -1,35 +1,35 @@
from textual.app import App, ComposeResult
from textual.containers import Horizontal
from textual.widgets import Checkbox, Static
from textual.widgets import Switch, Static
class CheckboxApp(App):
class SwitchApp(App):
def compose(self) -> ComposeResult:
yield Static("[b]Example checkboxes\n", classes="label")
yield Static("[b]Example switches\n", classes="label")
yield Horizontal(
Static("off: ", classes="label"),
Checkbox(animate=False),
Switch(animate=False),
classes="container",
)
yield Horizontal(
Static("on: ", classes="label"),
Checkbox(value=True),
Switch(value=True),
classes="container",
)
focused_checkbox = Checkbox()
focused_checkbox.focus()
focused_switch = Switch()
focused_switch.focus()
yield Horizontal(
Static("focused: ", classes="label"), focused_checkbox, classes="container"
Static("focused: ", classes="label"), focused_switch, classes="container"
)
yield Horizontal(
Static("custom: ", classes="label"),
Checkbox(id="custom-design"),
Switch(id="custom-design"),
classes="container",
)
app = CheckboxApp(css_path="checkbox.css")
app = SwitchApp(css_path="switch.css")
if __name__ == "__main__":
app.run()

View File

@@ -40,7 +40,7 @@ Widgets are key to making user-friendly interfaces. The builtin widgets should c
- [x] Buttons
* [x] Error / warning variants
- [ ] Color picker
- [x] Checkbox
- [ ] Checkbox
- [ ] Content switcher
- [x] DataTable
* [x] Cell select
@@ -70,6 +70,7 @@ Widgets are key to making user-friendly interfaces. The builtin widgets should c
* [ ] Style variants (solid, thin etc)
- [ ] Radio boxes
- [ ] Spark-lines
- [X] Switch
- [ ] Tabs
- [ ] TextArea (multi-line input)
* [ ] Basic controls

View File

@@ -1,63 +0,0 @@
# Checkbox
A simple checkbox widget which stores a boolean value.
- [x] Focusable
- [ ] Container
## Example
The example below shows checkboxes in various states.
=== "Output"
```{.textual path="docs/examples/widgets/checkbox.py"}
```
=== "checkbox.py"
```python
--8<-- "docs/examples/widgets/checkbox.py"
```
=== "checkbox.css"
```sass
--8<-- "docs/examples/widgets/checkbox.css"
```
## Reactive Attributes
| Name | Type | Default | Description |
| ------- | ------ | ------- | ---------------------------------- |
| `value` | `bool` | `False` | The default value of the checkbox. |
## Bindings
The checkbox widget defines directly the following bindings:
::: textual.widgets.Checkbox.BINDINGS
options:
show_root_heading: false
show_root_toc_entry: false
## Component Classes
The checkbox widget provides the following component classes:
::: textual.widgets.Checkbox.COMPONENT_CLASSES
options:
show_root_heading: false
show_root_toc_entry: false
## Messages
### ::: textual.widgets.Checkbox.Changed
## Additional Notes
- To remove the spacing around a checkbox, set `border: none;` and `padding: 0;`.
## See Also
- [Checkbox](../api/checkbox.md) code reference

63
docs/widgets/switch.md Normal file
View File

@@ -0,0 +1,63 @@
# Switch
A simple switch widget which stores a boolean value.
- [x] Focusable
- [ ] Container
## Example
The example below shows switches in various states.
=== "Output"
```{.textual path="docs/examples/widgets/switch.py"}
```
=== "switch.py"
```python
--8<-- "docs/examples/widgets/switch.py"
```
=== "switch.css"
```sass
--8<-- "docs/examples/widgets/switch.css"
```
## Reactive Attributes
| Name | Type | Default | Description |
|---------|--------|---------|----------------------------------|
| `value` | `bool` | `False` | The default value of the switch. |
## Bindings
The switch widget defines directly the following bindings:
::: textual.widgets.Switch.BINDINGS
options:
show_root_heading: false
show_root_toc_entry: false
## Component Classes
The switch widget provides the following component classes:
::: textual.widgets.Switch.COMPONENT_CLASSES
options:
show_root_heading: false
show_root_toc_entry: false
## Messages
### ::: textual.widgets.Switch.Changed
## Additional Notes
- To remove the spacing around a `Switch`, set `border: none;` and `padding: 0;`.
## See Also
- [Switch](../api/switch.md) code reference

View File

@@ -126,7 +126,6 @@ nav:
- "styles/width.md"
- Widgets:
- "widgets/button.md"
- "widgets/checkbox.md"
- "widgets/data_table.md"
- "widgets/directory_tree.md"
- "widgets/footer.md"
@@ -138,6 +137,7 @@ nav:
- "widgets/list_view.md"
- "widgets/placeholder.md"
- "widgets/static.md"
- "widgets/switch.md"
- "widgets/text_log.md"
- "widgets/tree.md"
- API:
@@ -145,7 +145,6 @@ nav:
- "api/app.md"
- "api/binding.md"
- "api/button.md"
- "api/checkbox.md"
- "api/color.md"
- "api/containers.md"
- "api/coordinate.md"
@@ -170,6 +169,7 @@ nav:
- "api/scroll_view.md"
- "api/static.md"
- "api/strip.md"
- "api/switch.md"
- "api/text_log.md"
- "api/timer.md"
- "api/tree.md"

View File

@@ -1,15 +1,14 @@
from __future__ import annotations
import inspect
from typing import TYPE_CHECKING, Callable
import rich.repr
from rich.console import RenderableType
from typing import Callable, TYPE_CHECKING
from ._context import active_app
from ._log import LogGroup, LogVerbosity
if TYPE_CHECKING:
from typing_extensions import TypeAlias

View File

@@ -1,6 +1,5 @@
from .demo import DemoApp
if __name__ == "__main__":
app = DemoApp()
app.run()

View File

@@ -3,11 +3,11 @@ from __future__ import annotations
from collections import defaultdict
from fractions import Fraction
from operator import attrgetter
from typing import Sequence, TYPE_CHECKING
from typing import TYPE_CHECKING, Sequence
from .geometry import Region, Size, Spacing
from ._layout import DockArrangeResult, WidgetPlacement
from ._partition import partition
from .geometry import Region, Size, Spacing
if TYPE_CHECKING:
from .widget import Widget

View File

@@ -5,7 +5,6 @@ Compatibility layer for asyncio.
from __future__ import annotations
import sys
__all__ = ["create_task"]

View File

@@ -1,8 +1,7 @@
from __future__ import annotations
from functools import lru_cache
from typing import cast, Tuple, Union
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Tuple, Union, cast
from rich.segment import Segment
from rich.style import Style

View File

@@ -2,8 +2,8 @@ from __future__ import annotations
import asyncio
from functools import lru_cache
from inspect import signature, isawaitable
from typing import Any, Callable, TYPE_CHECKING
from inspect import isawaitable, signature
from typing import TYPE_CHECKING, Any, Callable
from . import active_app

View File

@@ -2,7 +2,6 @@ import asyncio
from ._time import time
"""
A module that serves as the single source of truth for everything time-related in a Textual app.
Having this logic centralised makes it easier to simulate time in integration tests,

View File

@@ -25,14 +25,14 @@ from rich.style import Style
from . import errors
from ._cells import cell_len
from ._loop import loop_last
from .strip import Strip
from .geometry import NULL_OFFSET, Offset, Region, Size
from .strip import Strip
if TYPE_CHECKING:
from .widget import Widget
from typing_extensions import TypeAlias
from .widget import Widget
class ReflowResult(NamedTuple):
"""The result of a reflow operation. Describes the chances to widgets."""

View File

@@ -1,6 +1,5 @@
from typing import TYPE_CHECKING
from contextvars import ContextVar
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .app import App

View File

@@ -2,14 +2,13 @@ from __future__ import annotations
import hashlib
import os
from pathlib import Path
import shlex
from pathlib import Path
from typing import Iterable
from textual._import_app import import_app
from textual.app import App
from textual.pilot import Pilot
from textual._import_app import import_app
SCREENSHOT_CACHE = ".screenshot_cache"

View File

@@ -3,7 +3,7 @@ Define a series of easing functions for more natural-looking animations.
Taken from https://easings.net/ and translated from JavaScript.
"""
from math import pi, cos, sin, sqrt
from math import cos, pi, sin, sqrt
def _in_out_expo(x: float) -> float:

View File

@@ -1,8 +1,9 @@
"""Provides an immutable sequence view class."""
from __future__ import annotations
from sys import maxsize
from typing import Generic, TypeVar, Iterator, overload, Sequence
from typing import Generic, Iterator, Sequence, TypeVar, overload
T = TypeVar("T")

View File

@@ -1,12 +1,11 @@
from __future__ import annotations
import os
import sys
import runpy
import shlex
import sys
from pathlib import Path
from typing import cast, TYPE_CHECKING
from typing import TYPE_CHECKING, cast
if TYPE_CHECKING:
from textual.app import App
@@ -46,10 +45,10 @@ def import_app(import_name: str) -> App:
A Textual application
"""
import inspect
import importlib
import inspect
from textual.app import App, WINDOWS
from textual.app import WINDOWS, App
import_name, *argv = shlex.split(import_name, posix=not WINDOWS)
drive, import_name = os.path.splitdrive(import_name)

View File

@@ -1,12 +1,13 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import ClassVar, NamedTuple, TYPE_CHECKING
from typing import TYPE_CHECKING, ClassVar, NamedTuple
from .geometry import Region, Size, Spacing
if TYPE_CHECKING:
from typing_extensions import TypeAlias
from .widget import Widget
ArrangeResult: TypeAlias = "tuple[list[WidgetPlacement], set[Widget]]"

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
import sys
import sys
from typing import TYPE_CHECKING, Any, Iterator, Sequence, overload
import rich.repr

View File

@@ -1,16 +1,8 @@
from __future__ import annotations
from collections import deque
import io
from typing import (
Callable,
Deque,
Generator,
TypeVar,
Generic,
Union,
Iterable,
)
from collections import deque
from typing import Callable, Deque, Generator, Generic, Iterable, TypeVar, Union
class ParseError(Exception):

View File

@@ -2,7 +2,6 @@ from __future__ import annotations
from typing import Callable, Iterable, TypeVar
T = TypeVar("T")

View File

@@ -4,8 +4,8 @@ Timer context manager, only used in debug.
"""
import contextlib
from typing import Generator
from time import perf_counter
from typing import Generator
from . import log

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
from fractions import Fraction
from itertools import accumulate
from typing import Sequence, cast, TYPE_CHECKING
from typing import TYPE_CHECKING, Sequence, cast
from typing_extensions import Literal

View File

@@ -1,9 +1,8 @@
from __future__ import annotations
from time import sleep, perf_counter
from asyncio import get_running_loop, Future
from threading import Thread, Event
from asyncio import Future, get_running_loop
from threading import Event, Thread
from time import perf_counter, sleep
class Sleeper(Thread):

View File

@@ -1,5 +1,5 @@
from asyncio import sleep
from time import process_time, monotonic
from time import monotonic, process_time
SLEEP_GRANULARITY: float = 1 / 50
SLEEP_IDLE: float = SLEEP_GRANULARITY / 2.0

View File

@@ -7,7 +7,6 @@ This should only be imported on Windows.
from time import sleep as time_sleep
__all__ = ["sleep"]

View File

@@ -1,17 +1,15 @@
from __future__ import annotations
import unicodedata
import re
import unicodedata
from typing import Any, Callable, Generator, Iterable
from . import events
from . import messages
from . import events, messages
from ._ansi_sequences import ANSI_SEQUENCES_KEYS
from ._parser import Awaitable, Parser, TokenCallback
from ._types import MessageTarget
from .keys import KEY_NAME_REPLACEMENTS
# When trying to determine whether the current sequence is a supported/valid
# escape sequence, at which length should we give up and consider our search
# to be unsuccessful?

View File

@@ -48,9 +48,7 @@ from ._asyncio import create_task
from ._callback import invoke
from ._context import active_app
from ._event_broker import NoHandler, extract_handler_actions
from .filter import LineFilter, Monochrome
from ._path import _make_path_object_relative
from ._wait import wait_for_idle
from .actions import SkipAction
from .await_remove import AwaitRemove
@@ -63,6 +61,7 @@ from .driver import Driver
from .drivers.headless_driver import HeadlessDriver
from .features import FeatureFlag, parse_features
from .file_monitor import FileMonitor
from .filter import LineFilter, Monochrome
from .geometry import Offset, Region, Size
from .keys import REPLACED_KEYS, _get_key_display
from .messages import CallbackType

View File

@@ -1,5 +1,4 @@
import re
from typing import Match, Pattern

View File

@@ -10,8 +10,8 @@ except ImportError:
from importlib_metadata import version
from textual._import_app import AppFail, import_app
from textual.pilot import Pilot
from textual._import_app import import_app, AppFail
@click.group()
@@ -26,6 +26,7 @@ def run():
def console(verbose: bool, exclude: list[str]) -> None:
"""Launch the textual console."""
from rich.console import Console
from textual.devtools.server import _run_devtools
console = Console()

View File

@@ -1,8 +1,7 @@
from textual.app import App, ComposeResult
from textual.constants import BORDERS
from textual.widgets import Button, Label
from textual.containers import Vertical
from textual.widgets import Button, Label
TEXT = """I must not fear.
Fear is the mind-killer.

View File

@@ -2,7 +2,7 @@ from textual.app import App, ComposeResult
from textual.containers import Horizontal, Vertical
from textual.design import ColorSystem
from textual.widget import Widget
from textual.widgets import Button, Footer, Static, Label
from textual.widgets import Button, Footer, Label, Static
class ColorButtons(Vertical):

View File

@@ -1,14 +1,15 @@
from __future__ import annotations
from rich.console import RenderableType
from textual._easing import EASING
from textual.app import App, ComposeResult
from textual.cli.previews.borders import TEXT
from textual.containers import Container, Horizontal, Vertical
from textual.reactive import Reactive
from textual.reactive import reactive, var
from textual.scrollbar import ScrollBarRender
from textual.widget import Widget
from textual.widgets import Button, Footer, Label, Input
from textual.widgets import Button, Footer, Input, Label
VIRTUAL_SIZE = 100
WINDOW_SIZE = 10
@@ -23,8 +24,8 @@ class EasingButtons(Widget):
class Bar(Widget):
position = Reactive.init(START_POSITION)
animation_running = Reactive(False)
position = reactive(START_POSITION)
animation_running = reactive(False)
DEFAULT_CSS = """
@@ -53,8 +54,8 @@ class Bar(Widget):
class EasingApp(App):
position = Reactive.init(START_POSITION)
duration = Reactive.var(1.0)
position = reactive(START_POSITION)
duration = var(1.0)
def on_load(self):
self.bind(

View File

@@ -1,16 +1,14 @@
from __future__ import annotations
from rich.panel import Panel
from rich.text import Text
from textual.app import App, ComposeResult
from textual.reactive import var, Reactive
from textual import events
from textual.app import App, ComposeResult
from textual.containers import Horizontal
from textual.reactive import Reactive, var
from textual.widgets import Button, Header, TextLog
INSTRUCTIONS = """\
[u]Press some keys![/]

View File

@@ -1,11 +1,13 @@
"""Textual CLI command code to print diagnostic information."""
from __future__ import annotations
import os
import sys
import platform
from typing import Any
import sys
from functools import singledispatch
from typing import Any
from importlib_metadata import version
from rich.console import Console, ConsoleDimensions

View File

@@ -5,20 +5,20 @@ from typing import Iterable, Sequence
from typing_extensions import Literal
from textual.css._error_tools import friendly_list
from textual.css.scalar import SYMBOL_UNIT
from ..color import ColorParseError
from ._help_renderables import Example, Bullet, HelpText
from ._help_renderables import Bullet, Example, HelpText
from .constants import (
VALID_BORDER,
VALID_LAYOUT,
VALID_ALIGN_HORIZONTAL,
VALID_ALIGN_VERTICAL,
VALID_BORDER,
VALID_LAYOUT,
VALID_STYLE_FLAGS,
VALID_TEXT_ALIGN,
)
from textual.css._error_tools import friendly_list
from textual.css.scalar import SYMBOL_UNIT
StylingContext = Literal["inline", "css"]
"""The type of styling the user was using when the error was encountered.
Used to give help text specific to the context i.e. we give CSS help if the

View File

@@ -10,14 +10,7 @@ when setting and getting.
from __future__ import annotations
from operator import attrgetter
from typing import (
TYPE_CHECKING,
Generic,
Iterable,
NamedTuple,
TypeVar,
cast,
)
from typing import TYPE_CHECKING, Generic, Iterable, NamedTuple, TypeVar, cast
import rich.errors
import rich.repr
@@ -611,11 +604,8 @@ class LayoutProperty:
or a ``Layout`` object.
"""
from ..layouts.factory import (
Layout, # Prevents circular import
MissingLayout,
get_layout,
)
from ..layouts.factory import Layout # Prevents circular import
from ..layouts.factory import MissingLayout, get_layout
_rich_traceback_omit = True
if layout is None:

View File

@@ -1,4 +1,5 @@
from __future__ import annotations
import typing
from ..geometry import Spacing

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from rich.console import ConsoleOptions, Console, RenderResult
from rich.console import Console, ConsoleOptions, RenderResult
from rich.traceback import Traceback
from ._help_renderables import HelpText

View File

@@ -1,8 +1,8 @@
from __future__ import annotations
from typing import Iterable, TYPE_CHECKING
from .model import CombinatorType, Selector, SelectorSet
from typing import TYPE_CHECKING, Iterable
from .model import CombinatorType, Selector, SelectorSet
if TYPE_CHECKING:
from ..dom import DOMNode

View File

@@ -1,11 +1,10 @@
from __future__ import annotations
import rich.repr
from dataclasses import dataclass, field
from enum import Enum
from typing import Iterable, TYPE_CHECKING
from typing import TYPE_CHECKING, Iterable
import rich.repr
from .styles import Styles
from .tokenize import Token

View File

@@ -2,23 +2,23 @@ from __future__ import annotations
from functools import lru_cache
from pathlib import PurePath
from typing import Iterator, Iterable, NoReturn
from typing import Iterable, Iterator, NoReturn
from ..suggestions import get_suggestion
from ._styles_builder import DeclarationError, StylesBuilder
from .errors import UnresolvedVariableError
from .types import Specificity3
from ._styles_builder import StylesBuilder, DeclarationError
from .model import (
CombinatorType,
Declaration,
RuleSet,
Selector,
CombinatorType,
SelectorSet,
SelectorType,
)
from .styles import Styles
from ..suggestions import get_suggestion
from .tokenize import tokenize, tokenize_declarations, Token, tokenize_values
from .tokenize import Token, tokenize, tokenize_declarations, tokenize_values
from .tokenizer import EOFError, ReferencedBy
from .types import Specificity3
SELECTOR_MAP: dict[str, tuple[SelectorType, Specificity3]] = {
"selector": (SelectorType.TYPE, (0, 0, 1)),

View File

@@ -1,9 +1,9 @@
from __future__ import annotations
import re
from enum import Enum, unique
from fractions import Fraction
from functools import lru_cache
import re
from typing import Iterable, NamedTuple
import rich.repr

View File

@@ -2,15 +2,12 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from .scalar import ScalarOffset, Scalar
from .._animator import Animation
from .._animator import EasingFunction
from .._animator import Animation, EasingFunction
from .._types import CallbackType
from .scalar import Scalar, ScalarOffset
if TYPE_CHECKING:
from ..dom import DOMNode
from .styles import StylesBase

View File

@@ -4,8 +4,7 @@ from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from functools import lru_cache
from operator import attrgetter
from typing import Iterable, cast
from typing import TYPE_CHECKING, Any, NamedTuple
from typing import TYPE_CHECKING, Any, Iterable, NamedTuple, cast
import rich.repr
from rich.style import Style

View File

@@ -4,7 +4,7 @@ import re
from pathlib import PurePath
from typing import Iterable
from textual.css.tokenizer import Expect, Tokenizer, Token
from textual.css.tokenizer import Expect, Token, Tokenizer
PERCENT = r"-?\d+\.?\d*%"
DECIMAL = r"-?\d+\.?\d*"

View File

@@ -4,11 +4,11 @@ import re
from pathlib import PurePath
from typing import NamedTuple
import rich.repr
from rich.console import Group, RenderableType
from rich.highlighter import ReprHighlighter
from rich.padding import Padding
from rich.panel import Panel
import rich.repr
from rich.syntax import Syntax
from rich.text import Text

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
from typing import Tuple
from typing_extensions import Literal
from ..color import Color

View File

@@ -119,7 +119,7 @@ DarkSwitch .label {
color: $text-muted;
}
DarkSwitch Checkbox {
DarkSwitch Switch {
background: $boost;
dock: left;
}

View File

@@ -1,8 +1,8 @@
from __future__ import annotations
from importlib_metadata import version
from pathlib import Path
from importlib_metadata import version
from rich import box
from rich.console import RenderableType
from rich.json import JSON
@@ -15,15 +15,15 @@ from rich.text import Text
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.containers import Container, Horizontal
from textual.reactive import reactive, watch
from textual.reactive import reactive
from textual.widgets import (
Button,
Checkbox,
DataTable,
Footer,
Header,
Input,
Static,
Switch,
TextLog,
)
@@ -138,7 +138,7 @@ Build your own or use the builtin widgets.
- **Input** Text / Password input.
- **Button** Clickable button with a number of styles.
- **Checkbox** A checkbox to toggle between states.
- **Switch** A switch to toggle between states.
- **DataTable** A spreadsheet-like widget for navigating data. Cells may contain text or Rich renderables.
- **Tree** An generic tree with expandable nodes.
- **DirectoryTree** A tree of file and folders.
@@ -199,16 +199,16 @@ class Title(Static):
class DarkSwitch(Horizontal):
def compose(self) -> ComposeResult:
yield Checkbox(value=self.app.dark)
yield Switch(value=self.app.dark)
yield Static("Dark mode toggle", classes="label")
def on_mount(self) -> None:
watch(self.app, "dark", self.on_dark_change, init=False)
self.watch(self.app, "dark", self.on_dark_change, init=False)
def on_dark_change(self, dark: bool) -> None:
self.query_one(Checkbox).value = self.app.dark
self.query_one(Switch).value = self.app.dark
def on_checkbox_changed(self, event: Checkbox.Changed) -> None:
def on_switch_changed(self, event: Switch.Changed) -> None:
self.app.dark = event.value

View File

@@ -7,8 +7,7 @@ from rich.padding import Padding
from rich.table import Table
from rich.text import Text
from .color import Color, WHITE
from .color import WHITE, Color
NUMBER_OF_SHADES = 3

View File

@@ -9,21 +9,14 @@ from io import StringIO
from time import time
from typing import Any, NamedTuple, Type
import aiohttp
import msgpack
from aiohttp import ClientConnectorError, ClientResponseError, ClientWebSocketResponse
from rich.console import Console
from rich.segment import Segment
from .._log import LogGroup, LogVerbosity
import aiohttp
import msgpack
from aiohttp import (
ClientConnectorError,
ClientResponseError,
ClientWebSocketResponse,
)
DEVTOOLS_PORT = 8081
WEBSOCKET_CONNECT_TIMEOUT = 3
LOG_QUEUE_MAXSIZE = 512

View File

@@ -1,10 +1,10 @@
from __future__ import annotations
import inspect
from typing import TYPE_CHECKING, cast
from .client import DevtoolsLog
from .._log import LogGroup, LogVerbosity
from .client import DevtoolsLog
if TYPE_CHECKING:
from .client import DevtoolsClient

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
import asyncio
from aiohttp.web import run_app
from aiohttp.web_app import Application
from aiohttp.web_request import Request

View File

@@ -7,19 +7,19 @@ import pickle
from json import JSONDecodeError
from typing import Any, cast
import msgpack
from aiohttp import WSMessage, WSMsgType
from aiohttp.abc import Request
from aiohttp.web_ws import WebSocketResponse
from rich.console import Console
from rich.markup import escape
import msgpack
from textual._log import LogGroup
from textual._time import time
from textual.devtools.renderables import (
DevConsoleHeader,
DevConsoleLog,
DevConsoleNotice,
DevConsoleHeader,
)
QUEUEABLE_TYPES = {"client_log", "client_spillover"}

View File

@@ -22,6 +22,7 @@ from rich.tree import Tree
from ._context import NoActiveAppError
from ._node_list import NodeList
from ._types import CallbackType
from .binding import Bindings, BindingType
from .color import BLACK, WHITE, Color
from .css._error_tools import friendly_list
@@ -31,7 +32,7 @@ from .css.parse import parse_declarations
from .css.styles import RenderStyles, Styles
from .css.tokenize import IDENTIFIER
from .message_pump import MessagePump
from .reactive import Reactive
from .reactive import Reactive, _watch
from .timer import Timer
from .walk import walk_breadth_first, walk_depth_first
@@ -210,6 +211,10 @@ class DOMNode(MessagePump):
styles = self._component_styles[name]
return styles
def _post_mount(self):
"""Called after the object has been mounted."""
Reactive._initialize_object(self)
@property
def _node_bases(self) -> Iterator[Type[DOMNode]]:
"""Iterator[Type[DOMNode]]: The DOMNode bases classes (including self.__class__)"""
@@ -643,6 +648,23 @@ class DOMNode(MessagePump):
"""
return [child for child in self.children if child.display]
def watch(
self,
obj: DOMNode,
attribute_name: str,
callback: CallbackType,
init: bool = True,
) -> None:
"""Watches for modifications to reactive attributes on another object.
Args:
obj: Object containing attribute to watch.
attribute_name: Attribute to watch.
callback: A callback to run when attribute changes.
init: Check watchers on first call.
"""
_watch(self, obj, attribute_name, callback, init=init)
def get_pseudo_classes(self) -> Iterable[str]:
"""Get any pseudo classes applicable to this Node, e.g. hover, focus.

View File

@@ -1,9 +1,10 @@
from __future__ import annotations
import asyncio
from .. import events
from ..driver import Driver
from ..geometry import Size
from .. import events
class HeadlessDriver(Driver):

View File

@@ -2,27 +2,26 @@ from __future__ import annotations
import asyncio
import os
from codecs import getincrementaldecoder
import selectors
import signal
import sys
import termios
import tty
from typing import Any, TYPE_CHECKING
from codecs import getincrementaldecoder
from threading import Event, Thread
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from rich.console import Console
import rich.repr
from .. import log
from ..driver import Driver
from ..geometry import Size
from .. import events, log
from .._profile import timer
from .._types import MessageTarget
from .._xterm_parser import XTermParser
from .._profile import timer
from .. import events
from ..driver import Driver
from ..geometry import Size
@rich.repr.auto

View File

@@ -9,6 +9,7 @@ from __future__ import annotations
from functools import lru_cache
from operator import attrgetter, itemgetter
from typing import (
TYPE_CHECKING,
Any,
Collection,
NamedTuple,
@@ -16,7 +17,6 @@ from typing import (
TypeVar,
Union,
cast,
TYPE_CHECKING,
)
if TYPE_CHECKING:

View File

@@ -1,8 +1,8 @@
from __future__ import annotations
from .._layout import Layout
from .horizontal import HorizontalLayout
from .grid import GridLayout
from .horizontal import HorizontalLayout
from .vertical import VerticalLayout
LAYOUT_MAP: dict[str, type[Layout]] = {

View File

@@ -2,9 +2,9 @@ from __future__ import annotations
from fractions import Fraction
from .._resolve import resolve_box_models
from ..geometry import Size, Region
from .._layout import ArrangeResult, Layout, WidgetPlacement
from .._resolve import resolve_box_models
from ..geometry import Region, Size
from ..widget import Widget

View File

@@ -3,9 +3,9 @@ from __future__ import annotations
from fractions import Fraction
from typing import TYPE_CHECKING
from .._layout import ArrangeResult, Layout, WidgetPlacement
from .._resolve import resolve_box_models
from ..geometry import Region, Size
from .._layout import ArrangeResult, Layout, WidgetPlacement
if TYPE_CHECKING:
from ..widget import Widget

View File

@@ -1,15 +1,16 @@
from __future__ import annotations
from typing import ClassVar, TYPE_CHECKING
from typing import TYPE_CHECKING, ClassVar
import rich.repr
from . import _clock
from .case import camel_to_snake
from ._types import MessageTarget as MessageTarget
from .case import camel_to_snake
if TYPE_CHECKING:
from .message_pump import MessagePump
from .widget import Widget
@rich.repr.auto

View File

@@ -123,6 +123,19 @@ class MessagePump(metaclass=MessagePumpMeta):
"""
return self.app._logger
@property
def is_attached(self) -> bool:
"""Is the node is attached to the app via the DOM."""
from .app import App
node = self
while not isinstance(node, App):
if node._parent is None:
return False
node = node._parent
return True
def _attach(self, parent: MessagePump) -> None:
"""Set the parent, and therefore attach this node to the tree.
@@ -358,7 +371,10 @@ class MessagePump(metaclass=MessagePumpMeta):
finally:
# This is critical, mount may be waiting
self._mounted_event.set()
Reactive._initialize_object(self)
self._post_mount()
def _post_mount(self):
"""Called after the object has been mounted."""
async def _process_messages_loop(self) -> None:
"""Process messages until the queue is closed."""

View File

@@ -1,13 +1,13 @@
from __future__ import annotations
from typing import TYPE_CHECKING
import rich.repr
from .geometry import Region
from ._types import CallbackType
from .geometry import Region
from .message import Message
if TYPE_CHECKING:
from .message_pump import MessagePump
from .widget import Widget

View File

@@ -1,12 +1,12 @@
from __future__ import annotations
import rich.repr
import asyncio
from typing import Generic
from .app import App, ReturnType
import rich.repr
from ._wait import wait_for_idle
from .app import App, ReturnType
@rich.repr.auto(angular=True)
@@ -53,6 +53,7 @@ class Pilot(Generic[ReturnType]):
async def wait_for_scheduled_animations(self) -> None:
"""Wait for any current and scheduled animations to complete."""
await self._app.animator.wait_until_complete()
await wait_for_idle(0)
async def exit(self, result: ReturnType) -> None:
"""Exit the app with the given result.

View File

@@ -7,36 +7,26 @@ from typing import (
Any,
Awaitable,
Callable,
ClassVar,
Generic,
Type,
TypeVar,
Union,
)
import rich.repr
from . import events
from ._callback import count_parameters, invoke
from ._types import MessageTarget
from ._callback import count_parameters
from ._types import CallbackType, MessageTarget
if TYPE_CHECKING:
from .app import App
from .widget import Widget
from .dom import DOMNode
Reactable = Union[Widget, App]
Reactable = DOMNode
ReactiveType = TypeVar("ReactiveType")
class _NotSet:
pass
_NOT_SET = _NotSet()
T = TypeVar("T")
@rich.repr.auto
class Reactive(Generic[ReactiveType]):
"""Reactive descriptor.
@@ -50,7 +40,7 @@ class Reactive(Generic[ReactiveType]):
compute: Run compute methods when attribute is changed. Defaults to True.
"""
_reactives: TypeVar[dict[str, object]] = {}
_reactives: ClassVar[dict[str, object]] = {}
def __init__(
self,
@@ -77,37 +67,6 @@ class Reactive(Generic[ReactiveType]):
yield "always_update", self._always_update
yield "compute", self._run_compute
@classmethod
def init(
cls,
default: ReactiveType | Callable[[], ReactiveType],
*,
layout: bool = False,
repaint: bool = True,
always_update: bool = False,
compute: bool = True,
) -> Reactive:
"""A reactive variable that calls watchers and compute on initialize (post mount).
Args:
default: A default value or callable that returns a default.
layout: Perform a layout on change. Defaults to False.
repaint: Perform a repaint on change. Defaults to True.
always_update: Call watchers even when the new value equals the old value. Defaults to False.
compute: Run compute methods when attribute is changed. Defaults to True.
Returns:
A Reactive instance which calls watchers or initialize.
"""
return cls(
default,
layout=layout,
repaint=repaint,
init=True,
always_update=always_update,
compute=compute,
)
@classmethod
def var(
cls,
@@ -254,7 +213,7 @@ class Reactive(Generic[ReactiveType]):
def invoke_watcher(
watch_function: Callable, old_value: object, value: object
) -> bool:
) -> None:
"""Invoke a watch function.
Args:
@@ -262,8 +221,6 @@ class Reactive(Generic[ReactiveType]):
old_value: The old value of the attribute.
value: The new value of the attribute.
Returns:
True if the watcher was run, or False if it was posted.
"""
_rich_traceback_omit = True
param_count = count_parameters(watch_function)
@@ -280,17 +237,23 @@ class Reactive(Generic[ReactiveType]):
sender=obj, callback=partial(await_watcher, watch_result)
)
)
return False
else:
return True
watch_function = getattr(obj, f"watch_{name}", None)
if callable(watch_function):
invoke_watcher(watch_function, old_value, value)
watchers: list[Callable] = getattr(obj, "__watchers", {}).get(name, [])
for watcher in watchers:
invoke_watcher(watcher, old_value, value)
# Process "global" watchers
watchers: list[tuple[Reactable, Callable]]
watchers = getattr(obj, "__watchers", {}).get(name, [])
# Remove any watchers for reactables that have since closed
if watchers:
watchers[:] = [
(reactable, callback)
for reactable, callback in watchers
if reactable.is_attached and not reactable._closing
]
for _, callback in watchers:
invoke_watcher(callback, old_value, value)
@classmethod
def _compute(cls, obj: Reactable) -> None:
@@ -362,10 +325,12 @@ class var(Reactive[ReactiveType]):
)
def watch(
def _watch(
node: DOMNode,
obj: Reactable,
attribute_name: str,
callback: Callable[[Any], object],
callback: CallbackType,
*,
init: bool = True,
) -> None:
"""Watch a reactive variable on an object.
@@ -379,11 +344,11 @@ def watch(
if not hasattr(obj, "__watchers"):
setattr(obj, "__watchers", {})
watchers: dict[str, list[Callable]] = getattr(obj, "__watchers")
watchers: dict[str, list[tuple[Reactable, Callable]]] = getattr(obj, "__watchers")
watcher_list = watchers.setdefault(attribute_name, [])
if callback in watcher_list:
return
watcher_list.append(callback)
watcher_list.append((node, callback))
if init:
current_value = getattr(obj, attribute_name, None)
Reactive._check_watchers(obj, attribute_name, current_value)

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from rich.console import ConsoleOptions, Console, RenderResult
from rich.console import Console, ConsoleOptions, RenderResult
from rich.segment import Segment
from rich.style import Style

View File

@@ -1,7 +1,6 @@
from __future__ import annotations
from rich.console import ConsoleOptions, Console, RenderResult
from rich.console import Console, ConsoleOptions, RenderResult
from rich.segment import Segment
from rich.style import Style

View File

@@ -1,10 +1,10 @@
from __future__ import annotations
import statistics
from typing import Generic, Sequence, Iterable, Callable, TypeVar
from typing import Callable, Generic, Iterable, Sequence, TypeVar
from rich.color import Color
from rich.console import ConsoleOptions, Console, RenderResult
from rich.console import Console, ConsoleOptions, RenderResult
from rich.segment import Segment
from rich.style import Style

View File

@@ -3,7 +3,7 @@ from typing import Iterable, Tuple, cast
from rich.cells import cell_len
from rich.color import Color
from rich.console import ConsoleOptions, Console, RenderResult, RenderableType
from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
from rich.segment import Segment
from rich.style import Style

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
from typing import Iterable
from rich.console import ConsoleOptions, Console, RenderResult, RenderableType
from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
from rich.segment import Segment
from rich.style import Style

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from rich.console import ConsoleOptions, Console, RenderResult
from rich.console import Console, ConsoleOptions, RenderResult
from rich.style import StyleType
from rich.text import Text
@@ -99,6 +99,7 @@ class UnderlineBar:
if __name__ == "__main__":
import random
from time import sleep
from rich.color import ANSI_COLOR_NAMES
console = Console()

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from typing import Iterable, Iterator, TYPE_CHECKING
from typing import TYPE_CHECKING, Iterable, Iterator
import rich.repr
from rich.console import RenderableType
@@ -9,14 +9,14 @@ from rich.style import Style
from . import errors, events, messages
from ._callback import invoke
from ._compositor import Compositor, MapGeometry
from ._types import CallbackType
from .css.match import match
from .css.parse import parse_selectors
from .dom import DOMNode
from .timer import Timer
from ._types import CallbackType
from .geometry import Offset, Region, Size
from .reactive import Reactive
from .renderables.blank import Blank
from .timer import Timer
from .widget import Widget
if TYPE_CHECKING:

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
from collections import deque
from typing import Iterable, Iterator, TypeVar, overload, TYPE_CHECKING
from typing import TYPE_CHECKING, Iterable, Iterator, TypeVar, overload
if TYPE_CHECKING:
from textual.dom import DOMNode

View File

@@ -35,9 +35,9 @@ from rich.text import Text
from rich.traceback import Traceback
from . import errors, events, messages
from ._asyncio import create_task
from ._animator import DEFAULT_EASING, Animatable, BoundAnimator, EasingFunction
from ._arrange import DockArrangeResult, arrange
from ._asyncio import create_task
from ._context import active_app
from ._easing import DEFAULT_SCROLL_EASING
from ._layout import Layout

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
from importlib import import_module
import typing
from importlib import import_module
from ..case import camel_to_snake
@@ -8,8 +9,8 @@ from ..case import camel_to_snake
# but also to the `__init__.pyi` file in this same folder - otherwise text editors and type checkers won't
# be able to "see" them.
if typing.TYPE_CHECKING:
from ..widget import Widget
from ._button import Button
from ._checkbox import Checkbox
from ._data_table import DataTable
from ._directory_tree import DirectoryTree
from ._footer import Footer
@@ -21,15 +22,15 @@ if typing.TYPE_CHECKING:
from ._placeholder import Placeholder
from ._pretty import Pretty
from ._static import Static
from ._switch import Switch
from ._text_log import TextLog
from ._tree import Tree
from ._welcome import Welcome
from ..widget import Widget
__all__ = [
"Button",
"Checkbox",
"Switch",
"DataTable",
"DirectoryTree",
"Footer",

View File

@@ -1,18 +1,17 @@
# This stub file must re-export every classes exposed in the __init__.py's `__all__` list:
from ._button import Button as Button
from ._data_table import DataTable as DataTable
from ._checkbox import Checkbox as Checkbox
from ._directory_tree import DirectoryTree as DirectoryTree
from ._footer import Footer as Footer
from ._header import Header as Header
from ._input import Input as Input
from ._label import Label as Label
from ._list_view import ListView as ListView
from ._list_item import ListItem as ListItem
from ._list_view import ListView as ListView
from ._placeholder import Placeholder as Placeholder
from ._pretty import Pretty as Pretty
from ._static import Static as Static
from ._input import Input as Input
from ._switch import Switch as Switch
from ._text_log import TextLog as TextLog
from ._tree import Tree as Tree
from ._tree_node import TreeNode as TreeNode
from ._welcome import Welcome as Welcome

View File

@@ -2,19 +2,18 @@ from __future__ import annotations
from functools import partial
from typing import cast
from typing_extensions import Literal
import rich.repr
from rich.console import RenderableType
from rich.text import Text, TextType
from typing_extensions import Literal
from .. import events
from ..css._error_tools import friendly_list
from ..message import Message
from ..reactive import Reactive
from ..reactive import reactive
from ..widgets import Static
ButtonVariant = Literal["default", "primary", "success", "warning", "error"]
_VALID_BUTTON_VARIANTS = {"default", "primary", "success", "warning", "error"}
@@ -151,13 +150,13 @@ class Button(Static, can_focus=True):
ACTIVE_EFFECT_DURATION = 0.3
"""When buttons are clicked they get the `-active` class for this duration (in seconds)"""
label: Reactive[RenderableType] = Reactive("")
label: reactive[RenderableType] = reactive("")
"""The text label that appears within the button."""
variant = Reactive.init("default")
variant = reactive("default")
"""The variant name for the button."""
disabled = Reactive(False)
disabled = reactive(False)
"""The disabled state of the button; `True` if disabled, `False` if not."""
class Pressed(Message, bubble=True):

View File

@@ -15,7 +15,7 @@ from rich.style import Style
from rich.text import Text, TextType
from typing_extensions import Literal, TypeAlias
from .. import events, messages
from .. import events
from .._cache import LRUCache
from .._segment_tools import line_crop
from .._two_way_dict import TwoWayDict

View File

@@ -7,9 +7,9 @@ from typing import ClassVar
from rich.style import Style
from rich.text import Text, TextType
from ..message import Message
from ._tree import Tree, TreeNode, TOGGLE_STYLE
from .._types import MessageTarget
from ..message import Message
from ._tree import TOGGLE_STYLE, Tree, TreeNode
@dataclass

View File

@@ -8,7 +8,7 @@ from rich.console import RenderableType
from rich.text import Text
from .. import events
from ..reactive import Reactive, watch
from ..reactive import Reactive
from ..widget import Widget
@@ -66,7 +66,7 @@ class Footer(Widget):
self.refresh()
def on_mount(self) -> None:
watch(self.screen, "focused", self._focus_changed)
self.watch(self.screen, "focused", self._focus_changed)
def _focus_changed(self, focused: Widget | None) -> None:
self._key_text = None

View File

@@ -4,8 +4,8 @@ from datetime import datetime
from rich.text import Text
from ..reactive import Reactive
from ..widget import Widget
from ..reactive import Reactive, watch
class HeaderIcon(Widget):
@@ -133,5 +133,5 @@ class Header(Widget):
def set_sub_title(sub_title: str) -> None:
self.query_one(HeaderTitle).sub_text = sub_title
watch(self.app, "title", set_title)
watch(self.app, "sub_title", set_sub_title)
self.watch(self.app, "title", set_title)
self.watch(self.app, "sub_title", set_sub_title)

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
from typing import ClassVar
import re
from typing import ClassVar
from rich.cells import cell_len, get_character_cell_size
from rich.console import Console, ConsoleOptions, RenderableType, RenderResult

View File

@@ -1,4 +1,5 @@
from __future__ import annotations
from typing import ClassVar
from textual.await_remove import AwaitRemove

View File

@@ -7,8 +7,7 @@ from typing_extensions import Literal
from .. import events
from ..css._error_tools import friendly_list
from ..reactive import Reactive, reactive
from ..widget import Widget, RenderResult
from ..widget import RenderResult, Widget
PlaceholderVariant = Literal["default", "size", "text"]
_VALID_PLACEHOLDER_VARIANTS_ORDERED: list[PlaceholderVariant] = [

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
from typing import Any
from rich.pretty import Pretty as PrettyRenderable
from ..widget import Widget

View File

@@ -8,16 +8,16 @@ from ..binding import Binding, BindingType
from ..geometry import Size
from ..message import Message
from ..reactive import reactive
from ..widget import Widget
from ..scrollbar import ScrollBarRender
from ..widget import Widget
class Checkbox(Widget, can_focus=True):
"""A checkbox widget that represents a boolean value.
class Switch(Widget, can_focus=True):
"""A switch widget that represents a boolean value.
Can be toggled by clicking on it or through its [bindings][textual.widgets.Checkbox.BINDINGS].
Can be toggled by clicking on it or through its [bindings][textual.widgets.Switch.BINDINGS].
The checkbox widget also contains [component classes][textual.widgets.Checkbox.COMPONENT_CLASSES]
The switch widget also contains [component classes][textual.widgets.Switch.COMPONENT_CLASSES]
that enable more customization.
"""
@@ -27,20 +27,20 @@ class Checkbox(Widget, can_focus=True):
"""
| Key(s) | Description |
| :- | :- |
| enter,space | Toggle the checkbox status. |
| enter,space | Toggle the switch state. |
"""
COMPONENT_CLASSES: ClassVar[set[str]] = {
"checkbox--switch",
"switch--switch",
}
"""
| Class | Description |
| :- | :- |
| `checkbox--switch` | Targets the switch of the checkbox. |
| `switch--switch` | Targets the switch of the switch. |
"""
DEFAULT_CSS = """
Checkbox {
Switch {
border: tall transparent;
background: $panel;
height: auto;
@@ -48,49 +48,49 @@ class Checkbox(Widget, can_focus=True):
padding: 0 2;
}
Checkbox > .checkbox--switch {
Switch > .switch--switch {
background: $panel-darken-2;
color: $panel-lighten-2;
}
Checkbox:hover {
Switch:hover {
border: tall $background;
}
Checkbox:focus {
Switch:focus {
border: tall $accent;
}
Checkbox.-on {
Switch.-on {
}
Checkbox.-on > .checkbox--switch {
Switch.-on > .switch--switch {
color: $success;
}
"""
value = reactive(False, init=False)
"""The value of the checkbox; `True` for on and `False` for off."""
"""The value of the switch; `True` for on and `False` for off."""
slider_pos = reactive(0.0)
"""The position of the slider."""
class Changed(Message, bubble=True):
"""Posted when the status of the checkbox changes.
"""Posted when the status of the switch changes.
Can be handled using `on_checkbox_changed` in a subclass of `Checkbox`
Can be handled using `on_switch_changed` in a subclass of `Switch`
or in a parent widget in the DOM.
Attributes:
value: The value that the checkbox was changed to.
input: The `Checkbox` widget that was changed.
value: The value that the switch was changed to.
input: The `Switch` widget that was changed.
"""
def __init__(self, sender: Checkbox, value: bool) -> None:
def __init__(self, sender: Switch, value: bool) -> None:
super().__init__(sender)
self.value: bool = value
self.input: Checkbox = sender
self.input: Switch = sender
def __init__(
self,
@@ -101,14 +101,14 @@ class Checkbox(Widget, can_focus=True):
id: str | None = None,
classes: str | None = None,
):
"""Initialise the checkbox.
"""Initialise the switch.
Args:
value: The initial value of the checkbox. Defaults to False.
animate: True if the checkbox should animate when toggled. Defaults to True.
name: The name of the checkbox.
id: The ID of the checkbox in the DOM.
classes: The CSS classes of the checkbox.
value: The initial value of the switch. Defaults to False.
animate: True if the switch should animate when toggled. Defaults to True.
name: The name of the switch.
id: The ID of the switch in the DOM.
classes: The CSS classes of the switch.
"""
super().__init__(name=name, id=id, classes=classes)
if value:
@@ -128,7 +128,7 @@ class Checkbox(Widget, can_focus=True):
self.set_class(slider_pos == 1, "-on")
def render(self) -> RenderableType:
style = self.get_component_rich_style("checkbox--switch")
style = self.get_component_rich_style("switch--switch")
return ScrollBarRender(
virtual_size=100,
window_size=50,
@@ -150,6 +150,6 @@ class Checkbox(Widget, can_focus=True):
self.toggle()
def toggle(self) -> None:
"""Toggle the checkbox value. As a result of the value changing,
a Checkbox.Changed message will be posted."""
"""Toggle the switch value. As a result of the value changing,
a Switch.Changed message will be posted."""
self.value = not self.value

View File

@@ -1,10 +1,10 @@
from ..app import ComposeResult
from ._static import Static
from ._button import Button
from ..containers import Container
from rich.markdown import Markdown
from ..app import ComposeResult
from ..containers import Container
from ._button import Button
from ._static import Static
WELCOME_MD = """\
# Welcome!

View File

@@ -7,11 +7,11 @@ from typing import Iterable
from rich.cells import cell_len
from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
from rich.segment import Segment
from rich.style import StyleType, Style
from rich.style import Style, StyleType
from rich.text import Text
from textual import events
from textual._layout_resolve import layout_resolve, Edge
from textual._layout_resolve import Edge, layout_resolve
from textual.keys import Keys
from textual.reactive import Reactive
from textual.renderables.text_opacity import TextOpacity

View File

@@ -2,17 +2,17 @@ import pytest
from tests.utilities.render import render
from textual.css._help_text import (
spacing_wrong_number_of_values_help_text,
spacing_invalid_value_help_text,
scalar_help_text,
string_enum_help_text,
color_property_help_text,
border_property_help_text,
layout_property_help_text,
fractional_property_help_text,
offset_property_help_text,
align_help_text,
border_property_help_text,
color_property_help_text,
fractional_property_help_text,
layout_property_help_text,
offset_property_help_text,
offset_single_axis_help_text,
scalar_help_text,
spacing_invalid_value_help_text,
spacing_wrong_number_of_values_help_text,
string_enum_help_text,
style_flags_property_help_text,
)

View File

@@ -8,7 +8,7 @@ from textual.css.parse import substitute_references
from textual.css.scalar import Scalar, Unit
from textual.css.stylesheet import Stylesheet, StylesheetParseError
from textual.css.tokenize import tokenize
from textual.css.tokenizer import Token, ReferencedBy
from textual.css.tokenizer import ReferencedBy, Token
from textual.css.transition import Transition
from textual.geometry import Spacing
from textual.layouts.vertical import VerticalLayout

View File

@@ -1,13 +1,12 @@
from decimal import Decimal
import pytest
from rich.style import Style
from textual.color import Color
from textual.css.errors import StyleValueError
from textual.css.scalar import Scalar, Unit
from textual.css.styles import Styles, RenderStyles
from textual.css.styles import RenderStyles, Styles
from textual.dom import DOMNode
from textual.widget import Widget

View File

@@ -11,6 +11,4 @@ _WINDOWS = sys.platform == "win32"
# and the error messages suggest the event loop is being shutdown before async fixture
# teardown code has finished running. These are very rare, but are much more of an issue on
# CI since they can delay builds that have passed locally.
pytestmark = pytest.mark.skipif(
_MACOS_CI or _WINDOWS, reason="Issue #411"
)
pytestmark = pytest.mark.skipif(_MACOS_CI or _WINDOWS, reason="Issue #411")

Some files were not shown because too many files have changed in this diff Show More