# Árbol

> Un árbol de archivos plegable con iconos y colores de git. Un componente nativo de Pyzed para GUIs de escritorio en Python, con vista previa, ejemplos y su API.

Source: https://albakore.github.io/pyzed/es/library/tree/

:::tip[En pocas palabras]
Una lista de carpetas que puedes abrir y cerrar, como los archivos de un proyecto.
:::

Un panel de proyecto: carpetas plegables, un icono por tipo de archivo y colores de estado de git. `TreeNode.from_paths(paths, status=...)` crea los nodos, pone las carpetas primero y propaga el estado de git a las carpetas con cambios. El id de cada nodo es su ruta; el conjunto `expanded` y el id `selected` son tuyos.

`ui.tree` · Categoría: [Listas y menús](https://albakore.github.io/pyzed/es/library/#listas-y-menús) · [Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#tree)

**Código:**

```python
nodes = ui.TreeNode.from_paths(
    ["src/main.rs", "src/app.rs", "src/ui/button.rs", "src/ui/mod.rs", "tests/app.rs", "Cargo.toml"],
    status={"src/app.rs": "modified", "src/ui/button.rs": "created"},
)
pz.container(ui.tree(nodes, expanded={"src", "src/ui"}, selected="src/app.rs",
                     on_toggle=on_toggle, on_select=on_select), width=280)
```

## Cuándo usarlo

- Para archivos y cualquier jerarquía: esquemas, categorías, secciones de ajustes.
- `status` es `"modified"`, `"created"`, `"deleted"`, `"conflict"` o `"ignored"`.

## Uso

```python
import pyzed as pz
from pyzed import ui

class Explorer(pz.App):
    def __init__(self):
        self.nodes = ui.TreeNode.from_paths(["src/main.rs", "src/app.rs", "Cargo.toml"])
        self.expanded = {"src"}
        self.selected = None

    def toggle(self, folder):
        self.expanded ^= {folder}

    def view(self):
        return ui.tree(self.nodes, expanded=self.expanded, selected=self.selected,
                       on_toggle=self.toggle, on_select=lambda path: setattr(self, "selected", path))
```

## Variantes

:::note[Sobre estas capturas]
Cada captura la graba pyzed mismo, fuera de pantalla a 2x, en One Dark o One Light según el tema de este sitio, y recortada al componente para que se vea a su tamaño real. El puntero pasa por encima, pulsa o escribe en él; un componente que no reacciona al puntero recorre algunos temas. Con movimiento reducido ves el primer fotograma. Abre **Código** bajo una captura para ver el fragmento que la dibuja; los nombres libres como `on_save` son tus propios callbacks.
:::

### Desplegado

```python
nodes = ui.TreeNode.from_paths(["src/main.rs", "src/app.rs", "src/ui/mod.rs", "src/ui/button.rs", "Cargo.toml"])
pz.container(ui.tree(nodes, expanded=set()), width=240)
```

```python
nodes = ui.TreeNode.from_paths(["src/main.rs", "src/app.rs", "src/ui/mod.rs", "src/ui/button.rs", "Cargo.toml"])
pz.container(ui.tree(nodes, expanded={"src"}), width=240)
```

```python
nodes = ui.TreeNode.from_paths(["src/main.rs", "src/app.rs", "src/ui/mod.rs", "src/ui/button.rs", "Cargo.toml"])
pz.container(ui.tree(nodes, expanded={"src", "src/ui"}, selected="src/ui/button.rs"), width=240)
```

### Estado de git

```python
nodes = ui.TreeNode.from_paths(["src/app.rs", "src/new.rs", "src/old.rs", "build.log"],
    status={"src/app.rs": "modified", "src/new.rs": "created", "src/old.rs": "deleted", "build.log": "ignored"})
pz.container(ui.tree(nodes, expanded={"src"}), width=240)
```

```python
nodes = ui.TreeNode.from_paths(["merge.py", "ok.py"], status={"merge.py": "conflict"})
pz.container(ui.tree(nodes, expanded=set()), width=240)
```

### Densidad

```python
nodes = ui.TreeNode.from_paths(["src/main.rs", "src/app.rs", "src/ui/mod.rs", "src/ui/button.rs", "Cargo.toml"])
pz.container(ui.tree(nodes, expanded={"src", "src/ui"}, item_height=20, indent=10), width=240)
```

```python
nodes = ui.TreeNode.from_paths(["src/main.rs", "src/app.rs", "src/ui/mod.rs", "src/ui/button.rs", "Cargo.toml"])
pz.container(ui.tree(nodes, expanded={"src"}, item_height=30, indent=20), width=240)
```

### Estados y eventos

`on_open(id)` se ejecuta con un doble clic en un archivo y `on_context(id)` con un clic derecho.

```python
pz.container(ui.tree(ui.TreeNode.from_paths(["src/app.py", "src/ui.py", "README.md"]), expanded={"src"},
                     selected={"src/app.py", "README.md"}, focused="src/ui.py", on_open=on_open,
                     on_context=on_context), width=300)
```

## Ejemplos

### Una carpeta real

Crea las rutas con `pathlib` y pásalas a `from_paths`.

```python
from pathlib import Path

root = Path(".")
paths = [p.relative_to(root).as_posix() for p in root.rglob("*")
         if p.is_file() and ".git" not in p.parts]
nodes = ui.TreeNode.from_paths(paths)
```

### Nodos a mano

Las carpetas son nodos con `children`.

**Código:**

```python
nodes = [
    ui.TreeNode("Inbox", children=[ui.TreeNode("Welcome", id="inbox/1", icon="mail")], id="inbox"),
    ui.TreeNode("Archive", children=[], id="archive"),
    ui.TreeNode("Notes.md", id="notes", icon="notebook-pen"),
]
pz.container(ui.tree(nodes, expanded={"inbox"}, selected="inbox/1"), width=260)
```

## Eventos

Árbol tiene **4 eventos propios**, en la tabla de abajo. Como todo widget, también tiene los **18 eventos comunes** (`click`, `double_click`, `right_click`, `mouse_enter`, `drag`, `key_down`, `resize`…), que se conectan con `.on(...)`: mira [Eventos](https://albakore.github.io/pyzed/es/guides/events/) y [Eventos de cada componente](https://albakore.github.io/pyzed/es/library/events/).

| Evento | El handler recibe | Cuándo |
| --- | --- | --- |
| `on_toggle` | `(id)` | Se llama con el id de la carpeta pulsada. |
| `on_select` | `(id)` | Se llama con el id del archivo pulsado. |
| `on_open` | `(id)` | Recibe el id del archivo al que se hace doble clic. |
| `on_context` | `(id)` | Recibe el id de la fila en la que se hace clic derecho (muestra un `ui.menu` ahí). |

**El handler recibe** es lo que Pyzed le pasa a tu función: `()` es nada. Cualquier handler también puede ser un mensaje (un valor que no se puede llamar), que va a `update()`.

### Manejar los eventos

Los mismos eventos escritos de tres formas. **Imperativa**: métodos de la app que cambian `self`. **Declarativa**: los handlers producen mensajes y un único `update()` decide. **Funcional**: una función pura dibuja un estado inmutable y pide cambios con `set_state(...)`. Mira [Imperativo, declarativo y funcional](https://albakore.github.io/pyzed/es/guides/code-styles/).

**Imperativo:**

```python
class Explorer(pz.App):
    def __init__(self):
        self.nodes = ui.TreeNode.from_paths(["src/main.rs", "src/app.rs", "Cargo.toml"])
        self.expanded = {"src"}
        self.selected = None

    def toggle(self, folder):            # on_toggle: the folder's id (its path)
        self.expanded ^= {folder}

    def view(self):
        return ui.tree(self.nodes, expanded=self.expanded, selected=self.selected,
                       on_toggle=self.toggle,
                       on_select=lambda path: setattr(self, "selected", path),   # click on a file
                       on_open=lambda path: print("open", path),                 # double click
                       on_context=lambda path: print("menu for", path))          # right click
```

**Declarativo:**

```python
from dataclasses import dataclass

@dataclass
class Toggle:
    folder: str

@dataclass
class Select:
    path: str

class Explorer(pz.App):
    nodes = ui.TreeNode.from_paths(["src/main.rs", "src/app.rs", "Cargo.toml"])
    expanded = frozenset({"src"})
    selected = None

    def update(self, message):
        match message:
            case Toggle(folder):
                self.expanded = self.expanded ^ {folder}
            case Select(path):
                self.selected = path

    def view(self):
        return ui.tree(self.nodes, expanded=self.expanded, selected=self.selected,
                       on_toggle=Toggle, on_select=Select)
```

**Funcional:**

```python
from dataclasses import dataclass

NODES = ui.TreeNode.from_paths(["src/main.rs", "src/app.rs", "Cargo.toml"])

@dataclass(frozen=True)
class State:
    expanded: frozenset = frozenset({"src"})
    selected: str = ""

def view(state, set_state):
    return ui.tree(NODES, expanded=state.expanded, selected=state.selected,
                   on_toggle=lambda folder: set_state(expanded=state.expanded ^ {folder}),
                   on_select=lambda path: set_state(selected=path))
```

## Propiedades y API

### `ui.tree`

```python
tree(
    nodes,
    *,
    expanded,
    selected=None,
    on_toggle=None,
    on_select=None,
    on_open=None,
    on_context=None,
    focused=None,
    indent=14,
    item_height=24,
    theme=None,
)
```

Un árbol de archivos plegable, como el panel de proyecto de un editor de código.

| Propiedad | Por defecto | Descripción |
| --- | --- | --- |
| `nodes` | *obligatorio* | Lista de `TreeNode`. |
| `expanded` | *obligatorio* | Conjunto de ids de carpetas abiertas. |
| `selected` | `None` | Un id o una colección de ids (selección múltiple). |
| `focused` | `None` | Resalta el borde de la fila en la que está el teclado. |
| `indent` | `14` | Sangría a la izquierda, en píxeles. |
| `item_height` | `24` | Alto de cada fila. |
| `theme` | `None` | Tema a usar en lugar del actual (`pyzed.theme.current()`). |

Eventos: `on_toggle`, `on_select`, `on_open`, `on_context` ([eventos](#eventos)).

[Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#tree)

### `ui.TreeNode`

```python
@dataclass
class TreeNode(name, children=None, id=None, status=None, icon=None, icon_color=None)
```

Un archivo o carpeta para `ui.tree`. Las carpetas tienen `children` (lista). `status` es `"modified"`, `"created"`, `"deleted"`, `"conflict"` o `"ignored"`. `TreeNode.from_paths(paths, status=None)` construye el árbol a partir de rutas como `"src/main.rs"`, con las carpetas primero y el estado de git propagado a las carpetas.

| Propiedad | Por defecto | Descripción |
| --- | --- | --- |
| `name` | *obligatorio* | Nombre visible. |
| `children` | `None` | Hijos (`None` si es un archivo). |
| `id` | `None` | Identificador estable para enfocarlo o desplazarlo con un `Command`. |
| `status` | `None` | Estado de git. |
| `icon` | `None` | Nombre de un icono de Lucide (`pyzed icons` los lista). |
| `icon_color` | `None` | Color del icono: un token del tema o un color hex. |

[Referencia completa →](https://albakore.github.io/pyzed/es/reference/ui/#treenode)

## Relacionados

- [Fila de lista](https://albakore.github.io/pyzed/es/library/list-item/): Una fila de lista con icono, texto y un hueco al final.
- [Panel](https://albakore.github.io/pyzed/es/library/panel/): Un panel lateral acoplable y redimensionable.
- [Espacio de trabajo](https://albakore.github.io/pyzed/es/library/workspace/): La disposición de una ventana estilo editor: barra de título, docks, centro y barra de estado.
