chill-chinese / stroke-order-animator

Stroke order animations and quizzes for Chinese characters in Flutter.
https://chill-chinese.github.io/stroke-order-animator/
BSD 3-Clause "New" or "Revised" License
86 stars 22 forks source link

Add options for background grids #5

Closed Mr-Pepe closed 1 month ago

Mr-Pepe commented 4 years ago

It should be easily possible to change the background of the stroke order diagram. Examples:

Mr-Pepe commented 1 month ago

Reopening due to the request in https://github.com/chill-chinese/stroke-order-animator/issues/94#issuecomment-2296081697.

Mr-Pepe commented 1 month ago

I think this feature should be implemented by consumers of the library to fit their specific needs. Otherwise, people will probably want to customize every aspect of the grid (dashed vs. solid lines, dash length, gap length between dashes, line width, line color, ...) which sounds like a maintenance nightmare.

Here is some example code to draw an outline and a combination of the cross and diagonals with dashed lines:

class StrokeOrderDiagramBackgroundPainter extends CustomPainter {
  const StrokeOrderDiagramBackgroundPainter(this.theme);

  final StrokeOrderDiagramTheme theme;

  @override
  void paint(Canvas canvas, Size size) {
    const color = Colors.black;
    final paint = Paint()..color = color;

    const dashLength = 10.0;
    const dashSpace = 5.0;

    const topLeftCorner = Offset.zero;
    final topRightCorner = Offset(size.width, 0);
    final bottomLeftCorner = Offset(0, size.height);
    final bottomRightCorner = Offset(size.width, size.height);
    final topMiddle = Offset(size.width / 2, 0);
    final bottomMiddle = Offset(size.width / 2, size.height);
    final leftMiddle = Offset(0, size.height / 2);
    final rightMiddle = Offset(size.width, size.height / 2);

    void paintDashedLine(Offset p1, Offset p2) {
      double getOffset(double length) {
        double d = 0.0;
        while (d + dashLength < length) {
          d += dashLength + dashSpace;
        }
        d -= dashSpace;
        return (length - d) / 2;
      }

      final dx = p2.dx - p1.dx;
      final dy = p2.dy - p1.dy;
      final length = sqrt(dx * dx + dy * dy);

      final unitDx = dx / length;
      final unitDy = dy / length;

      double p = getOffset(length);

      while (p < length) {
        final p1d = Offset(p1.dx + p * unitDx, p1.dy + p * unitDy);
        final p2d = Offset(
          p1.dx + (p + dashLength) * unitDx,
          p1.dy + (p + dashLength) * unitDy,
        );
        canvas.drawLine(p1d, p2d, paint);
        p += dashLength + dashSpace;
      }
    }

    // Outline
    canvas.drawLine(topLeftCorner, topRightCorner, paint);
    canvas.drawLine(topRightCorner, bottomRightCorner, paint);
    canvas.drawLine(bottomRightCorner, bottomLeftCorner, paint);
    canvas.drawLine(bottomLeftCorner, topLeftCorner, paint);

    // Cross
    paintDashedLine(topMiddle, bottomMiddle);
    paintDashedLine(leftMiddle, rightMiddle);

    // Diagonals
    paintDashedLine(topLeftCorner, bottomRightCorner);
    paintDashedLine(topRightCorner, bottomLeftCorner);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

Wrap the painter in a CustomPaint widget and stack it with the StrokeOrderAnimator:

Container(
  constraints: BoxConstraints.loose(const Size.square(diagramSize)),
  child: Stack(
    children: [
      CustomPaint(
        size: const Size.square(diagramSize),
        painter: StrokeOrderDiagramBackgroundPainter(diagramTheme),
      ),
      StrokeOrderAnimator(
        controller,
        size: const Size.square(diagramSize),
      ),
    ],
  ),
)

The result looks like this:

image