Skip to content
CrossFly is 100% open sourceFollow us on GitHub
CrossFlyCrossFly
Get started

This page was machine-translated from Portuguese and may contain errors. The Portuguese version is canonical.

Custom drawing (onPaint)

When no widget fits, create a Component and override onPaint(Paint paint). The coordinates are local (0,0 = top-left corner; width/height are the component’s size).

Complete example (copy, paste and run)

package com.exemplo;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.graphics.*;    // Paint, GRect, Font
import dev.crossfly.ui.style.*;       // Styles

/** Runnable main screen. Point the launcher (classname) and run: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   /** Custom component: draws a "badge" (colored pill with a number). */
   static class Badge extends Component
   {
      private int valor;

      Badge(int valor) { this.valor = valor; }

      @Override
      public void onPaint(Paint paint) throws Exception
      {
         // LOCAL coordinates: (0,0) = top-left; width/height = component size.
         GRect r = new GRect()
            .bounds(0, 0, width, height)
            .fill(0xFFE53935)          // red ARGB
            .radius(height / 2);       // pill / circle
         paint.rect(r);

         Font f = Styles.font;
         String s = String.valueOf(valor);
         int tx = (width - (int) f.width(s)) / 2;
         int ty = (height - f.scaledHeight()) / 2;
         paint.text(s, tx, ty, 0xFFFFFFFF, f);   // centered white text
      }
   }

   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());
      Badge badge = new Badge(7);
      add(LAY_AUTO, badge);   // adds the custom component to the screen
   }
}

The snippets below show only the core (what goes inside onShow()). To run them, place them in the MainWindow skeleton above.

Skeleton

public class Badge extends Component {
   private int valor;
   public Badge(int valor) { this.valor = valor; }

   @Override public void onPaint(Paint paint) throws Exception {
      // filled round background
      GRect r = new GRect()
         .bounds(0, 0, width, height)
         .fill(0xFFE53935)               // red
         .radius(height / 2);            // pill/circle
      paint.rect(r);

      // centered text
      Font f = Styles.font;
      String s = String.valueOf(valor);
      int tx = (width - (int) f.width(s)) / 2;
      int ty = (height - f.scaledHeight()) / 2;
      paint.text(s, tx, ty, 0xFFFFFFFF, f);
   }
}
// to redraw after changing 'value': repaint();

Paint primitives

Call Draws
paint.rect(GRect r) rectangle (filled/border/corners/shadow) — configure the GRect
paint.text(String, x, y, color, Font) text
paint.line(x1, y1, x2, y2, color, thick) line
paint.image(Image, x, y[, w, h]) image
paint.polyline(GLines) / paint.polygon(GLines) polyline / polygon

GRect — the configurable rectangle

Fluent: .bounds(x,y,w,h) · .fill(color) · .border(color, thick) · .radius(r) / .radius(tl,tr,br,bl). Then paint.rect(gr).

GRect card = new GRect().bounds(0, 0, width, height)
   .fill(0xFFFFFFFF).border(0xFFE0E0E0, 1).radius(8);
paint.rect(card);

Best practices

  • Draw relative to width/height (not fixed values) — keeps it responsive.
  • Reuse GRect/Font objects (fields) instead of allocating per frame.
  • Call repaint() when the state that affects the drawing changes (rendering is on demand).

Common mistakes

  • Using absolute screen coordinates in onPaint — they are local to the component.
  • Forgetting repaint() after changing the drawn data.
  • Passing color as an object — colors are int ARGB (the Color class does not exist).

See also

Was this page helpful?