# Una app con varias vistas

> Construye una app de Pyzed con varias páginas: una barra lateral que cambia de vista, pestañas dentro de una página, migas de pan y atajos de teclado, todo controlado por un atributo del estado.

Source: https://albakore.github.io/pyzed/es/tutorials/intermediate/navigation/

Las apps reales tienen más de una pantalla: un inicio, una bandeja de entrada, ajustes. En Pyzed, "qué pantalla se
ve" es solo **un dato del estado**, como cualquier otro. Lo cambias y `view()` dibuja la otra pantalla. No hay un
router que aprender.

| Objetivo | Tiempo | Nivel | Necesitas |
| --- | --- | --- | --- |
| Una app con barra lateral, tres páginas y pestañas | 25 minutos | Intermedio · Python | [Bases del diseño de pantallas](https://albakore.github.io/pyzed/es/tutorials/basic/layout-basics/) |

## Qué vas a aprender

- Guardar la página actual en `self.page` y elegir la vista a partir de ella.
- Construir una **barra lateral** con filas `ui.list_item`, un estado seleccionado y un contador.
- Usar `ui.segmented` como **pestañas** dentro de una página.
- Mostrar **migas de pan** y saltar entre páginas con **atajos** (`on_key`).

## Pasos

1. **Un atributo decide la página.**

```python
PAGES = {"Home": "house", "Inbox": "inbox", "Settings": "settings"}   # nombre → icono

def __init__(self):
    self.page = "Home"

def go(self, page):
    self.page = page
```

2. **La barra lateral.** Un `ui.list_item` por página. `selected=` resalta la actual y `end=` pone un contador a la
   derecha. El `name=name` del lambda guarda el nombre de cada página:

```python
items = [
    ui.list_item(name, lambda name=name: self.go(name), icon=icon,
                 selected=name == self.page, height=34)
    for name, icon in PAGES.items()
]
```

3. **Elige la página en `view()`.** Cada página es un método que devuelve widgets; un dict relaciona nombres con
   métodos:

```python
page = {"Home": self.home, "Inbox": self.inbox, "Settings": self.settings}[self.page]()
return pz.row([self.sidebar(), pz.column([breadcrumbs, page], padding=24, width="fill")])
```

4. **Pestañas dentro de una página.** Un segundo atributo, `self.tab`, y un control `ui.segmented` para cambiarlo.
   El contenido de abajo depende de él:

```python
tabs = ui.segmented(["General", "Notifications", "About"], self.tab,
                    lambda tab: setattr(self, "tab", tab))
```

5. **Atajos.** `on_key` recibe cada combinación de teclas como texto. <kbd>Ctrl</kbd>+<kbd>1</kbd>,
   <kbd>Ctrl</kbd>+<kbd>2</kbd> y <kbd>Ctrl</kbd>+<kbd>3</kbd> abren las páginas:

```python
def on_key(self, combo):
    shortcuts = {f"ctrl-{i}": page for i, page in enumerate(PAGES, start=1)}
    if combo in shortcuts:
        self.go(shortcuts[combo])
```

**El estado sobrevive al cambiar de página:**

Como las páginas se dibujan a partir de tu estado, no se pierde nada al salir de una: la bandeja recuerda qué
mensajes leíste y Settings recuerda su pestaña. Para reiniciar una página, reinicia sus atributos en `go()`.

**En Pyzed Design:**

En el diseñador, un control **Pestañas** (*Tab bar*) lanza `change` con la pestaña elegida; muestra y oculta
paneles en su manejador (`form.general.visible = event.value == "General"`), o abre otro formulario en su propia
ventana con `form.open("settings")`. Mira [Eventos y manejadores](https://albakore.github.io/pyzed/es/editor/events/).

## Código completo

`examples/tutorial/navigation.py`:

```python
"""A multi-view app: a sidebar that switches pages, and tabs inside a page.

    python examples/tutorial/navigation.py

- The current page is one attribute (``self.page``); ``view()`` draws the page it names.
- The sidebar is a column of ``ui.list_item`` rows; the selected one is highlighted.
- Inside "Settings", ``ui.segmented`` works as a row of tabs (``self.tab``).
- ``ui.breadcrumbs`` shows where you are; Ctrl+1, Ctrl+2 and Ctrl+3 jump between pages.

Walkthrough: https://albakore.github.io/pyzed/tutorials/intermediate/navigation/
"""

import pyzed as pz
from pyzed import ui

PAGES = {"Home": "house", "Inbox": "inbox", "Settings": "settings"}
MESSAGES = [("Grace", "The compiler works!"), ("Alan", "Lunch on Friday?"), ("Linus", "Patch attached")]

class Navigation(pz.App):
    title = "Navigation"
    size = (820, 520)

    def __init__(self):
        self.page = "Home"
        self.tab = "General"
        self.notifications = True
        self.read = set()

    # -- moving around ------------------------------------------------------------------
    def go(self, page):
        self.page = page

    def on_key(self, combo):
        shortcuts = {f"ctrl-{i}": page for i, page in enumerate(PAGES, start=1)}
        if combo in shortcuts:
            self.go(shortcuts[combo])

    # -- the pieces ---------------------------------------------------------------------
    def sidebar(self):
        t = self.resolved_theme()
        unread = len(MESSAGES) - len(self.read)
        items = [
            ui.list_item(name, lambda name=name: self.go(name), icon=icon, selected=name == self.page, height=34,
                         end=ui.count_badge(unread) if name == "Inbox" and unread else None)
            for name, icon in PAGES.items()
        ]
        return pz.container(
            pz.column([ui.label("My app", weight="semibold"), pz.vspace(8), *items], spacing=2),
            padding=12, width=200, height="fill", bg=t.panel,
        )

    def home(self):
        return pz.column([
            ui.heading("Welcome back", level=2),
            ui.muted("Pick a page on the left, or press Ctrl+1, Ctrl+2 or Ctrl+3."),
            pz.row([ui.button("Open the inbox", lambda: self.go("Inbox"), variant="solid", icon="inbox"),
                    ui.button("Settings", lambda: self.go("Settings"), icon="settings")], spacing=8),
        ], spacing=12)

    def inbox(self):
        rows = [
            ui.list_item(pz.row([ui.label(who, weight="medium" if i not in self.read else "normal"),
                                 ui.muted(text)], spacing=12),
                         lambda i=i: self.read.add(i), icon="mail-open" if i in self.read else "mail", height=36)
            for i, (who, text) in enumerate(MESSAGES)
        ]
        return pz.column([ui.heading("Inbox", level=2), ui.muted("Click a message to mark it as read."), *rows],
                         spacing=8)

    def settings(self):
        tabs = ui.segmented(["General", "Notifications", "About"], self.tab, lambda tab: setattr(self, "tab", tab))
        if self.tab == "General":
            body = ui.field("Theme", ui.select(["One Dark", "One Light"], self.resolved_theme().name,
                                               lambda name: setattr(self, "theme", name), width=180))
        elif self.tab == "Notifications":
            body = ui.field("Show notifications", ui.switch(self.notifications,
                                                            lambda on: setattr(self, "notifications", on)))
        else:
            body = ui.label("Version 1.0 · made with Pyzed")
        return pz.column([ui.heading("Settings", level=2), tabs, body], spacing=16)

    def view(self):
        t = self.resolved_theme()
        page = {"Home": self.home, "Inbox": self.inbox, "Settings": self.settings}[self.page]()
        content = pz.column([
            ui.breadcrumbs(["My app", self.page], on_press=lambda i: self.go("Home") if i == 0 else None),
            page,
        ], spacing=16, padding=24, width="fill")
        return pz.container(pz.row([self.sidebar(), content], height="fill"),
                            width="fill", height="fill", bg=t.background)

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

## Eventos de este tutorial

| Widget | Evento | Handler |
| --- | --- | --- |
| `ui.list_item` de la barra lateral | `on_press` | `lambda name=name: self.go(name)` |
| La app | `on_key` | `ctrl-1`, `ctrl-2`, `ctrl-3` van a una página |
| `ui.breadcrumbs` | `on_press` | recibe el índice de la parte: `0` vuelve al inicio |
| `ui.list_item` del buzón | `on_press` | marca el mensaje como leído |
| `ui.segmented`, `ui.select`, `ui.switch` | `on_select`, `on_select`, `on_toggle` | la pestaña, el tema, sí/no |

### Prueba más eventos

Atrás y adelante con un historial, como un navegador:

```python
def __init__(self):
    ...
    self.history, self.future = [], []

def go(self, page):
    if page != self.page:
        self.history.append(self.page)
        self.future.clear()
        self.page = page

def on_key(self, combo):
    if combo == "alt-left" and self.history:
        self.future.append(self.page)
        self.page = self.history.pop()
    elif combo == "alt-right" and self.future:
        self.history.append(self.page)
        self.page = self.future.pop()
```

Los ratones con botones de atrás y adelante los mandan como `mouse_down` con `event.button`: añade
`.on(mouse_down=lambda e: ...)` a la página.

## Siguiente

- [Listas y tablas con datos](https://albakore.github.io/pyzed/es/tutorials/intermediate/lists-tables/).
- [Varias ventanas](https://albakore.github.io/pyzed/es/guides/windows/): varias ventanas reales que comparten estado.
- El [Dashboard](https://albakore.github.io/pyzed/es/showcase/dashboards/#dashboard) y los [Ajustes](https://albakore.github.io/pyzed/es/showcase/forms/#ajustes) del
  Showcase usan la misma barra lateral.
