Add support for HSL and HSLA

This commit is contained in:
Darren Burns
2022-06-07 15:23:24 +01:00
committed by Will McGugan
parent 566eb837b7
commit a97a2c6bfd
6 changed files with 79 additions and 13 deletions

11
sandbox/hsl.py Normal file
View File

@@ -0,0 +1,11 @@
from textual.app import App, ComposeResult
from textual.widgets import Static
class HSLApp(App):
def compose(self) -> ComposeResult:
yield Static(classes="box")
app = HSLApp(css_path="hsl.scss", watch_css=True)
app.run()

5
sandbox/hsl.scss Normal file
View File

@@ -0,0 +1,5 @@
.box {
height: 1fr;
/*background: rgb(180,50, 50);*/
background: hsl(180,50%, 50%);
}

View File

@@ -23,6 +23,8 @@ from rich.color import Color as RichColor
from rich.style import Style
from rich.text import Text
from textual.css.scalar import percentage_string_to_float
from textual.css.tokenize import COMMA, OPEN_BRACE, CLOSE_BRACE, DECIMAL, PERCENT
from textual.suggestions import get_suggestion
from ._color_constants import COLOR_NAME_TO_RGB
from .geometry import clamp
@@ -53,13 +55,15 @@ class Lab(NamedTuple):
RE_COLOR = re.compile(
r"""^
\#([0-9a-fA-F]{3})$|
\#([0-9a-fA-F]{4})$|
\#([0-9a-fA-F]{6})$|
\#([0-9a-fA-F]{8})$|
rgb\((\-?\d+\.?\d*,\-?\d+\.?\d*,\-?\d+\.?\d*)\)$|
rgba\((\-?\d+\.?\d*,\-?\d+\.?\d*,\-?\d+\.?\d*,\-?\d+\.?\d*)\)$
rf"""^
\#([0-9a-fA-F]{{3}})$|
\#([0-9a-fA-F]{{4}})$|
\#([0-9a-fA-F]{{6}})$|
\#([0-9a-fA-F]{{8}})$|
rgb{OPEN_BRACE}({DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}){CLOSE_BRACE}$|
rgba{OPEN_BRACE}({DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}){CLOSE_BRACE}$|
hsl{OPEN_BRACE}({DECIMAL}{COMMA}{PERCENT}{COMMA}{PERCENT}){CLOSE_BRACE}$|
hsla{OPEN_BRACE}({DECIMAL}{COMMA}{PERCENT}{COMMA}{PERCENT}{COMMA}{DECIMAL}){CLOSE_BRACE}$|
""",
re.VERBOSE,
)
@@ -123,7 +127,9 @@ class Color(NamedTuple):
Returns:
Color: A new color.
"""
print("A")
r, g, b = hls_to_rgb(h, l, s)
print("B")
return cls(int(r * 255 + 0.5), int(g * 255 + 0.5), int(b * 255 + 0.5))
def __rich__(self) -> Text:
@@ -296,6 +302,8 @@ class Color(NamedTuple):
rgba_hex,
rgb,
rgba,
hsl,
hsla,
) = color_match.groups()
if rgb_hex_triple is not None:
@@ -328,6 +336,19 @@ class Color(NamedTuple):
clamp(int(float_b), 0, 255),
clamp(float_a, 0.0, 1.0),
)
elif hsl is not None:
h, s, l = [value.strip() for value in hsl.split(",")]
h = clamp(int(h), 0, 360) / 360
s = percentage_string_to_float(s)
l = percentage_string_to_float(l)
color = Color.from_hls(h, l, s)
elif hsla is not None:
h, s, l, a = [value.strip() for value in hsl.split(",")]
h = clamp(h, 0, 360)
s = percentage_string_to_float(s)
l = percentage_string_to_float(l)
a = clamp(a, 0.0, 1.0)
color = Color.from_hls(h, l, s).with_alpha(a)
else:
raise AssertionError("Can't get here if RE_COLOR matches")
return color

View File

@@ -1,5 +1,7 @@
from __future__ import annotations
import re
import sys
from functools import lru_cache
from typing import cast, Iterable, NoReturn, Sequence
@@ -40,7 +42,14 @@ from .constants import (
)
from .errors import DeclarationError, StyleValueError
from .model import Declaration
from .scalar import Scalar, ScalarOffset, Unit, ScalarError, ScalarParseError
from .scalar import (
Scalar,
ScalarOffset,
Unit,
ScalarError,
ScalarParseError,
percentage_string_to_float,
)
from .styles import DockGroup, Styles
from .tokenize import Token
from .transition import Transition
@@ -333,9 +342,8 @@ class StylesBuilder:
token_name = token.name
value = token.value
if token_name == "scalar" and value.endswith("%"):
percentage = value[:-1]
try:
opacity = clamp(float(percentage) / 100, 0, 1)
opacity = percentage_string_to_float(value)
self.styles.set_rule(name, opacity)
except ValueError:
error = True

View File

@@ -8,7 +8,7 @@ from typing import Iterable, NamedTuple
import rich.repr
from ..geometry import Offset, Size
from ..geometry import Offset, Size, clamp
class ScalarError(Exception):
@@ -334,6 +334,17 @@ class ScalarOffset(NamedTuple):
NULL_SCALAR = ScalarOffset(Scalar.from_number(0), Scalar.from_number(0))
def percentage_string_to_float(string: str) -> float:
string = string.strip()
if string.endswith("%"):
percentage = string[:-1]
float_percentage = clamp(float(percentage) / 100, 0, 1)
else:
float_percentage = float(string)
return float_percentage
if __name__ == "__main__":
print(Scalar.parse("3.14fr"))
s = Scalar.parse("23")

View File

@@ -6,11 +6,21 @@ from typing import Iterable
from textual.css.tokenizer import Expect, Tokenizer, Token
PERCENT = r"-?\d+\.?\d*%"
DECIMAL = r"-?\d+\.?\d*"
COMMA = r",\s*"
OPEN_BRACE = r"\(\s*"
CLOSE_BRACE = r"\s*\)"
HEX_COLOR = r"\#[0-9a-fA-F]{8}|\#[0-9a-fA-F]{6}|\#[0-9a-fA-F]{4}|\#[0-9a-fA-F]{3}"
RGB_COLOR = rf"rgb{OPEN_BRACE}{DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}{CLOSE_BRACE}|rgba{OPEN_BRACE}{DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}{COMMA}{DECIMAL}{CLOSE_BRACE}"
HSL_COLOR = rf"hsl{OPEN_BRACE}{DECIMAL}{COMMA}{PERCENT}{COMMA}{PERCENT}{CLOSE_BRACE}|hsla{OPEN_BRACE}{DECIMAL}{COMMA}{PERCENT}{COMMA}{PERCENT}{COMMA}{DECIMAL}{CLOSE_BRACE}"
COMMENT_START = r"\/\*"
SCALAR = r"\-?\d+\.?\d*(?:fr|%|w|h|vw|vh)"
SCALAR = rf"{DECIMAL}(?:fr|%|w|h|vw|vh)"
DURATION = r"\d+\.?\d*(?:ms|s)"
NUMBER = r"\-?\d+\.?\d*"
COLOR = r"\#[0-9a-fA-F]{8}|\#[0-9a-fA-F]{6}|\#[0-9a-fA-F]{4}|\#[0-9a-fA-F]{3}|rgb\(\-?\d+\.?\d*,\-?\d+\.?\d*,\-?\d+\.?\d*\)|rgba\(\-?\d+\.?\d*,\-?\d+\.?\d*,\-?\d+\.?\d*,\-?\d+\.?\d*\)"
COLOR = f"{HEX_COLOR}|{RGB_COLOR}|{HSL_COLOR}"
KEY_VALUE = r"[a-zA-Z_-][a-zA-Z0-9_-]*=[0-9a-zA-Z_\-\/]+"
TOKEN = "[a-zA-Z][a-zA-Z0-9_-]*"
STRING = r"\".*?\""