# Lienzo

> Dibujo 2D con formas, trazos, texto y eventos de ratón. 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/canvas/

:::tip[En pocas palabras]
Una hoja en blanco para dibujar formas, líneas y texto, para gráficos y juegos.
:::

Una superficie de dibujo 2D. `shapes` es una lista hecha con [`pyzed.draw`](https://albakore.github.io/pyzed/es/reference/draw/) (rectángulos, círculos, arcos, trazos, texto, imágenes…). Los callbacks del ratón reciben coordenadas relativas al lienzo; con un `id` la geometría se guarda entre fotogramas. Los gráficos de esta biblioteca están dibujados con él.

`pz.canvas` · Categoría: [Contenido](https://albakore.github.io/pyzed/es/library/#contenido) · [Referencia completa →](https://albakore.github.io/pyzed/es/reference/widgets/#canvas)

**Código:**

```python
pz.canvas([
    draw.rect(10, 10, 120, 80, fill="#74ade8", radius=8),
    draw.circle(200, 50, 40, fill="#a1c181"),
    draw.arc(300, 50, 40, 0, 270, stroke="#e5c07b", width=8, cap="round"),
    draw.polygon([(360, 90), (400, 10), (440, 90)], fill="#d07277"),
    draw.line(10, 120, 440, 120, stroke="#5d636f", width=2, dash=[6, 4]),
    draw.text(225, 150, "pz.canvas + pyzed.draw", color="#a9afbc", size=16, align_x="center"),
], width=450, height=170)
```

## Cuándo usarlo

- Para gráficos, juegos, diagramas, apps de dibujo y widgets propios.

## Uso

```python
import pyzed as pz
from pyzed import draw

pz.canvas([
    draw.rect(10, 10, 120, 80, fill="#74ade8", radius=8),
    draw.circle(200, 50, 40, fill="#a1c181"),
    draw.arc(300, 50, 40, 0, 270, stroke="#e5c07b", width=8, cap="round"),
    draw.polygon([(360, 90), (400, 10), (440, 90)], fill="#d07277"),
    draw.line(10, 120, 440, 120, stroke="#5d636f", width=2, dash=[6, 4]),
    draw.text(225, 150, "pz.canvas + pyzed.draw", color="#a9afbc", size=16, align_x="center"),
], width=450, height=170)
```

## 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.
:::

### Formas

```python
pz.canvas([draw.rect(10, 10, 140, 70, fill="#74ade8", radius=10)], width=160, height=90)
```

```python
pz.canvas([draw.circle(50, 45, 36, fill="#a1c181")], width=100, height=90)
```

```python
pz.canvas([draw.arc(50, 45, 34, 0, 270, stroke="#e5c07b", width=8, cap="round")], width=100, height=90)
```

```python
pz.canvas([draw.polygon([(10, 80), (50, 10), (90, 80)], fill="#d07277")], width=100, height=90)
```

```python
pz.canvas([draw.line(10, 45, 190, 45, stroke="#c678dd", width=3, dash=[8, 5])], width=200, height=90)
```

```python
pz.canvas([draw.text(100, 45, "pyzed.draw", color="#74ade8", size=20, align_x="center",
                     align_y="center")], width=200, height=90)
```

## Eventos

Lienzo tiene **5 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_press` | `(x, y)` | Callable que recibe `(x, y)` al pulsar. |
| `on_release` | `(x, y)` | Callable que recibe `(x, y)` al soltar. |
| `on_right_press` | `(x, y)` | Callable que recibe `(x, y)` con clic derecho. |
| `on_move` | `(x, y)` | Se llama con `(x, y)` al mover el ratón. |
| `on_scroll` | `(dx, dy)` | Callable que recibe `(dx, dy)` en líneas al usar la rueda. |

**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 Paint(pz.App):
    def __init__(self):
        self.dots, self.drawing = [], False

    def press(self, x, y):               # on_press / on_move get the position
        self.drawing = True
        self.dots.append((x, y))

    def move(self, x, y):
        if self.drawing:
            self.dots.append((x, y))

    def view(self):
        return pz.canvas([draw.circle(x, y, 3, fill="#74ade8") for x, y in self.dots],
                         width=420, height=200, cursor="crosshair",
                         on_press=self.press, on_move=self.move,
                         on_release=lambda x, y: setattr(self, "drawing", False),
                         on_right_press=lambda x, y: self.dots.clear())
```

**Declarativo:**

```python
from dataclasses import dataclass

@dataclass
class Dot:
    x: float
    y: float

class Paint(pz.App):
    dots = []

    def update(self, message):
        match message:
            case Dot(x, y):
                self.dots = self.dots + [(x, y)]
            case "clear":
                self.dots = []

    def view(self):
        return pz.canvas([draw.circle(x, y, 3, fill="#74ade8") for x, y in self.dots],
                         width=420, height=200, on_press=Dot, on_right_press=lambda x, y: "clear")
```

**Funcional:**

```python
from dataclasses import dataclass

@dataclass(frozen=True)
class State:
    dots: tuple = ()
    zoom: float = 1.0

def view(state, set_state):
    shapes = [draw.circle(x, y, 3 * state.zoom, fill="#74ade8") for x, y in state.dots]
    return pz.canvas(shapes, width=420, height=200,
                     on_press=lambda x, y: set_state(dots=state.dots + ((x, y),)),
                     on_scroll=lambda dx, dy: set_state(zoom=max(0.5, state.zoom + dy * 0.1)))
```

## Propiedades y API

### `pz.canvas`

```python
canvas(
    shapes=None,
    *,
    width=None,
    height=None,
    id=None,
    on_press=None,
    on_release=None,
    on_right_press=None,
    on_move=None,
    on_scroll=None,
    cursor=None,
)
```

Una superficie de dibujo 2D. `shapes` es una lista creada con [`pyzed.draw`](https://albakore.github.io/pyzed/es/reference/draw/). Los callbacks del ratón reciben coordenadas relativas al canvas. Con un `id`, la geometría se cachea entre frames y solo se recalcula cuando cambian las formas. Ver la guía de [dibujo con canvas](https://albakore.github.io/pyzed/es/guides/canvas/).

| Propiedad | Por defecto | Descripción |
| --- | --- | --- |
| `shapes` | `None` | Lista de formas de `pyzed.draw` (se aceptan listas anidadas). |
| `width` | `None` | Ancho: píxeles, `"fill"`, `"shrink"` o `"fill:N"` (proporción). |
| `height` | `None` | Alto: píxeles, `"fill"`, `"shrink"` o `"fill:N"` (proporción). |
| `id` | `None` | Activa la caché de geometría. |
| `cursor` | `None` | Cursor del ratón (`"crosshair"`, `"pointer"`…). |

Eventos: `on_press`, `on_release`, `on_right_press`, `on_move`, `on_scroll` ([eventos](#eventos)).

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

## Relacionados

- [Gráfico de barras](https://albakore.github.io/pyzed/es/library/bar-chart/): Un gráfico de barras verticales con etiquetas.
- [Dona](https://albakore.github.io/pyzed/es/library/donut/): Un gráfico de dona con una etiqueta en el centro.
- [Pila](https://albakore.github.io/pyzed/es/library/stack/): Superpone los hijos; `pin` coloca uno en x, y.
