Saltar al contenido
zabloo

ProgressBar

Un track que se llena hasta la fracción que fija el juego. Es la mitad de solo lectura del par que completa un Slider: esta el jugador la mira en vez de moverla.

primitivedesde v1sin focus

Una ProgressBar es un track que se llena hasta una fracción entre 0 y 1. El juego fija ese número y el jugador solo lo lee: la vida que baja, una descarga que avanza, un temporizador de recarga que se agota. Piensa en un HUD cuyo value es un binding a player.hp: cada vez que el juego llama a SetData, la barra le sigue. Se gana su sitio como tipo de nodo porque en la v1 no hay nada más que sepa decir una fracción de mi padre: los tamaños del layout son píxeles, no son enlazables, y grow tampoco.

progressbar-bars.viewIR v1
Three bars: a red Health bar filled to about two thirds and labelled 64 of 100, a gold Reload bar filled from the right, and a tall vertical purple Stamina bar filled from the bottom to about a third.
ESTADO
  • hover — inactivo
  • pressed — inactivo
  • focused — inactivo
  • selected — inactivo
  • disabled — inactivo

Se lee del último frame dibujado

Parado: pulsa Ejecutar para dibujarlo en la GPU.

import { Column, ProgressBar, Row, Text } from "@zabloo/react";


const TRACK = {
  background: "{color.slot}",
  radius: "{radius.pill}",
} as const;

export default function ProgressBarBars() {
  return (
    <Column
      layout={{ grow: 1, justify: "center", align: "center", padding: "{space.6}" }}
      style={{ background: "{color.bg}" }}
    >
      <Row layout={{ gap: "{space.5}", align: "center" }}>
        <Column
          id="panel"
          layout={{ width: 320, padding: "{space.5}", gap: "{space.4}", align: "stretch" }}
          style={{
            background: "{color.surface}",
            radius: "{radius.lg}",
            borderWidth: "{border.hairline}",
            borderColor: "{color.line}",
          }}
        >
          <Column layout={{ gap: "{space.2}", align: "stretch" }}>
            <Row layout={{ justify: "space-between", align: "center" }}>
              <Text style={{ color: "{color.muted}", fontSize: "{text.xs}" }}>Health</Text>
              <Text
                bind="player.hpLabel"
                style={{ color: "{color.faint}", fontSize: "{text.xs}" }}
              />
            </Row>
            <ProgressBar
              id="hp"
              value={{ bind: "player.hp" }}
              size={8}
              transition={{ duration: "{motion.fast}" }}
              style={TRACK}
              fill={{ background: "{color.danger}", radius: "{radius.pill}" }}
            />
          </Column>

          <Column layout={{ gap: "{space.2}", align: "stretch" }}>
            <Text style={{ color: "{color.muted}", fontSize: "{text.xs}" }}>
              Reload · drains from the end
            </Text>
            {/* `justify: "end"` anchors the fill at the other side, which is
                what a bar that empties looks like. */}
            <ProgressBar
              id="reload"
              value={{ bind: "weapon.reload" }}
              size={8}
              layout={{ justify: "end" }}
              style={TRACK}
              fill={{ background: "{color.gold}", radius: "{radius.pill}" }}
            />
          </Column>
        </Column>

        {/* A column bar: the same node, main axis turned. */}
        <Column layout={{ gap: "{space.2}", align: "center" }}>
          <ProgressBar
            id="stamina"
            value={{ bind: "player.stamina" }}
            layout={{ direction: "column", justify: "end", height: 120 }}
            size={10}
            style={TRACK}
            fill={{ background: "{color.brand}", radius: "{radius.pill}" }}
          />
          <Text style={{ color: "{color.faint}", fontSize: "{text.xs}" }}>Stamina</Text>
        </Column>
      </Row>
    </Column>
  );
}
Tres barras, un solo tipo de nodo. Busca lo que de verdad cambia entre ellas: la primera crece desde la izquierda, la segunda está anclada al extremo contrario y se vacía hacia él, la tercera va hacia arriba. Eso es un valor, una direction y un justify — aquí no hay ninguna prop que hable de progreso.

Las dos tablas de abajo están más lejos entre sí de lo que su tamaño sugiere. size y fill son comodidades que escribes tú: el grosor se convierte en layout normal, y el estilo del relleno se convierte en el estilo de un nodo hijo de verdad. Se publican dos props, y una de ellas es un slot.

Props de autoría

<ProgressBar> emite este nodo con el fill ya construido — no hay una exportación en crudo que te lo deje a ti, porque el fill es children[0] por convención.

PropTipoPor defectoDescripción
valueBindable<number>0Progreso entre 0 y 1, normalmente un binding de lectura.
fillStyleabsentEstilo del fill, mezclado sobre la barra por defecto.
sizenumber8Grosor de la barra en px — alto si va en fila, ancho si va en columna.

Además de estas, todo componente acepta las props base del nodo —id, visible, disabled, layout, style, states, transition, autofocus, clip— más variant, que el theme resuelve al exportar y que nunca aparece en la IR. El componente viene por defecto como barra horizontal (direction: "row") y con clip: true, para que un fill cuadrado se quede dentro de un track redondeado.

Props de IR

Lo que llega al juego, cuando ya no queda capa de autoría.

PropTipoPor defectoDescripción
valueBindable<number>0Progreso entre 0 y 1, acotado. Estático o un binding de lectura.
childrenZNode[][]children[0] = el fill. Los demás hijos están reservados.

Todo el aspecto de una barra —la ranura, el color, el radio, el grosor— es el estilo normal de dos nodos normales.

La geometría

El nodo es el track —su style pinta la ranura y su layout lo dimensiona— y children[0] es el fill, un nodo normal cuyo propio estilo pinta la barra. Un slot posicional, como la cabecera de un Collapse: el pintado sigue siendo implícito, así que el fill es un hijo compuesto y no un comando de dibujo nuevo.

En el eje principal (layout.direction, que por defecto es "column" como en todo el resto del formato — el componente te pone "row"):

  • El SDK dimensiona el fill a contentMain × value, donde contentMain es el eje principal del track menos su padding.
  • El fill se estira a lo largo de todo el eje transversal.
  • layout.justify lo ancla: "start" (el valor por defecto) crece desde la izquierda o desde arriba, "end" desde la derecha o desde abajo —una barra que se vacía al revés— y "center" crece desde el medio hacia fuera.
  • El width/height/grow propios del fill en el eje principal se ignoran: ese número es del SDK.

children[1..] están reservados. La v1 no coloca nada más dentro del track — una etiqueta encima de la barra necesitaría colocación superpuesta, que el formato no tiene.

Un binding roto enseña una barra vacía

value se acota a 0..1, y uno no finito —datos que faltan, una cadena, NaN— se lee como 0. Hay una sola respuesta a “qué enseña un binding roto”, y no es nunca una barra llena ni un crash. (Implementación de referencia: clampProgress, en @zabloo/format.)

Comportamiento

Estados

Solo disabled, heredado. No se aplica nada más, porque no le puede pasar nada más.

Focusable

No, y aquí no hay nada que el jugador pueda hacer. Un número que fija el jugador señalando es un Slider, y esa es la diferencia entera entre los dos nodos.

Movimiento

Una transition en este nodo interpola el valor, no el rect calculado: el SDK interpola la fracción y luego corre su paso de layout normal con ella, así que sigue habiendo un solo paso por frame y los dos targets aterrizan en el mismo número. La transition propia del fill no llega a ver el cambio, porque su tamaño principal no es una de sus entradas declaradas.

Actions

Ninguna — aquí el tráfico va en el otro sentido. El juego llama a SetData sobre el path enlazado → el SDK vuelve a leer la fracción → el fill se redimensiona en el siguiente frame. No vuelve nada.

Degradación

En un SDK más antiguo

En un SDK más antiguo la ranura conserva su forma exacta y el fill se queda en nada. La barra deja de informar; a su alrededor no se mueve nada.

Como Container: el track con un fill sin tamaño dentro. La barra pierde su fracción — nunca el layout de alrededor, porque el tamaño del track siempre fue suyo. Un HUD que degrada conserva su forma y deja de informar, que es la diferencia entre una lectura vieja y una pantalla rota.

Composición

Una barra con etiqueta es un Column que contiene un Row y la barra, nunca una prop, porque children[1..] está reservado y la v1 no tiene colocación superpuesta.

  • Una barra enlazada con transition — el caso común: el juego es dueño del número, y la interpolación evita que un golpe de vida dé un salto seco.
  • Una barra vertical — gira el eje principal para un medidor de aguante pegado al borde de la pantalla.
  • justify: "end" — cuando la barra deba vaciarse hacia su principio, que es como se lee un temporizador de recarga.
  • Una barra con etiqueta — un Column con un Row encima, siempre que el número haya que decirlo también con palabras.
// The common case: a read binding the game moves with SetData.
<ProgressBar value={{ bind: "player.hp" }} transition={{ duration: 200 }} />

// A vertical bar: the same node with its main axis turned.
<ProgressBar value={0.4} layout={{ direction: "column", height: 120 }} size={10} />

// Draining from the other side.
<ProgressBar value={{ bind: "weapon.reload" }} layout={{ justify: "end" }} />

// A labelled bar is composition, not a prop.
<Column layout={{ gap: 8, align: "stretch" }}>
<Row layout={{ justify: "space-between" }}>
  <Text>Health</Text>
  <Text bind="player.hpLabel" />
</Row>
<ProgressBar value={{ bind: "player.hp" }} fill={{ background: "{color.danger}" }} />
</Column>

Relacionado