Pular para o conteúdo
CrossFly é 100% open sourceSiga no GitHub
CrossFlyCrossFly
Começar
Todos os exemplos
ReceitasComponentes próprios161 linhas · 1 arquivos

Writing your own component

Search dev/crossfly/ui first — most of what you are about to write is already there. When it genuinely is not, this is the shape.

O que isto mostra

Writing a component of your own — and the check to run before you do.

Search the inventory first. CrossFly is larger than it looks and its components are not discoverable by guessing; the reference app on this framework rebuilt a single-choice selector, a character limit and a grid column that all already existed, with the framework source compiled into its own build. A missing knob on an existing component is a smaller change than a new component, and it is the change the framework wants.

When nothing fits, a component is four decisions, and the one below makes all four.

  1. What it holds — plain fields, set through a fluent setter that returns this, so it reads like every other widget.
  2. How big it wants to be — getAutoWidth/getAutoHeight, in font units, so it scales with density instead of being right on one screen.
  3. What it looks like — onPaint, taking every colour from a token so the theme and the skin reach it without a branch.
  4. What a tap means — onTouchUp, then tell the app through a callback. A component that acts on its own tap is a component that can only be used once.

Rodar na sua máquina

Todo exemplo é uma Window comum, então o launcher só precisa do nome dela.

gradlew lwjgl3:run --args="classname dev.cf.recipe.R28Component"
Editar o exemplo

Java é compilado antes de rodar, então o navegador não consegue reconstruir uma edição sozinho — o código que você vê aqui virou JavaScript pelo TeaVM antes de ser publicado. Copie o arquivo para um projeto e ele roda sem mudanças nas quatro plataformas; o comando acima é toda a preparação.

Rodando aquiCrossFly 0.1.32
Iniciando o aplicativo…

Compilado para JavaScript pelo TeaVM — é o mesmo código, não um vídeo dele. · Todos os exemplos

O que ele usa

Tipos do framework que este exemplo toca. Os que têm link têm página de referência.

ComponentLabelMaterialIconPaintPalColorScreenStylesWidgets

O código

Completo, sem cortes. É o arquivo, não um trecho dele.

Bruto
dev/cf/recipe/R28Component.java
package dev.cf.recipe;

import dev.cf.Tones;

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

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

/**
 * Writing a component of your own — and the check to run before you do.
 *
 * <p><b>Search the inventory first.</b> CrossFly is larger than it looks and its components are not
 * discoverable by guessing; the reference app on this framework rebuilt a single-choice selector, a
 * character limit and a grid column that all already existed, with the framework source compiled
 * into its own build. A missing knob on an existing component is a smaller change than a new
 * component, and it is the change the framework wants.</p>
 *
 * <p>When nothing fits, a component is four decisions, and the one below makes all four.</p>
 *
 * <ol>
 *   <li><b>What it holds</b> — plain fields, set through a fluent setter that returns {@code this},
 *       so it reads like every other widget.</li>
 *   <li><b>How big it wants to be</b> — {@code getAutoWidth}/{@code getAutoHeight}, in font units,
 *       so it scales with density instead of being right on one screen.</li>
 *   <li><b>What it looks like</b> — {@code onPaint}, taking every colour from a token so the theme
 *       and the skin reach it without a branch.</li>
 *   <li><b>What a tap means</b> — {@code onTouchUp}, then tell the app through a callback. A
 *       component that acts on its own tap is a component that can only be used once.</li>
 * </ol>
 */
public class R28Component extends Recipe
{
   private Label chosen;

   public String title()
   {
      return "Writing your own component";
   }

   public String note()
   {
      return "Search dev/crossfly/ui first — most of what you are about to write is already there."
         + " When it genuinely is not, this is the shape.";
   }

   public void build(Component body) throws Exception
   {
      chosen = label("Nothing rated yet.", 1.5f, true, Tones.accent(), Position.START);
      body.add(LAY_AUTO, outgap(chosen, 0, 0, 0, .8f));

      Rating r = new Rating().stars(5)
         .value(3)
         .onChange(v ->
         {
            chosen.setText(v + " of 5");
            chosen.setValidLayout(false);
            Screen.repaint();
         });
      r.testTag("recipe.rating");
      body.add(LAY_AUTO, r);

      body.add(LAY_AUTO, outgap(label("Tap a star. The component reports the number and does"
         + " nothing else with it — which is what lets the same class drive a filter here and a"
         + " form field somewhere else.", 1f, false, Styles.captionColor, Position.START), 0, 1f, 0,
         0));
   }

   /** A row of stars: pick a value from one to N. */
   private static class Rating extends Component
   {
      /** Material star and star_border. */
      private static final String FILLED = "", EMPTY = "";

      private int stars = 5, value;
      private RunnableWithInt onChange;

      /** Glyphs and the shape are FIELDS: onPaint runs every frame and must not allocate. */
      private final MaterialIcon on = new MaterialIcon(FILLED, 1.6f),
         off = new MaterialIcon(EMPTY, 1.6f);

      Rating stars(int n)
      {
         stars = n;
         return this;
      }

      Rating value(int v)
      {
         value = v;
         return this;
      }

      Rating onChange(RunnableWithInt r)
      {
         onChange = r;
         return this;
      }

      /** One star per slot, square, sized from the font like everything else. */
      private int slot()
      {
         return Math.round(Styles.fontHeight * 2f);
      }

      @Override
      public int getAutoWidth()
      {
         return slot() * stars;
      }

      @Override
      public int getAutoHeight()
      {
         return slot();
      }

      @Override
      public void onPaint(Paint paint) throws Exception
      {
         int s = slot();
         for (int i = 0; i < stars; i++)
         {
            MaterialIcon g = i < value ? on : off;
            // Colour from a token, never a literal: this is what makes the component correct in
            // dark mode and under every skin without knowing which one is in force.
            PalColor tone = i < value ? Styles.primaryColor : Styles.captionColor;
            // drawCentered takes the CENTRE, so the glyph's own metrics stay the icon's business.
            g.drawCentered(paint, i * s + s / 2, height / 2, tone.color());
         }
      }

      /**
       * A tap picks a star and REPORTS it. It does not decide what that means — the screen does,
       * and that separation is the whole reason this class is reusable.
       */
      @Override
      public void onTouchUp(float x, float y, int button) throws Exception
      {
         int picked = Math.min(stars, Math.max(1, (int) (x / slot()) + 1));
         if (picked != value)
         {
            value = picked;
            if (onChange != null)
               onChange.run(picked);
            Screen.repaint();
         }
      }
   }

   /** A one-method callback carrying an int — the framework's RunnableWith, narrowed. */
   private interface RunnableWithInt
   {
      void run(int value);
   }
}
Para uma IA

Versões desta página legíveis por máquina, para um modelo ler a prateleira em vez de adivinhar a API.

Este exemplo, em JSONEste exemplo, em Markdown
AnteriorKeyboard and focusPróximoBrand and dark mode