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 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
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