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

Brand and dark mode

One seed generates the whole palette, for both themes. Nothing below names a colour, which is why the switch needs no rebuild.

Qué muestra esto

Declaring a brand: one seed, and everything else follows.

You do not pick colours in a CrossFly app. You declare one seed, and the framework derives the rest — primary and what reads on it, surfaces, containers, outlines, the tonal fill behind an active tab, both themes. Tap the swatches below: nothing on this screen is rebuilt, and every colour moves at once, because none of them was ever written down.

Skip this and your app is not "unstyled", it is grey — the skin's neutral palette is what you get when nobody declared anything, and it reads as a rendering bug rather than as a default. That is the first thing to check when a screen looks broken and nothing is broken.

Two calls, and the difference between them matters exactly once. setBrand restyles every open window; setBrandSilent does not, and exists for code running before there is anything to restyle — class initialisation, the line before the first window is shown.

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.R29Brand"
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.

ButtonCardColorSchemeComponentGapHorizontalLayoutStylesSwitchTextFieldWidgets

El código

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

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

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

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

/**
 * Declaring a brand: one seed, and everything else follows.
 *
 * <p>You do not pick colours in a CrossFly app. You declare one seed, and the framework derives the
 * rest — primary and what reads on it, surfaces, containers, outlines, the tonal fill behind an
 * active tab, both themes. Tap the swatches below: nothing on this screen is rebuilt, and every
 * colour moves at once, because none of them was ever written down.</p>
 *
 * <p><b>Skip this and your app is not "unstyled", it is grey</b> — the skin's neutral palette is
 * what you get when nobody declared anything, and it reads as a rendering bug rather than as a
 * default. That is the first thing to check when a screen looks broken and nothing is broken.</p>
 *
 * <p>Two calls, and the difference between them matters exactly once. {@code setBrand} restyles
 * every open window; {@code setBrandSilent} does not, and exists for code running before there is
 * anything to restyle — class initialisation, the line before the first window is shown.</p>
 */
public class R29Brand extends Recipe
{
   /** A few seeds. Any ARGB works: the palette is generated, not looked up. */
   private static final int[] SEED = { 0xFF1565C0, 0xFFE2622C, 0xFF2E7D32, 0xFF6A1B9A,
      0xFFB71C1C };
   private static final String[] SEED_NAME = { "Blue", "Orange", "Green", "Purple", "Red" };

   /** What was in force when this recipe opened, so it can be handed back. */
   private Integer previousBrand;
   private ColorScheme previousScheme;

   public String title()
   {
      return "Brand and dark mode";
   }

   public String note()
   {
      return "One seed generates the whole palette, for both themes. Nothing below names a colour,"
         + " which is why the switch needs no rebuild.";
   }

   public void build(Component body) throws Exception
   {
      remember();

      body.add(LAY_AUTO, outgap(label("THE SEED", 1f, true, Styles.captionColor, Position.START), 0,
         0, 0, .5f));

      Component swatches = new Component();
      swatches.setLayout(new HorizontalLayout(Position.START, Position.CENTER));
      for (int i = 0; i < SEED.length; i++)
      {
         final int seed = SEED[i];
         Button b = new Button(SEED_NAME[i]);
         b.getStyle()
            .backColor(seed)
            .textColor(0xFFFFFFFF)
            .outgap(Gap.ofFont(0, 0, .4f, 0));
         b.onAction(c -> Styles.setBrand(seed));
         swatches.add(LAY_AUTO, b);
      }
      body.add(LAY_AUTO, outgap(swatches, 0, 0, 0, 1f));

      Switch dark = new Switch("Dark mode");
      dark.checked(Styles.isDarkMode());
      // One call. Every PalColor token re-resolves in place and every window is restyled — no
      // screen tracks a flag of its own, and none rebuilds.
      dark.onAction(c -> Styles.setDarkMode(dark.isChecked()));
      body.add(LAY_AUTO, outgap(dark, 0, 0, 0, 1.2f));

      body.add(LAY_AUTO, outgap(label("WHAT FOLLOWS, WITHOUT BEING TOLD", 1f, true,
         Styles.captionColor, Position.START), 0, 0, 0, .5f));

      Card c = new Card();
      c.add(LAY_AUTO, label("A card on a surface", 1.2f, true, Styles.textColor, Position.START));
      c.add(LAY_AUTO, outgap(label("Caption text, the tone below the title.", 1f, false,
         Styles.captionColor, Position.START), 0, .2f, 0, .5f));
      c.add(LAY_AUTO, outgap(new TextField().caption("A field"), 0, 0, 0, .5f));
      c.add(LAY_AUTO, outgap(new Button("A button"), 0, 0, 0, .5f));
      Component chips = new Component();
      chips.setLayout(new HorizontalLayout(Position.START, Position.CENTER));
      chips.add(LAY_AUTO, outgap(toneBadge(Styles.primaryColor, "primary"), 0, 0, .4f, 0));
      chips.add(LAY_AUTO, outgap(toneBadge(secondary(), "secondary"), 0, 0, .4f, 0));
      chips.add(LAY_AUTO, toneBadge(tertiary(), "tertiary"));
      c.add(LAY_AUTO, chips);
      body.add(LAY_AUTO, c);
   }

   /**
    * Snapshots the palette, so leaving this recipe does not repaint the app it was opened from.
    *
    * <p>A <b>copy</b>, never the reference: {@link Styles} keeps one {@link ColorScheme} instance
    * for the life of the app and mutates its roles in place on every seed change — deliberately, so
    * that a {@code PalColor} captured into a style at build time keeps resolving. Save the
    * reference and you have saved the object that is about to be overwritten.</p>
    */
   private void remember()
   {
      previousBrand = Styles.hasBrand() ? Integer.valueOf(Styles.brand()) : null;
      ColorScheme live = Styles.colorScheme();
      if (live != null)
      {
         previousScheme = ColorScheme.fromSeed(SEED[0], live.isDark);
         previousScheme.copyFrom(live);
      }
   }

   @Override
   public void onRemove()
   {
      super.onRemove();
      if (previousBrand != null)
         Styles.setBrand(previousBrand.intValue());
      else
      {
         Styles.clearBrand();
         if (previousScheme != null)
            Styles.setColorScheme(previousScheme);
      }
   }
}
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
AnteriorWriting your own componentSiguienteEmpty, loading and broken