Ir al contenido

Tutorial en 5 pasos

Cinco archivos cortos en examples/tutorial/. Cada uno se ejecuta solo y añade una idea al anterior, así que puedes parar después de cualquier paso y seguir teniendo un programa que funciona.

Objetivo Tiempo Nivel Necesitas
De una ventana vacía a una app de notas, en Python puro 45 minutos Básico · Python Pyzed instalado (Instalación)
  • Cómo funciona una app de Pyzed: una clase App, su estado y un view() que describe la ventana.
  • Dos formas de reaccionar al usuario: mensajes enviados a update() y callbacks normales.
  • Layout, componentes listos para usar y temas.
  • Trabajo lento con async def sin congelar la ventana.
  • Atajos de teclado, un panel dividido redimensionable y una segunda ventana.
Windows, macOS y Linux
uv run --with pyzed-ui python examples/tutorial/step1_hello.py

Una app es una clase que hereda de pz.App y define view(). view() devuelve un árbol de widgets (pz.text, pz.column, pz.row, pz.container…). Pyzed la vuelve a llamar después de cada cambio, así que nunca modificas widgets: describes cómo se ve el estado actual. Atributos de clase como title y size configuran la ventana.

Paso 1

examples/tutorial/step1_hello.py
"""Tutorial, step 1: a window with some text.
python examples/tutorial/step1_hello.py
"""
import pyzed as pz
class Hello(pz.App):
title = "Hello"
size = (400, 200)
def view(self):
# view() returns a tree of widgets; Pyzed calls it again after every change.
return pz.center(pz.text("Hello, Pyzed!", size=28))
if __name__ == "__main__":
pz.run(Hello)

El estado son atributos normales de tu objeto. Los widgets reciben qué hacer de dos formas:

  • Un valor (ui.button("Greet", "greet")): se envía a update(message).
  • Un callable (ui.button("Reset", self.reset)): se llama directamente.

Los callbacks que traen datos reciben el valor nuevo: on_input=lambda text: ..., on_toggle=lambda checked: ..., on_change=lambda value: ....

Paso 2

examples/tutorial/step2_state.py
"""Tutorial, step 2: state, messages and callbacks.
python examples/tutorial/step2_state.py
"""
import pyzed as pz
from pyzed import ui
class Greeter(pz.App):
title = "Greeter"
size = (420, 260)
def __init__(self):
# State is plain attributes on your object.
self.name = ""
self.greetings = 0
def update(self, message):
# Widgets given a plain value (here the string "greet") send it to update().
if message == "greet" and self.name:
self.greetings += 1
def view(self):
return pz.container(
pz.column([
# Callbacks that receive data get the new value: here the input's text.
ui.input(self.name, lambda text: setattr(self, "name", text), placeholder="Your name",
on_submit="greet"),
ui.button("Greet", "greet", variant="solid"),
# A callable is called directly, no update() needed.
ui.button("Reset", self.reset, variant="ghost"),
ui.label(f"Hello, {self.name}! ({self.greetings})" if self.greetings else "Nobody greeted yet."),
], spacing=12),
padding=24,
)
def reset(self):
self.name, self.greetings = "", 0
if __name__ == "__main__":
pz.run(Greeter)

pyzed.ui trae componentes listos para usar (button, input, select, number_field, settings_group, callout…) que toman sus colores del tema activo. theme puede ser un Theme, el nombre de un tema incluido (pz.themes.names()) o pz.themes.auto(...) para seguir al sistema. self.resolved_theme() devuelve el Theme en uso. Más en Temas.

Paso 3

examples/tutorial/step3_layout.py
"""Tutorial, step 3: layout, ready-made components and themes.
python examples/tutorial/step3_layout.py
"""
import pyzed as pz
from pyzed import ui
class Settings(pz.App):
title = "Settings"
size = (640, 420)
theme = "One Dark" # any name from pz.themes.names(), a Theme, or pz.themes.auto()
def __init__(self):
self.font_size = 14
self.wrap = True
self.language = "Python"
def view(self):
t = self.resolved_theme() # the Theme in use, handy for colors
return pz.container(
pz.column([
pz.row([ui.heading("Settings", level=2), pz.hspace(),
ui.select(pz.themes.names(), t.name, lambda n: setattr(self, "theme", n), width=200)],
align="center"),
ui.settings_group("Editor", [
ui.field("Font size", ui.number_field(self.font_size, lambda v: setattr(self, "font_size", v),
min=8, max=32)),
ui.field("Soft wrap", ui.switch(self.wrap, lambda v: setattr(self, "wrap", v)),
description="Wrap long lines at the window edge"),
ui.field("Language", ui.select(["Python", "Rust", "Go"], self.language,
lambda v: setattr(self, "language", v), width=140)),
]),
ui.callout("Tip", "Every component reads its colors from the theme.", tint="hint"),
], spacing=16),
padding=24, width="fill", height="fill", bg=t.background,
)
if __name__ == "__main__":
pz.run(Settings)

El trabajo lento no debe bloquear la ventana. Command.perform acepta funciones async def, que corren en el loop de asyncio de Pyzed; el resultado llega a tu callback en el hilo de la interfaz. Mientras carga, una suscripción pz.every redibuja la ventana para animar el spinner. También puedes devolver corrutinas o generadores desde update y los callbacks: lo explica la guía Async y suscripciones.

Paso 4

examples/tutorial/step4_async.py
"""Tutorial, step 4: async work without freezing the window.
python examples/tutorial/step4_async.py
"""
import asyncio
import random
import pyzed as pz
from pyzed import ui
async def fetch_quote():
await asyncio.sleep(1.2) # pretend this is a network request (aiohttp, httpx...)
return random.choice(["Simple is better than complex.", "Readability counts.", "Now is better than never."])
class Quotes(pz.App):
title = "Quotes"
size = (480, 260)
def __init__(self):
self.quote = "Press the button."
self.loading = False
def load(self):
self.loading = True
# Return a Command.perform: fetch_quote runs on Pyzed's asyncio loop and its
# result is passed to self.loaded on the UI thread.
return pz.Command.perform(fetch_quote, self.loaded)
def loaded(self, quote):
self.quote, self.loading = quote, False
def subscriptions(self):
# Redraw ~30 times per second while loading so the spinner turns.
return [pz.every(1 / 30, lambda: None)] if self.loading else []
def view(self):
return pz.container(
pz.column([
ui.label(self.quote, size="lg"),
pz.row([ui.button("New quote", self.load, disabled=self.loading, variant="solid"),
ui.spinner() if self.loading else None], spacing=10, align="center"),
], spacing=16),
padding=24,
)
if __name__ == "__main__":
pz.run(Quotes)

Una app de notas: lista lateral, editor con vista previa de Markdown en un ui.split redimensionable, un atajo (on_key recibe combinaciones como "ctrl-n") y una segunda ventana (pz.Window) que se abre con return About().open() y comparte el estado de la app. Más en Varias ventanas.

Paso 5

examples/tutorial/step5_notes.py
"""Tutorial, step 5: a small notes app that puts it all together.
python examples/tutorial/step5_notes.py
A list of notes, a code editor with a live Markdown preview in a resizable split,
a command (ctrl-n) and a separate "About" window.
"""
import pyzed as pz
from pyzed import ui
class About(pz.Window):
title = "About Notes"
size = (320, 180)
resizable = False
def view(self):
return pz.container(pz.column([ui.heading("Notes", level=3), ui.muted("Built with Pyzed."),
ui.button("Close", self.close)], spacing=10),
padding=20, width="fill", height="fill", bg=self.resolved_theme().background)
class Notes(pz.App):
title = "Notes"
size = (1000, 640)
def __init__(self):
self.notes = {"Welcome": "# Welcome\n\nWrite **Markdown** on the left.", "Ideas": "- One\n- Two"}
self.current = "Welcome"
self.ratio = 0.5
def new_note(self):
name = f"Note {len(self.notes) + 1}"
self.notes[name] = f"# {name}\n"
self.current = name
def on_key(self, combo):
if combo == "ctrl-n":
self.new_note()
def view(self):
t = self.resolved_theme()
sidebar = pz.container(
pz.column([
pz.row([ui.label("Notes", weight="semibold"), pz.hspace(),
ui.icon_button("plus", self.new_note, tooltip="New note (ctrl-n)"),
ui.icon_button("info", lambda: About().open(), tooltip="About")], align="center"),
*[ui.list_item(name, lambda n=name: setattr(self, "current", n), icon="file-text",
selected=name == self.current) for name in self.notes],
], spacing=2),
padding=8, width=220, height="fill", bg=t.panel,
)
# The editor's id is the note name, so each note keeps its own cursor and undo history.
editor = ui.editor(self.current, self.notes[self.current], language="md", wrap=True,
on_change=lambda text: self.notes.__setitem__(self.current, text))
preview = pz.container(pz.scrollable(pz.container(ui.markdown(self.notes[self.current]), padding=20),
height="fill"), width="fill", height="fill", bg=t.background)
return pz.row([sidebar, ui.split(editor, preview, ratio=self.ratio,
on_resize=lambda r: setattr(self, "ratio", r))])
if __name__ == "__main__":
pz.run(Notes)
Paso Widget Evento El handler recibe
2 ui.input on_input El texto nuevo, en cada tecla.
2 ui.input, ui.button on_submit, on_press Nada. Aquí son el mensaje "greet", que va a update.
3 ui.number_field, ui.switch, ui.select on_change, on_toggle, on_select El número, True/False, la opción.
4 subscriptions() un temporizador pz.every(segundos, mensaje) manda el mensaje a update.
5 on_key(combo) cada tecla La combinación, como "ctrl-n".
5 ui.editor, ui.split on_change, on_resize El texto; la parte del primer panel.

El paso 2 maneja sus eventos en dos estilos a la vez: la caja llama a una lambda que cambia self (imperativo) y el botón manda el mensaje "greet" a update (declarativo). El mismo saludador solo con mensajes:

from dataclasses import dataclass
@dataclass
class Typed:
text: str
class Greeter(pz.App):
def __init__(self):
self.name, self.greetings = "", 0
def update(self, message):
match message:
case Typed(text):
self.name = text
case "greet" if self.name:
self.greetings += 1
def view(self):
return pz.column([
ui.input(self.name, Typed, placeholder="Tu nombre", on_submit="greet"),
ui.button("Saludar", "greet", variant="solid"),
], spacing=12, padding=24)

Mira Imperativo, declarativo y funcional para la tercera forma.