Ir al contenido

Accesibilidad

Las apps de Pyzed se pueden usar con el teclado y respetan la preferencia de movimiento del sistema. Los lectores de pantalla todavía no están soportados: en Lectores de pantalla se explica por qué y qué viene después.

Tab mueve el foco del teclado al siguiente campo de la ventana y Shift+Tab al anterior, en el orden en que aparecen en view(). Tras el último vuelve al primero. Los campos son los text_input, text_editor y code_editor (y los componentes construidos con ellos, como ui.input o ui.search), los desplegables (pz.pick_list y ui.select) y los controles envueltos en pz.focusable: ui.switch, ui.checkbox, ui.slider, ui.segmented y ui.radio_group de la librería. Cada ventana recorre solo sus propios campos.

Un code_editor con tab_size se queda con Tab para indentar, como un editor de código. Los editores sin él dejan pasar la tecla, así que Tab sale de ellos.

on_key ve la tecla primero ("tab" o "shift-tab"). Si devuelve algo (un mensaje o un Command), la app la ha gestionado y el foco no se mueve; devolver None deja que se mueva. Desactívalo para toda una app o ventana con tab_focus = False:

class App(pz.App):
tab_focus = False # Tab no hace nada salvo que on_key lo gestione
def on_key(self, combo):
if combo == "tab" and self.completion_open:
return self.accept_completion()

Command.focus_next() y Command.focus_previous() hacen lo mismo desde código, y Command.focus(id) salta a un campo concreto (pasa id= al control).

Un clic o Tab le dan el foco a un control, y lo mantiene después de usarlo, como un campo de texto. Al usar las teclas aparece a su alrededor un anillo del color de foco del tema (un clic solo no lo dibuja, como el :focus-visible de un navegador); un desplegable ilumina su borde.

Control Teclas mientras tiene el foco
ui.select, pz.pick_list ↓/↑ (y →/←) eligen la opción siguiente o la anterior sin abrir el menú, Inicio/Fin la primera y la última, y las letras saltan a la opción que empieza por ellas. Enter, Espacio, Alt+↓ o F4 abren el menú; ahí ↑/↓ se mueven, Enter elige y Esc cierra.
ui.switch Espacio o Enter lo cambian.
ui.checkbox Espacio la marca o desmarca (una mixta queda marcada).
ui.segmented, ui.radio_group Las flechas eligen la opción anterior o la siguiente, saltando las desactivadas; Inicio/Fin la primera y la última.
ui.slider Las flechas lo mueven un step, con Shift (o Re Pág/Av Pág) diez; Inicio/Fin van a min/max.
ui.number_field ↑/↓ lo cambian un paso (diez con Shift), Inicio/Fin van a min/max.

Las teclas que usa un control con foco no llegan al on_key de la app, así que ↓ en un desplegable no desplaza también una lista ni mueve otra selección. El resto de teclas (atajos como Ctrl+S) siguen llegando.

Tus propios controles tienen lo mismo con pz.focusable(contenido, on_key, keys=[...]): mientras tiene el foco, las combinaciones de keys llaman a on_key(combo) y nadie más las ve.

def estrellas(self):
t = self.theme
fila = pz.row([pz.icon("star", color=t.warning if i < self.puntos else t.text_muted)
for i in range(5)], spacing=2)
def puntuar(combo):
self.puntos = max(0, min(5, self.puntos + (1 if combo == "right" else -1)))
return pz.focusable(fila, puntuar, keys=["left", "right"], id="puntos",
ring_color=self.theme.border_focused)

Cuando el sistema pide a las apps reducir el movimiento, Pyzed apaga sus animaciones: cada animate(), transition(), presence(), transición de ventana y scroll animado muestra al instante su estado final. El ajuste se lee al arrancar la app:

Sistema Ajuste
macOS Ajustes del Sistema → Accesibilidad → Pantalla → Reducir movimiento
Windows Configuración → Accesibilidad → Efectos visuales → Efectos de animación (desactivado)
Linux (GNOME) Configuración → Accesibilidad → Reducir animación (enable-animations)
Linux (KDE) Preferencias del sistema → Velocidad de animación en Instantánea

pz.system_reduced_motion() devuelve lo que dice el sistema (None si no lo sabe). Tu app puede decidir igualmente:

pz.set_reduced_motion(True) # siempre apagado, p. ej. desde los ajustes de tu app
pz.set_reduced_motion(False) # siempre encendido
pz.set_reduced_motion(None) # volver a seguir al sistema (por defecto)

La variable de entorno PYZED_REDUCED_MOTION (1 o 0) se impone a ambos. Las capturas muestran siempre el estado de reposo. Ver Animación.

Pyzed dibuja su interfaz con Iced, que pinta cada widget en la GPU en vez de usar los controles nativos del sistema. Los lectores de pantalla (VoiceOver, Narrador, NVDA, Orca) necesitan un árbol de accesibilidad que describa esos widgets, e Iced 0.14 no lo genera. La forma habitual de darlo en Rust es AccessKit, y evaluamos añadirlo a Pyzed para la 1.1:

  • Iced 0.14 no soporta AccessKit. Ni iced ni iced_winit dependen de él, y winit 0.30 deja la accesibilidad a adaptadores como accesskit_winit.
  • La ventana es de Iced. accesskit_winit debe crearse antes de mostrar la ventana y recibir todos sus eventos; el bucle de eventos de Iced no da sitio para ninguna de las dos cosas. Los adaptadores de cada plataforma (accesskit_windows y accesskit_macos subclasifican la ventana nativa, accesskit_unix habla con AT-SPI) se podrían enganchar desde el handle de la ventana, pero son tres integraciones distintas, cada una probada con un lector de pantalla real.
  • Los widgets no se describen a sí mismos. Las operaciones de widgets de Iced informan de texto, campos de texto, scrollables y foco, pero no de roles (botón, casilla, slider), estados ni acciones (“pulsar”), así que Pyzed tendría que construir el árbol desde sus propias descripciones de widgets y sincronizarlo con el layout en cada frame.

Eso es un proyecto en sí mismo, no algo para publicar a medias en una versión menor. La accesibilidad está en la hoja de ruta de Iced y algunos forks de Iced ya incluyen AccessKit, así que el plan es:

  1. Seguir a Iced y adoptar su soporte de accesibilidad cuando llegue; los widgets de Pyzed corresponden uno a uno con los de Iced, así que lo ganarían con poco trabajo extra.
  2. Mientras tanto, completar el uso con teclado (foco para botones y otros controles cuando Iced lo permita) y mantener las etiquetas en el texto de la interfaz, no solo en iconos.

Si el soporte de lectores de pantalla es importante para tu app, dilo en los issues: ayuda a decidir prioridades.