Skip to content

Commit

Permalink
background tint style
Browse files Browse the repository at this point in the history
  • Loading branch information
willmcgugan committed Oct 14, 2024
1 parent 4182ad4 commit 3ac0790
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 5 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

- Fixed `RadioSet` not being scrollable https://github.com/Textualize/textual/issues/5100

### Added

- Added `background-tint` CSS rule

## [0.83.0] - 2024-10-10

### Added
Expand Down
24 changes: 24 additions & 0 deletions docs/examples/styles/background_tint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from textual.app import App, ComposeResult
from textual.containers import Vertical
from textual.widgets import Label


class BackgroundTintApp(App):
CSS_PATH = "background_tint.tcss"

def compose(self) -> ComposeResult:
with Vertical(id="tint1"):
yield Label("0%")
with Vertical(id="tint2"):
yield Label("25%")
with Vertical(id="tint3"):
yield Label("50%")
with Vertical(id="tint4"):
yield Label("75%")
with Vertical(id="tint5"):
yield Label("100%")


if __name__ == "__main__":
app = BackgroundTintApp()
app.run()
9 changes: 9 additions & 0 deletions docs/examples/styles/background_tint.tcss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Vertical {
background: $panel;
color: auto 90%;
}
#tint1 { background-tint: $foreground 0%; }
#tint2 { background-tint: $foreground 25%; }
#tint3 { background-tint: $foreground 50%; }
#tint4 { background-tint: $foreground 75% }
#tint5 { background-tint: $foreground 100% }
1 change: 1 addition & 0 deletions docs/styles/background.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,5 @@ widget.styles.background = Color(120, 60, 100)

## See also

- [`background-tint`](./background_tint.md) to blend a color with the background.
- [`color`](./color.md) to set the color of text in a widget.
77 changes: 77 additions & 0 deletions docs/styles/background_tint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Background-tint

The `background-tint` style modifies the background color by tinting (blending) it with a new color.

This style is typically used to subtly change the background of a widget for emphasis.
For instance the following would make a focused widget have a slightly lighter background.

```css
MyWidget:focus {
background-tint: white 10%
}
```

The background tint color should typically have less than 100% alpha, in order to modify the background color.
If the alpha component is 100% then the tint color will replace the background color entirely.

## Syntax

--8<-- "docs/snippets/syntax_block_start.md"
background-tint: <a href="../../css_types/color">&lt;color&gt;</a> [<a href="../../css_types/percentage">&lt;percentage&gt;</a>];
--8<-- "docs/snippets/syntax_block_end.md"

The `background-tint` style requires a [`<color>`](../css_types/color.md) optionally followed by [`<percentage>`](../css_types/percentage.md) to specify the color's opacity (clamped between `0%` and `100%`).

## Examples

### Basic usage

This example shows background tint applied with alpha from 0 to 100%.

=== "Output"

```{.textual path="docs/examples/styles/background_tint.py"}
```

=== "background_tint.py"

```python
--8<-- "docs/examples/styles/background_tint.py"
```

=== "background.tcss"

```css hl_lines="9 13 17"
--8<-- "docs/examples/styles/background_tint.tcss"
```


## CSS

```css
/* 10% backgrouhnd tint */
background-tint: blue 10%;


/* 20% RGB color */
background: rgb(100, 120, 200, 0.2);

```

## Python

You can use the same syntax as CSS, or explicitly set a `Color` object for finer-grained control.

```python
# Set 20% blue background tint
widget.styles.background = "blue 20%"

from textual.color import Color
# Set with a color object
widget.styles.background_tint = Color(120, 60, 100, 0.5)
```

## See also

- [`background`](./background.md) to set the background color of a widget.
- [`color`](./color.md) to set the color of text in a widget.
1 change: 1 addition & 0 deletions mkdocs-nav.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ nav:
- "styles/index.md"
- "styles/align.md"
- "styles/background.md"
- "styles/background_tint.md"
- "styles/border.md"
- "styles/border_subtitle_align.md"
- "styles/border_subtitle_background.md"
Expand Down
1 change: 1 addition & 0 deletions src/textual/css/_styles_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,7 @@ def process_color(self, name: str, tokens: list[Token]) -> None:

process_tint = process_color
process_background = process_color
process_background_tint = process_color
process_scrollbar_color = process_color
process_scrollbar_color_hover = process_color
process_scrollbar_color_active = process_color
Expand Down
10 changes: 10 additions & 0 deletions src/textual/css/styles.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ class RulesMap(TypedDict, total=False):
background: Color
text_style: Style

background_tint: Color

opacity: float
text_opacity: float

Expand Down Expand Up @@ -215,6 +217,7 @@ class StylesBase:
"auto_color",
"color",
"background",
"background_tint",
"opacity",
"text_opacity",
"tint",
Expand Down Expand Up @@ -285,6 +288,11 @@ class StylesBase:
Supports `Color` objects but also strings e.g. "red" or "#ff0000"
You can also specify an opacity after a color e.g. "blue 10%"
"""
background_tint = ColorProperty(Color(0, 0, 0, 0))
"""Set a color to tint (blend) with the background.
Supports `Color` objects but also strings e.g. "red" or "#ff0000"
You can also specify an opacity after a color e.g. "blue 10%"
"""
text_style = StyleFlagsProperty()
"""Set the text style of the widget using Rich StyleFlags.
e.g. `"bold underline"` or `"b u strikethrough"`.
Expand Down Expand Up @@ -1011,6 +1019,8 @@ def append_declaration(name: str, value: str) -> None:
append_declaration("color", self.color.hex)
if "background" in rules:
append_declaration("background", self.background.hex)
if "background_tint" in rules:
append_declaration("background-tint", self.background_tint.hex)
if "text_style" in rules:
append_declaration("text-style", str(get_rule("text_style")))
if "tint" in rules:
Expand Down
16 changes: 11 additions & 5 deletions src/textual/dom.py
Original file line number Diff line number Diff line change
Expand Up @@ -1026,8 +1026,12 @@ def rich_style(self) -> Style:
has_rule = styles.has_rule
opacity *= styles.opacity
if has_rule("background"):
text_background = background + styles.background
background += styles.background.multiply_alpha(opacity)
text_background = (
background + styles.background + styles.background_tint
)
background += (
styles.background + styles.background_tint
).multiply_alpha(opacity)
else:
text_background = background
if has_rule("color"):
Expand Down Expand Up @@ -1115,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
background += styles.background + styles.background_tint
return (base_background, background)

@property
Expand All @@ -1131,7 +1135,9 @@ def _opacity_background_colors(self) -> tuple[Color, Color]:
styles = node.styles
base_background = background
opacity *= styles.opacity
background += styles.background.multiply_alpha(opacity)
background += (styles.background + styles.background_tint).multiply_alpha(
opacity
)
return (base_background, background)

@property
Expand All @@ -1146,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
background += styles.background + styles.background_tint
if styles.has_rule("color"):
base_color = color
if styles.auto_color:
Expand Down
25 changes: 25 additions & 0 deletions tests/snapshot_tests/test_snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -2311,3 +2311,28 @@ def compose(self) -> ComposeResult:
yield Footer() # Not allowed

assert snap_compare(MaximizeApp(), press=["m"])


def test_background_tint(snap_compare):
class BackgroundTintApp(App):
CSS = """
Vertical {
background: $panel;
}
#tint1 { background-tint: $foreground 0%; }
#tint2 { background-tint: $foreground 33%; }
#tint3 { background-tint: $foreground 66%; }
#tint4 { background-tint: $foreground 100% }
"""

def compose(self) -> ComposeResult:
with Vertical(id="tint1"):
yield Label("0%")
with Vertical(id="tint2"):
yield Label("33%")
with Vertical(id="tint3"):
yield Label("66%")
with Vertical(id="tint4"):
yield Label("100%")

assert snap_compare(BackgroundTintApp())

0 comments on commit 3ac0790

Please sign in to comment.