improved color harmony

This commit is contained in:
Will McGugan
2022-07-19 21:41:34 +01:00
parent 1b6c4273c7
commit 49764a3ec7
13 changed files with 233 additions and 210 deletions

View File

@@ -2,9 +2,9 @@
* { * {
transition: color 300ms linear, background 300ms linear; transition: color 300ms linear, background 300ms linear;
} }
*:hover { *:hover {
@@ -29,8 +29,8 @@ DataTable {
} }
#sidebar { #sidebar {
color: $text-primary; color: $text-panel;
background: $primary-background; background: $panel;
dock: side; dock: side;
width: 30; width: 30;
offset-x: -100%; offset-x: -100%;
@@ -43,33 +43,35 @@ DataTable {
} }
#sidebar .title { #sidebar .title {
height: 3; height: 1;
background: $primary-background-darken-2; background: $primary-background-darken-1;
color: $text-primary-darken-2 ; color: $text-primary-background-darken-1;
border-right: outer $primary-darken-3; border-right: wide $background;
content-align: center middle; content-align: center middle;
} }
#sidebar .user { #sidebar .user {
height: 8; height: 8;
background: $primary-background-darken-1; background: $panel-darken-1;
color: $text-primary-darken-1; color: $text-panel-darken-1;
border-right: outer $primary-background-darken-3; border-right: wide $background;
content-align: center middle; content-align: center middle;
} }
#sidebar .content { #sidebar .content {
background: $primary-background; background: $surface;
color: $text-primary-background; color: $text-surface;
border-right: outer $primary-background-darken-3; border-right: wide $background;
content-align: center middle; content-align: center middle;
} }
#header { #header {
color: $text-secondary-background-darken-1; color: $text-secondary-background;
background: $secondary-background-darken-1; background: $secondary-background;
height: 3; height: 1;
content-align: center middle; content-align: center middle;
} }
#content { #content {
@@ -90,7 +92,7 @@ Tweet {
layout: vertical; layout: vertical;
/* border: outer $primary; */ /* border: outer $primary; */
padding: 1; padding: 1;
border: wide $panel-darken-2; border: wide $panel;
overflow: auto; overflow: auto;
/* scrollbar-gutter: stable; */ /* scrollbar-gutter: stable; */
align-horizontal: center; align-horizontal: center;
@@ -175,16 +177,16 @@ Tweet.scroll-horizontal TweetBody {
OptionItem { OptionItem {
height: 3; height: 3;
background: $primary-background; background: $panel;
border-right: outer $primary-background-darken-2; border-right: wide $background;
border-left: blank; border-left: blank;
content-align: center middle; content-align: center middle;
} }
OptionItem:hover { OptionItem:hover {
height: 3; height: 3;
color: $secondary; color: $text-primary;
background: $primary-background-darken-1; background: $primary-darken-1;
/* border-top: hkey $accent2-darken-3; /* border-top: hkey $accent2-darken-3;
border-bottom: hkey $accent2-darken-3; */ border-bottom: hkey $accent2-darken-3; */
text-style: bold; text-style: bold;
@@ -196,8 +198,8 @@ Error {
height:3; height:3;
background: $error; background: $error;
color: $text-error; color: $text-error;
border-top: wide $error-darken-1; border-top: tall $error-darken-1;
border-bottom: wide $error-darken-1; border-bottom: tall $error-darken-1;
padding: 0; padding: 0;
text-style: bold; text-style: bold;
@@ -209,8 +211,8 @@ Warning {
height:3; height:3;
background: $warning; background: $warning;
color: $text-warning-fade-1; color: $text-warning-fade-1;
border-top: wide $warning-darken-1; border-top: tall $warning-darken-1;
border-bottom: wide $warning-darken-1; border-bottom: tall $warning-darken-1;
text-style: bold; text-style: bold;
align-horizontal: center; align-horizontal: center;
@@ -218,7 +220,7 @@ Warning {
Success { Success {
width: 100%; width: 100%;
width:90%;
height:auto; height:auto;
box-sizing: border-box; box-sizing: border-box;
background: $success; background: $success;
@@ -227,7 +229,7 @@ Success {
border-top: hkey $success-darken-1; border-top: hkey $success-darken-1;
border-bottom: hkey $success-darken-1; border-bottom: hkey $success-darken-1;
text-style: bold underline; text-style: bold ;
align-horizontal: center; align-horizontal: center;
} }

View File

@@ -179,21 +179,21 @@ app = BasicApp()
if __name__ == "__main__": if __name__ == "__main__":
app.run() app.run()
from textual.geometry import Region # from textual.geometry import Region
from textual.color import Color # from textual.color import Color
print(Region.intersection.cache_info()) # print(Region.intersection.cache_info())
print(Region.overlaps.cache_info()) # print(Region.overlaps.cache_info())
print(Region.union.cache_info()) # print(Region.union.cache_info())
print(Region.split_vertical.cache_info()) # print(Region.split_vertical.cache_info())
print(Region.__contains__.cache_info()) # print(Region.__contains__.cache_info())
from textual.css.scalar import Scalar # from textual.css.scalar import Scalar
print(Scalar.resolve_dimension.cache_info()) # print(Scalar.resolve_dimension.cache_info())
from rich.style import Style # from rich.style import Style
from rich.cells import cached_cell_len # from rich.cells import cached_cell_len
print(Style._add.cache_info()) # print(Style._add.cache_info())
print(cached_cell_len.cache_info()) # print(cached_cell_len.cache_info())

View File

@@ -80,16 +80,26 @@ _ASYNCIO_GET_EVENT_LOOP_IS_DEPRECATED = sys.version_info >= (3, 10, 0)
LayoutDefinition = "dict[str, Any]" LayoutDefinition = "dict[str, Any]"
DEFAULT_COLORS = ColorSystem( DEFAULT_COLORS = {
primary="#2A4E6E", "dark": ColorSystem(
secondary="#ffa62b", primary="#004578",
warning="#ffa62b", secondary="#ffa62b",
error="#ba3c5b", warning="#ffa62b",
success="#4EBF71", error="#ba3c5b",
accent="#1A75B4", success="#4EBF71",
system="#5a4599", accent="#0178D4",
dark_surface="#292929", dark=True,
) ),
"light": ColorSystem(
primary="#004578",
secondary="#ffa62b",
warning="#ffa62b",
error="#ba3c5b",
success="#4EBF71",
accent="#0178D4",
dark=False,
),
}
ComposeResult = Iterable[Widget] ComposeResult = Iterable[Widget]
@@ -338,7 +348,7 @@ class App(Generic[ReturnType], DOMNode):
Returns: Returns:
dict[str, str]: A mapping of variable name to value. dict[str, str]: A mapping of variable name to value.
""" """
variables = self.design.generate(self.dark) variables = self.design["dark" if self.dark else "light"].generate()
return variables return variables
def watch_dark(self, dark: bool) -> None: def watch_dark(self, dark: bool) -> None:

View File

@@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from rich.console import ConsoleOptions, Console from rich.console import ConsoleOptions, Console, RenderResult
from rich.traceback import Traceback from rich.traceback import Traceback
from ._help_renderables import HelpText from ._help_renderables import HelpText
@@ -15,10 +15,6 @@ class DeclarationError(Exception):
super().__init__(message) super().__init__(message)
class UnresolvedVariableError(NameError):
pass
class StyleTypeError(TypeError): class StyleTypeError(TypeError):
pass pass
@@ -35,7 +31,9 @@ class StyleValueError(ValueError):
super().__init__(*args) super().__init__(*args)
self.help_text = help_text self.help_text = help_text
def __rich_console__(self, console: Console, options: ConsoleOptions): def __rich_console__(
self, console: Console, options: ConsoleOptions
) -> RenderResult:
yield Traceback.from_exception(type(self), self, self.__traceback__) yield Traceback.from_exception(type(self), self, self.__traceback__)
if self.help_text is not None: if self.help_text is not None:
yield "" yield ""

View File

@@ -2,11 +2,11 @@ from __future__ import annotations
from functools import lru_cache from functools import lru_cache
from pathlib import PurePath from pathlib import PurePath
from typing import Iterator, Iterable from typing import Iterator, Iterable, NoReturn, Sequence
from rich import print from rich import print
from textual.css.errors import UnresolvedVariableError from textual.css.tokenizer import TokenError
from textual.css.types import Specificity3 from textual.css.types import Specificity3
from ._styles_builder import StylesBuilder, DeclarationError from ._styles_builder import StylesBuilder, DeclarationError
from .model import ( from .model import (
@@ -18,6 +18,7 @@ from .model import (
SelectorType, SelectorType,
) )
from .styles import Styles from .styles import Styles
from ..suggestions import get_suggestion
from .tokenize import tokenize, tokenize_declarations, Token, tokenize_values from .tokenize import tokenize, tokenize_declarations, Token, tokenize_values
from .tokenizer import EOFError, ReferencedBy from .tokenizer import EOFError, ReferencedBy
@@ -209,12 +210,20 @@ def parse_declarations(css: str, path: str) -> Styles:
return styles_builder.styles return styles_builder.styles
def _unresolved( def _unresolved(variable_name: str, variables: Sequence[str], token: Token) -> NoReturn:
variable_name: str, location: tuple[int, int]
) -> UnresolvedVariableError: message = f"reference to undefined variable '${variable_name}'"
line_idx, col_idx = location
return UnresolvedVariableError( suggested_variable = get_suggestion(variable_name, variables)
f"reference to undefined variable '${variable_name}' at line {line_idx + 1}, column {col_idx + 1}." if suggested_variable:
message += f"; did you mean '{suggested_variable}'?"
raise TokenError(
token.path,
token.code,
token.start,
message,
end=token.end,
) )
@@ -284,9 +293,7 @@ def substitute_references(
) )
) )
else: else:
raise _unresolved( _unresolved(ref_name, variables.keys(), token)
variable_name=ref_name, location=token.location
)
else: else:
variables.setdefault(variable_name, []).append(token) variables.setdefault(variable_name, []).append(token)
yield token yield token
@@ -306,7 +313,7 @@ def substitute_references(
) )
) )
else: else:
raise _unresolved(variable_name=variable_name, location=token.location) _unresolved(variable_name, variables.keys(), token)
else: else:
yield token yield token

View File

@@ -22,7 +22,7 @@ from .model import RuleSet
from .parse import parse from .parse import parse
from .styles import RulesMap, Styles from .styles import RulesMap, Styles
from .tokenize import tokenize_values, Token from .tokenize import tokenize_values, Token
from .tokenizer import TokenizeError from .tokenizer import TokenError
from .types import Specificity3, Specificity4 from .types import Specificity3, Specificity4
from ..dom import DOMNode from ..dom import DOMNode
from .. import messages from .. import messages
@@ -198,7 +198,7 @@ class Stylesheet:
is_default_rules=is_default_rules, is_default_rules=is_default_rules,
) )
) )
except TokenizeError: except TokenError:
raise raise
except Exception as error: except Exception as error:
raise StylesheetError(f"failed to parse css; {error}") raise StylesheetError(f"failed to parse css; {error}")

View File

@@ -15,28 +15,43 @@ from rich.text import Text
from .._loop import loop_last from .._loop import loop_last
class TokenizeError(Exception): class TokenError(Exception):
"""Error raised when the CSS cannot be tokenized (syntax error).""" """Error raised when the CSS cannot be tokenized (syntax error)."""
def __init__( def __init__(
self, path: str, code: str, line_no: int, col_no: int, message: str self,
path: str,
code: str,
start: tuple[int, int],
message: str,
end: tuple[int, int] | None = None,
) -> None: ) -> None:
""" """
Args: Args:
path (str): Path to source or "<object>" if source is parsed from a literal. path (str): Path to source or "<object>" if source is parsed from a literal.
code (str): The code being parsed. code (str): The code being parsed.
line_no (int): Line number of the error. start (tuple[int, int]): Line number of the error.
col_no (int): Column number of the error.
message (str): A message associated with the error. message (str): A message associated with the error.
end (tuple[int, int]): End location of .
""" """
"""_summary_
Args:
path (str): Path to source or "<object>" if source is parsed from a literal.
code (str): The code being parsed.
start (tuple[int, int]): Location of the error.
message (str): A message associated with the error.
end (tuple[int, int] | None, optional): End location of the error, or None for
the same as start. Defaults to None.
"""
self.path = path self.path = path
self.code = code self.code = code
self.line_no = line_no self.start = start
self.col_no = col_no self.end = end or start
super().__init__(message) super().__init__(message)
@classmethod def _get_snippet(self) -> Panel:
def _get_snippet(cls, code: str, line_no: int) -> Panel:
"""Get a short snippet of code around a given line number. """Get a short snippet of code around a given line number.
Args: Args:
@@ -46,9 +61,10 @@ class TokenizeError(Exception):
Returns: Returns:
Panel: A renderable. Panel: A renderable.
""" """
line_no = self.start[0]
# TODO: Highlight column number # TODO: Highlight column number
syntax = Syntax( syntax = Syntax(
code, self.code,
lexer="scss", lexer="scss",
theme="ansi_light", theme="ansi_light",
line_numbers=True, line_numbers=True,
@@ -56,6 +72,7 @@ class TokenizeError(Exception):
line_range=(max(0, line_no - 2), line_no + 2), line_range=(max(0, line_no - 2), line_no + 2),
highlight_lines={line_no}, highlight_lines={line_no},
) )
syntax.stylize_range("reverse bold", self.start, self.end)
return Panel(syntax, border_style="red") return Panel(syntax, border_style="red")
def __rich__(self) -> RenderableType: def __rich__(self) -> RenderableType:
@@ -63,14 +80,12 @@ class TokenizeError(Exception):
errors: list[RenderableType] = [] errors: list[RenderableType] = []
message = str(self) message = str(self)
errors.append(Text(" Tokenizer error in stylesheet:", style="bold red")) errors.append(Text(" Error in stylesheet:", style="bold red"))
errors.append( line_no, col_no = self.start
highlighter(
f" {self.path or '<unknown>'}:{self.line_no + 1}:{self.col_no + 1}" errors.append(highlighter(f" {self.path or '<unknown>'}:{line_no}:{col_no}"))
) errors.append(self._get_snippet())
)
errors.append(self._get_snippet(self.code, self.line_no + 1))
final_message = "" final_message = ""
for is_last, message_part in loop_last(message.split(";")): for is_last, message_part in loop_last(message.split(";")):
end = "" if is_last else "\n" end = "" if is_last else "\n"
@@ -80,7 +95,7 @@ class TokenizeError(Exception):
return Group(*errors) return Group(*errors)
class EOFError(TokenizeError): class EOFError(TokenError):
pass pass
@@ -120,6 +135,18 @@ class Token(NamedTuple):
location: tuple[int, int] location: tuple[int, int]
referenced_by: ReferencedBy | None = None referenced_by: ReferencedBy | None = None
@property
def start(self) -> tuple[int, int]:
"""Start line and column (1 indexed)."""
line, offset = self.location
return (line + 1, offset)
@property
def end(self) -> tuple[int, int]:
"""End line and column (1 indexed)."""
line, offset = self.location
return (line + 1, offset + len(self.value))
def with_reference(self, by: ReferencedBy | None) -> "Token": def with_reference(self, by: ReferencedBy | None) -> "Token":
"""Return a copy of the Token, with reference information attached. """Return a copy of the Token, with reference information attached.
This is used for variable substitution, where a variable reference This is used for variable substitution, where a variable reference
@@ -161,19 +188,28 @@ class Tokenizer:
col_no = self.col_no col_no = self.col_no
if line_no >= len(self.lines): if line_no >= len(self.lines):
if expect._expect_eof: if expect._expect_eof:
return Token("eof", "", self.path, self.code, (line_no, col_no), None) return Token(
"eof",
"",
self.path,
self.code,
(line_no + 1, col_no + 1),
None,
)
else: else:
raise EOFError( raise EOFError(
self.path, self.code, line_no, col_no, "Unexpected end of file" self.path,
self.code,
(line_no + 1, col_no + 1),
"Unexpected end of file",
) )
line = self.lines[line_no] line = self.lines[line_no]
match = expect.match(line, col_no) match = expect.match(line, col_no)
if match is None: if match is None:
raise TokenizeError( raise TokenError(
self.path, self.path,
self.code, self.code,
line_no, (line_no, col_no),
col_no,
"expected " + ", ".join(name.upper() for name in expect.names), "expected " + ", ".join(name.upper() for name in expect.names),
) )
iter_groups = iter(match.groups()) iter_groups = iter(match.groups())

View File

@@ -9,39 +9,18 @@ from rich.text import Text
from .color import Color, WHITE from .color import Color, WHITE
NUMBER_OF_SHADES = 3 NUMBER_OF_SHADES = 3
# Where no content exists # Where no content exists
DEFAULT_DARK_BACKGROUND = "#000000" DEFAULT_DARK_BACKGROUND = "#000000"
# What text usually goes on top off # What text usually goes on top off
DEFAULT_DARK_SURFACE = "#121212" DEFAULT_DARK_SURFACE = "#292929"
DEFAULT_LIGHT_SURFACE = "#f5f5f5" DEFAULT_LIGHT_SURFACE = "#f5f5f5"
DEFAULT_LIGHT_BACKGROUND = "#efefef" DEFAULT_LIGHT_BACKGROUND = "#efefef"
class ColorProperty:
"""Descriptor to parse colors."""
def __set_name__(self, owner: ColorSystem, name: str) -> None:
self._name = f"_{name}"
def __get__(
self, obj: ColorSystem, objtype: type[ColorSystem] | None = None
) -> Color | None:
color = getattr(obj, self._name)
if color is None:
return None
else:
return Color.parse(color)
def __set__(self, obj: ColorSystem, value: Color | str | None) -> None:
if isinstance(value, Color):
setattr(obj, self._name, value.css)
else:
setattr(obj, self._name, value)
class ColorSystem: class ColorSystem:
"""Defines a standard set of colors and variations for building a UI. """Defines a standard set of colors and variations for building a UI.
@@ -63,7 +42,6 @@ class ColorSystem:
"error", "error",
"success", "success",
"accent", "accent",
"system",
] ]
def __init__( def __init__(
@@ -74,42 +52,30 @@ class ColorSystem:
error: str | None = None, error: str | None = None,
success: str | None = None, success: str | None = None,
accent: str | None = None, accent: str | None = None,
system: str | None = None,
background: str | None = None, background: str | None = None,
surface: str | None = None, surface: str | None = None,
dark_background: str | None = None,
dark_surface: str | None = None,
panel: str | None = None, panel: str | None = None,
dark: bool = False,
luminosity_spread: float = 0.15,
text_alpha: float = 0.95,
): ):
self._primary = primary def parse(color: str | None) -> Color | None:
self._secondary = secondary if color is None:
self._warning = warning return None
self._error = error return Color.parse(color)
self._success = success
self._accent = accent
self._system = system
self._background = background
self._surface = surface
self._dark_background = dark_background
self._dark_surface = dark_surface
self._panel = panel
@property self.primary = Color.parse(primary)
def primary(self) -> Color: self.secondary = parse(secondary)
"""Get the primary color.""" self.warning = parse(warning)
return Color.parse(self._primary) self.error = parse(error)
self.success = parse(success)
secondary = ColorProperty() self.accent = parse(accent)
warning = ColorProperty() self.background = parse(background)
error = ColorProperty() self.surface = parse(surface)
success = ColorProperty() self.panel = parse(panel)
accent = ColorProperty() self._dark = dark
system = ColorProperty() self._luminosity_spread = luminosity_spread
surface = ColorProperty() self._text_alpha = text_alpha
background = ColorProperty()
dark_surface = ColorProperty()
dark_background = ColorProperty()
panel = ColorProperty()
@property @property
def shades(self) -> Iterable[str]: def shades(self) -> Iterable[str]:
@@ -123,12 +89,7 @@ class ColorSystem:
else: else:
yield color yield color
def generate( def generate(self) -> dict[str, str]:
self,
dark: bool = False,
luminosity_spread: float = 0.15,
text_alpha: float = 0.9,
) -> dict[str, str]:
"""Generate a mapping of color name on to a CSS color. """Generate a mapping of color name on to a CSS color.
Args: Args:
@@ -148,22 +109,20 @@ class ColorSystem:
error = self.error or secondary error = self.error or secondary
success = self.success or secondary success = self.success or secondary
accent = self.accent or primary accent = self.accent or primary
system = self.system or accent
light_background = self.background or Color.parse(DEFAULT_LIGHT_BACKGROUND) dark = self._dark
dark_background = self.dark_background or Color.parse(DEFAULT_DARK_BACKGROUND) luminosity_spread = self._luminosity_spread
text_alpha = self._text_alpha
light_surface = self.surface or Color.parse(DEFAULT_LIGHT_SURFACE) if dark:
dark_surface = self.dark_surface or Color.parse(DEFAULT_DARK_SURFACE) background = self.background or Color.parse(DEFAULT_DARK_BACKGROUND)
surface = self.surface or Color.parse(DEFAULT_DARK_SURFACE)
else:
background = self.background or Color.parse(DEFAULT_LIGHT_BACKGROUND)
surface = self.surface or Color.parse(DEFAULT_LIGHT_SURFACE)
background = dark_background if dark else light_background
surface = dark_surface if dark else light_surface
text = background.get_contrast_text(1.0)
if self.panel is None: if self.panel is None:
panel = background.blend( panel = surface.blend(primary, luminosity_spread)
text, luminosity_spread if dark else luminosity_spread
)
else: else:
panel = self.panel panel = self.panel
@@ -199,7 +158,6 @@ class ColorSystem:
("error", error), ("error", error),
("success", success), ("success", success),
("accent", accent), ("accent", accent),
("system", system),
] ]
# Colors names that have a dark variant # Colors names that have a dark variant
@@ -207,7 +165,7 @@ class ColorSystem:
for name, color in COLORS: for name, color in COLORS:
is_dark_shade = dark and name in DARK_SHADES is_dark_shade = dark and name in DARK_SHADES
spread = luminosity_spread / 1.5 if is_dark_shade else luminosity_spread spread = luminosity_spread
if name == "panel": if name == "panel":
spread /= 2 spread /= 2
for shade_name, luminosity_delta in luminosity_range(spread): for shade_name, luminosity_delta in luminosity_range(spread):
@@ -231,27 +189,39 @@ class ColorSystem:
return colors return colors
def __rich__(self) -> Table:
@group()
def make_shades(dark: bool):
colors = self.generate(dark)
for name in self.shades:
background = colors[name]
foreground = colors[f"text-{name}"]
text = Text(f"{background} ", style=f"{foreground} on {background}")
for fade in range(3):
foreground = colors[
f"text-{name}-fade-{fade}" if fade else f"text-{name}"
]
text.append(f"{name} ", style=f"{foreground} on {background}")
yield Padding(text, 1, style=f"{foreground} on {background}") def show_design(light: ColorSystem, dark: ColorSystem) -> Table:
"""Generate a renderable to show color systems.
table = Table(box=None, expand=True) Args:
table.add_column("Light", justify="center") light (ColorSystem): Light ColorSystem.
table.add_column("Dark", justify="center") dark (ColorSystem): Dark ColorSystem
table.add_row(make_shades(False), make_shades(True))
return table Returns:
Table: Table showing all colors.
"""
@group()
def make_shades(system: ColorSystem):
colors = system.generate()
for name in system.shades:
background = colors[name]
foreground = colors[f"text-{name}"]
text = Text(f"{background} ", style=f"{foreground} on {background}")
for fade in range(3):
foreground = colors[
f"text-{name}-fade-{fade}" if fade else f"text-{name}"
]
text.append(f"{name} ", style=f"{foreground} on {background}")
yield Padding(text, 1, style=f"{foreground} on {background}")
table = Table(box=None, expand=True)
table.add_column("Light", justify="center")
table.add_column("Dark", justify="center")
table.add_row(make_shades(light), make_shades(dark))
return table
if __name__ == "__main__": if __name__ == "__main__":
@@ -259,4 +229,4 @@ if __name__ == "__main__":
from rich import print from rich import print
print(DEFAULT_COLORS) print(show_design(DEFAULT_COLORS["light"], DEFAULT_COLORS["dark"]))

View File

@@ -71,9 +71,9 @@ class Widget(DOMNode):
CSS = """ CSS = """
Widget{ Widget{
scrollbar-background: $panel-darken-2; scrollbar-background: $panel-darken-1;
scrollbar-background-hover: $panel-darken-3; scrollbar-background-hover: $panel-darken-2;
scrollbar-color: $system; scrollbar-color: $primary-lighten-1;
scrollbar-color-active: $warning-darken-1; scrollbar-color-active: $warning-darken-1;
scrollbar-size-vertical: 2; scrollbar-size-vertical: 2;
scrollbar-size-horizontal: 1; scrollbar-size-horizontal: 1;

View File

@@ -33,11 +33,11 @@ class Button(Widget, can_focus=True):
Button { Button {
width: auto; width: auto;
height: 3; height: 3;
background: $primary; background: $panel;
color: $text-primary; color: $text-panel;
border: none; border: none;
border-top: tall $primary-lighten-2; border-top: tall $panel-lighten-2;
border-bottom: tall $primary-darken-3; border-bottom: tall $panel-darken-3;
content-align: center middle; content-align: center middle;
text-style: bold; text-style: bold;
} }
@@ -47,15 +47,15 @@ class Button(Widget, can_focus=True):
} }
Button:hover { Button:hover {
border-top: tall $primary-lighten-1; border-top: tall $panel-lighten-1;
background: $primary-darken-2; background: $panel-darken-2;
color: $text-primary-darken-2; color: $text-panel-darken-2;
} }
Button.-active { Button.-active {
background: $primary; background: $panel;
border-bottom: tall $primary-lighten-2; border-bottom: tall $panel-lighten-2;
border-top: tall $primary-darken-2; border-top: tall $panel-darken-2;
} }

View File

@@ -113,13 +113,13 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
} }
DataTable > .datatable--header { DataTable > .datatable--header {
text-style: bold; text-style: bold;
background: $primary-darken-1; background: $primary;
color: $text-primary-darken-1; color: $text-primary;
} }
DataTable > .datatable--fixed { DataTable > .datatable--fixed {
text-style: bold; text-style: bold;
background: $primary-darken-2; background: $primary;
color: $text-primary-darken-2; color: $text-primary;
} }
DataTable > .datatable--odd-row { DataTable > .datatable--odd-row {

View File

@@ -6,7 +6,7 @@ import pytest
from textual.color import Color from textual.color import Color
from textual.css._help_renderables import HelpText from textual.css._help_renderables import HelpText
from textual.css.stylesheet import Stylesheet, StylesheetParseError, CssSource from textual.css.stylesheet import Stylesheet, StylesheetParseError, CssSource
from textual.css.tokenizer import TokenizeError from textual.css.tokenizer import TokenError
from textual.dom import DOMNode from textual.dom import DOMNode
from textual.geometry import Spacing from textual.geometry import Spacing
from textual.widget import Widget from textual.widget import Widget
@@ -145,7 +145,7 @@ def test_stylesheet_apply_user_css_over_widget_css():
["ansi_dark_cyan", pytest.raises(StylesheetParseError), None], ["ansi_dark_cyan", pytest.raises(StylesheetParseError), None],
["red 4", pytest.raises(StylesheetParseError), None], # space in it ["red 4", pytest.raises(StylesheetParseError), None], # space in it
["1", pytest.raises(StylesheetParseError), None], # invalid value ["1", pytest.raises(StylesheetParseError), None], # invalid value
["()", pytest.raises(TokenizeError), None], # invalid tokens ["()", pytest.raises(TokenError), None], # invalid tokens
], ],
) )
def test_color_property_parsing(css_value, expectation, expected_color): def test_color_property_parsing(css_value, expectation, expected_color):

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import pytest import pytest
from textual.css.tokenize import tokenize from textual.css.tokenize import tokenize
from textual.css.tokenizer import Token, TokenizeError from textual.css.tokenizer import Token, TokenError
VALID_VARIABLE_NAMES = [ VALID_VARIABLE_NAMES = [
"warning-text", "warning-text",
@@ -331,7 +331,7 @@ def test_variable_declaration_no_semicolon():
def test_variable_declaration_invalid_value(): def test_variable_declaration_invalid_value():
css = "$x:(@$12x)" css = "$x:(@$12x)"
with pytest.raises(TokenizeError): with pytest.raises(TokenError):
list(tokenize(css, "")) list(tokenize(css, ""))