Skip to content

Commit

Permalink
tint fix
Browse files Browse the repository at this point in the history
  • Loading branch information
willmcgugan committed Oct 16, 2024
1 parent 4a822bc commit c40c265
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 11 deletions.
25 changes: 25 additions & 0 deletions src/textual/color.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,31 @@ def blend(
new_alpha,
)

@lru_cache(maxsize=1024)
def tint(self, color: Color) -> Color:
"""Apply a tint to a color.
Similar to blend, but combines color and alpha.
Args:
color: A color with alpha component.
Returns:
New color
"""
r2, g2, b2, a2, ansi2 = color
if ansi2 is not None:
return color
r1, g1, b1, a1, ansi1 = self
if ansi1 is not None:
return color
return Color(
int(r1 + (r2 - r1) * a2),
int(g1 + (g2 - g1) * a2),
int(b1 + (b2 - b1) * a2),
a1,
)

def __add__(self, other: object) -> Color:
if isinstance(other, Color):
return self.blend(other, other.a, 1.0)
Expand Down
12 changes: 6 additions & 6 deletions src/textual/dom.py
Original file line number Diff line number Diff line change
Expand Up @@ -1026,11 +1026,11 @@ def rich_style(self) -> Style:
has_rule = styles.has_rule
opacity *= styles.opacity
if has_rule("background"):
text_background = (
background + styles.background + styles.background_tint
text_background = background + styles.background.tint(
styles.background_tint
)
background += (
styles.background + styles.background_tint
styles.background.tint(styles.background_tint)
).multiply_alpha(opacity)
else:
text_background = background
Expand Down Expand Up @@ -1119,7 +1119,7 @@ def background_colors(self) -> tuple[Color, Color]:
for node in reversed(self.ancestors_with_self):
styles = node.styles
base_background = background
background += styles.background + styles.background_tint
background += styles.background.tint(styles.background_tint)
return (base_background, background)

@property
Expand All @@ -1135,7 +1135,7 @@ def _opacity_background_colors(self) -> tuple[Color, Color]:
styles = node.styles
base_background = background
opacity *= styles.opacity
background += (styles.background + styles.background_tint).multiply_alpha(
background += styles.background.tint(styles.background_tint).multiply_alpha(
opacity
)
return (base_background, background)
Expand All @@ -1152,7 +1152,7 @@ def colors(self) -> tuple[Color, Color, Color, Color]:
for node in reversed(self.ancestors_with_self):
styles = node.styles
base_background = background
background += styles.background + styles.background_tint
background += styles.background.tint(styles.background_tint)
if styles.has_rule("color"):
base_color = color
if styles.auto_color:
Expand Down
22 changes: 17 additions & 5 deletions tests/snapshot_tests/test_snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -2314,15 +2314,27 @@ def compose(self) -> ComposeResult:


def test_background_tint(snap_compare):
"""Test background tint with alpha."""

# The screen background is dark blue
# The vertical is 20% white
# With no background tint, the verticals will be a light blue
# With a 100% tint, the vertical should be 20% red plus the blue (i.e. purple)

# tl;dr you should see 4 bars, blue at the top, purple at the bottom, and two shades in betweenm

class BackgroundTintApp(App):
CSS = """
Screen {
background: rgb(0,0,100)
}
Vertical {
background: $panel;
background: rgba(255,255,255,0.2);
}
#tint1 { background-tint: $foreground 0%; }
#tint2 { background-tint: $foreground 33%; }
#tint3 { background-tint: $foreground 66%; }
#tint4 { background-tint: $foreground 100% }
#tint1 { background-tint: rgb(255,0,0) 0%; }
#tint2 { background-tint: rgb(255,0,0) 33%; }
#tint3 { background-tint: rgb(255,0,0) 66%; }
#tint4 { background-tint: rgb(255,0,0) 100% }
"""

def compose(self) -> ComposeResult:
Expand Down
24 changes: 24 additions & 0 deletions tests/test_color.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,27 @@ def test_is_transparent():
assert not Color(20, 20, 30, a=0.01).is_transparent
assert not Color(20, 20, 30, a=1).is_transparent
assert not Color(20, 20, 30, 0, ansi=1).is_transparent


@pytest.mark.parametrize(
"base,tint,expected",
[
(
Color(0, 0, 0),
Color(10, 20, 30),
Color(10, 20, 30),
),
(
Color(0, 0, 0, 0.5),
Color(255, 255, 255, 0.5),
Color(127, 127, 127, 0.5),
),
(
Color(100, 0, 0, 0.2),
Color(0, 100, 0, 0.5),
Color(50, 50, 0, 0.2),
),
],
)
def test_tint(base: Color, tint: Color, expected: Color) -> None:
assert base.tint(tint) == expected

0 comments on commit c40c265

Please sign in to comment.