mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
[css] Add the management of a "blank" value for our borders
This commit is contained in:
65
sandbox/borders.py
Normal file
65
sandbox/borders.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
from rich.console import RenderableType
|
||||||
|
from rich.text import Text
|
||||||
|
|
||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.css.types import EdgeType
|
||||||
|
from textual.widget import Widget
|
||||||
|
from textual.widgets import Placeholder
|
||||||
|
|
||||||
|
|
||||||
|
class VerticalContainer(Widget):
|
||||||
|
CSS = """
|
||||||
|
VerticalContainer {
|
||||||
|
layout: vertical;
|
||||||
|
overflow: hidden auto;
|
||||||
|
background: darkblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticalContainer Placeholder {
|
||||||
|
margin: 1 0;
|
||||||
|
height: 5;
|
||||||
|
align: center top;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Introduction(Widget):
|
||||||
|
CSS = """
|
||||||
|
Introduction {
|
||||||
|
background: indigo;
|
||||||
|
color: white;
|
||||||
|
height: 3;
|
||||||
|
padding: 1 0;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def render(self, styles) -> RenderableType:
|
||||||
|
return Text("Here are the color edge types we support.", justify="center")
|
||||||
|
|
||||||
|
|
||||||
|
class MyTestApp(App):
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
placeholders = []
|
||||||
|
for border_edge_type in EdgeType.__args__:
|
||||||
|
border_placeholder = Placeholder(
|
||||||
|
id=f"placeholder_{border_edge_type}",
|
||||||
|
title=(border_edge_type or " ").upper(),
|
||||||
|
name=f"border: {border_edge_type} white",
|
||||||
|
)
|
||||||
|
border_placeholder.styles.border = (border_edge_type, "white")
|
||||||
|
placeholders.append(border_placeholder)
|
||||||
|
|
||||||
|
yield VerticalContainer(Introduction(), *placeholders, id="root")
|
||||||
|
|
||||||
|
def on_mount(self):
|
||||||
|
self.bind("q", "quit")
|
||||||
|
self.bind("t", "tree")
|
||||||
|
|
||||||
|
def action_tree(self):
|
||||||
|
self.log(self.tree)
|
||||||
|
|
||||||
|
|
||||||
|
app = MyTestApp()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run()
|
||||||
@@ -5,9 +5,8 @@ from functools import lru_cache
|
|||||||
from rich.console import Console, ConsoleOptions, RenderResult, RenderableType
|
from rich.console import Console, ConsoleOptions, RenderResult, RenderableType
|
||||||
import rich.repr
|
import rich.repr
|
||||||
from rich.segment import Segment, SegmentLines
|
from rich.segment import Segment, SegmentLines
|
||||||
from rich.style import Style, StyleType
|
from rich.style import Style
|
||||||
|
|
||||||
from .color import Color
|
|
||||||
from .css.types import EdgeStyle, EdgeType
|
from .css.types import EdgeStyle, EdgeType
|
||||||
|
|
||||||
|
|
||||||
@@ -15,10 +14,14 @@ INNER = 1
|
|||||||
OUTER = 2
|
OUTER = 2
|
||||||
|
|
||||||
BORDER_CHARS: dict[EdgeType, tuple[str, str, str]] = {
|
BORDER_CHARS: dict[EdgeType, tuple[str, str, str]] = {
|
||||||
# TODO: in "browsers' CSS" `none` and `hidden` both set the border width to zero. Should we do the same?
|
# Each string of the tuple represents a sub-tuple itself:
|
||||||
|
# - 1st string represents `(top1, top2, top3)`
|
||||||
|
# - 2nd string represents (mid1, mid2, mid3)
|
||||||
|
# - 3rd string represents (bottom1, bottom2, bottom3)
|
||||||
"": (" ", " ", " "),
|
"": (" ", " ", " "),
|
||||||
"none": (" ", " ", " "),
|
"none": (" ", " ", " "),
|
||||||
"hidden": (" ", " ", " "),
|
"hidden": (" ", " ", " "),
|
||||||
|
"blank": (" ", " ", " "),
|
||||||
"round": ("╭─╮", "│ │", "╰─╯"),
|
"round": ("╭─╮", "│ │", "╰─╯"),
|
||||||
"solid": ("┌─┐", "│ │", "└─┘"),
|
"solid": ("┌─┐", "│ │", "└─┘"),
|
||||||
"double": ("╔═╗", "║ ║", "╚═╝"),
|
"double": ("╔═╗", "║ ║", "╚═╝"),
|
||||||
@@ -40,6 +43,7 @@ BORDER_LOCATIONS: dict[
|
|||||||
"": ((0, 0, 0), (0, 0, 0), (0, 0, 0)),
|
"": ((0, 0, 0), (0, 0, 0), (0, 0, 0)),
|
||||||
"none": ((0, 0, 0), (0, 0, 0), (0, 0, 0)),
|
"none": ((0, 0, 0), (0, 0, 0), (0, 0, 0)),
|
||||||
"hidden": ((0, 0, 0), (0, 0, 0), (0, 0, 0)),
|
"hidden": ((0, 0, 0), (0, 0, 0), (0, 0, 0)),
|
||||||
|
"blank": ((0, 0, 0), (0, 0, 0), (0, 0, 0)),
|
||||||
"round": ((0, 0, 0), (0, 0, 0), (0, 0, 0)),
|
"round": ((0, 0, 0), (0, 0, 0), (0, 0, 0)),
|
||||||
"solid": ((0, 0, 0), (0, 0, 0), (0, 0, 0)),
|
"solid": ((0, 0, 0), (0, 0, 0), (0, 0, 0)),
|
||||||
"double": ((0, 0, 0), (0, 0, 0), (0, 0, 0)),
|
"double": ((0, 0, 0), (0, 0, 0), (0, 0, 0)),
|
||||||
@@ -53,6 +57,8 @@ BORDER_LOCATIONS: dict[
|
|||||||
"wide": ((1, 1, 1), (0, 1, 0), (1, 1, 1)),
|
"wide": ((1, 1, 1), (0, 1, 0), (1, 1, 1)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_INVISIBLE_EDGE_TYPES: tuple[EdgeType, ...] = ("none", "hidden")
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=1024)
|
@lru_cache(maxsize=1024)
|
||||||
def get_box(
|
def get_box(
|
||||||
@@ -135,7 +141,12 @@ class Border:
|
|||||||
(bottom, bottom_color),
|
(bottom, bottom_color),
|
||||||
(left, left_color),
|
(left, left_color),
|
||||||
) = edge_styles
|
) = edge_styles
|
||||||
self._sides = (top or "none", right or "none", bottom or "none", left or "none")
|
self._sides: tuple[EdgeType, ...] = (
|
||||||
|
top or "none",
|
||||||
|
right or "none",
|
||||||
|
bottom or "none",
|
||||||
|
left or "none",
|
||||||
|
)
|
||||||
from_color = Style.from_color
|
from_color = Style.from_color
|
||||||
|
|
||||||
self._styles = (
|
self._styles = (
|
||||||
@@ -159,10 +170,10 @@ class Border:
|
|||||||
width (int): Desired width.
|
width (int): Desired width.
|
||||||
"""
|
"""
|
||||||
top, right, bottom, left = self._sides
|
top, right, bottom, left = self._sides
|
||||||
has_left = left != "none"
|
has_left = left not in _INVISIBLE_EDGE_TYPES
|
||||||
has_right = right != "none"
|
has_right = right not in _INVISIBLE_EDGE_TYPES
|
||||||
has_top = top != "none"
|
has_top = top not in _INVISIBLE_EDGE_TYPES
|
||||||
has_bottom = bottom != "none"
|
has_bottom = bottom not in _INVISIBLE_EDGE_TYPES
|
||||||
|
|
||||||
if has_top:
|
if has_top:
|
||||||
lines.pop(0)
|
lines.pop(0)
|
||||||
@@ -188,10 +199,10 @@ class Border:
|
|||||||
outer_style = console.get_style(self.outer_style)
|
outer_style = console.get_style(self.outer_style)
|
||||||
top_style, right_style, bottom_style, left_style = self._styles
|
top_style, right_style, bottom_style, left_style = self._styles
|
||||||
|
|
||||||
has_left = left != "none"
|
has_left = left not in _INVISIBLE_EDGE_TYPES
|
||||||
has_right = right != "none"
|
has_right = right not in _INVISIBLE_EDGE_TYPES
|
||||||
has_top = top != "none"
|
has_top = top not in _INVISIBLE_EDGE_TYPES
|
||||||
has_bottom = bottom != "none"
|
has_bottom = bottom not in _INVISIBLE_EDGE_TYPES
|
||||||
|
|
||||||
width = options.max_width - has_left - has_right
|
width = options.max_width - has_left - has_right
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
from __future__ import annotations
|
||||||
import sys
|
import sys
|
||||||
|
import typing
|
||||||
|
|
||||||
if sys.version_info >= (3, 8):
|
if sys.version_info >= (3, 8):
|
||||||
from typing import Final
|
from typing import Final
|
||||||
@@ -7,12 +9,16 @@ else:
|
|||||||
|
|
||||||
from ..geometry import Spacing
|
from ..geometry import Spacing
|
||||||
|
|
||||||
|
if typing.TYPE_CHECKING:
|
||||||
|
from .types import EdgeType
|
||||||
|
|
||||||
VALID_VISIBILITY: Final = {"visible", "hidden"}
|
VALID_VISIBILITY: Final = {"visible", "hidden"}
|
||||||
VALID_DISPLAY: Final = {"block", "none"}
|
VALID_DISPLAY: Final = {"block", "none"}
|
||||||
VALID_BORDER: Final = {
|
VALID_BORDER: Final[set[EdgeType]] = {
|
||||||
"none",
|
"none",
|
||||||
"hidden",
|
"hidden",
|
||||||
"round",
|
"round",
|
||||||
|
"blank",
|
||||||
"solid",
|
"solid",
|
||||||
"double",
|
"double",
|
||||||
"dashed",
|
"dashed",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ EdgeType = Literal[
|
|||||||
"",
|
"",
|
||||||
"none",
|
"none",
|
||||||
"hidden",
|
"hidden",
|
||||||
|
"blank",
|
||||||
"round",
|
"round",
|
||||||
"solid",
|
"solid",
|
||||||
"double",
|
"double",
|
||||||
@@ -35,6 +36,6 @@ AlignVertical = Literal["top", "middle", "bottom"]
|
|||||||
ScrollbarGutter = Literal["auto", "stable"]
|
ScrollbarGutter = Literal["auto", "stable"]
|
||||||
BoxSizing = Literal["border-box", "content-box"]
|
BoxSizing = Literal["border-box", "content-box"]
|
||||||
Overflow = Literal["scroll", "hidden", "auto"]
|
Overflow = Literal["scroll", "hidden", "auto"]
|
||||||
EdgeStyle = Tuple[str, Color]
|
EdgeStyle = Tuple[EdgeType, Color]
|
||||||
Specificity3 = Tuple[int, int, int]
|
Specificity3 = Tuple[int, int, int]
|
||||||
Specificity4 = Tuple[int, int, int, int]
|
Specificity4 = Tuple[int, int, int, int]
|
||||||
|
|||||||
@@ -19,6 +19,19 @@ class Placeholder(Widget, can_focus=True):
|
|||||||
has_focus: Reactive[bool] = Reactive(False)
|
has_focus: Reactive[bool] = Reactive(False)
|
||||||
mouse_over: Reactive[bool] = Reactive(False)
|
mouse_over: Reactive[bool] = Reactive(False)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
# parent class constructor signature:
|
||||||
|
self,
|
||||||
|
*children: Widget,
|
||||||
|
name: str | None = None,
|
||||||
|
id: str | None = None,
|
||||||
|
classes: str | None = None,
|
||||||
|
# ...and now for our own class specific params:
|
||||||
|
title: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(*children, name=name, id=id, classes=classes)
|
||||||
|
self.title = title
|
||||||
|
|
||||||
def __rich_repr__(self) -> rich.repr.Result:
|
def __rich_repr__(self) -> rich.repr.Result:
|
||||||
yield from super().__rich_repr__()
|
yield from super().__rich_repr__()
|
||||||
yield "has_focus", self.has_focus, False
|
yield "has_focus", self.has_focus, False
|
||||||
@@ -32,7 +45,7 @@ class Placeholder(Widget, can_focus=True):
|
|||||||
Pretty(self, no_wrap=True, overflow="ellipsis"),
|
Pretty(self, no_wrap=True, overflow="ellipsis"),
|
||||||
vertical="middle",
|
vertical="middle",
|
||||||
),
|
),
|
||||||
title=self.__class__.__name__,
|
title=self.title or self.__class__.__name__,
|
||||||
border_style="green" if self.mouse_over else "blue",
|
border_style="green" if self.mouse_over else "blue",
|
||||||
box=box.HEAVY if self.has_focus else box.ROUNDED,
|
box=box.HEAVY if self.has_focus else box.ROUNDED,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user