mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Border colour percentage (#1954)
* Allow setting an additional alpha on a border See #1863. * Update the ChangeLog * Add snapshot tests for the border alpha value * Extend the border snapshot tests While this doesn't test *every* permutation, it covers enough bases that if something were to change it should catch it. * Tweak a typo in the border style examples * Add border transparency percentage to the border docs * Add a CSS example for using border transparency * Add Color.multiply_alpha * Update the CHANGELOG * Multiply the alpha on a colour rather than replace it As requested in https://github.com/Textualize/textual/pull/1954#pullrequestreview-1328170386 (actually required while talking in person with Will, but noted in the above) * Multiply the alpha on a border colour rather than replace it As requested in https://github.com/Textualize/textual/pull/1954#pullrequestreview-1328170386 (actually requested while talking in person with Will, but noted in the above)
This commit is contained in:
@@ -24,6 +24,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Added `radio_set` attribute to `RadioSet` events https://github.com/Textualize/textual/pull/1940
|
||||
- Added `switch` attribute to `Switch` events https://github.com/Textualize/textual/pull/1940
|
||||
- Breaking change: Added `toggle_button` attribute to RadioButton and Checkbox events, replaces `input` https://github.com/Textualize/textual/pull/1940
|
||||
- A percentage alpha can now be applied to a border https://github.com/Textualize/textual/issues/1863
|
||||
- Added `Color.multiply_alpha`.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -9,15 +9,15 @@ The `border` rule enables the drawing of a box around a widget.
|
||||
## Syntax
|
||||
|
||||
--8<-- "docs/snippets/syntax_block_start.md"
|
||||
border: [<a href="../../css_types/border"><border></a>] [<a href="../../css_types/color"><color></a>];
|
||||
border: [<a href="../../css_types/border"><border></a>] [<a href="../../css_types/color"><color></a>] [<a href="../../css_types/percentage"><percentage></a>];
|
||||
|
||||
border-top: [<a href="../../css_types/border"><border></a>] [<a href="../../css_types/color"><color></a>];
|
||||
border-right: [<a href="../../css_types/border"><border></a>] [<a href="../../css_types/color"><color></a>];
|
||||
border-bottom: [<a href="../../css_types/border"><border></a>] [<a href="../../css_types/color"><color></a>];
|
||||
border-left: [<a href="../../css_types/border"><border></a>] [<a href="../../css_types/color"><color></a>];
|
||||
border-top: [<a href="../../css_types/border"><border></a>] [<a href="../../css_types/color"><color></a>] [<a href="../../css_types/percentage"><percentage></a>];
|
||||
border-right: [<a href="../../css_types/border"><border></a>] [<a href="../../css_types/color"><color></a> [<a href="../../css_types/percentage"><percentage></a>]];
|
||||
border-bottom: [<a href="../../css_types/border"><border></a>] [<a href="../../css_types/color"><color></a> [<a href="../../css_types/percentage"><percentage></a>]];
|
||||
border-left: [<a href="../../css_types/border"><border></a>] [<a href="../../css_types/color"><color></a> [<a href="../../css_types/percentage"><percentage></a>]];
|
||||
--8<-- "docs/snippets/syntax_block_end.md"
|
||||
|
||||
The style `border` accepts an optional [`<border>`](../../css_types/border) that sets the visual style of the widget border and an optional [`<color>`](../../css_types/color) to set the color of the border.
|
||||
The style `border` accepts an optional [`<border>`](../../css_types/border) that sets the visual style of the widget border, an optional [`<color>`](../../css_types/color) to set the color of the border, and an optional [`<percentage>`](../../css_types/percentage) to specify the color transparency.
|
||||
|
||||
Borders may also be set individually for the four edges of a widget with the `border-top`, `border-right`, `border-bottom` and `border-left` rules.
|
||||
|
||||
@@ -41,6 +41,7 @@ The CSS snippet above will add a solid green border around `Static` widgets, exc
|
||||
|
||||
If `<color>` is specified but `<border>` is not, it defaults to `"solid"`.
|
||||
If `<border>` is specified but `<color>`is not, it defaults to green (RGB color `"#00FF00"`).
|
||||
If `<percentage>` is not specified it defaults to `100%`.
|
||||
|
||||
## Border command
|
||||
|
||||
@@ -126,8 +127,11 @@ This example also shows that a widget cannot contain both a `border` and an `out
|
||||
/* Set a heavy white border */
|
||||
border: heavy white;
|
||||
|
||||
/* set a red border on the left */
|
||||
/* Set a red border on the left */
|
||||
border-left: outer red;
|
||||
|
||||
/* Set a rounded orange border, 50% transparency. */
|
||||
border: round orange 50%;
|
||||
```
|
||||
|
||||
## Python
|
||||
|
||||
@@ -341,6 +341,18 @@ class Color(NamedTuple):
|
||||
r, g, b, _ = self
|
||||
return Color(r, g, b, alpha)
|
||||
|
||||
def multiply_alpha(self, alpha: float) -> Color:
|
||||
"""Create a new color, multiplying the alpha with a new alpha.
|
||||
|
||||
Args:
|
||||
alpha: The alpha value to multiple by.
|
||||
|
||||
Returns:
|
||||
A new color.
|
||||
"""
|
||||
r, g, b, a = self
|
||||
return Color(r, g, b, a * alpha)
|
||||
|
||||
def blend(
|
||||
self, destination: Color, factor: float, alpha: float | None = None
|
||||
) -> Color:
|
||||
|
||||
@@ -441,6 +441,7 @@ class StylesBuilder:
|
||||
def _parse_border(self, name: str, tokens: list[Token]) -> BorderValue:
|
||||
border_type: EdgeType = "solid"
|
||||
border_color = Color(0, 255, 0)
|
||||
border_alpha: float | None = None
|
||||
|
||||
def border_value_error():
|
||||
self.error(name, token, border_property_help_text(name, context="css"))
|
||||
@@ -462,9 +463,18 @@ class StylesBuilder:
|
||||
except ColorParseError:
|
||||
border_value_error()
|
||||
|
||||
elif token_name == "scalar":
|
||||
alpha_scalar = Scalar.parse(token.value)
|
||||
if alpha_scalar.unit != Unit.PERCENT:
|
||||
self.error(name, token, "alpha must be given as a percentage.")
|
||||
border_alpha = alpha_scalar.value / 100.0
|
||||
|
||||
else:
|
||||
border_value_error()
|
||||
|
||||
if border_alpha is not None:
|
||||
border_color = border_color.multiply_alpha(border_alpha)
|
||||
|
||||
return normalize_border_value((border_type, border_color))
|
||||
|
||||
def _process_border_edge(self, edge: str, name: str, tokens: list[Token]) -> None:
|
||||
@@ -612,7 +622,7 @@ class StylesBuilder:
|
||||
|
||||
if color is not None or alpha is not None:
|
||||
if alpha is not None:
|
||||
color = (color or Color(255, 255, 255)).with_alpha(alpha)
|
||||
color = (color or Color(255, 255, 255)).multiply_alpha(alpha)
|
||||
self.styles._rules[name] = color # type: ignore
|
||||
|
||||
process_tint = process_color
|
||||
|
||||
File diff suppressed because one or more lines are too long
43
tests/snapshot_tests/snapshot_apps/border_alpha.py
Normal file
43
tests/snapshot_tests/snapshot_apps/border_alpha.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.containers import Grid
|
||||
from textual.widget import Widget
|
||||
|
||||
class BorderAlphaApp(App[None]):
|
||||
|
||||
CSS = """
|
||||
Grid {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
grid-size: 2 2;
|
||||
}
|
||||
|
||||
#b00 { border: 0%; }
|
||||
#b01 { border: 33%; }
|
||||
#b02 { border: 66%; }
|
||||
#b03 { border: 100%; }
|
||||
|
||||
#b10 { border: solid 0%; }
|
||||
#b11 { border: dashed 33%; }
|
||||
#b12 { border: round 66%; }
|
||||
#b13 { border: ascii 100%; }
|
||||
|
||||
#b20 { border: 0% red; }
|
||||
#b21 { border: 33% orange; }
|
||||
#b22 { border: 66% green; }
|
||||
#b23 { border: 100% blue; }
|
||||
|
||||
#b30 { border: solid 0% red; }
|
||||
#b31 { border: dashed 33% orange; }
|
||||
#b32 { border: round 66% green; }
|
||||
#b33 { border: ascii 100% blue; }
|
||||
"""
|
||||
|
||||
def compose( self ) -> ComposeResult:
|
||||
with Grid():
|
||||
for outer in range(4):
|
||||
with Grid():
|
||||
for inner in range(4):
|
||||
yield Widget(id=f"b{outer}{inner}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
BorderAlphaApp().run()
|
||||
@@ -240,6 +240,11 @@ def test_label_widths(snap_compare):
|
||||
assert snap_compare(SNAPSHOT_APPS_DIR / "label_widths.py")
|
||||
|
||||
|
||||
def test_border_alpha(snap_compare):
|
||||
"""Test setting a border alpha."""
|
||||
assert snap_compare(SNAPSHOT_APPS_DIR / "border_alpha.py")
|
||||
|
||||
|
||||
def test_auto_width_input(snap_compare):
|
||||
assert snap_compare(
|
||||
SNAPSHOT_APPS_DIR / "auto_width_input.py", press=["tab", *"Hello"]
|
||||
|
||||
Reference in New Issue
Block a user