Ir al contenido

Centro de control de cristal

Un centro de control domótico con el estilo de cristal esmerilado de los sistemas operativos modernos. Las manchas de color detrás de las tarjetas son un grafo de shader (shaders/blobs.pzshad), así que el desenfoque que ves a través de cada tarjeta se mueve con ellas.

Centro de control de cristal

Windows, macOS y Linux
uv run --with pyzed-ui python examples/showcase/glass_dashboard.py

glass desenfoca lo que hay detrás de la tarjeta; no es un degradado falso.

GLASS = {"tint": "#ffffff", "strength": 0.10, "blur": 28}
card.style(glass=GLASS, radius=22, padding=20)

La portada gira mientras suena la música; las bombillas encendidas brillan con bloom.

cover.animate("spin", duration=12000)

Los sliders y switches cambian el estado como en cualquier app de Pyzed.

pz.slider(0, 100, self.volume, on_change=self.set_volume)
  • Cambia tint y strength en GLASS y mira cuánto del fondo se ve a través.
  • Enciende todas las luces y mira el bloom.
  • Cambia las manchas por otro grafo de Shaders y efectos, como nebula.pzshad.
examples/showcase/glass_dashboard.py
"""A frosted-glass control center: music, lights, climate and a schedule over moving color.
python examples/showcase/glass_dashboard.py
- The background is a shader graph of soft color blobs that drift with time
(``shaders/blobs.pzshad``, made of nodes in Pyzed Design).
- Every card is ``glass``: a tinted panel that blurs what moves behind it (``backdrop_blur``).
- Switches, sliders and the play button update the cards; values move with ``transition``.
"""
from pathlib import Path
import pyzed as pz
SHADER = Path(__file__).parent / "shaders" / "blobs.pzshad"
from pyzed import ui
GLASS = {"tint": "#ffffff", "strength": 0.10, "blur": 28}
WHITE = "#ffffff"
MUTED = "#ffffffaa"
class Dashboard(pz.App):
title = "Control center"
size = (1180, 700)
theme = pz.themes.ONE_DARK
def __init__(self):
self.playing = True
self.volume = 64.0
self.lights = {"Living room": True, "Kitchen": False, "Studio": True}
self.brightness = 72.0
self.temperature = 21.5
def toggle_play(self):
self.playing = not self.playing
def set_volume(self, value):
self.volume = value
def toggle_light(self, name):
self.lights[name] = not self.lights[name]
def set_brightness(self, value):
self.brightness = value
def warmer(self):
self.temperature = min(28.0, self.temperature + 0.5)
def cooler(self):
self.temperature = max(16.0, self.temperature - 0.5)
def card(self, title, icon, body, width):
head = pz.row([pz.icon(icon, size=16, color=WHITE), pz.text(title, size=13, weight="semibold", color=WHITE)],
spacing=8, align="center")
return pz.column([head, body], spacing=14).style(glass=GLASS, radius=22, padding=20, width=width)
def music(self):
cover = pz.container(pz.icon("disc-3", size=44, color="#ffffffdd"), width=112, height=112,
align_x="center", align_y="center").style(
background=pz.Gradient(["#f472b6", "#8b5cf6", "#22d3ee"], angle=135), radius=18,
rotate=0, bloom=("#c084fc", 0.4))
if self.playing:
cover = cover.animate("spin", duration=12000)
controls = pz.row([
pz.icon("skip-back", size=20, color=WHITE),
pz.mouse_area(pz.container(pz.icon("pause" if self.playing else "play", size=22, color="#111827"),
width=48, height=48, align_x="center", align_y="center").style(
background=WHITE, radius=24, glow=("#ffffff88", 14)), on_press=self.toggle_play).animate(
"shrink", on="press"),
pz.icon("skip-forward", size=20, color=WHITE),
], spacing=22, align="center")
info = pz.column([
pz.text("Midnight City", size=20, weight="bold", color=WHITE),
pz.text("Neon Avenue · Afterglow", size=13, color=MUTED),
pz.container(pz.container(None, height=4).style(background=WHITE, radius=2).transition(
width=230 * (0.42 if self.playing else 0.41), duration=400), width=230).style(
background="#ffffff33", radius=2),
pz.row([pz.text("1:48", size=11, color=MUTED), pz.space(width="fill"),
pz.text("4:16", size=11, color=MUTED)], width=230),
controls,
], spacing=8)
volume = pz.row([
pz.icon("volume-1", size=16, color=MUTED),
pz.slider(0, 100, self.volume, on_change=self.set_volume, width="fill", accent=WHITE,
bg="#ffffff33"),
pz.icon("volume-2", size=16, color=MUTED),
], spacing=10, align="center")
return self.card("Now playing", "music", pz.column([pz.row([cover, info], spacing=20, align="center"),
volume], spacing=16), 440)
def lights_card(self):
rows = []
for name, on in self.lights.items():
rows.append(pz.row([
pz.container(pz.icon("lightbulb", size=16, color="#1f2937" if on else MUTED), width=34,
height=34, align_x="center", align_y="center").style(
background="#fde68a" if on else "#ffffff1a", radius=17,
bloom=("#fbbf24", 0.6) if on else None),
pz.column([pz.text(name, size=14, weight="semibold", color=WHITE),
pz.text(f"{round(self.brightness)}%" if on else "Off", size=11, color=MUTED)],
spacing=1, width="fill"),
ui.switch(on, lambda _v, name=name: self.toggle_light(name)),
], spacing=12, align="center"))
rows.append(pz.slider(0, 100, self.brightness, on_change=self.set_brightness, width="fill",
accent="#fde68a", bg="#ffffff33"))
return self.card("Lights", "sun", pz.column(rows, spacing=12), 330)
def climate(self):
ring = pz.column([
pz.text(f"{self.temperature:.1f}°", size=44, weight="bold", color=WHITE).transition(
scale=1.0, duration=200),
pz.text("Heating to 22°", size=12, color=MUTED),
], spacing=0, align="center")
buttons = pz.row([
pz.mouse_area(pz.container(pz.icon("minus", size=18, color=WHITE), width=44, height=44,
align_x="center", align_y="center").style(
background="#ffffff1f", radius=22), on_press=self.cooler).animate("shrink", on="press"),
pz.mouse_area(pz.container(pz.icon("plus", size=18, color=WHITE), width=44, height=44,
align_x="center", align_y="center").style(
background="#ffffff1f", radius=22), on_press=self.warmer).animate("shrink", on="press"),
], spacing=16)
dial = pz.container(ring, width=170, height=170, align_x="center", align_y="center").style(
radius=85, border=(6, "#fb923c"), bloom=("#fb923c", 0.5), background="#ffffff0d")
return self.card("Climate", "thermometer", pz.column([dial, buttons], spacing=14, align="center", width="fill"), 330)
def schedule(self):
events = (
("09:30", "Design review", "#22d3ee"),
("12:00", "Lunch with Sam", "#a3e635"),
("15:00", "Ship 1.2", "#f472b6"),
("18:30", "Climbing", "#fbbf24"),
)
rows = [pz.row([
pz.text(time, size=12, color=MUTED, font="mono", width=48),
pz.container(None, width=4, height=28).style(background=color, radius=2, bloom=(color, 0.6)),
pz.text(title, size=14, color=WHITE),
], spacing=12, align="center") for time, title, color in events]
return self.card("Today", "calendar", pz.column(rows, spacing=10), 440)
def header(self):
return pz.row([
pz.column([pz.text("Good evening, Kim", size=28, weight="bold", color=WHITE),
pz.text("Saturday · 3 rooms on · 21.5° inside", size=14, color=MUTED)], spacing=2),
pz.space(width="fill"),
pz.row([pz.icon("search", size=16, color=MUTED), pz.text("Search devices", size=13, color=MUTED)],
spacing=8, align="center").style(glass=GLASS, radius=20, padding=(10, 16), width=240),
], align="center", width=1124)
def view(self):
grid = pz.row([
pz.column([self.music(), self.schedule()], spacing=20),
self.lights_card(),
self.climate(),
], spacing=20, align="start")
content = pz.column([self.header(), pz.row(pz.stagger([grid], "fade_in_up", step=60))], spacing=24)
return pz.stack([
pz.shader(SHADER, width="fill", height="fill",
fallback=pz.Gradient(["#0b1026", "#5b1a4a", "#0f4c5c"], angle=135)),
pz.container(content, width="fill", height="fill", align_x="center", align_y="center"),
])
if __name__ == "__main__":
pz.run(Dashboard)