paint method

  1. @override
void paint(
  1. Canvas cv,
  2. Size size,
  3. double p,
  4. Offset center,
  5. Color baseColor, {
  6. double radiusMultiplier = 1,
  7. Offset positionOffset = Offset.zero,
})
override

Implementation

@override
void paint(Canvas cv, Size size, double p, Offset center, Color baseColor,
    {double radiusMultiplier = 1, Offset positionOffset = Offset.zero}) {
  final c = center + positionOffset;
  final outerR = (math.max(size.width, size.height) / 2 + orbitMargin) *
      radiusMultiplier;

  // radius setiap ring (dalam → luar)
  final List<double> ringR = List.generate(
    ringCount,
    (i) => outerR - (ringCount - 1 - i) * ringGap * radiusMultiplier,
  );

  // durasi spawn per dot (agar dot‑N selesai tepat di _spawnEnd)
  final spawnSlice = _spawnEnd / particlesPerRing;

  for (int ringIdx = 0; ringIdx < ringCount; ringIdx++) {
    final rBase = ringR[ringIdx];

    for (int j = 0; j < particlesPerRing; j++) {
      final angle0 =
          (j + (ringIdx.isOdd ? .5 : 0)) * 2 * math.pi / particlesPerRing;

      // ─── progress lokal (0‑1) per dot ───
      final double delay = j * spawnSlice;
      double t;
      if (p < delay) continue; // belum lahir
      if (p < _spawnEnd) {
        t = (p - delay) / spawnSlice; // 0‑1 dalam fase spawn
        if (t > 1) t = 1;
      } else {
        // semua dot sudah muncul
        t = 1 + (p - _spawnEnd) / (1 - _spawnEnd); // 1‑2 rentang orbit‑return
      }

      // ─── radius & sudut ───
      double r, theta;
      if (t <= 1) {
        // fase Spawn (per‑dot)
        r = _easeOutBack(t) * rBase;
        theta = angle0; // belum berputar
      } else if (p < _orbitEnd) {
        // fase Orbit
        final tt = (p - _spawnEnd) / (_orbitEnd - _spawnEnd);
        r = rBase;
        theta = angle0 + 2 * math.pi * rotations * tt;
      } else {
        // fase Return
        final tt = (p - _orbitEnd) / (1 - _orbitEnd);
        r = rBase * (1 - _easeIn(tt));
        theta = angle0 + 2 * math.pi * rotations;
      }

      final pos = c + Offset(math.cos(theta), math.sin(theta)) * r;

      // ─── opacity & skala ───
      final globalFade = p < .92 ? 1.0 : 1 - (p - .92) / .08;
      final scale = globalFade;

      // ─── warna (HSV) ───
      Color col = baseColor;
      if (enableHueTilt) {
        final hsl = HSLColor.fromColor(baseColor);
        final shift = (angle0 / (2 * math.pi)) * 360 * hueTiltRange;
        col = hsl
            .withHue((hsl.hue + shift) % 360)
            .withSaturation((hsl.saturation * saturationBoost).clamp(0, 1))
            .toColor();
      }
      col = col.withOpacity(globalFade);

      final drawR = circleRadius * radiusMultiplier * scale;

      // glow
      cv.drawCircle(
          pos,
          drawR * 1.6,
          Paint()
            ..color = col.withOpacity(.25)
            ..maskFilter = MaskFilter.blur(BlurStyle.normal, glowSigma));

      // dot utama
      cv.drawCircle(pos, drawR, Paint()..color = col);
    }
  }
}