ThemeProvider

This commit is contained in:
Darren Burns
2024-10-07 14:19:32 +01:00
parent 538b0ffb74
commit 0fa81a33b4
3 changed files with 64 additions and 12 deletions

View File

@@ -122,7 +122,7 @@ from textual.screen import (
SystemModalScreen,
)
from textual.signal import Signal
from textual.theme import BUILTIN_THEMES, Theme
from textual.theme import BUILTIN_THEMES, Theme, ThemeProvider
from textual.timer import Timer
from textual.widget import AwaitMount, Widget
from textual.widgets._toast import ToastRack
@@ -1527,7 +1527,12 @@ class App(Generic[ReturnType], DOMNode):
def action_change_theme(self) -> None:
"""An [action](/guide/actions) to change the current theme."""
self.push_screen(CommandPalette())
self.app.push_screen(
CommandPalette(
[ThemeProvider],
placeholder="Search for themes…",
),
)
def action_screenshot(
self, filename: str | None = None, path: str | None = None
@@ -4138,7 +4143,7 @@ class App(Generic[ReturnType], DOMNode):
def action_command_palette(self) -> None:
"""Show the Textual command palette."""
if self.use_command_palette and not CommandPalette.is_open(self):
self.push_screen(CommandPalette())
self.push_screen(CommandPalette(id="--command-palette"))
def _suspend_signal(self) -> None:
"""Signal that the application is being suspended."""

View File

@@ -597,9 +597,6 @@ class CommandPalette(SystemModalScreen):
_calling_screen: var[Screen[Any] | None] = var(None)
"""A record of the screen that was active when we were called."""
_PALETTE_ID: Final[str] = "--command-palette"
"""The internal ID for the command palette."""
@dataclass
class OptionHighlighted(Message):
"""Posted to App when an option is highlighted in the command palette."""
@@ -618,14 +615,29 @@ class CommandPalette(SystemModalScreen):
option_selected: bool
"""True if an option was selected, False if the palette was closed without selecting an option."""
def __init__(self, providers: ProviderSource | None = None) -> None:
def __init__(
self,
providers: ProviderSource | None = None,
*,
placeholder: str = "Search for commands…",
name: str | None = None,
id: str | None = None,
classes: str | None = None,
) -> None:
"""Initialise the command palette.
Args:
providers: An optional list of providers to use. If None, the providers supplied
in the App or Screen will be used.
placeholder: The placeholder text for the command palette.
"""
super().__init__(id=self._PALETTE_ID)
super().__init__(
id=id,
classes=classes,
name=name,
)
self.add_class("--textual-command-palette")
self._selected_command: DiscoveryHit | Hit | None = None
"""The command that was selected by the user."""
self._busy_timer: Timer | None = None
@@ -637,18 +649,19 @@ class CommandPalette(SystemModalScreen):
"""List of Provider instances involved in searches."""
self._hit_count: int = 0
"""Number of hits displayed."""
self._placeholder = placeholder
@staticmethod
def is_open(app: App[object]) -> bool:
"""Is the command palette current open?
"""Is a command palette current open?
Args:
app: The app to test.
Returns:
`True` if the command palette is currently open, `False` if not.
`True` if a command palette is currently open, `False` if not.
"""
return app.screen.id == CommandPalette._PALETTE_ID
return app.screen.has_class("--textual-command-palette")
@property
def _provider_classes(self) -> set[type[Provider]]:
@@ -697,7 +710,7 @@ class CommandPalette(SystemModalScreen):
with Vertical(id="--container"):
with Horizontal(id="--input"):
yield SearchIcon()
yield CommandInput(placeholder="Search for commands…")
yield CommandInput(placeholder=self._placeholder)
if not self.run_on_select:
yield Button("\u25b6")
with Vertical(id="--results"):

View File

@@ -1,7 +1,10 @@
from __future__ import annotations
from dataclasses import dataclass
from functools import partial
from typing import Callable
from textual.command import DiscoveryHit, Hit, Hits, Provider
from textual.design import ColorSystem
@@ -366,3 +369,34 @@ BUILTIN_THEMES: dict[str, Theme] = {
panel="#2A2A2A", # Dark Gray
),
}
class ThemeProvider(Provider):
"""A provider for themes."""
@property
def commands(self) -> list[tuple[str, Callable[[], None]]]:
themes = self.app.available_themes
def set_app_theme(name: str) -> None:
self.app.theme = name
return [
(theme.name, partial(set_app_theme, theme.name))
for theme in themes.values()
]
async def discover(self) -> Hits:
for command in self.commands:
yield DiscoveryHit(*command)
async def search(self, query: str) -> Hits:
matcher = self.matcher(query)
for name, callback in self.commands:
if (match := matcher.match(name)) > 0:
yield Hit(
match,
matcher.highlight(name),
callback,
)