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:
Will McGugan
2023-04-19 09:31:59 +01:00
committed by GitHub
parent 4981effcba
commit db1b784606
2 changed files with 144 additions and 25 deletions

View File

@@ -3,6 +3,7 @@ from __future__ import annotations
from abc import ABC, abstractmethod
from functools import lru_cache
from rich.color import Color as RichColor
from rich.segment import Segment
from rich.style import Style
@@ -14,21 +15,18 @@ class LineFilter(ABC):
@abstractmethod
def apply(self, segments: list[Segment]) -> list[Segment]:
"""Transform a list of segments."""
"""Transform a list of segments.
Args:
segments: A list of segments.
class Monochrome(LineFilter):
"""Convert all colors to monochrome."""
Returns:
A new list of segments.
"""
def apply(self, segments: list[Segment]) -> list[Segment]:
to_monochrome = self.to_monochrome
_Segment = Segment
return [
_Segment(text, to_monochrome(style), None) for text, style, _ in segments
]
@lru_cache(1024)
def to_monochrome(self, style: Style) -> Style:
def monochrome_style(style: Style) -> Style:
"""Convert colors in a style to monochrome.
Args:
@@ -50,3 +48,106 @@ class Monochrome(LineFilter):
else Color.from_rich_color(style_background).monochrome.rich_color
)
return style + Style.from_color(color, background)
class Monochrome(LineFilter):
"""Convert all colors to monochrome."""
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.
"""
_monochrome_style = monochrome_style
_Segment = Segment
return [
_Segment(text, _monochrome_style(style), None)
for text, style, _ in segments
]
NO_DIM = Style(dim=False)
"""A Style to set dim to False."""
@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
View 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