fix(plotting): afficher l'image brute dézoomée et corriger le TargetOverlay
This commit is contained in:
@@ -479,123 +479,114 @@ 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: Transform(
|
// CORRECTIF : Le widget Transform forçant le zoom/décalage initial a été supprimé d'ici.
|
||||||
transform: Matrix4.identity()
|
// L'image s'affiche maintenant de manière brute et entière, à plat.
|
||||||
..setTranslationRaw(
|
child: GestureDetector(
|
||||||
widget.cropOffset?.dx ?? 0.0,
|
onDoubleTapDown: (TapDownDetails details) {
|
||||||
widget.cropOffset?.dy ?? 0.0,
|
final RenderBox? box =
|
||||||
0.0,
|
_imageKey.currentContext?.findRenderObject() as RenderBox?;
|
||||||
)
|
if (box == null) return;
|
||||||
..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;
|
|
||||||
|
|
||||||
final localOffset = box.globalToLocal(details.globalPosition);
|
if (closestShot != null) {
|
||||||
final relX = localOffset.dx / box.size.width;
|
setState(() {
|
||||||
final relY = localOffset.dy / box.size.height;
|
_movingShotId = closestShot!.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongPressMoveUpdate: (LongPressMoveUpdateDetails details) {
|
||||||
|
if (_movingShotId == null) return;
|
||||||
|
|
||||||
if (provider.shots.isEmpty) return;
|
final RenderBox? box =
|
||||||
|
_imageKey.currentContext?.findRenderObject() as RenderBox?;
|
||||||
|
if (box == null) return;
|
||||||
|
|
||||||
Shot? closestShot;
|
final adjustedGlobalPosition =
|
||||||
double minDistance = double.infinity;
|
details.globalPosition + const Offset(-25, -35);
|
||||||
const double dragTolerance = 0.06;
|
final localOffset = box.globalToLocal(adjustedGlobalPosition);
|
||||||
|
|
||||||
for (final shot in provider.shots) {
|
final relX = (localOffset.dx / box.size.width).clamp(0.0, 1.0);
|
||||||
final dx = shot.x - relX;
|
final relY = (localOffset.dy / box.size.height).clamp(0.0, 1.0);
|
||||||
final dy = shot.y - relY;
|
|
||||||
final distance = math.sqrt(dx * dx + dy * dy);
|
|
||||||
|
|
||||||
if (distance < minDistance && distance < dragTolerance) {
|
provider.updateShotPosition(_movingShotId!, relX, relY);
|
||||||
minDistance = distance;
|
},
|
||||||
closestShot = shot;
|
onLongPressEnd: (_) {
|
||||||
}
|
if (_movingShotId != null) {
|
||||||
}
|
setState(() {
|
||||||
|
_movingShotId = null;
|
||||||
if (closestShot != null) {
|
});
|
||||||
setState(() {
|
}
|
||||||
_movingShotId = closestShot!.id;
|
},
|
||||||
});
|
child: Stack(
|
||||||
}
|
children: [
|
||||||
},
|
Image.file(
|
||||||
onLongPressMoveUpdate: (LongPressMoveUpdateDetails details) {
|
File(provider.imagePath!),
|
||||||
if (_movingShotId == null) return;
|
key: _imageKey,
|
||||||
|
fit: BoxFit.contain,
|
||||||
final RenderBox? box =
|
),
|
||||||
_imageKey.currentContext?.findRenderObject() as RenderBox?;
|
TargetOverlay(
|
||||||
if (box == null) return;
|
targetCenterX: provider.targetCenterX,
|
||||||
|
targetCenterY: provider.targetCenterY,
|
||||||
final adjustedGlobalPosition =
|
targetRadius: provider.targetRadius,
|
||||||
details.globalPosition + const Offset(-25, -35);
|
targetType: provider.targetType!,
|
||||||
final localOffset = box.globalToLocal(adjustedGlobalPosition);
|
shots: provider.shots,
|
||||||
|
showRings: true,
|
||||||
final relX = (localOffset.dx / box.size.width).clamp(0.0, 1.0);
|
zoomScale: _currentZoomScale,
|
||||||
final relY = (localOffset.dy / box.size.height).clamp(0.0, 1.0);
|
onShotTapped: (shot) =>
|
||||||
|
_showShotDetails(context, provider, shot),
|
||||||
provider.updateShotPosition(_movingShotId!, relX, relY);
|
onAddShot: (relX, relY) => provider.addShot(relX, relY),
|
||||||
},
|
),
|
||||||
onLongPressEnd: (_) {
|
],
|
||||||
if (_movingShotId != null) {
|
|
||||||
setState(() {
|
|
||||||
_movingShotId = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Image.file(
|
|
||||||
File(provider.imagePath!),
|
|
||||||
key: _imageKey,
|
|
||||||
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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
/// d'impacts et la sélection d'impacts existants.
|
/// d'impacts et la sélection d'impacts existants.
|
||||||
library;
|
library;
|
||||||
|
|
||||||
|
import 'dart:math' as math;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../../../core/theme/app_theme.dart';
|
import '../../../core/theme/app_theme.dart';
|
||||||
import '../../../data/models/shot.dart';
|
import '../../../data/models/shot.dart';
|
||||||
@@ -211,8 +212,8 @@ class _TargetOverlayPainter extends CustomPainter {
|
|||||||
final prevMultiplier = i == 0
|
final prevMultiplier = i == 0
|
||||||
? 0.0
|
? 0.0
|
||||||
: (ringRadii != null && ringRadii!.length == ringCount)
|
: (ringRadii != null && ringRadii!.length == ringCount)
|
||||||
? ringRadii![i - 1]
|
? ringRadii![i - 1]
|
||||||
: i / ringCount;
|
: i / ringCount;
|
||||||
final zoneRadius = maxRadius * (currentMultiplier + prevMultiplier) / 2;
|
final zoneRadius = maxRadius * (currentMultiplier + prevMultiplier) / 2;
|
||||||
final score = 10 - i;
|
final score = 10 - i;
|
||||||
|
|
||||||
@@ -260,7 +261,10 @@ class _TargetOverlayPainter extends CustomPainter {
|
|||||||
void _drawGroupingCircle(Canvas canvas, Size size) {
|
void _drawGroupingCircle(Canvas canvas, Size size) {
|
||||||
final centerX = groupingCenterX! * size.width;
|
final centerX = groupingCenterX! * size.width;
|
||||||
final centerY = groupingCenterY! * size.height;
|
final centerY = groupingCenterY! * size.height;
|
||||||
final diameter = groupingDiameter! * size.width.clamp(0, size.height);
|
|
||||||
|
// CORRECTIF : Utilisation de math.min pour éviter les écrasements d'aspect ratio asymétriques
|
||||||
|
final minDim = math.min(size.width, size.height);
|
||||||
|
final diameter = groupingDiameter! * minDim;
|
||||||
|
|
||||||
// Draw filled circle
|
// Draw filled circle
|
||||||
final fillPaint = Paint()
|
final fillPaint = Paint()
|
||||||
@@ -381,4 +385,4 @@ class _TargetOverlayPainter extends CustomPainter {
|
|||||||
zoomScale != oldDelegate.zoomScale ||
|
zoomScale != oldDelegate.zoomScale ||
|
||||||
showRings != oldDelegate.showRings;
|
showRings != oldDelegate.showRings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user