Saltar al contenido
CrossFly es 100% open sourceSíguenos en GitHub
CrossFlyCrossFly
Empezar
Todos los ejemplos
RecetasAvisosListas160 líneas · 1 archivos

Empty, loading and broken

Three of these are usually left to render as blank space. Blank space reads as a bug, never as an answer.

Qué muestra esto

The four states every screen has, and the three nobody designs.

A screen is drawn once, for the case where the data arrived and there is plenty of it. Then it ships, and users meet the other three: nothing yet, nothing matching, and something went wrong. All three render as an empty rectangle unless somebody decided otherwise, and an empty rectangle says only one thing to the person looking at it — this is broken.

The distinction that matters most is the one between the first two. "You have not added anything yet" invites an action and is a good first impression. "Nothing matches that search" is an answer to a question the user asked. Showing the first when the second is true tells a long-standing customer their account is empty.

Ejecutarlo en tu máquina

Cada ejemplo es una Window normal, así que el lanzador solo necesita su nombre.

gradlew lwjgl3:run --args="classname dev.cf.recipe.R30Empty"
Editarlo

Java se compila antes de ejecutarse, así que el navegador no puede reconstruir una edición por su cuenta: el código que ves aquí se convirtió en JavaScript con TeaVM antes de publicarse. Copia el archivo a un proyecto y funciona sin cambios en las cuatro plataformas; el comando de arriba es toda la preparación.

Ejecutándose aquíCrossFly 0.1.32
Iniciando la aplicación…

Compilado a JavaScript por TeaVM: es el mismo código, no un vídeo de él. · Todos los ejemplos

Qué usa

Tipos del framework que toca este ejemplo. Los enlazados tienen página de referencia.

ButtonCardChipComponentDebugGapGridLayoutHintHorizontalLayoutPalColorScreenStylesVerticalLayoutWidgets

El código

Completo, sin recortes. Es el archivo, no un fragmento.

Crudo
dev/cf/recipe/R30Empty.java
package dev.cf.recipe;

import dev.cf.Tones;

import dev.crossfly.ui.*;
import dev.crossfly.ui.color.*;
import dev.crossfly.ui.graphics.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.style.*;

import static dev.crossfly.ui.Widgets.*;

/**
 * The four states every screen has, and the three nobody designs.
 *
 * <p>A screen is drawn once, for the case where the data arrived and there is plenty of it. Then it
 * ships, and users meet the other three: nothing yet, nothing matching, and something went wrong.
 * All three render as an empty rectangle unless somebody decided otherwise, and an empty rectangle
 * says only one thing to the person looking at it — <b>this is broken</b>.</p>
 *
 * <p>The distinction that matters most is the one between the first two. "You have not added
 * anything yet" invites an action and is a good first impression. "Nothing matches that search" is
 * an answer to a question the user asked. Showing the first when the second is true tells a
 * long-standing customer their account is empty.</p>
 */
public class R30Empty extends Recipe
{
   private enum State
   {
      LOADED, FIRST_RUN, NO_MATCH, FAILED
   }

   private State state = State.FIRST_RUN;
   private Component stage;

   public String title()
   {
      return "Empty, loading and broken";
   }

   public String note()
   {
      return "Three of these are usually left to render as blank space. Blank space reads as a bug,"
         + " never as an answer.";
   }

   public void build(Component body) throws Exception
   {
      Component switcher = new Component();
      switcher.setLayout(new HorizontalLayout(Position.START, Position.CENTER));
      for (final State s : State.values())
      {
         Chip c = new Chip(s.name()
            .toLowerCase()
            .replace('_', ' '));
         c.setSelected(s == state);
         c.getStyle()
            .outgap(Gap.ofFont(0, 0, .4f, 0));
         c.onAction(e ->
         {
            state = s;
            rebuild();
            Screen.repaint();
         });
         switcher.add(LAY_AUTO, c);
      }
      body.add(LAY_AUTO, outgap(switcher, 0, 0, 0, 1f));

      stage = new Component();
      stage.setLayout(new VerticalLayout(Position.START, Position.START));
      stage.testTag("recipe.states");
      body.add(LAY_AUTO, stage);
      rebuild();
   }

   private void rebuild()
   {
      try
      {
         stage.removeAll();
         // removeAll() takes the layout with it.
         stage.setLayout(new VerticalLayout(Position.START, Position.START));

         switch (state)
         {
            case LOADED:
               for (int i = 1; i <= 4; i++)
               {
                  Card c = new Card();
                  c.setLayout(new GridLayout(Position.START, Position.CENTER));
                  c.getStyle()
                     .outgap(Gap.ofFont(0, 0, 0, .5f));
                  Component t = new Component();
                  t.setLayout(new VerticalLayout(Position.START, Position.START));
                  t.add(LAY_AUTO, label("Order " + (4180 + i), 1.2f, true, Styles.textColor,
                     Position.START));
                  t.add(LAY_AUTO, label("Placed this week", 1f, false, Styles.captionColor,
                     Position.START));
                  c.add(0f, 1f, Tones.tile(0xE8CC, Tones.cycle(i)), outgap(t, .6f, 0, 0, 0));
                  stage.add(LAY_AUTO, c);
               }
               break;

            case FIRST_RUN:
               // An invitation, with the action right there. This is a first impression, and the
               // one empty state worth spending a sentence on.
               // An invitation is not a problem: it gets the tone of the thing being offered.
               stage.add(LAY_AUTO, state(0xE8CC, Tones.accent(), "No orders yet",
                  "When you place one, it will show up here.", "Place your first order"));
               break;

            case NO_MATCH:
               // An ANSWER to what the user typed, not an invitation. Offering "add your first
               // order" to somebody who searched for one they know exists reads as being ignored.
               // Nothing is broken — the search simply has no answer. Not red.
               stage.add(LAY_AUTO, state(0xE8B6, Tones.support(), "Nothing matches “ironvale”",
                  "Check the spelling, or clear the search to see everything.",
                  "Clear the search"));
               break;

            case FAILED:
               // What happened, and what they can do. Not the exception text: it names something
               // the user has no way to act on, and it frightens them.
               // The only one of the three that actually failed, and the only one in the error
               // tone. Three identical grey glyphs said all three were the same event.
               stage.add(LAY_AUTO, state(0xE000, Tones.danger(), "We could not load your orders",
                  "Nothing was lost. Check your connection and try again.", "Try again"));
               break;

            default:
               break;
         }
         stage.setValidLayout(false);
      }
      catch (Exception e)
      {
         dev.crossfly.util.Debug.handle(e);
      }
   }

   /** One shape for all three: a glyph, a sentence, a reason, and exactly one way forward. */
   private Component state(int glyph, PalColor tom, String title, String detail, String action)
      throws Exception
   {
      Component c = new Component();
      c.setLayout(new VerticalLayout(Position.CENTER, Position.CENTER));
      c.getStyle()
         .ingap(Gap.ofFont(1f, 2f, 1f, 2f));
      c.add(LAY_AUTO, outgap(matglyph(glyph, tom, 3f), 0, 0, 0, .8f));
      c.add(LAY_AUTO, outgap(label(title, 1.5f, true, Styles.textColor, Position.CENTER), 0, 0, 0,
         .3f));
      c.add(LAY_AUTO, outgap(label(detail, 1f, false, Styles.captionColor, Position.CENTER), 0, 0,
         0, 1f));
      Button b = Tones.action(action, tom);
      b.onAction(e -> Hint.show(action, 1500));
      c.add(LAY_AUTO, b);
      return c;
   }
}
Para una IA

Versiones de esta página legibles por máquina, para que un modelo lea el estante en vez de adivinar la API.

Este ejemplo, en JSONEste ejemplo, en Markdown
AnteriorBrand and dark modeSiguienteFiles, sharing and printing