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