# 自定义绘制

> 重写 onPaint(Paint) 并使用绘图原语 — rect (GRect)、text、line、image。

Source: https://crossfly.dev/zh/docs/ui/images-and-graphics/custom-drawing — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# 自定义绘制（`onPaint`）

当没有任何 widget 合适时，创建一个 `Component` 并重写 **`onPaint(Paint paint)`**。坐标是
**局部的**（0,0 = 左上角；`width`/`height` 是组件的尺寸）。

## 完整示例（复制、粘贴并运行）
```java
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

/** 可执行的主界面。将 launcher 指向此类（classname）并运行：gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   /** 自定义组件：绘制一个"徽章"（带数字的彩色药丸）。 */
   static class Badge extends Component
   {
      private int valor;

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

      @Override
      public void onPaint(Paint paint) throws Exception
      {
         // 本地坐标：(0,0) = 左上角；width/height = 组件尺寸。
         GRect r = new GRect()
            .bounds(0, 0, width, height)
            .fill(0xFFE53935)          // 红色 ARGB
            .radius(height / 2);       // 药丸 / 圆形
         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);   // 居中的白色文本
      }
   }

   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());
      Badge badge = new Badge(7);
      add(LAY_AUTO, badge);   // 将自定义组件添加到屏幕
   }
}
```
> 下面的片段只展示了**核心部分**（放入 `onShow()` 内部的内容）。要运行它们，请将其放入上面的 `MainWindow` 骨架中。

## 骨架
```java
public class Badge extends Component {
   private int valor;
   public Badge(int valor) { this.valor = valor; }

   @Override public void onPaint(Paint paint) throws Exception {
      // 填充的圆形背景
      GRect r = new GRect()
         .bounds(0, 0, width, height)
         .fill(0xFFE53935)               // 红色
         .radius(height / 2);            // 药丸形/圆形
      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);
   }
}
// 修改“值”后重绘：repaint();
```

## `Paint` 的绘图原语
| 调用 | 绘制 |
|---|---|
| `paint.rect(GRect r)` | 矩形（填充/边框/圆角/阴影）— 配置 `GRect` |
| `paint.text(String, x, y, color, Font)` | 文本 |
| `paint.line(x1, y1, x2, y2, color, thick)` | 线条 |
| `paint.image(Image, x, y[, w, h])` | 图像 |
| `paint.polyline(GLines)` / `paint.polygon(GLines)` | 折线 / 多边形 |

## `GRect` — 可配置的矩形
流式：`.bounds(x,y,w,h)` · `.fill(color)` · `.border(color, thick)` · `.radius(r)` /
`.radius(tl,tr,br,bl)`。然后 `paint.rect(gr)`。
```java
GRect card = new GRect().bounds(0, 0, width, height)
   .fill(0xFFFFFFFF).border(0xFFE0E0E0, 1).radius(8);
paint.rect(card);
```

## 最佳实践
- 绘制时**相对于 `width`/`height`**（而非固定值）— 保持响应式。
- 复用 `GRect`/`Font` 对象（作为字段），而不是每帧分配。
- 当影响绘制的状态改变时，调用 `repaint()`（渲染是按需的）。

## 常见错误
- 在 `onPaint` 中使用屏幕的绝对坐标 — 它们是相对于组件的**局部**坐标。
- 改变被绘制的数据后忘记 `repaint()`。
- 将颜色作为对象传递 — 颜色是 `int` ARGB（`Color` 类不存在）。

## 另请参阅
- [图像](/docs/ui/images-and-graphics/images) · [Whiteboard](/docs/ui/images-and-graphics/whiteboard) · [颜色](/docs/ui/theming/color-schemes)
