import 'package:flutter/material.dart'; class CanvasRuler extends StatelessWidget { final double lengthMm; final double scale; // pixels per mm final bool isHorizontal; final double thickness; const CanvasRuler({ super.key, required this.lengthMm, required this.scale, this.isHorizontal = true, this.thickness = 20.0, }); @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return CustomPaint( size: isHorizontal ? Size(lengthMm * scale, thickness) : Size(thickness, lengthMm * scale), painter: _RulerPainter( lengthMm: lengthMm, scale: scale, isHorizontal: isHorizontal, isDark: isDark, ), ); } } class _RulerPainter extends CustomPainter { final double lengthMm; final double scale; final bool isHorizontal; final bool isDark; _RulerPainter({ required this.lengthMm, required this.scale, required this.isHorizontal, required this.isDark, }); @override void paint(Canvas canvas, Size size) { final bgPaint = Paint() ..color = isDark ? const Color(0xFF1E293B) : const Color(0xFFF1F5F9); canvas.drawRect(Offset.zero & size, bgPaint); final linePaint = Paint() ..color = isDark ? Colors.white30 : Colors.black26 ..strokeWidth = 1.0; final textStyle = TextStyle( color: isDark ? Colors.white60 : Colors.black54, fontSize: 8, fontWeight: FontWeight.w500, ); final textPainter = TextPainter( textDirection: TextDirection.ltr, ); // Draw ticks every 1mm, with labels every 5mm or 10mm for (int mm = 0; mm <= lengthMm.ceil(); mm++) { final pos = mm * scale; final isTen = mm % 10 == 0; final isFive = mm % 5 == 0; final tickLength = isTen ? 12.0 : (isFive ? 7.0 : 4.0); if (isHorizontal) { canvas.drawLine( Offset(pos, size.height - tickLength), Offset(pos, size.height), linePaint, ); if (isTen && mm > 0 && mm < lengthMm) { textPainter.text = TextSpan(text: '$mm', style: textStyle); textPainter.layout(); textPainter.paint(canvas, Offset(pos - (textPainter.width / 2), 2)); } } else { canvas.drawLine( Offset(size.width - tickLength, pos), Offset(size.width, pos), linePaint, ); if (isTen && mm > 0 && mm < lengthMm) { textPainter.text = TextSpan(text: '$mm', style: textStyle); textPainter.layout(); textPainter.paint(canvas, Offset(2, pos - (textPainter.height / 2))); } } } } @override bool shouldRepaint(covariant _RulerPainter oldDelegate) { return oldDelegate.lengthMm != lengthMm || oldDelegate.scale != scale || oldDelegate.isHorizontal != isHorizontal || oldDelegate.isDark != isDark; } }