Skip to content

Commit

Permalink
Merge pull request #5197 from Textualize/content
Browse files Browse the repository at this point in the history
New render protocol
  • Loading branch information
willmcgugan authored Nov 13, 2024
2 parents 5de3a80 + d426e69 commit 365abe6
Show file tree
Hide file tree
Showing 333 changed files with 21,494 additions and 19,757 deletions.
4 changes: 1 addition & 3 deletions src/textual/_segment_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,6 @@ def blank_lines(count: int) -> list[list[Segment]]:
if top_blank_lines:
yield from blank_lines(top_blank_lines)

horizontal_excess_space = max(0, width - shape_width)

if horizontal == "left":
for cell_length, line in zip(line_lengths, lines):
if cell_length == width:
Expand All @@ -241,7 +239,7 @@ def blank_lines(count: int) -> list[list[Segment]]:
yield line_pad(line, 0, width - cell_length, style)

elif horizontal == "center":
left_space = horizontal_excess_space // 2
left_space = max(0, width - shape_width) // 2
for cell_length, line in zip(line_lengths, lines):
if cell_length == width:
yield line
Expand Down
3 changes: 2 additions & 1 deletion src/textual/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@
_ASYNCIO_GET_EVENT_LOOP_IS_DEPRECATED = sys.version_info >= (3, 10, 0)

ComposeResult = Iterable[Widget]
RenderResult = RenderableType
RenderResult = "RenderableType | Visual | SupportsTextualize"
"""Result of Widget.render()"""

AutopilotCallbackType: TypeAlias = (
"Callable[[Pilot[object]], Coroutine[Any, Any, None]]"
Expand Down
56 changes: 37 additions & 19 deletions src/textual/color.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,16 @@ class Color(NamedTuple):
"""Alpha (opacity) component in range 0 to 1."""
ansi: int | None = None
"""ANSI color index. `-1` means default color. `None` if not an ANSI color."""
auto: bool = False
"""Is the color automatic? (automatic colors may be white or black, to provide maximum contrast)"""

@classmethod
def from_rich_color(cls, rich_color: RichColor) -> Color:
def automatic(cls, alpha_percentage: float = 100.0) -> Color:
"""Create an automatic color."""
return cls(0, 0, 0, alpha_percentage / 100.0, auto=True)

@classmethod
def from_rich_color(cls, rich_color: RichColor | None) -> Color:
"""Create a new color from Rich's Color class.
Args:
Expand All @@ -178,6 +185,8 @@ def from_rich_color(cls, rich_color: RichColor) -> Color:
Returns:
A new Color instance.
"""
if rich_color is None:
return TRANSPARENT
r, g, b = rich_color.get_truecolor()
return cls(r, g, b)

Expand All @@ -203,7 +212,7 @@ def inverse(self) -> Color:
Returns:
Inverse color.
"""
r, g, b, a, _ = self
r, g, b, a, _, _ = self
return Color(255 - r, 255 - g, 255 - b, a)

@property
Expand All @@ -214,14 +223,15 @@ def is_transparent(self) -> bool:
@property
def clamped(self) -> Color:
"""A clamped color (this color with all values in expected range)."""
r, g, b, a, _ = self
r, g, b, a, ansi, auto = self
_clamp = clamp
color = Color(
_clamp(r, 0, 255),
_clamp(g, 0, 255),
_clamp(b, 0, 255),
_clamp(a, 0.0, 1.0),
self.ansi,
ansi,
auto,
)
return color

Expand All @@ -233,7 +243,7 @@ def rich_color(self) -> RichColor:
Returns:
A color object as used by Rich.
"""
r, g, b, _a, ansi = self
r, g, b, _a, ansi, _ = self
if ansi is not None:
return RichColor.parse("default") if ansi < 0 else RichColor.from_ansi(ansi)
return RichColor(
Expand All @@ -247,13 +257,13 @@ def normalized(self) -> tuple[float, float, float]:
Returns:
Normalized components.
"""
r, g, b, _a, _ = self
r, g, b, _a, _, _ = self
return (r / 255, g / 255, b / 255)

@property
def rgb(self) -> tuple[int, int, int]:
"""The red, green, and blue color components as a tuple of ints."""
r, g, b, _, _ = self
r, g, b, _, _, _ = self
return (r, g, b)

@property
Expand Down Expand Up @@ -286,7 +296,7 @@ def hex(self) -> str:
For example, `"#46b3de"` for an RGB color, or `"#3342457f"` for a color with alpha.
"""
r, g, b, a, ansi = self.clamped
r, g, b, a, ansi, _ = self.clamped
if ansi is not None:
return "ansi_default" if ansi == -1 else f"ansi_{ANSI_COLORS[ansi]}"
return (
Expand All @@ -301,7 +311,7 @@ def hex6(self) -> str:
For example, `"#46b3de"`.
"""
r, g, b, _a, _ = self.clamped
r, g, b, _a, _, _ = self.clamped
return f"#{r:02X}{g:02X}{b:02X}"

@property
Expand All @@ -310,7 +320,12 @@ def css(self) -> str:
For example, `"rgb(10,20,30)"` for an RGB color, or `"rgb(50,70,80,0.5)"` for an RGBA color.
"""
r, g, b, a, ansi = self
r, g, b, a, ansi, auto = self
if auto:
alpha_percentage = clamp(a, 0.0, 1.0) * 100.0
if not alpha_percentage % 1:
return f"auto {int(alpha_percentage)}%"
return f"auto {alpha_percentage:.1f}%"
if ansi is not None:
return "ansi_default" if ansi == -1 else f"ansi_{ANSI_COLORS[ansi]}"
return f"rgb({r},{g},{b})" if a == 1 else f"rgba({r},{g},{b},{a})"
Expand All @@ -322,17 +337,18 @@ def monochrome(self) -> Color:
Returns:
The monochrome (black and white) version of this color.
"""
r, g, b, a, _ = self
r, g, b, a, _, _ = self
gray = round(r * 0.2126 + g * 0.7152 + b * 0.0722)
return Color(gray, gray, gray, a)

def __rich_repr__(self) -> rich.repr.Result:
r, g, b, a, ansi = self
r, g, b, a, ansi, auto = self
yield r
yield g
yield b
yield "a", a, 1.0
yield "ansi", ansi, None
yield "auto", auto, False

def with_alpha(self, alpha: float) -> Color:
"""Create a new color with the given alpha.
Expand All @@ -343,7 +359,7 @@ def with_alpha(self, alpha: float) -> Color:
Returns:
A new color.
"""
r, g, b, _, _ = self
r, g, b, _, _, _ = self
return Color(r, g, b, alpha)

def multiply_alpha(self, alpha: float) -> Color:
Expand All @@ -357,7 +373,7 @@ def multiply_alpha(self, alpha: float) -> Color:
"""
if self.ansi is not None:
return self
r, g, b, a, _ = self
r, g, b, a, _, _ = self
return Color(r, g, b, a * alpha)

@lru_cache(maxsize=1024)
Expand All @@ -378,14 +394,16 @@ def blend(
Returns:
A new color.
"""
if destination.auto:
destination = self.get_contrast_text(destination.a)
if destination.ansi is not None:
return destination
if factor <= 0:
return self
elif factor >= 1:
return destination
r1, g1, b1, a1, _ = self
r2, g2, b2, a2, _ = destination
r1, g1, b1, a1, _, _ = self
r2, g2, b2, a2, _, _ = destination

if alpha is None:
new_alpha = a1 + (a2 - a1) * factor
Expand All @@ -412,10 +430,10 @@ def tint(self, color: Color) -> Color:
New color
"""

r1, g1, b1, a1, ansi1 = self
r1, g1, b1, a1, ansi1, _ = self
if ansi1 is not None:
return self
r2, g2, b2, a2, ansi2 = color
r2, g2, b2, a2, ansi2, _ = color
if ansi2 is not None:
return self
return Color(
Expand Down Expand Up @@ -551,7 +569,7 @@ def parse(cls, color_text: str | Color) -> Color:
l = percentage_string_to_float(l)
a = clamp(float(a), 0.0, 1.0)
color = Color.from_hsl(h, s, l).with_alpha(a)
else:
else: # pragma: no-cover
raise AssertionError( # pragma: no-cover
"Can't get here if RE_COLOR matches"
)
Expand Down
Loading

0 comments on commit 365abe6

Please sign in to comment.