Implémentation du zoom et pan pour le mode ajout d'impact

corrigé les impactes en mode zoom
This commit is contained in:
2026-01-18 16:31:45 +01:00
parent d3bbc9c718
commit 6b0cb8f837
7 changed files with 1034 additions and 273 deletions

View File

@@ -17,13 +17,14 @@ class TargetOverlay extends StatelessWidget {
final double targetRadius;
final TargetType targetType;
final int ringCount;
final List<double>? ringRadii; // Individual ring radii multipliers
final List<double>? ringRadii;
final void Function(Shot shot)? onShotTapped;
final void Function(double x, double y)? onAddShot;
final double? groupingCenterX;
final double? groupingCenterY;
final double? groupingDiameter;
final List<Shot>? referenceImpacts;
final double zoomScale;
const TargetOverlay({
super.key,
@@ -40,70 +41,80 @@ class TargetOverlay extends StatelessWidget {
this.groupingCenterY,
this.groupingDiameter,
this.referenceImpacts,
this.zoomScale = 1.0,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapUp: (details) {
if (onAddShot != null) {
final RenderBox box = context.findRenderObject() as RenderBox;
final localPosition = details.localPosition;
final relX = localPosition.dx / box.size.width;
final relY = localPosition.dy / box.size.height;
onAddShot!(relX, relY);
}
},
child: CustomPaint(
painter: _TargetOverlayPainter(
shots: shots,
targetCenterX: targetCenterX,
targetCenterY: targetCenterY,
targetRadius: targetRadius,
targetType: targetType,
ringCount: ringCount,
ringRadii: ringRadii,
groupingCenterX: groupingCenterX,
groupingCenterY: groupingCenterY,
groupingDiameter: groupingDiameter,
referenceImpacts: referenceImpacts,
),
child: Stack(
children: shots.map((shot) {
return Positioned(
left: 0,
top: 0,
right: 0,
bottom: 0,
child: LayoutBuilder(
builder: (context, constraints) {
final x = shot.x * constraints.maxWidth;
final y = shot.y * constraints.maxHeight;
return Stack(
children: [
Positioned(
left: x - 15,
top: y - 15,
child: GestureDetector(
onTap: () => onShotTapped?.call(shot),
child: Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.transparent,
shape: BoxShape.circle,
return LayoutBuilder(
builder: (context, constraints) {
return GestureDetector(
behavior: HitTestBehavior.translucent,
onTapUp: (details) {
if (onAddShot != null) {
// Utiliser les constraints pour un calcul précis
final relX = details.localPosition.dx / constraints.maxWidth;
final relY = details.localPosition.dy / constraints.maxHeight;
onAddShot!(relX, relY);
}
},
child: CustomPaint(
painter: _TargetOverlayPainter(
shots: shots,
targetCenterX: targetCenterX,
targetCenterY: targetCenterY,
targetRadius: targetRadius,
targetType: targetType,
ringCount: ringCount,
ringRadii: ringRadii,
groupingCenterX: groupingCenterX,
groupingCenterY: groupingCenterY,
groupingDiameter: groupingDiameter,
referenceImpacts: referenceImpacts,
zoomScale: zoomScale,
),
child: Stack(
children: shots.map((shot) {
return Positioned(
left: 0,
top: 0,
right: 0,
bottom: 0,
child: LayoutBuilder(
builder: (context, innerConstraints) {
final x = shot.x * innerConstraints.maxWidth;
final y = shot.y * innerConstraints.maxHeight;
// Zone de tap qui s'adapte au zoom (taille fixe à l'écran)
final tapSize = 30 / zoomScale;
final halfTapSize = tapSize / 2;
return Stack(
children: [
Positioned(
left: x - halfTapSize,
top: y - halfTapSize,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => onShotTapped?.call(shot),
child: Container(
width: tapSize,
height: tapSize,
decoration: const BoxDecoration(
color: Colors.transparent,
shape: BoxShape.circle,
),
),
),
),
),
),
],
);
},
),
);
}).toList(),
),
),
],
);
},
),
);
}).toList(),
),
),
);
},
);
}
}
@@ -120,6 +131,7 @@ class _TargetOverlayPainter extends CustomPainter {
final double? groupingCenterY;
final double? groupingDiameter;
final List<Shot>? referenceImpacts;
final double zoomScale;
_TargetOverlayPainter({
required this.shots,
@@ -133,6 +145,7 @@ class _TargetOverlayPainter extends CustomPainter {
this.groupingCenterY,
this.groupingDiameter,
this.referenceImpacts,
this.zoomScale = 1.0,
});
@override
@@ -266,26 +279,32 @@ class _TargetOverlayPainter extends CustomPainter {
final x = shot.x * size.width;
final y = shot.y * size.height;
// Tailles fixes divisées par le zoom pour rester constantes à l'écran
final outerRadius = 10 / zoomScale;
final innerRadius = 8 / zoomScale;
final strokeWidth = 3 / zoomScale;
final fontSize = 10 / zoomScale;
// Draw outer circle (white outline for visibility)
final outlinePaint = Paint()
..color = AppTheme.impactOutlineColor
..style = PaintingStyle.stroke
..strokeWidth = 3;
canvas.drawCircle(Offset(x, y), 10, outlinePaint);
..strokeWidth = strokeWidth;
canvas.drawCircle(Offset(x, y), outerRadius, outlinePaint);
// Draw impact marker
final impactPaint = Paint()
..color = AppTheme.impactColor
..style = PaintingStyle.fill;
canvas.drawCircle(Offset(x, y), 8, impactPaint);
canvas.drawCircle(Offset(x, y), innerRadius, impactPaint);
// Draw score number
final textPainter = TextPainter(
text: TextSpan(
text: '${shot.score}',
style: const TextStyle(
style: TextStyle(
color: Colors.white,
fontSize: 10,
fontSize: fontSize,
fontWeight: FontWeight.bold,
),
),
@@ -302,26 +321,32 @@ class _TargetOverlayPainter extends CustomPainter {
final x = ref.x * size.width;
final y = ref.y * size.height;
// Tailles fixes divisées par le zoom pour rester constantes à l'écran
final outerRadius = 12 / zoomScale;
final innerRadius = 10 / zoomScale;
final strokeWidth = 3 / zoomScale;
final fontSize = 12 / zoomScale;
// Draw outer circle (white outline for visibility)
final outlinePaint = Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = 3;
canvas.drawCircle(Offset(x, y), 12, outlinePaint);
..strokeWidth = strokeWidth;
canvas.drawCircle(Offset(x, y), outerRadius, outlinePaint);
// Draw reference marker (purple)
final refPaint = Paint()
..color = Colors.deepPurple
..style = PaintingStyle.fill;
canvas.drawCircle(Offset(x, y), 10, refPaint);
canvas.drawCircle(Offset(x, y), innerRadius, refPaint);
// Draw "R" to indicate reference
final textPainter = TextPainter(
text: const TextSpan(
text: TextSpan(
text: 'R',
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontSize: fontSize,
fontWeight: FontWeight.bold,
),
),
@@ -345,6 +370,7 @@ class _TargetOverlayPainter extends CustomPainter {
groupingCenterX != oldDelegate.groupingCenterX ||
groupingCenterY != oldDelegate.groupingCenterY ||
groupingDiameter != oldDelegate.groupingDiameter ||
referenceImpacts != oldDelegate.referenceImpacts;
referenceImpacts != oldDelegate.referenceImpacts ||
zoomScale != oldDelegate.zoomScale;
}
}