# Change the theme

> Give a Pyzed app another look: pick a bundled theme, change the accent color, follow the system's light or dark mode, or generate your own theme from three colors, in code and in Pyzed Design.

Source: https://albakore.github.io/pyzed/tutorials/basic/themes/

A **theme** is the set of colors (and a few sizes, like the corner radius) every component reads when it draws
itself. Change the theme and the whole window changes at once: buttons, inputs, badges, the code editor... You
never color components one by one.

| Goal | Time | Level | You need |
| --- | --- | --- | --- |
| A window with a theme picker | 15 minutes | Basic · Python, or no code in the designer | [Layout basics](https://albakore.github.io/pyzed/tutorials/basic/layout-basics/) |

## What you'll learn

- Pick a theme in code (`theme = "One Light"`) and in the designer (**Properties → Look → Theme**).
- Change only the **accent** color.
- Follow the operating system's **light or dark** mode.
- Make **your own theme** from three colors and give it a name.
- Use **theme colors** (`"accent"`, `"text_muted"`) instead of fixed `#hex` values.

## Steps

1. **Pick a bundled theme.** `theme` is a class attribute of your app. Any name from `pz.themes.names()` works:
   the One, Ayu and Gruvbox families, light and dark.

```python
class ThemeSwitcher(pz.App):
    theme = "One Dark"
```

2. **Change it while the app runs.** Assign `self.theme`; the next redraw uses it. A drop-down of every theme is
   one line:

```python
ui.select(pz.themes.names(), self.base, lambda name: setattr(self, "theme", name))
```

3. **Change only the accent.** The accent is the color of primary buttons, focus rings, selection and links:

```python
self.theme = pz.themes.get("One Dark").with_accent("#e5c07b")
```

4. **Follow the system.** `pz.themes.auto(light, dark)` switches by itself when the user changes their operating
   system between light and dark mode:

```python
self.theme = pz.themes.auto("One Light", "One Dark")
```

5. **Make your own.** `pz.Theme.generate` derives every surface, border, hover state and muted text from three
   colors; `pz.themes.register` adds it to the list under its name:

```python
SUNSET = pz.themes.register(
    pz.Theme.generate("Sunset", background="#2b1d2f", text="#f5e6e8", accent="#ff8a65")
)
```

6. **Read the theme in your view.** `self.resolved_theme()` returns the `Theme` in use, handy for a background or
   to say "dark" or "light":

```python
t = self.resolved_theme()
pz.container(..., bg=t.background)
ui.muted(f"Using {t.name} ({'dark' if t.is_dark else 'light'})")
```

**Use theme colors, not #hex:**

Wherever a component takes a color, prefer a **token** such as `"accent"`, `"text_muted"`, `"success"` or
`"error"` over a fixed `"#3b82f6"`. Tokens follow the theme, so your app stays readable in light and dark themes.

## In Pyzed Design

Select an empty spot of the canvas so the Properties panel shows the **form**, then open the **Look** section:

- **Theme**: every registered theme.
- **Accent**: seven preset colors, or a custom `#rrggbb`.
- **Background**, **Corner radius** and **Font size** for the whole form.

**View → Preview theme…** shows the canvas in another theme without changing the form, so you can check both looks.
Each control can also override its own colors in its **Style** section.

## Full code

`examples/tutorial/theme_switcher.py`:

```python
"""Changing the theme: a bundled theme, an accent color, follow the system, or your own.

    python examples/tutorial/theme_switcher.py

- ``theme`` (a class attribute or ``self.theme``) takes the name of a bundled theme, a ``pz.Theme``
  or ``pz.themes.auto(light, dark)``, which follows the operating system's light/dark setting.
- ``theme.with_accent("#e5c07b")`` changes only the accent (primary buttons, focus, selection).
- ``pz.Theme.generate(...)`` builds a whole theme from three colors; ``pz.themes.register``
  adds it to ``pz.themes.names()``.

Every component reads its colors from the active theme, so the window restyles at once.
Walkthrough: https://albakore.github.io/pyzed/tutorials/basic/themes/
"""

import pyzed as pz
from pyzed import ui

# A theme of your own, made from three colors and registered by name.
SUNSET = pz.themes.register(pz.Theme.generate("Sunset", background="#2b1d2f", text="#f5e6e8", accent="#ff8a65"))

ACCENTS = {"Theme": None, "Blue": "#74ade8", "Purple": "#b477cf", "Green": "#a1c181", "Gold": "#e5c07b"}

class ThemeSwitcher(pz.App):
    title = "Themes"
    size = (760, 520)
    theme = "One Dark"  # the theme the window opens with

    def __init__(self):
        self.base = "One Dark"
        self.accent = "Theme"
        self.follow_system = False
        self.name = "Ada"

    def apply(self):
        """Builds the theme from the choices and assigns it: the next view() uses it."""
        if self.follow_system:
            self.theme = pz.themes.auto("One Light", "One Dark")
            return
        theme = pz.themes.get(self.base)
        color = ACCENTS[self.accent]
        self.theme = theme.with_accent(color) if color else theme

    def choose(self, field):
        def setter(value):
            setattr(self, field, value)
            self.apply()
        return setter

    def controls(self):
        return ui.settings_group("Appearance", [
            ui.field("Follow the system", ui.switch(self.follow_system, self.choose("follow_system")),
                     description="Light or dark, as your operating system"),
            ui.field("Theme", ui.select(pz.themes.names(), self.base, self.choose("base"), width=220)),
            ui.field("Accent", ui.segmented(list(ACCENTS), self.accent, self.choose("accent"))),
        ])

    def preview(self):
        return ui.card(pz.column([
            ui.input(self.name, lambda v: setattr(self, "name", v), placeholder="Your name", icon="user"),
            pz.row([
                ui.button("Save", lambda: None, variant="solid", icon="check"),
                ui.button("Cancel", lambda: None, variant="ghost"),
                pz.hspace(),
                ui.badge("New"), ui.badge("Done", tint="success"), ui.badge("Late", tint="error"),
            ], spacing=8, align="center"),
            ui.progress(0.65),
            ui.callout("Hello " + (self.name or "there"), "Every color here comes from the theme.", tint="info"),
        ], spacing=12), title="Preview", width="fill")

    def view(self):
        t = self.resolved_theme()  # the Theme in use right now
        return pz.container(
            pz.column([
                ui.heading("Pick a look", level=2),
                ui.muted(f"Using {t.name} ({'dark' if t.is_dark else 'light'})"),
                self.controls(),
                self.preview(),
            ], spacing=16),
            padding=24, width="fill", height="fill", bg=t.background,
        )

if __name__ == "__main__":
    pz.run(ThemeSwitcher)
```

**Going further:**

This is the quick tour. The [Theming](https://albakore.github.io/pyzed/theming/) section explains every token, how to style single widgets
with `.style(...)`, gradients, fonts and icons, and how to save a theme to a file.

## Events in this tutorial

| Widget | Event | Handler gets |
| --- | --- | --- |
| `ui.switch` (Follow the system) | `on_toggle` | `True` or `False`. |
| `ui.select` (Theme) | `on_select` | The theme's name. |
| `ui.segmented` (Accent) | `on_select` | The accent's name. |
| `ui.input` (Your name) | `on_input` | The text. |

`self.choose("base")` returns the handler: a small function that stores what it receives in that field. One factory
serves the three controls.

### Try more events

```python
def on_key(self, combo):                    # a shortcut for every window of the app
    if combo == "ctrl-t":                   # Ctrl+T: flip between light and dark
        self.base = "One Light" if self.resolved_theme().is_dark else "One Dark"
        self.follow_system = False

def on_resize(self, width, height):         # the window's own resize event
    self.compact = width < 600              # e.g. hide the preview on small windows
```

## Next

- [5-step tutorial](https://albakore.github.io/pyzed/tutorials/basic/step-by-step/): state, layout, async and a notes app in pure Python.
- [Themes](https://albakore.github.io/pyzed/theming/themes/) and [Styles](https://albakore.github.io/pyzed/theming/styles/) in the Theming section.
