diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5e6f13bafd..1ca8138dd5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
+## [0.65.1] - 2024-06-05
+
+### Fixed
+
+- Fixed hot reloading with hatch rule https://github.com/Textualize/textual/pull/4606
+- Fixed hatch style parsing https://github.com/Textualize/textual/pull/4606
+
## [0.65.0] - 2024-06-05
### Added
@@ -2062,6 +2069,7 @@ https://textual.textualize.io/blog/2022/11/08/version-040/#version-040
- New handler system for messages that doesn't require inheritance
- Improved traceback handling
+[0.65.1]: https://github.com/Textualize/textual/compare/v0.65.0...v0.65.1
[0.65.0]: https://github.com/Textualize/textual/compare/v0.64.0...v0.65.0
[0.64.0]: https://github.com/Textualize/textual/compare/v0.63.6...v0.64.0
[0.63.6]: https://github.com/Textualize/textual/compare/v0.63.5...v0.63.6
diff --git a/docs/examples/styles/hatch.tcss b/docs/examples/styles/hatch.tcss
index 989d35efaa..b2bcbce119 100644
--- a/docs/examples/styles/hatch.tcss
+++ b/docs/examples/styles/hatch.tcss
@@ -1,6 +1,7 @@
.hatch {
height: 1fr;
border: solid $secondary;
+
&.cross {
hatch: cross $success;
}
diff --git a/pyproject.toml b/pyproject.toml
index 6f66f4609d..cdc7e92414 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "textual"
-version = "0.65.0"
+version = "0.65.1"
homepage = "https://github.com/Textualize/textual"
repository = "https://github.com/Textualize/textual"
documentation = "https://textual.textualize.io/"
diff --git a/src/textual/css/_style_properties.py b/src/textual/css/_style_properties.py
index c21c3b9e14..361cdfae40 100644
--- a/src/textual/css/_style_properties.py
+++ b/src/textual/css/_style_properties.py
@@ -1148,7 +1148,11 @@ class HatchProperty:
def __get__(self, obj: StylesBase, type: type[StylesBase]) -> tuple[str, Color]:
return cast("tuple[str, Color]", obj.get_rule("hatch", (" ", TRANSPARENT)))
- def __set__(self, obj: StylesBase, value: tuple[str, Color | str]) -> None:
+ def __set__(self, obj: StylesBase, value: tuple[str, Color | str] | None) -> None:
+ _rich_traceback_omit = True
+ if value is None:
+ obj.clear_rule("hatch")
+ return
character, color = value
if len(character) != 1:
try:
diff --git a/src/textual/css/_styles_builder.py b/src/textual/css/_styles_builder.py
index 760024cd52..622312a861 100644
--- a/src/textual/css/_styles_builder.py
+++ b/src/textual/css/_styles_builder.py
@@ -1058,51 +1058,73 @@ def process_constrain(self, name: str, tokens: list[Token]) -> None:
self.styles._rules[name] = value # type: ignore
def process_hatch(self, name: str, tokens: list[Token]) -> None:
- character = " "
+ if not tokens:
+ return
+ character: str | None = None
color = TRANSPARENT
opacity = 1.0
- for token in tokens:
- if token.name == "token":
- if token.value not in VALID_HATCH:
- self.error(
- name,
- tokens[0],
- string_enum_help_text(name, VALID_HATCH, context="css"),
- )
- character = HATCHES[token.value]
- elif token.name == "string":
- character = token.value[1:-1]
- if len(character) != 1:
- self.error(
- name,
- token,
- f"Hatch requires a string of length 1; got {token.value}",
- )
- if cell_len(character) != 1:
- self.error(
- name,
- token,
- f"Hatch requires a string with a *cell length* of 1; got {token.value}",
- )
- elif token.name == "color":
- try:
- color = Color.parse(token.value)
- except Exception as error:
- self.error(
- name,
- token,
- color_property_help_text(name, context="css", error=error),
- )
- elif token.name == "scalar":
- opacity_scalar = opacity = Scalar.parse(token.value)
+ if len(tokens) not in (2, 3):
+ self.error(name, tokens[0], "2 or 3 values expected here")
+
+ character_token, color_token, *opacity_tokens = tokens
+
+ if character_token.name == "token":
+ if character_token.value not in VALID_HATCH:
+ self.error(
+ name,
+ tokens[0],
+ string_enum_help_text(name, VALID_HATCH, context="css"),
+ )
+ character = HATCHES[character_token.value]
+ elif character_token.name == "string":
+ character = character_token.value[1:-1]
+ if len(character) != 1:
+ self.error(
+ name,
+ character_token,
+ f"Hatch type requires a string of length 1; got {character_token.value}",
+ )
+ if cell_len(character) != 1:
+ self.error(
+ name,
+ character_token,
+ f"Hatch type requires a string with a *cell length* of 1; got {character_token.value}",
+ )
+
+ if color_token.name in ("color", "token"):
+ try:
+ color = Color.parse(color_token.value)
+ except Exception as error:
+ self.error(
+ name,
+ color_token,
+ color_property_help_text(name, context="css", error=error),
+ )
+ else:
+ self.error(
+ name, color_token, f"Expected a color; found {color_token.value!r}"
+ )
+
+ if opacity_tokens:
+ opacity_token = opacity_tokens[0]
+ if opacity_token.name == "scalar":
+ opacity_scalar = opacity = Scalar.parse(opacity_token.value)
if opacity_scalar.unit != Unit.PERCENT:
self.error(
- name, token, "hatch alpha must be given as a percentage."
+ name,
+ opacity_token,
+ "hatch alpha must be given as a percentage.",
)
opacity = clamp(opacity_scalar.value / 100.0, 0, 1.0)
+ else:
+ self.error(
+ name,
+ opacity_token,
+ f"expected a percentage here; found {opacity_token.value!r}",
+ )
- self.styles._rules[name] = (character, color.multiply_alpha(opacity))
+ self.styles._rules[name] = (character or " ", color.multiply_alpha(opacity))
def _get_suggested_property_name_for_rule(self, rule_name: str) -> str | None:
"""
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
index 9499dc7aab..b53d32da31 100644
--- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
@@ -22282,138 +22282,139 @@
font-weight: 700;
}
- .terminal-3393210531-matrix {
+ .terminal-2628811343-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
- .terminal-3393210531-title {
+ .terminal-2628811343-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
- .terminal-3393210531-r1 { fill: #004578 }
- .terminal-3393210531-r2 { fill: #c5c8c6 }
- .terminal-3393210531-r3 { fill: #4ebf71 }
- .terminal-3393210531-r4 { fill: #fea62b }
- .terminal-3393210531-r5 { fill: #b93c5b }
- .terminal-3393210531-r6 { fill: #ff0000 }
- .terminal-3393210531-r7 { fill: #366e47 }
- .terminal-3393210531-r8 { fill: #ff00ff;font-weight: bold }
+ .terminal-2628811343-r1 { fill: #6a5acd }
+ .terminal-2628811343-r2 { fill: #c5c8c6 }
+ .terminal-2628811343-r3 { fill: #4ebf71 }
+ .terminal-2628811343-r4 { fill: #fea62b }
+ .terminal-2628811343-r5 { fill: #b93c5b }
+ .terminal-2628811343-r6 { fill: #ff0000 }
+ .terminal-2628811343-r7 { fill: #004578 }
+ .terminal-2628811343-r8 { fill: #366e47 }
+ .terminal-2628811343-r9 { fill: #ff00ff;font-weight: bold }
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- HatchApp
+ HatchApp
-
+
-
- ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱
- ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱
- ╱╱╱╱╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╳╳╳╳────────────────────────────────────────────────────────╳╳╳╳╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╳╳╳╳──┌─ Hello World ────────────────────────────────────┐──╳╳╳╳╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╳╳╳╳──││││││││││││││││││││││││││││││││││││││││││││││││││││──╳╳╳╳╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╳╳╳╳──││││││││││││││││││││││││││││││││││││││││││││││││││││──╳╳╳╳╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╳╳╳╳──│││││┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼│││││──╳╳╳╳╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╳╳╳╳──│││││┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼Hatched┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼│││││──╳╳╳╳╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╳╳╳╳──│││││┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼│││││──╳╳╳╳╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╳╳╳╳──│││││┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼│││││──╳╳╳╳╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╳╳╳╳──││││││││││││││││││││││││││││││││││││││││││││││││││││──╳╳╳╳╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╳╳╳╳──││││││││││││││││││││││││││││││││││││││││││││││││││││──╳╳╳╳╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╳╳╳╳──└──────────────────────────────────────────────────┘──╳╳╳╳╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╳╳╳╳────────────────────────────────────────────────────────╳╳╳╳╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╱╱╱╱
- ╱╱╱╱╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╱╱╱╱
- ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱
- ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱
+
+ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱
+ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱
+ ╱╱╱╱╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╳╳╳╳────────────────────────────────────────────────────────╳╳╳╳╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╳╳╳╳──┌─ Hello World ────────────────────────────────────┐──╳╳╳╳╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╳╳╳╳──││││││││││││││││││││││││││││││││││││││││││││││││││││──╳╳╳╳╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╳╳╳╳──││││││││││││││││││││││││││││││││││││││││││││││││││││──╳╳╳╳╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╳╳╳╳──│││││┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼│││││──╳╳╳╳╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╳╳╳╳──│││││┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼Hatched┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼│││││──╳╳╳╳╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╳╳╳╳──│││││┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼│││││──╳╳╳╳╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╳╳╳╳──│││││┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼┼│││││──╳╳╳╳╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╳╳╳╳──││││││││││││││││││││││││││││││││││││││││││││││││││││──╳╳╳╳╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╳╳╳╳──││││││││││││││││││││││││││││││││││││││││││││││││││││──╳╳╳╳╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╳╳╳╳──└──────────────────────────────────────────────────┘──╳╳╳╳╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╳╳╳╳────────────────────────────────────────────────────────╳╳╳╳╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╱╱╱╱
+ ╱╱╱╱╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╱╱╱╱
+ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱
+ ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱
diff --git a/tests/snapshot_tests/snapshot_apps/hatch.py b/tests/snapshot_tests/snapshot_apps/hatch.py
index 12c1c3a7b2..19859fd35f 100644
--- a/tests/snapshot_tests/snapshot_apps/hatch.py
+++ b/tests/snapshot_tests/snapshot_apps/hatch.py
@@ -6,7 +6,7 @@
class HatchApp(App):
CSS = """
Screen {
- hatch: right $primary;
+ hatch: right slateblue;
}
#one {
hatch: left $success;