Skip to content

Commit cc29f66

Browse files
authored
Merge pull request #5715 from Textualize/true-dim
True dim
2 parents 5ac6891 + 1c6374f commit cc29f66

25 files changed

+636
-594
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1616

1717
- Collapsible title now accepts str, Text, or Content https://github.com/Textualize/textual/pull/5697
1818
- Rich Text objects will be converted to Content in OptionList and other widgets https://github.com/Textualize/textual/pull/5712
19+
- Textual will always convert dim attributes to RGB by default
20+
21+
### Added
22+
23+
- Added `TEXTUAL_DIM_FACTOR` env var to set the opacity of the 'dim' ANSI attribute
1924

2025
## [3.0.1] - 2025-04-01
2126

src/textual/_styles_cache.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,9 @@ def post(segments: Iterable[Segment]) -> Iterable[Segment]:
350350
ansi_theme = DEFAULT_TERMINAL_THEME
351351

352352
if styles.tint.a:
353-
segments = Tint.process_segments(segments, styles.tint, ansi_theme)
353+
segments = Tint.process_segments(
354+
segments, styles.tint, ansi_theme, background
355+
)
354356
if opacity != 1.0:
355357
segments = _apply_opacity(segments, base_background, opacity)
356358
return segments

src/textual/constants.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ def _get_environ_bool(name: str) -> bool:
2727
return has_environ
2828

2929

30-
def _get_environ_int(name: str, default: int, minimum: int | None = None) -> int:
30+
def _get_environ_int(
31+
name: str, default: int, minimum: int | None = None, maximum: int | None = None
32+
) -> int:
3133
"""Retrieves an integer environment variable.
3234
3335
Args:
@@ -48,6 +50,8 @@ def _get_environ_int(name: str, default: int, minimum: int | None = None) -> int
4850
return default
4951
if minimum is not None:
5052
return max(minimum, value)
53+
if maximum is not None:
54+
return min(maximum, value)
5155
return value
5256

5357

@@ -159,5 +163,10 @@ def _get_textual_animations() -> AnimationLevel:
159163
"""
160164

161165
SMOOTH_SCROLL: Final[bool] = _get_environ_int("TEXTUAL_SMOOTH_SCROLL", 1) == 1
162-
"""Should smooth scrolling be enabled? set `TEXTUAL_SMOOTH_SCROLL=0` to disable smooth
166+
"""Should smooth scrolling be enabled? set `TEXTUAL_SMOOTH_SCROLL=0` to disable smooth scrolling.
163167
"""
168+
169+
DIM_FACTOR: Final[float] = (
170+
_get_environ_int("TEXTUAL_DIM_FACTOR", 66, minimum=0, maximum=100) / 100
171+
)
172+
"""Percentage to use as opacity when converting ANSI 'dim' attribute to RGB."""

src/textual/filter.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from rich.terminal_theme import TerminalTheme
2323

2424
from textual.color import Color
25+
from textual.constants import DIM_FACTOR
2526

2627

2728
class LineFilter(ABC):
@@ -125,7 +126,9 @@ def apply(self, segments: list[Segment], background: Color) -> list[Segment]:
125126

126127

127128
@lru_cache(1024)
128-
def dim_color(background: RichColor, color: RichColor, factor: float) -> RichColor:
129+
def dim_color(
130+
background: RichColor, color: RichColor, factor: float = DIM_FACTOR
131+
) -> RichColor:
129132
"""Dim a color by blending towards the background
130133
131134
Args:
@@ -227,7 +230,7 @@ def __init__(self, terminal_theme: TerminalTheme, enabled: bool = True):
227230
super().__init__(enabled=enabled)
228231

229232
@lru_cache(1024)
230-
def truecolor_style(self, style: Style) -> Style:
233+
def truecolor_style(self, style: Style, background: RichColor) -> Style:
231234
"""Replace system colors with truecolor equivalent.
232235
233236
Args:
@@ -247,6 +250,10 @@ def truecolor_style(self, style: Style) -> Style:
247250
bgcolor = RichColor.from_rgb(
248251
*bgcolor.get_truecolor(terminal_theme, foreground=False)
249252
)
253+
# Convert dim style to RGB
254+
if style.dim and color is not None:
255+
color = dim_color(background, color)
256+
style += NO_DIM
250257

251258
return style + Style.from_color(color, bgcolor)
252259

@@ -263,10 +270,16 @@ def apply(self, segments: list[Segment], background: Color) -> list[Segment]:
263270
_Segment = Segment
264271
truecolor_style = self.truecolor_style
265272

273+
background_rich_color = background.rich_color
274+
266275
return [
267276
_Segment(
268277
text,
269-
None if style is None else truecolor_style(style),
278+
(
279+
None
280+
if style is None
281+
else truecolor_style(style, background_rich_color)
282+
),
270283
None,
271284
)
272285
for text, style, _ in segments

src/textual/renderables/text_opacity.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,18 @@ def __init__(self, renderable: RenderableType, opacity: float = 1.0) -> None:
5252

5353
@classmethod
5454
def process_segments(
55-
cls, segments: Iterable[Segment], opacity: float, ansi_theme: TerminalTheme
55+
cls,
56+
segments: Iterable[Segment],
57+
opacity: float,
58+
ansi_theme: TerminalTheme,
5659
) -> Iterable[Segment]:
5760
"""Apply opacity to segments.
5861
5962
Args:
6063
segments: Incoming segments.
6164
opacity: Opacity to apply.
6265
ansi_theme: Terminal theme.
66+
background: Color of background.
6367
6468
Returns:
6569
Segments with applied opacity.

src/textual/renderables/tint.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from rich.style import Style
88
from rich.terminal_theme import TerminalTheme
99

10-
from textual.color import Color
10+
from textual.color import TRANSPARENT, Color
1111
from textual.filter import ANSIToTruecolor
1212

1313

@@ -30,14 +30,19 @@ def __init__(
3030

3131
@classmethod
3232
def process_segments(
33-
cls, segments: Iterable[Segment], color: Color, ansi_theme: TerminalTheme
33+
cls,
34+
segments: Iterable[Segment],
35+
color: Color,
36+
ansi_theme: TerminalTheme,
37+
background: Color = TRANSPARENT,
3438
) -> Iterable[Segment]:
3539
"""Apply tint to segments.
3640
3741
Args:
3842
segments: Incoming segments.
3943
color: Color of tint.
4044
ansi_theme: The TerminalTheme defining how to map ansi colors to hex.
45+
background: Background color.
4146
4247
Returns:
4348
Segments with applied tint.
@@ -54,7 +59,11 @@ def process_segments(
5459
if control:
5560
yield segment
5661
else:
57-
style = truecolor_style(style) if style is not None else NULL_STYLE
62+
style = (
63+
truecolor_style(style, background)
64+
if style is not None
65+
else NULL_STYLE
66+
)
5867
yield _Segment(
5968
text,
6069
(

0 commit comments

Comments
 (0)