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

Painting your own

A component is a rectangle that can draw. width/height are yours, getAutoHeight says how much room you want, and onPaint puts something in it.

Qué muestra esto

Painting something the framework does not have — and when not to.

Reach for onPaint when the thing you need is a drawing: a gauge, a sparkline, a seat map, a signature. Do not reach for it to make a card look different — that is a ComponentStyle, and a card painted by hand loses the skin, the theme, the press state and the shadow, all of which you then rebuild worse.

Three rules hold for everything drawn here. Sizes come from Styles.fontHeight, so the drawing scales with density instead of shrinking on a good screen. Colours come from tokens, so it follows the theme. And onPaint runs every frame — it may compute, but it must not allocate, which is why the shapes below are fields and not locals.

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

CardColorUtilsComponentGRectPaintStylesWidgets

El código

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

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

import dev.cf.Tones;

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

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

/**
 * Painting something the framework does not have — and when not to.
 *
 * <p>Reach for {@code onPaint} when the thing you need is <b>a drawing</b>: a gauge, a sparkline, a
 * seat map, a signature. Do not reach for it to make a card look different — that is a
 * {@code ComponentStyle}, and a card painted by hand loses the skin, the theme, the press state and
 * the shadow, all of which you then rebuild worse.</p>
 *
 * <p>Three rules hold for everything drawn here. Sizes come from {@code Styles.fontHeight}, so the
 * drawing scales with density instead of shrinking on a good screen. Colours come from tokens, so
 * it follows the theme. And {@code onPaint} runs every frame — it may compute, but it must not
 * allocate, which is why the shapes below are fields and not locals.</p>
 */
public class R23Painting extends Recipe
{
   public String title()
   {
      return "Painting your own";
   }

   public String note()
   {
      return "A component is a rectangle that can draw. width/height are yours, getAutoHeight says"
         + " how much room you want, and onPaint puts something in it.";
   }

   public void build(Component body) throws Exception
   {
      body.add(LAY_AUTO, outgap(label("A bar with a target line — the kind of thing no widget set"
         + " has, because the target is your idea.", 1f, false, Styles.captionColor,
         Position.START), 0, 0, 0, .8f));

      for (int i = 0; i < 4; i++)
      {
         Card c = new Card();
         // The bar is NOT toned: the class above reasons that over-target must read as more of the
         // same hue, and four hues would take that away. The name carries the colour instead.
         c.add(LAY_AUTO, label(new String[] { "North", "South", "East", "West" }[i], 1.2f, true,
            Tones.accent(), Position.START));
         Meter m = new Meter(new float[] { .82f, .41f, 1.12f, .67f }[i]);
         c.add(LAY_AUTO, outgap(m, 0, .3f, 0, 0));
         body.add(LAY_AUTO, outgap(c, 0, 0, 0, .5f));
      }
   }

   /**
    * A bar against a target: filled to {@code value}, with a line at 100% and the excess in a
    * different tone.
    */
   private static class Meter extends Component
   {
      /** Shapes as FIELDS: onPaint runs every frame, and allocating there is how you make garbage. */
      private final GRect track = new GRect(), fill = new GRect(), mark = new GRect();

      private final float value;

      Meter(float value)
      {
         this.value = value;
      }

      /**
       * How tall this wants to be. Expressed in font heights, so it grows with the type rather than
       * staying a fixed number of pixels that is right on exactly one screen.
       */
      @Override
      public int getAutoHeight()
      {
         return Math.round(Styles.fontHeight * 1.1f);
      }

      @Override
      public void onPaint(Paint paint) throws Exception
      {
         int r = height / 2;
         int reached = Math.round(Math.min(value, 1f) * width);

         track.clear()
            .bounds(0, 0, width, height)
            .radius(r)
            .fill(ColorUtils.lerpColor(Styles.backColor.color(), Styles.textColor.color(), .1f));
         paint.rect(track);

         // Over target gets its own tone. A bar that just stops at 100% hides the best news on the
         // screen, and one that keeps growing past the edge hides the worst.
         fill.clear()
            .bounds(0, 0, reached, height)
            .radius(r)
            // Over target gets a DEEPER shade of the same hue, not a different colour: the reader
            // should see "more of the same thing", and tertiary() on a blue seed resolves to a
            // muted tone that reads as less rather than more.
            .fill(value > 1f
               ? ColorUtils.lerpColor(Styles.primaryColor.color(), Styles.textColor.color(), .4f)
               : Styles.primaryColor.color());
         paint.rect(fill);

         // The target line. One pixel is invisible on a dense screen — measure it in font units.
         int t = Math.max(2, Math.round(Styles.fontHeight * .08f));
         mark.clear()
            .bounds(width - t, -t, t, height + t * 2)
            .fill(Styles.textColor.color());
         paint.rect(mark);
      }
   }
}
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
AnteriorAnimationSiguienteFloating action button