mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
improved color harmony
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
|
||||
|
||||
* {
|
||||
* {
|
||||
transition: color 300ms linear, background 300ms linear;
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ DataTable {
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
color: $text-primary;
|
||||
background: $primary-background;
|
||||
color: $text-panel;
|
||||
background: $panel;
|
||||
dock: side;
|
||||
width: 30;
|
||||
offset-x: -100%;
|
||||
@@ -43,33 +43,35 @@ DataTable {
|
||||
}
|
||||
|
||||
#sidebar .title {
|
||||
height: 3;
|
||||
background: $primary-background-darken-2;
|
||||
color: $text-primary-darken-2 ;
|
||||
border-right: outer $primary-darken-3;
|
||||
height: 1;
|
||||
background: $primary-background-darken-1;
|
||||
color: $text-primary-background-darken-1;
|
||||
border-right: wide $background;
|
||||
content-align: center middle;
|
||||
}
|
||||
|
||||
#sidebar .user {
|
||||
height: 8;
|
||||
background: $primary-background-darken-1;
|
||||
color: $text-primary-darken-1;
|
||||
border-right: outer $primary-background-darken-3;
|
||||
background: $panel-darken-1;
|
||||
color: $text-panel-darken-1;
|
||||
border-right: wide $background;
|
||||
content-align: center middle;
|
||||
}
|
||||
|
||||
#sidebar .content {
|
||||
background: $primary-background;
|
||||
color: $text-primary-background;
|
||||
border-right: outer $primary-background-darken-3;
|
||||
background: $surface;
|
||||
color: $text-surface;
|
||||
border-right: wide $background;
|
||||
content-align: center middle;
|
||||
}
|
||||
|
||||
#header {
|
||||
color: $text-secondary-background-darken-1;
|
||||
background: $secondary-background-darken-1;
|
||||
height: 3;
|
||||
color: $text-secondary-background;
|
||||
background: $secondary-background;
|
||||
height: 1;
|
||||
content-align: center middle;
|
||||
|
||||
|
||||
}
|
||||
|
||||
#content {
|
||||
@@ -90,7 +92,7 @@ Tweet {
|
||||
layout: vertical;
|
||||
/* border: outer $primary; */
|
||||
padding: 1;
|
||||
border: wide $panel-darken-2;
|
||||
border: wide $panel;
|
||||
overflow: auto;
|
||||
/* scrollbar-gutter: stable; */
|
||||
align-horizontal: center;
|
||||
@@ -175,16 +177,16 @@ Tweet.scroll-horizontal TweetBody {
|
||||
|
||||
OptionItem {
|
||||
height: 3;
|
||||
background: $primary-background;
|
||||
border-right: outer $primary-background-darken-2;
|
||||
background: $panel;
|
||||
border-right: wide $background;
|
||||
border-left: blank;
|
||||
content-align: center middle;
|
||||
}
|
||||
|
||||
OptionItem:hover {
|
||||
height: 3;
|
||||
color: $secondary;
|
||||
background: $primary-background-darken-1;
|
||||
color: $text-primary;
|
||||
background: $primary-darken-1;
|
||||
/* border-top: hkey $accent2-darken-3;
|
||||
border-bottom: hkey $accent2-darken-3; */
|
||||
text-style: bold;
|
||||
@@ -196,8 +198,8 @@ Error {
|
||||
height:3;
|
||||
background: $error;
|
||||
color: $text-error;
|
||||
border-top: wide $error-darken-1;
|
||||
border-bottom: wide $error-darken-1;
|
||||
border-top: tall $error-darken-1;
|
||||
border-bottom: tall $error-darken-1;
|
||||
|
||||
padding: 0;
|
||||
text-style: bold;
|
||||
@@ -209,8 +211,8 @@ Warning {
|
||||
height:3;
|
||||
background: $warning;
|
||||
color: $text-warning-fade-1;
|
||||
border-top: wide $warning-darken-1;
|
||||
border-bottom: wide $warning-darken-1;
|
||||
border-top: tall $warning-darken-1;
|
||||
border-bottom: tall $warning-darken-1;
|
||||
|
||||
text-style: bold;
|
||||
align-horizontal: center;
|
||||
@@ -218,7 +220,7 @@ Warning {
|
||||
|
||||
Success {
|
||||
width: 100%;
|
||||
width:90%;
|
||||
|
||||
height:auto;
|
||||
box-sizing: border-box;
|
||||
background: $success;
|
||||
@@ -227,7 +229,7 @@ Success {
|
||||
border-top: hkey $success-darken-1;
|
||||
border-bottom: hkey $success-darken-1;
|
||||
|
||||
text-style: bold underline;
|
||||
text-style: bold ;
|
||||
|
||||
align-horizontal: center;
|
||||
}
|
||||
|
||||
@@ -179,21 +179,21 @@ app = BasicApp()
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
|
||||
from textual.geometry import Region
|
||||
from textual.color import Color
|
||||
# from textual.geometry import Region
|
||||
# from textual.color import Color
|
||||
|
||||
print(Region.intersection.cache_info())
|
||||
print(Region.overlaps.cache_info())
|
||||
print(Region.union.cache_info())
|
||||
print(Region.split_vertical.cache_info())
|
||||
print(Region.__contains__.cache_info())
|
||||
from textual.css.scalar import Scalar
|
||||
# print(Region.intersection.cache_info())
|
||||
# print(Region.overlaps.cache_info())
|
||||
# print(Region.union.cache_info())
|
||||
# print(Region.split_vertical.cache_info())
|
||||
# print(Region.__contains__.cache_info())
|
||||
# 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.cells import cached_cell_len
|
||||
# from rich.style import Style
|
||||
# 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())
|
||||
|
||||
@@ -80,16 +80,26 @@ _ASYNCIO_GET_EVENT_LOOP_IS_DEPRECATED = sys.version_info >= (3, 10, 0)
|
||||
LayoutDefinition = "dict[str, Any]"
|
||||
|
||||
|
||||
DEFAULT_COLORS = ColorSystem(
|
||||
primary="#2A4E6E",
|
||||
DEFAULT_COLORS = {
|
||||
"dark": ColorSystem(
|
||||
primary="#004578",
|
||||
secondary="#ffa62b",
|
||||
warning="#ffa62b",
|
||||
error="#ba3c5b",
|
||||
success="#4EBF71",
|
||||
accent="#1A75B4",
|
||||
system="#5a4599",
|
||||
dark_surface="#292929",
|
||||
)
|
||||
accent="#0178D4",
|
||||
dark=True,
|
||||
),
|
||||
"light": ColorSystem(
|
||||
primary="#004578",
|
||||
secondary="#ffa62b",
|
||||
warning="#ffa62b",
|
||||
error="#ba3c5b",
|
||||
success="#4EBF71",
|
||||
accent="#0178D4",
|
||||
dark=False,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
ComposeResult = Iterable[Widget]
|
||||
@@ -338,7 +348,7 @@ class App(Generic[ReturnType], DOMNode):
|
||||
Returns:
|
||||
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
|
||||
|
||||
def watch_dark(self, dark: bool) -> None:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from rich.console import ConsoleOptions, Console
|
||||
from rich.console import ConsoleOptions, Console, RenderResult
|
||||
from rich.traceback import Traceback
|
||||
|
||||
from ._help_renderables import HelpText
|
||||
@@ -15,10 +15,6 @@ class DeclarationError(Exception):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class UnresolvedVariableError(NameError):
|
||||
pass
|
||||
|
||||
|
||||
class StyleTypeError(TypeError):
|
||||
pass
|
||||
|
||||
@@ -35,7 +31,9 @@ class StyleValueError(ValueError):
|
||||
super().__init__(*args)
|
||||
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__)
|
||||
if self.help_text is not None:
|
||||
yield ""
|
||||
|
||||
@@ -2,11 +2,11 @@ from __future__ import annotations
|
||||
|
||||
from functools import lru_cache
|
||||
from pathlib import PurePath
|
||||
from typing import Iterator, Iterable
|
||||
from typing import Iterator, Iterable, NoReturn, Sequence
|
||||
|
||||
from rich import print
|
||||
|
||||
from textual.css.errors import UnresolvedVariableError
|
||||
from textual.css.tokenizer import TokenError
|
||||
from textual.css.types import Specificity3
|
||||
from ._styles_builder import StylesBuilder, DeclarationError
|
||||
from .model import (
|
||||
@@ -18,6 +18,7 @@ from .model import (
|
||||
SelectorType,
|
||||
)
|
||||
from .styles import Styles
|
||||
from ..suggestions import get_suggestion
|
||||
from .tokenize import tokenize, tokenize_declarations, Token, tokenize_values
|
||||
from .tokenizer import EOFError, ReferencedBy
|
||||
|
||||
@@ -209,12 +210,20 @@ def parse_declarations(css: str, path: str) -> Styles:
|
||||
return styles_builder.styles
|
||||
|
||||
|
||||
def _unresolved(
|
||||
variable_name: str, location: tuple[int, int]
|
||||
) -> UnresolvedVariableError:
|
||||
line_idx, col_idx = location
|
||||
return UnresolvedVariableError(
|
||||
f"reference to undefined variable '${variable_name}' at line {line_idx + 1}, column {col_idx + 1}."
|
||||
def _unresolved(variable_name: str, variables: Sequence[str], token: Token) -> NoReturn:
|
||||
|
||||
message = f"reference to undefined variable '${variable_name}'"
|
||||
|
||||
suggested_variable = get_suggestion(variable_name, variables)
|
||||
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:
|
||||
raise _unresolved(
|
||||
variable_name=ref_name, location=token.location
|
||||
)
|
||||
_unresolved(ref_name, variables.keys(), token)
|
||||
else:
|
||||
variables.setdefault(variable_name, []).append(token)
|
||||
yield token
|
||||
@@ -306,7 +313,7 @@ def substitute_references(
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise _unresolved(variable_name=variable_name, location=token.location)
|
||||
_unresolved(variable_name, variables.keys(), token)
|
||||
else:
|
||||
yield token
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ from .model import RuleSet
|
||||
from .parse import parse
|
||||
from .styles import RulesMap, Styles
|
||||
from .tokenize import tokenize_values, Token
|
||||
from .tokenizer import TokenizeError
|
||||
from .tokenizer import TokenError
|
||||
from .types import Specificity3, Specificity4
|
||||
from ..dom import DOMNode
|
||||
from .. import messages
|
||||
@@ -198,7 +198,7 @@ class Stylesheet:
|
||||
is_default_rules=is_default_rules,
|
||||
)
|
||||
)
|
||||
except TokenizeError:
|
||||
except TokenError:
|
||||
raise
|
||||
except Exception as error:
|
||||
raise StylesheetError(f"failed to parse css; {error}")
|
||||
|
||||
@@ -15,28 +15,43 @@ from rich.text import Text
|
||||
from .._loop import loop_last
|
||||
|
||||
|
||||
class TokenizeError(Exception):
|
||||
class TokenError(Exception):
|
||||
"""Error raised when the CSS cannot be tokenized (syntax error)."""
|
||||
|
||||
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:
|
||||
"""
|
||||
Args:
|
||||
path (str): Path to source or "<object>" if source is parsed from a literal.
|
||||
code (str): The code being parsed.
|
||||
line_no (int): Line number of the error.
|
||||
col_no (int): Column number of the error.
|
||||
start (tuple[int, int]): Line number of 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.code = code
|
||||
self.line_no = line_no
|
||||
self.col_no = col_no
|
||||
self.start = start
|
||||
self.end = end or start
|
||||
super().__init__(message)
|
||||
|
||||
@classmethod
|
||||
def _get_snippet(cls, code: str, line_no: int) -> Panel:
|
||||
def _get_snippet(self) -> Panel:
|
||||
"""Get a short snippet of code around a given line number.
|
||||
|
||||
Args:
|
||||
@@ -46,9 +61,10 @@ class TokenizeError(Exception):
|
||||
Returns:
|
||||
Panel: A renderable.
|
||||
"""
|
||||
line_no = self.start[0]
|
||||
# TODO: Highlight column number
|
||||
syntax = Syntax(
|
||||
code,
|
||||
self.code,
|
||||
lexer="scss",
|
||||
theme="ansi_light",
|
||||
line_numbers=True,
|
||||
@@ -56,6 +72,7 @@ class TokenizeError(Exception):
|
||||
line_range=(max(0, line_no - 2), line_no + 2),
|
||||
highlight_lines={line_no},
|
||||
)
|
||||
syntax.stylize_range("reverse bold", self.start, self.end)
|
||||
return Panel(syntax, border_style="red")
|
||||
|
||||
def __rich__(self) -> RenderableType:
|
||||
@@ -63,14 +80,12 @@ class TokenizeError(Exception):
|
||||
errors: list[RenderableType] = []
|
||||
|
||||
message = str(self)
|
||||
errors.append(Text(" Tokenizer error in stylesheet:", style="bold red"))
|
||||
errors.append(Text(" Error in stylesheet:", style="bold red"))
|
||||
|
||||
errors.append(
|
||||
highlighter(
|
||||
f" {self.path or '<unknown>'}:{self.line_no + 1}:{self.col_no + 1}"
|
||||
)
|
||||
)
|
||||
errors.append(self._get_snippet(self.code, self.line_no + 1))
|
||||
line_no, col_no = self.start
|
||||
|
||||
errors.append(highlighter(f" {self.path or '<unknown>'}:{line_no}:{col_no}"))
|
||||
errors.append(self._get_snippet())
|
||||
final_message = ""
|
||||
for is_last, message_part in loop_last(message.split(";")):
|
||||
end = "" if is_last else "\n"
|
||||
@@ -80,7 +95,7 @@ class TokenizeError(Exception):
|
||||
return Group(*errors)
|
||||
|
||||
|
||||
class EOFError(TokenizeError):
|
||||
class EOFError(TokenError):
|
||||
pass
|
||||
|
||||
|
||||
@@ -120,6 +135,18 @@ class Token(NamedTuple):
|
||||
location: tuple[int, int]
|
||||
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":
|
||||
"""Return a copy of the Token, with reference information attached.
|
||||
This is used for variable substitution, where a variable reference
|
||||
@@ -161,19 +188,28 @@ class Tokenizer:
|
||||
col_no = self.col_no
|
||||
if line_no >= len(self.lines):
|
||||
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:
|
||||
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]
|
||||
match = expect.match(line, col_no)
|
||||
if match is None:
|
||||
raise TokenizeError(
|
||||
raise TokenError(
|
||||
self.path,
|
||||
self.code,
|
||||
line_no,
|
||||
col_no,
|
||||
(line_no, col_no),
|
||||
"expected " + ", ".join(name.upper() for name in expect.names),
|
||||
)
|
||||
iter_groups = iter(match.groups())
|
||||
|
||||
@@ -9,39 +9,18 @@ from rich.text import Text
|
||||
|
||||
from .color import Color, WHITE
|
||||
|
||||
|
||||
NUMBER_OF_SHADES = 3
|
||||
|
||||
# Where no content exists
|
||||
DEFAULT_DARK_BACKGROUND = "#000000"
|
||||
# What text usually goes on top off
|
||||
DEFAULT_DARK_SURFACE = "#121212"
|
||||
DEFAULT_DARK_SURFACE = "#292929"
|
||||
|
||||
DEFAULT_LIGHT_SURFACE = "#f5f5f5"
|
||||
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:
|
||||
"""Defines a standard set of colors and variations for building a UI.
|
||||
|
||||
@@ -63,7 +42,6 @@ class ColorSystem:
|
||||
"error",
|
||||
"success",
|
||||
"accent",
|
||||
"system",
|
||||
]
|
||||
|
||||
def __init__(
|
||||
@@ -74,42 +52,30 @@ class ColorSystem:
|
||||
error: str | None = None,
|
||||
success: str | None = None,
|
||||
accent: str | None = None,
|
||||
system: str | None = None,
|
||||
background: str | None = None,
|
||||
surface: str | None = None,
|
||||
dark_background: str | None = None,
|
||||
dark_surface: str | None = None,
|
||||
panel: str | None = None,
|
||||
dark: bool = False,
|
||||
luminosity_spread: float = 0.15,
|
||||
text_alpha: float = 0.95,
|
||||
):
|
||||
self._primary = primary
|
||||
self._secondary = secondary
|
||||
self._warning = warning
|
||||
self._error = error
|
||||
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
|
||||
def parse(color: str | None) -> Color | None:
|
||||
if color is None:
|
||||
return None
|
||||
return Color.parse(color)
|
||||
|
||||
@property
|
||||
def primary(self) -> Color:
|
||||
"""Get the primary color."""
|
||||
return Color.parse(self._primary)
|
||||
|
||||
secondary = ColorProperty()
|
||||
warning = ColorProperty()
|
||||
error = ColorProperty()
|
||||
success = ColorProperty()
|
||||
accent = ColorProperty()
|
||||
system = ColorProperty()
|
||||
surface = ColorProperty()
|
||||
background = ColorProperty()
|
||||
dark_surface = ColorProperty()
|
||||
dark_background = ColorProperty()
|
||||
panel = ColorProperty()
|
||||
self.primary = Color.parse(primary)
|
||||
self.secondary = parse(secondary)
|
||||
self.warning = parse(warning)
|
||||
self.error = parse(error)
|
||||
self.success = parse(success)
|
||||
self.accent = parse(accent)
|
||||
self.background = parse(background)
|
||||
self.surface = parse(surface)
|
||||
self.panel = parse(panel)
|
||||
self._dark = dark
|
||||
self._luminosity_spread = luminosity_spread
|
||||
self._text_alpha = text_alpha
|
||||
|
||||
@property
|
||||
def shades(self) -> Iterable[str]:
|
||||
@@ -123,12 +89,7 @@ class ColorSystem:
|
||||
else:
|
||||
yield color
|
||||
|
||||
def generate(
|
||||
self,
|
||||
dark: bool = False,
|
||||
luminosity_spread: float = 0.15,
|
||||
text_alpha: float = 0.9,
|
||||
) -> dict[str, str]:
|
||||
def generate(self) -> dict[str, str]:
|
||||
"""Generate a mapping of color name on to a CSS color.
|
||||
|
||||
Args:
|
||||
@@ -148,22 +109,20 @@ class ColorSystem:
|
||||
error = self.error or secondary
|
||||
success = self.success or secondary
|
||||
accent = self.accent or primary
|
||||
system = self.system or accent
|
||||
|
||||
light_background = self.background or Color.parse(DEFAULT_LIGHT_BACKGROUND)
|
||||
dark_background = self.dark_background or Color.parse(DEFAULT_DARK_BACKGROUND)
|
||||
dark = self._dark
|
||||
luminosity_spread = self._luminosity_spread
|
||||
text_alpha = self._text_alpha
|
||||
|
||||
light_surface = self.surface or Color.parse(DEFAULT_LIGHT_SURFACE)
|
||||
dark_surface = self.dark_surface or Color.parse(DEFAULT_DARK_SURFACE)
|
||||
if dark:
|
||||
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:
|
||||
panel = background.blend(
|
||||
text, luminosity_spread if dark else luminosity_spread
|
||||
)
|
||||
panel = surface.blend(primary, luminosity_spread)
|
||||
else:
|
||||
panel = self.panel
|
||||
|
||||
@@ -199,7 +158,6 @@ class ColorSystem:
|
||||
("error", error),
|
||||
("success", success),
|
||||
("accent", accent),
|
||||
("system", system),
|
||||
]
|
||||
|
||||
# Colors names that have a dark variant
|
||||
@@ -207,7 +165,7 @@ class ColorSystem:
|
||||
|
||||
for name, color in COLORS:
|
||||
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":
|
||||
spread /= 2
|
||||
for shade_name, luminosity_delta in luminosity_range(spread):
|
||||
@@ -231,11 +189,23 @@ class ColorSystem:
|
||||
|
||||
return colors
|
||||
|
||||
def __rich__(self) -> Table:
|
||||
|
||||
def show_design(light: ColorSystem, dark: ColorSystem) -> Table:
|
||||
"""Generate a renderable to show color systems.
|
||||
|
||||
Args:
|
||||
light (ColorSystem): Light ColorSystem.
|
||||
dark (ColorSystem): Dark ColorSystem
|
||||
|
||||
Returns:
|
||||
Table: Table showing all colors.
|
||||
|
||||
"""
|
||||
|
||||
@group()
|
||||
def make_shades(dark: bool):
|
||||
colors = self.generate(dark)
|
||||
for name in self.shades:
|
||||
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}")
|
||||
@@ -250,7 +220,7 @@ class ColorSystem:
|
||||
table = Table(box=None, expand=True)
|
||||
table.add_column("Light", justify="center")
|
||||
table.add_column("Dark", justify="center")
|
||||
table.add_row(make_shades(False), make_shades(True))
|
||||
table.add_row(make_shades(light), make_shades(dark))
|
||||
return table
|
||||
|
||||
|
||||
@@ -259,4 +229,4 @@ if __name__ == "__main__":
|
||||
|
||||
from rich import print
|
||||
|
||||
print(DEFAULT_COLORS)
|
||||
print(show_design(DEFAULT_COLORS["light"], DEFAULT_COLORS["dark"]))
|
||||
|
||||
@@ -71,9 +71,9 @@ class Widget(DOMNode):
|
||||
|
||||
CSS = """
|
||||
Widget{
|
||||
scrollbar-background: $panel-darken-2;
|
||||
scrollbar-background-hover: $panel-darken-3;
|
||||
scrollbar-color: $system;
|
||||
scrollbar-background: $panel-darken-1;
|
||||
scrollbar-background-hover: $panel-darken-2;
|
||||
scrollbar-color: $primary-lighten-1;
|
||||
scrollbar-color-active: $warning-darken-1;
|
||||
scrollbar-size-vertical: 2;
|
||||
scrollbar-size-horizontal: 1;
|
||||
|
||||
@@ -33,11 +33,11 @@ class Button(Widget, can_focus=True):
|
||||
Button {
|
||||
width: auto;
|
||||
height: 3;
|
||||
background: $primary;
|
||||
color: $text-primary;
|
||||
background: $panel;
|
||||
color: $text-panel;
|
||||
border: none;
|
||||
border-top: tall $primary-lighten-2;
|
||||
border-bottom: tall $primary-darken-3;
|
||||
border-top: tall $panel-lighten-2;
|
||||
border-bottom: tall $panel-darken-3;
|
||||
content-align: center middle;
|
||||
text-style: bold;
|
||||
}
|
||||
@@ -47,15 +47,15 @@ class Button(Widget, can_focus=True):
|
||||
}
|
||||
|
||||
Button:hover {
|
||||
border-top: tall $primary-lighten-1;
|
||||
background: $primary-darken-2;
|
||||
color: $text-primary-darken-2;
|
||||
border-top: tall $panel-lighten-1;
|
||||
background: $panel-darken-2;
|
||||
color: $text-panel-darken-2;
|
||||
}
|
||||
|
||||
Button.-active {
|
||||
background: $primary;
|
||||
border-bottom: tall $primary-lighten-2;
|
||||
border-top: tall $primary-darken-2;
|
||||
background: $panel;
|
||||
border-bottom: tall $panel-lighten-2;
|
||||
border-top: tall $panel-darken-2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -113,13 +113,13 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
}
|
||||
DataTable > .datatable--header {
|
||||
text-style: bold;
|
||||
background: $primary-darken-1;
|
||||
color: $text-primary-darken-1;
|
||||
background: $primary;
|
||||
color: $text-primary;
|
||||
}
|
||||
DataTable > .datatable--fixed {
|
||||
text-style: bold;
|
||||
background: $primary-darken-2;
|
||||
color: $text-primary-darken-2;
|
||||
background: $primary;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
DataTable > .datatable--odd-row {
|
||||
|
||||
@@ -6,7 +6,7 @@ import pytest
|
||||
from textual.color import Color
|
||||
from textual.css._help_renderables import HelpText
|
||||
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.geometry import Spacing
|
||||
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],
|
||||
["red 4", pytest.raises(StylesheetParseError), None], # space in it
|
||||
["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):
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import pytest
|
||||
|
||||
from textual.css.tokenize import tokenize
|
||||
from textual.css.tokenizer import Token, TokenizeError
|
||||
from textual.css.tokenizer import Token, TokenError
|
||||
|
||||
VALID_VARIABLE_NAMES = [
|
||||
"warning-text",
|
||||
@@ -331,7 +331,7 @@ def test_variable_declaration_no_semicolon():
|
||||
|
||||
def test_variable_declaration_invalid_value():
|
||||
css = "$x:(@$12x)"
|
||||
with pytest.raises(TokenizeError):
|
||||
with pytest.raises(TokenError):
|
||||
list(tokenize(css, ""))
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user