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

Capturing a signature

White pad, not the theme surface: the image is captured to be printed. Under dark mode a themed pad captures white ink on navy, which is unusable downstream.

Qué muestra esto

Capturing a signature, and the three details that separate one that works in the field.

None of them is about drawing — DrawingBoard handles that. They are about what happens around it.

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

ButtonComponentComponentStyleDrawingBoardGapGridLayoutHintMaterialIconRelativeScreenStylesVerticalLayoutWidgets

El código

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

Crudo
dev/cf/recipe/R19Signature.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.*;

/**
 * Capturing a signature, and the three details that separate one that works in the field.
 *
 * <p>None of them is about drawing — {@link DrawingBoard} handles that. They are about what happens
 * around it.</p>
 */
public class R19Signature extends Recipe
{
   private DrawingBoard board;
   private Button confirm;
   private boolean drawn;

   public String title()
   {
      return "Capturing a signature";
   }

   public String note()
   {
      return "White pad, not the theme surface: the image is captured to be printed. Under dark"
         + " mode a themed pad captures white ink on navy, which is unusable downstream.";
   }

   public void build(Component body) throws Exception
   {
      Component pad = new Component();
      pad.setLayout(new VerticalLayout(Position.START, Position.START));
      // 1 · The pad has a colour of its own. This is the ONE place ignoring the theme is right,
      // because the output is not a screen — it is an attachment on a document.
      pad.getStyle()
         .backColor(0xFFFFFFFF)
         .borderColor(Styles.borderColor)
         .borderThick(Styles.borderThick)
         .borderRadius(Styles.borderRadius)
         .autoHeight(Relative.ofFont(10f));
      pad.testTag("sign.pad");

      // 2 · The DrawingBoard reports strokes by letting you override its touch handlers. There is no
      // listener to look for, and adding one to the framework for this would be the wrong fix.
      board = new DrawingBoard()
      {
         @Override
         public void onTouchUp(float x, float y, int button) throws Exception
         {
            super.onTouchUp(x, y, button);
            setDrawn(true);
         }
      };
      board.setPenColor(0xFF111111);
      pad.add(LAY_FILL, board);
      body.add(LAY_AUTO, outgap(pad, 0, 0, 0, .8f));

      // Ink and pen, as in the DrawingBoard sample: a few ink colours and the thin/thick toggle.
      // A signature was black and hairline only (asked for on the iPhone, 17/10/2026).
      body.add(LAY_AUTO, outgap(inkPalette(), 0, 0, 0, .8f));

      Component row = new Component();
      row.setLayout(new GridLayout(Position.START, Position.CENTER));

      Button clear = new Button("Clear");
      clear.testTag("sign.clear");
      // Clear asks nothing: it is instantly and obviously reversible — you sign again.
      clear.onAction(c ->
      {
         board.clear();
         setDrawn(false);
         Screen.repaint();
      });

      // 3 · Confirm is dead until something is drawn. An empty signature is the easiest thing to
      // capture by accident and is indistinguishable from a real one once it is stored.
      confirm = Tones.action("Confirm");
      confirm.testTag("sign.confirm");
      confirm.setEnabled(false);
      pintarConfirm();
      confirm.onAction(c -> Hint.show("Signature captured", 2000));

      row.add(.5f, .5f, clear, confirm);
      body.add(LAY_AUTO, row);
   }

   /** Ink colours a signature is made with: black, navy, blue. */
   private static final int[] INKS = {0xFF111111, 0xFF1A237E, 0xFF1E88E5};

   /**
    * The ink row: one dot per ink and the pen-thickness toggle - the DrawingBoard sample's palette.
    *
    * @return the row
    * @throws Exception when building fails
    */
   private Component inkPalette() throws Exception
   {
      Component row = new Component();
      row.setLayout(new GridLayout(Position.START, Position.CENTER));
      Object[] params = new Object[(INKS.length + 1) * 2];
      final Button[] dots = new Button[INKS.length];
      for (int i = 0; i < INKS.length; i++)
      {
         final int ink = INKS[i], index = i;
         ComponentStyle dot = Styles.button()
            .copy()
            .backColor(0)
            .borderColor(0)
            .iconColor(ink)
            .ingap(Gap.ofFont(.4f, .3f, .4f, .3f));
         Button b = new Button(inkIcon(i == 0)).style(dot);
         b.testTag("sign.ink." + i);
         // The chosen ink carries a check: a pen colour you cannot see until you draw is a guess
         // (found by Test73, 19/10/2026 - tapping a dot changed nothing on the screen).
         b.onAction(e ->
         {
            board.setPenColor(ink);
            for (int k = 0; k < dots.length; k++)
            {
               dots[k].icon(inkIcon(k == index), dots[k].textPos);
               dots[k].setValidLayout(false);
            }
            Screen.repaint();
         });
         dots[i] = b;
         params[i] = 0f;
         params[INKS.length + 1 + i] = b;
      }
      ComponentStyle pen = Styles.button()
         .copy()
         .backColor(0)
         .borderColor(0)
         .iconColor(Styles.textColor)
         .ingap(Gap.ofFont(.4f, .3f, .4f, .3f));
      // brush, with the pen it gives: like the ink, the thickness in force is on the screen.
      Button thick = new Button("Thin", new MaterialIcon("\ue3ae", 1.4f), TextPos.TEXT_AT_RIGHT).style(pen);
      thick.testTag("sign.thick");
      thick.onAction(e ->
      {
         board.thick = !board.thick;
         thick.setText(board.thick ? "Thick" : "Thin");
      });
      params[INKS.length] = 0f;
      params[INKS.length * 2 + 1] = thick;
      row.add(params);
      return row;
   }

   /** A filled dot, and the same dot with a check for the ink in force. */
   private static MaterialIcon inkIcon(boolean chosen)
   {
      return new MaterialIcon(chosen ? "" : "", 1.5f);
   }

   private void setDrawn(boolean b)
   {
      if (drawn != b)
      {
         drawn = b;
         confirm.setEnabled(b);
         pintarConfirm();
      }
   }

   /**
    * Fills Confirm only while it can be pressed.
    *
    * <p>{@code setEnabled(false)} dims the label and stops the touch, and on the skin's outlined
    * button that is the whole story. A FILLED button is different: the fill keeps shouting "press
    * me" at a control that will not answer, which is worse than no styling at all. So the fill is
    * part of the state, not part of the button.</p>
    */
   private void pintarConfirm()
   {
      confirm.getStyle()
         .backColor(drawn ? Tones.accent() : Tones.accent().withAlpha(0x22))
         .textColor(drawn ? Styles.colorScheme().onPrimary : Tones.accent().withAlpha(0x88));
      confirm.setValidLayout(false);
   }
}
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
AnteriorStoring somethingSiguientePickers