mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
dim filter (#2323)
* dim filter * optimization * Remove test code * move functions out of filter * docstring * move function to module scope * docstring * docstrings
This commit is contained in:
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
|
from rich.color import Color as RichColor
|
||||||
from rich.segment import Segment
|
from rich.segment import Segment
|
||||||
from rich.style import Style
|
from rich.style import Style
|
||||||
|
|
||||||
@@ -14,39 +15,139 @@ class LineFilter(ABC):
|
|||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def apply(self, segments: list[Segment]) -> list[Segment]:
|
def apply(self, segments: list[Segment]) -> list[Segment]:
|
||||||
"""Transform a list of segments."""
|
"""Transform a list of segments.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
segments: A list of segments.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new list of segments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(1024)
|
||||||
|
def monochrome_style(style: Style) -> Style:
|
||||||
|
"""Convert colors in a style to monochrome.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
style: A Rich Style.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new Rich style.
|
||||||
|
"""
|
||||||
|
style_color = style.color
|
||||||
|
style_background = style.bgcolor
|
||||||
|
color = (
|
||||||
|
None
|
||||||
|
if style_color is None
|
||||||
|
else Color.from_rich_color(style_color).monochrome.rich_color
|
||||||
|
)
|
||||||
|
background = (
|
||||||
|
None
|
||||||
|
if style_background is None
|
||||||
|
else Color.from_rich_color(style_background).monochrome.rich_color
|
||||||
|
)
|
||||||
|
return style + Style.from_color(color, background)
|
||||||
|
|
||||||
|
|
||||||
class Monochrome(LineFilter):
|
class Monochrome(LineFilter):
|
||||||
"""Convert all colors to monochrome."""
|
"""Convert all colors to monochrome."""
|
||||||
|
|
||||||
def apply(self, segments: list[Segment]) -> list[Segment]:
|
def apply(self, segments: list[Segment]) -> list[Segment]:
|
||||||
to_monochrome = self.to_monochrome
|
"""Transform a list of segments.
|
||||||
_Segment = Segment
|
|
||||||
return [
|
|
||||||
_Segment(text, to_monochrome(style), None) for text, style, _ in segments
|
|
||||||
]
|
|
||||||
|
|
||||||
@lru_cache(1024)
|
|
||||||
def to_monochrome(self, style: Style) -> Style:
|
|
||||||
"""Convert colors in a style to monochrome.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
style: A Rich Style.
|
segments: A list of segments.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A new Rich style.
|
A new list of segments.
|
||||||
"""
|
"""
|
||||||
style_color = style.color
|
_monochrome_style = monochrome_style
|
||||||
style_background = style.bgcolor
|
_Segment = Segment
|
||||||
color = (
|
return [
|
||||||
None
|
_Segment(text, _monochrome_style(style), None)
|
||||||
if style_color is None
|
for text, style, _ in segments
|
||||||
else Color.from_rich_color(style_color).monochrome.rich_color
|
]
|
||||||
)
|
|
||||||
background = (
|
|
||||||
None
|
NO_DIM = Style(dim=False)
|
||||||
if style_background is None
|
"""A Style to set dim to False."""
|
||||||
else Color.from_rich_color(style_background).monochrome.rich_color
|
|
||||||
)
|
|
||||||
return style + Style.from_color(color, background)
|
@lru_cache(1024)
|
||||||
|
def dim_color(background: RichColor, color: RichColor, factor: float) -> RichColor:
|
||||||
|
"""Dim a color by blending towards the background
|
||||||
|
|
||||||
|
Args:
|
||||||
|
background: background color.
|
||||||
|
color: Foreground color.
|
||||||
|
factor: Blend factor
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
New dimmer color.
|
||||||
|
"""
|
||||||
|
red1, green1, blue1 = background.triplet
|
||||||
|
red2, green2, blue2 = color.triplet
|
||||||
|
|
||||||
|
return RichColor.from_rgb(
|
||||||
|
red1 + (red2 - red1) * factor,
|
||||||
|
green1 + (green2 - green1) * factor,
|
||||||
|
blue1 + (blue2 - blue1) * factor,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(1024)
|
||||||
|
def dim_style(style: Style, factor: float) -> Style:
|
||||||
|
"""Replace dim attribute with a dim color.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
style: Style to dim.
|
||||||
|
factor: Blend factor.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
New dimmed style.
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
style
|
||||||
|
+ Style.from_color(dim_color(style.bgcolor, style.color, factor), None)
|
||||||
|
+ NO_DIM
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Can be used as a workaround for https://github.com/xtermjs/xterm.js/issues/4161
|
||||||
|
class DimFilter(LineFilter):
|
||||||
|
"""Replace dim attributes with modified colors."""
|
||||||
|
|
||||||
|
def __init__(self, dim_factor: float = 0.5) -> None:
|
||||||
|
"""Initialize the filter.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dim_factor: The factor to dim by; 0 is 100% background (i.e. invisible), 1.0 is no change.
|
||||||
|
"""
|
||||||
|
self.dim_factor = dim_factor
|
||||||
|
|
||||||
|
def apply(self, segments: list[Segment]) -> list[Segment]:
|
||||||
|
"""Transform a list of segments.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
segments: A list of segments.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new list of segments.
|
||||||
|
"""
|
||||||
|
_Segment = Segment
|
||||||
|
_dim_style = dim_style
|
||||||
|
factor = self.dim_factor
|
||||||
|
|
||||||
|
return [
|
||||||
|
(
|
||||||
|
_Segment(
|
||||||
|
segment.text,
|
||||||
|
_dim_style(segment.style, factor),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if segment.style is not None and segment.style.dim
|
||||||
|
else segment
|
||||||
|
)
|
||||||
|
for segment in segments
|
||||||
|
]
|
||||||
|
|||||||
18
tests/test_line_filter.py
Normal file
18
tests/test_line_filter.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from rich.segment import Segment
|
||||||
|
from rich.style import Style
|
||||||
|
|
||||||
|
from textual.filter import DimFilter
|
||||||
|
|
||||||
|
|
||||||
|
def test_dim_apply():
|
||||||
|
"""Check dim filter changes color and resets dim attribute."""
|
||||||
|
|
||||||
|
dim_filter = DimFilter()
|
||||||
|
|
||||||
|
segments = [Segment("Hello, World!", Style.parse("dim #ffffff on #0000ff"))]
|
||||||
|
|
||||||
|
dimmed_segments = dim_filter.apply(segments)
|
||||||
|
|
||||||
|
expected = [Segment("Hello, World!", Style.parse("not dim #7f7fff on #0000ff"))]
|
||||||
|
|
||||||
|
assert dimmed_segments == expected
|
||||||
Reference in New Issue
Block a user