Skip to content

Commit

Permalink
Themes and test fixes. Reduce dependence on App.dark
Browse files Browse the repository at this point in the history
  • Loading branch information
darrenburns committed Oct 3, 2024
1 parent 57f1285 commit edcf706
Show file tree
Hide file tree
Showing 18 changed files with 1,040 additions and 373 deletions.
9 changes: 5 additions & 4 deletions src/textual/_text_area_theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@

from rich.style import Style

from textual.app import DEFAULT_COLORS
from textual.color import Color
from textual.design import DEFAULT_DARK_SURFACE

if TYPE_CHECKING:
from textual.widgets import TextArea
Expand Down Expand Up @@ -92,9 +90,12 @@ def apply_css(self, text_area: TextArea) -> None:
if self.base_style.color is None:
self.base_style = Style(color="#f3f3f3", bgcolor=self.base_style.bgcolor)

app = text_area.app
app_theme = app.get_theme(app.theme)

if self.base_style.bgcolor is None:
self.base_style = Style(
color=self.base_style.color, bgcolor=DEFAULT_DARK_SURFACE
color=self.base_style.color, bgcolor=app_theme.surface
)

configured = self._theme_configured_attributes.__contains__
Expand Down Expand Up @@ -148,7 +149,7 @@ def apply_css(self, text_area: TextArea) -> None:
self.selection_style = selection_style
else:
selection_background_color = background_color.blend(
DEFAULT_COLORS["dark"].primary, factor=0.75
app_theme.primary, factor=0.75
)
self.selection_style = Style.from_color(
bgcolor=selection_background_color.rich_color
Expand Down
69 changes: 57 additions & 12 deletions src/textual/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
SystemModalScreen,
)
from textual.signal import Signal
from textual.theme import BUILTIN_THEMES, Theme
from textual.timer import Timer
from textual.widget import AwaitMount, Widget
from textual.widgets._toast import ToastRack
Expand Down Expand Up @@ -187,6 +188,7 @@
),
}


ComposeResult = Iterable[Widget]
RenderResult = RenderableType

Expand Down Expand Up @@ -544,6 +546,11 @@ def __init__(
super().__init__()
self.features: frozenset[FeatureFlag] = parse_features(os.getenv("TEXTUAL", ""))

self._registered_themes: dict[str, Theme] = {}
"""Themes that have been registered with the App using `App.register_theme`.
This excludes the built-in themes."""

ansi_theme = self.ansi_theme_dark if self.dark else self.ansi_theme_light
self.set_reactive(App.ansi_color, ansi_color)
self._filters: list[LineFilter] = [
Expand Down Expand Up @@ -646,9 +653,10 @@ def __init__(

self._refresh_required = False

self.design = DEFAULT_COLORS

self._css_has_errors = False

# Note that the theme must be set *before* self.get_css_variables() is called
# to ensure that the variables are retrieved from the currently active theme.
self.stylesheet = Stylesheet(variables=self.get_css_variables())

css_path = css_path or self.CSS_PATH
Expand Down Expand Up @@ -898,8 +906,9 @@ def get_pseudo_classes(self) -> Iterable[str]:
Returns:
Names of the pseudo classes.
"""
app_theme = self.get_theme(self.theme)
yield "focus" if self.app_focus else "blur"
yield "dark" if self.dark else "light"
yield "dark" if app_theme.dark else "light"
if self.is_inline:
yield "inline"
if self.ansi_color:
Expand Down Expand Up @@ -1166,14 +1175,47 @@ def get_css_variables(self) -> dict[str, str]:
Returns:
A mapping of variable name to value.
"""
theme = self.get_theme(self.theme)
variables = theme.to_color_system().generate()
return variables

if self.dark:
design = self.design["dark"]
else:
design = self.design["light"]
def get_theme(self, theme_name: str) -> Theme:
"""Get a theme by name.
variables = design.generate()
return variables
Args:
theme_name: The name of the theme to get.
Returns:
A Theme instance.
"""
return self.available_themes[theme_name]

def register_theme(self, theme: Theme) -> None:
"""Register a theme with the app.
A theme must be registered before it is set as the `App.theme`.
Args:
theme: The theme to register.
"""
self._registered_themes[theme.name] = theme

@property
def available_themes(self) -> dict[str, Theme]:
"""All available themes (all built-in themes plus any that have been registered)."""
return {**BUILTIN_THEMES, **self._registered_themes}

def watch_theme(self, theme_name: str) -> None:
"""Apply a theme to the application.
This method is called when the theme reactive attribute is set.
"""
theme = self.get_theme(theme_name)
dark = theme.dark
self.set_class(dark, "-dark-mode", update=False)
self.set_class(not dark, "-light-mode", update=False)
self._refresh_truecolor_filter(self.ansi_theme)
self.call_next(self.refresh_css)

def watch_dark(self, dark: bool) -> None:
"""Watches the dark bool.
Expand All @@ -1187,12 +1229,14 @@ def watch_dark(self, dark: bool) -> None:
self.call_next(self.refresh_css)

def watch_ansi_theme_dark(self, theme: TerminalTheme) -> None:
if self.dark:
app_theme = self.get_theme(self.theme)
if app_theme.dark:
self._refresh_truecolor_filter(theme)
self.call_next(self.refresh_css)

def watch_ansi_theme_light(self, theme: TerminalTheme) -> None:
if not self.dark:
app_theme = self.get_theme(self.theme)
if not app_theme.dark:
self._refresh_truecolor_filter(theme)
self.call_next(self.refresh_css)

Expand All @@ -1203,7 +1247,8 @@ def ansi_theme(self) -> TerminalTheme:
Defines how colors defined as ANSI (e.g. `magenta`) inside Rich renderables
are mapped to hex codes.
"""
return self.ansi_theme_dark if self.dark else self.ansi_theme_light
app_theme = self.get_theme(self.theme)
return self.ansi_theme_dark if app_theme.dark else self.ansi_theme_light

def _refresh_truecolor_filter(self, theme: TerminalTheme) -> None:
"""Update the ANSI to Truecolor filter, if available, with a new theme mapping.
Expand Down
Loading

0 comments on commit edcf706

Please sign in to comment.