fix(analysis): appliquer la transformation globale sur le GestureDetector pour restaurer le drag-and-drop

This commit is contained in:
qguillaume
2026-05-27 12:16:35 +02:00
parent 95e62abe47
commit ab45786d0c

View File

@@ -403,120 +403,119 @@ class _AnalysisScreenContentState extends State<_AnalysisScreenContent> {
maxScale: 10.0,
boundaryMargin: const EdgeInsets.all(double.infinity),
panEnabled: _movingShotId == null,
child: GestureDetector(
onDoubleTapDown: (TapDownDetails details) {
final RenderBox? box = _imageKey.currentContext?.findRenderObject() as RenderBox?;
if (box == null) return;
child: Transform(
// CORRECTION GÉOMÉTRIQUE : On déplace le Transform ICI pour englober le GestureDetector et le Stack d'un coup !
transform: Matrix4.identity()
..setTranslationRaw(widget.cropOffset?.dx ?? 0.0, widget.cropOffset?.dy ?? 0.0, 0.0)
..scale(1.0, 1.0)
..rotateZ((provider.cropRotation) * (math.pi / 180)),
alignment: Alignment.center,
child: GestureDetector(
onDoubleTapDown: (TapDownDetails details) {
final RenderBox? box = _imageKey.currentContext?.findRenderObject() as RenderBox?;
if (box == null) return;
final localOffset = box.globalToLocal(details.globalPosition);
final relX = localOffset.dx / box.size.width;
final relY = localOffset.dy / box.size.height;
final localOffset = box.globalToLocal(details.globalPosition);
final relX = localOffset.dx / box.size.width;
final relY = localOffset.dy / box.size.height;
if (provider.shots.isEmpty) return;
if (provider.shots.isEmpty) return;
Shot? closestShot;
double minDistance = double.infinity;
const double clickTolerance = 0.05;
Shot? closestShot;
double minDistance = double.infinity;
const double clickTolerance = 0.05;
for (final shot in provider.shots) {
final dx = shot.x - relX;
final dy = shot.y - relY;
final distance = math.sqrt(dx * dx + dy * dy);
for (final shot in provider.shots) {
final dx = shot.x - relX;
final dy = shot.y - relY;
final distance = math.sqrt(dx * dx + dy * dy);
if (distance < minDistance && distance < clickTolerance) {
minDistance = distance;
closestShot = shot;
if (distance < minDistance && distance < clickTolerance) {
minDistance = distance;
closestShot = shot;
}
}
}
if (closestShot != null) {
_showShotDetails(context, provider, closestShot);
}
},
onLongPressStart: (LongPressStartDetails details) {
final RenderBox? box = _imageKey.currentContext?.findRenderObject() as RenderBox?;
if (box == null) return;
final localOffset = box.globalToLocal(details.globalPosition);
final relX = localOffset.dx / box.size.width;
final relY = localOffset.dy / box.size.height;
if (provider.shots.isEmpty) return;
Shot? closestShot;
double minDistance = double.infinity;
const double dragTolerance = 0.06;
for (final shot in provider.shots) {
final dx = shot.x - relX;
final dy = shot.y - relY;
final distance = math.sqrt(dx * dx + dy * dy);
if (distance < minDistance && distance < dragTolerance) {
minDistance = distance;
closestShot = shot;
if (closestShot != null) {
_showShotDetails(context, provider, closestShot);
}
}
},
onLongPressStart: (LongPressStartDetails details) {
final RenderBox? box = _imageKey.currentContext?.findRenderObject() as RenderBox?;
if (box == null) return;
if (closestShot != null) {
setState(() {
_movingShotId = closestShot!.id;
});
}
},
onLongPressMoveUpdate: (LongPressMoveUpdateDetails details) {
if (_movingShotId == null) return;
final localOffset = box.globalToLocal(details.globalPosition);
final relX = localOffset.dx / box.size.width;
final relY = localOffset.dy / box.size.height;
final RenderBox? box = _imageKey.currentContext?.findRenderObject() as RenderBox?;
if (box == null) return;
if (provider.shots.isEmpty) return;
final adjustedGlobalPosition = details.globalPosition + const Offset(-25, -35);
final localOffset = box.globalToLocal(adjustedGlobalPosition);
Shot? closestShot;
double minDistance = double.infinity;
const double dragTolerance = 0.06;
final relX = (localOffset.dx / box.size.width).clamp(0.0, 1.0);
final relY = (localOffset.dy / box.size.height).clamp(0.0, 1.0);
for (final shot in provider.shots) {
final dx = shot.x - relX;
final dy = shot.y - relY;
final distance = math.sqrt(dx * dx + dy * dy);
provider.updateShotPosition(_movingShotId!, relX, relY);
},
onLongPressEnd: (_) {
if (_movingShotId != null) {
setState(() {
_movingShotId = null;
});
}
},
child: Stack(
children: [
// FIX CRITIQUE PLOTTING : L'image de fond doit appliquer exactement le même angle de rotation qu'en calibration !
Transform(
transform: Matrix4.identity()
..setTranslationRaw(widget.cropOffset?.dx ?? 0.0, widget.cropOffset?.dy ?? 0.0, 0.0)
..scale(1.0, 1.0)
..rotateZ((provider.cropRotation) * (math.pi / 180)),
alignment: Alignment.center,
child: Image.file(
if (distance < minDistance && distance < dragTolerance) {
minDistance = distance;
closestShot = shot;
}
}
if (closestShot != null) {
setState(() {
_movingShotId = closestShot!.id;
});
}
},
onLongPressMoveUpdate: (LongPressMoveUpdateDetails details) {
if (_movingShotId == null) return;
final RenderBox? box = _imageKey.currentContext?.findRenderObject() as RenderBox?;
if (box == null) return;
final adjustedGlobalPosition = details.globalPosition + const Offset(-25, -35);
final localOffset = box.globalToLocal(adjustedGlobalPosition);
final relX = (localOffset.dx / box.size.width).clamp(0.0, 1.0);
final relY = (localOffset.dy / box.size.height).clamp(0.0, 1.0);
provider.updateShotPosition(_movingShotId!, relX, relY);
},
onLongPressEnd: (_) {
if (_movingShotId != null) {
setState(() {
_movingShotId = null;
});
}
},
child: Stack(
children: [
Image.file(
File(provider.imagePath!),
key: _imageKey,
fit: BoxFit.contain, // Remplace BoxFit.fill pour s'aligner sur la Calibration
fit: BoxFit.contain,
),
),
TargetOverlay(
targetCenterX: provider.targetCenterX,
targetCenterY: provider.targetCenterY,
targetRadius: provider.targetRadius,
targetType: provider.targetType!,
shots: provider.shots,
showRings: true,
zoomScale: _currentZoomScale,
onShotTapped: (shot) => _showShotDetails(context, provider, shot),
onAddShot: (relX, relY) => provider.addShot(relX, relY),
),
],
TargetOverlay(
targetCenterX: provider.targetCenterX,
targetCenterY: provider.targetCenterY,
targetRadius: provider.targetRadius,
targetType: provider.targetType!,
shots: provider.shots,
showRings: true,
zoomScale: _currentZoomScale,
onShotTapped: (shot) => _showShotDetails(context, provider, shot),
onAddShot: (relX, relY) => provider.addShot(relX, relY),
),
],
),
),
),
);
}
Widget _buildActionButtons(BuildContext context, AnalysisProvider provider) {
return const Column(children: [Row(children: [])]);
}