Various changes/fixes

This commit is contained in:
Darren Burns
2022-04-20 13:54:34 +01:00
parent 00112ef740
commit b9d00f301b
7 changed files with 106 additions and 31 deletions

View File

@@ -2,7 +2,7 @@
layout: vertical;
background: dark_green;
overflow: hidden auto;
overflow: auto auto;
border: heavy white;
}
@@ -10,4 +10,5 @@
height: 8;
min-width: 80;
background: dark_blue;
margin-bottom: 4;
}

View File

@@ -1,7 +1,9 @@
import random
import sys
from textual import events
from textual.app import App
from textual.geometry import Spacing
from textual.widget import Widget
from textual.widgets import Placeholder
@@ -14,8 +16,13 @@ class BasicApp(App):
self.bind("d", "dump")
self.bind("t", "log_tree")
self.bind("p", "print")
self.bind("o", "toggle_visibility")
self.bind("p", "toggle_display")
self.bind("f", "modify_first_child")
self.bind("b", "toggle_border")
self.bind("m", "increase_margin")
def on_mount(self):
async def on_mount(self):
"""Build layout here."""
uber2 = Widget()
@@ -23,8 +30,9 @@ class BasicApp(App):
Widget(id="uber2-child1"),
Widget(id="uber2-child2"),
)
self.first_child = Placeholder(id="child1", classes={"list-item"})
uber1 = Widget(
Placeholder(id="child1", classes={"list-item"}),
self.first_child,
Placeholder(id="child2", classes={"list-item"}),
Placeholder(id="child3", classes={"list-item"}),
Placeholder(classes={"list-item"}),
@@ -32,6 +40,7 @@ class BasicApp(App):
Placeholder(classes={"list-item"}),
)
self.mount(uber1=uber1)
await self.first_child.focus()
async def on_key(self, event: events.Key) -> None:
await self.dispatch_key(event)
@@ -55,5 +64,31 @@ class BasicApp(App):
sys.stdout.write("abcdef")
def action_modify_first_child(self):
"""Increment height of first child widget, randomise border and bg color"""
previous_height = self.focused.styles.height.value
new_height = previous_height + 1
self.focused.styles.height = self.focused.styles.height.copy_with(
value=new_height
)
color = random.choice(["red", "green", "blue"])
self.focused.styles.background = color
self.focused.styles.border = ("dashed", color)
def action_toggle_visibility(self):
self.focused.visible = not self.focused.visible
def action_toggle_display(self):
# TODO: Doesn't work
self.focused.display = not self.focused.display
def action_toggle_border(self):
self.focused.styles.border = [("solid", "red"), ("solid", "white")]
def action_increase_margin(self):
old_margin = self.focused.styles.margin
new_margin = old_margin + Spacing.all(1)
self.focused.styles.margin = new_margin
BasicApp.run(css_file="uber.css", log="textual.log", log_verbosity=1)

View File

@@ -439,7 +439,7 @@ class App(DOMNode):
await widget.post_message(events.MouseCapture(self, self.mouse_position))
def panic(self, *renderables: RenderableType) -> None:
"""Exits the app after displaying a message.
"""Exits the app then displays a message.
Args:
*renderables (RenderableType, optional): Rich renderables to display on exit.

View File

@@ -14,7 +14,6 @@ from typing import Iterable, NamedTuple, TYPE_CHECKING, cast
import rich.repr
from rich.style import Style
from .. import log
from ..color import Color, ColorPair
from ._error_tools import friendly_list
from .constants import NULL_SPACING
@@ -34,10 +33,8 @@ if TYPE_CHECKING:
from ..layout import Layout
from .styles import DockGroup, Styles, StylesBase
from .types import EdgeType
BorderDefinition = (
"Sequence[tuple[EdgeType, str | Color] | None] | tuple[EdgeType, str | Color]"
)
@@ -71,13 +68,15 @@ class ScalarProperty:
value = obj.get_rule(self.name)
return value
def __set__(self, obj: StylesBase, value: float | Scalar | str | None) -> None:
def __set__(
self, obj: StylesBase, value: float | int | Scalar | str | None
) -> None:
"""Set the scalar property
Args:
obj (Styles): The ``Styles`` object.
value (float | Scalar | str | None): The value to set the scalar property to.
You can directly pass a float value, which will be interpreted with
value (float | int | Scalar | str | None): The value to set the scalar property to.
You can directly pass a float or int value, which will be interpreted with
a default unit of Cells. You may also provide a string such as ``"50%"``,
as you might do when writing CSS. If a string with no units is supplied,
Cells will be used as the unit. Alternatively, you can directly supply
@@ -89,8 +88,9 @@ class ScalarProperty:
"""
if value is None:
obj.clear_rule(self.name)
obj.refresh(layout=True)
return
if isinstance(value, float):
if isinstance(value, float) or isinstance(value, int):
new_value = Scalar(float(value), Unit.CELLS, Unit.WIDTH)
elif isinstance(value, Scalar):
new_value = value
@@ -100,7 +100,7 @@ class ScalarProperty:
except ScalarParseError:
raise StyleValueError("unable to parse scalar from {value!r}")
else:
raise StyleValueError("expected float, Scalar, or None")
raise StyleValueError("expected float, int, Scalar, or None")
if new_value is not None and new_value.unit not in self.units:
raise StyleValueError(
f"{self.name} units must be one of {friendly_list(get_symbols(self.units))}"
@@ -108,7 +108,7 @@ class ScalarProperty:
if new_value is not None and new_value.is_percent:
new_value = Scalar(float(new_value.value), self.percent_unit, Unit.WIDTH)
if obj.set_rule(self.name, new_value):
obj.refresh()
obj.refresh(layout=True)
class BoxProperty:
@@ -208,7 +208,15 @@ class Edges(NamedTuple):
class BorderProperty:
"""Descriptor for getting and setting full borders and outlines."""
"""Descriptor for getting and setting full borders and outlines.
Args:
layout (bool): True if the layout should be refreshed after setting, False otherwise.
"""
def __init__(self, layout: bool) -> None:
self._layout = layout
def __set_name__(self, owner: StylesBase, name: str) -> None:
self.name = name
@@ -262,20 +270,22 @@ class BorderProperty:
StyleValueError: When the supplied ``tuple`` is not of valid length (1, 2, or 4).
"""
top, right, bottom, left = self._properties
obj.refresh()
if border is None:
clear_rule = obj.clear_rule
clear_rule(top)
clear_rule(right)
clear_rule(bottom)
clear_rule(left)
obj.refresh(layout=self._layout)
return
if isinstance(border, tuple):
setattr(obj, top, border)
setattr(obj, right, border)
setattr(obj, bottom, border)
setattr(obj, left, border)
obj.refresh(layout=self._layout)
return
count = len(border)
if count == 1:
_border = border[0]
@@ -286,8 +296,8 @@ class BorderProperty:
elif count == 2:
_border1, _border2 = border
setattr(obj, top, _border1)
setattr(obj, right, _border1)
setattr(obj, bottom, _border2)
setattr(obj, bottom, _border1)
setattr(obj, right, _border2)
setattr(obj, left, _border2)
elif count == 4:
_border1, _border2, _border3, _border4 = border
@@ -297,6 +307,7 @@ class BorderProperty:
setattr(obj, left, _border4)
else:
raise StyleValueError("expected 1, 2, or 4 values")
obj.refresh(layout=self._layout)
class StyleProperty:
@@ -537,9 +548,10 @@ class StringEnumProperty:
value belongs in the set of valid values.
"""
def __init__(self, valid_values: set[str], default: str) -> None:
def __init__(self, valid_values: set[str], default: str, layout=False) -> None:
self._valid_values = valid_values
self._default = default
self._layout = layout
def __set_name__(self, owner: StylesBase, name: str) -> None:
self.name = name
@@ -569,14 +581,14 @@ class StringEnumProperty:
if value is None:
if obj.clear_rule(self.name):
obj.refresh()
obj.refresh(layout=self._layout)
else:
if value not in self._valid_values:
raise StyleValueError(
f"{self.name} must be one of {friendly_list(self._valid_values)}"
)
if obj.set_rule(self.name, value):
obj.refresh()
obj.refresh(layout=self._layout)
class NameProperty:

View File

@@ -156,6 +156,25 @@ class Scalar(NamedTuple):
raise ScalarResolveError(f"expected dimensions; found {str(self)!r}")
return dimension
def copy_with(
self,
value: float | None = None,
unit: Unit | None = None,
percent_unit: Unit | None = None,
) -> Scalar:
"""Get a copy of this Scalar, with values optionally modified
Args:
value (float | None): The new value, or None to keep the same value
unit (Unit | None): The new unit, or None to keep the same unit
percent_unit (Unit | None): The new percent_unit, or None to keep the same unit
"""
return Scalar(
value if value is not None else self.value,
unit if unit is not None else self.unit,
percent_unit if percent_unit is not None else self.percent_unit,
)
@rich.repr.auto(angular=True)
class ScalarOffset(NamedTuple):

View File

@@ -59,10 +59,9 @@ if TYPE_CHECKING:
class RulesMap(TypedDict, total=False):
"""A typed dict for CSS rules.
Any key may be absent, indiciating that rule has not been set.
Any key may be absent, indicating that rule has not been set.
Does not define composite rules, that is a rule that is made of a combination of other rules.
"""
display: Display
@@ -150,7 +149,7 @@ class StylesBase(ABC):
"scrollbar_background_active",
}
display = StringEnumProperty(VALID_DISPLAY, "block")
display = StringEnumProperty(VALID_DISPLAY, "block", layout=True)
visibility = StringEnumProperty(VALID_VISIBILITY, "visible")
layout = LayoutProperty()
@@ -164,19 +163,19 @@ class StylesBase(ABC):
margin = SpacingProperty()
offset = OffsetProperty()
border = BorderProperty()
border = BorderProperty(layout=True)
border_top = BoxProperty(Color(0, 255, 0))
border_right = BoxProperty(Color(0, 255, 0))
border_bottom = BoxProperty(Color(0, 255, 0))
border_left = BoxProperty(Color(0, 255, 0))
outline = BorderProperty()
outline = BorderProperty(layout=False)
outline_top = BoxProperty(Color(0, 255, 0))
outline_right = BoxProperty(Color(0, 255, 0))
outline_bottom = BoxProperty(Color(0, 255, 0))
outline_left = BoxProperty(Color(0, 255, 0))
box_sizing = StringEnumProperty(VALID_BOX_SIZING, "border-box")
box_sizing = StringEnumProperty(VALID_BOX_SIZING, "border-box", layout=True)
width = ScalarProperty(percent_unit=Unit.WIDTH)
height = ScalarProperty(percent_unit=Unit.HEIGHT)
min_width = ScalarProperty(percent_unit=Unit.WIDTH)

View File

@@ -4,16 +4,13 @@ Functions and classes to manage terminal geometry (anything involving coordinate
"""
from __future__ import annotations
from math import sqrt
from typing import Any, cast, NamedTuple, Tuple, Union, TypeVar
SpacingDimensions = Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]]
T = TypeVar("T", int, float)
@@ -632,11 +629,11 @@ class Spacing(NamedTuple):
def unpack(cls, pad: SpacingDimensions) -> Spacing:
"""Unpack padding specified in CSS style."""
if isinstance(pad, int):
return cls(pad, pad, pad, pad)
return Spacing.all(pad)
pad_len = len(pad)
if pad_len == 1:
_pad = pad[0]
return cls(_pad, _pad, _pad, _pad)
return Spacing.all(_pad)
if pad_len == 2:
pad_top, pad_right = cast(Tuple[int, int], pad)
return cls(pad_top, pad_right, pad_top, pad_right)
@@ -645,6 +642,18 @@ class Spacing(NamedTuple):
return cls(top, right, bottom, left)
raise ValueError(f"1, 2 or 4 integers required for spacing; {pad_len} given")
@classmethod
def vertical(cls, amount: int) -> Spacing:
return Spacing(amount, 0, amount, 0)
@classmethod
def horizontal(cls, amount: int) -> Spacing:
return Spacing(0, amount, 0, amount)
@classmethod
def all(cls, amount: int) -> Spacing:
return Spacing(amount, amount, amount, amount)
def __add__(self, other: object) -> Spacing:
if isinstance(other, tuple):
top1, right1, bottom1, left1 = self