canvasframework 0.5.60 → 0.5.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/Camera.js +146 -140
- package/components/FloatedCamera.js +173 -228
- package/package.json +1 -1
package/components/Camera.js
CHANGED
|
@@ -280,35 +280,34 @@ class Camera extends Component {
|
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
handlePress(relX, relY) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
283
|
+
// Capture centrale
|
|
284
|
+
const captureX = this.width / 2;
|
|
285
|
+
const captureY = this.height - 60;
|
|
286
|
+
if (Math.hypot(relX - captureX, relY - captureY) < this.captureButtonRadius + 10) {
|
|
287
|
+
this.capturePhoto();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Switch caméra (haut gauche)
|
|
292
|
+
if (relX < 70 && relY < 70) {
|
|
293
|
+
this.switchCamera();
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Torch (haut droite) - zone précise
|
|
298
|
+
if (this.torchSupported && relX > this.width - 75 && relX < this.width - 25 && relY > 20 && relY < 70) {
|
|
299
|
+
this.toggleTorch();
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Switch mode (haut droite, décalé à gauche de la torche)
|
|
304
|
+
const modeButtonX = this.width - 130;
|
|
305
|
+
const modeButtonY = 20;
|
|
306
|
+
if (relX > modeButtonX && relX < modeButtonX + this.modeButtonSize &&
|
|
307
|
+
relY > modeButtonY && relY < modeButtonY + this.modeButtonSize) {
|
|
308
|
+
this.switchFitMode();
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
312
311
|
}
|
|
313
312
|
|
|
314
313
|
drawContainIcon(ctx, x, y, size) {
|
|
@@ -524,132 +523,139 @@ class Camera extends Component {
|
|
|
524
523
|
}
|
|
525
524
|
|
|
526
525
|
draw(ctx) {
|
|
527
|
-
|
|
526
|
+
ctx.save();
|
|
528
527
|
|
|
529
|
-
|
|
530
|
-
|
|
528
|
+
ctx.fillStyle = '#000';
|
|
529
|
+
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
531
530
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
531
|
+
// Flash blanc après capture
|
|
532
|
+
if (this.flashTimer) {
|
|
533
|
+
ctx.fillStyle = 'rgba(255,255,255,0.6)';
|
|
534
|
+
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
535
|
+
}
|
|
537
536
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
ctx.drawImage(this.video, this.x + offsetX, this.y + offsetY, drawWidth, drawHeight);
|
|
582
|
-
|
|
583
|
-
// Mini preview dernière photo (bas droite, 3s)
|
|
584
|
-
if (this.previewPhoto) {
|
|
585
|
-
const previewSize = 80;
|
|
586
|
-
const img = new Image();
|
|
587
|
-
img.src = this.previewPhoto;
|
|
588
|
-
ctx.drawImage(img, this.x + this.width - previewSize - 10, this.y + this.height - previewSize - 10, previewSize, previewSize);
|
|
589
|
-
ctx.strokeStyle = '#fff';
|
|
590
|
-
ctx.lineWidth = 2;
|
|
591
|
-
ctx.strokeRect(this.x + this.width - previewSize - 10, this.y + this.height - previewSize - 10, previewSize, previewSize);
|
|
537
|
+
if (this.error) {
|
|
538
|
+
ctx.fillStyle = '#ff4444';
|
|
539
|
+
ctx.font = '16px Arial';
|
|
540
|
+
ctx.textAlign = 'center';
|
|
541
|
+
ctx.textBaseline = 'middle';
|
|
542
|
+
ctx.fillText(this.error, this.x + this.width/2, this.y + this.height/2);
|
|
543
|
+
} else if (!this.loaded) {
|
|
544
|
+
ctx.fillStyle = '#fff';
|
|
545
|
+
ctx.font = '16px Arial';
|
|
546
|
+
ctx.textAlign = 'center';
|
|
547
|
+
ctx.textBaseline = 'middle';
|
|
548
|
+
ctx.fillText('Démarrage caméra...', this.x + this.width/2, this.y + this.height/2);
|
|
549
|
+
} else if (this.video && this.loaded) {
|
|
550
|
+
const videoRatio = this.video.videoWidth / this.video.videoHeight;
|
|
551
|
+
const canvasRatio = this.width / this.height;
|
|
552
|
+
|
|
553
|
+
let drawWidth = this.width;
|
|
554
|
+
let drawHeight = this.height;
|
|
555
|
+
let offsetX = 0;
|
|
556
|
+
let offsetY = 0;
|
|
557
|
+
|
|
558
|
+
if (this.fitMode === 'cover') {
|
|
559
|
+
if (videoRatio > canvasRatio) {
|
|
560
|
+
drawHeight = this.height;
|
|
561
|
+
drawWidth = drawHeight * videoRatio;
|
|
562
|
+
offsetX = (this.width - drawWidth) / 2;
|
|
563
|
+
} else {
|
|
564
|
+
drawWidth = this.width;
|
|
565
|
+
drawHeight = drawWidth / videoRatio;
|
|
566
|
+
offsetY = (this.height - drawHeight) / 2;
|
|
567
|
+
}
|
|
568
|
+
} else if (this.fitMode === 'contain') {
|
|
569
|
+
if (videoRatio > canvasRatio) {
|
|
570
|
+
drawWidth = this.width;
|
|
571
|
+
drawHeight = drawWidth / videoRatio;
|
|
572
|
+
offsetY = (this.height - drawHeight) / 2;
|
|
573
|
+
} else {
|
|
574
|
+
drawHeight = this.height;
|
|
575
|
+
drawWidth = drawHeight * videoRatio;
|
|
576
|
+
offsetX = (this.width - drawWidth) / 2;
|
|
592
577
|
}
|
|
593
578
|
}
|
|
594
579
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
580
|
+
ctx.drawImage(this.video, this.x + offsetX, this.y + offsetY, drawWidth, drawHeight);
|
|
581
|
+
|
|
582
|
+
// Mini preview dernière photo (bas droite, 3s)
|
|
583
|
+
if (this.previewPhoto) {
|
|
584
|
+
const previewSize = 80;
|
|
585
|
+
const img = new Image();
|
|
586
|
+
img.src = this.previewPhoto;
|
|
587
|
+
ctx.drawImage(img, this.x + this.width - previewSize - 10, this.y + this.height - previewSize - 10, previewSize, previewSize);
|
|
588
|
+
ctx.strokeStyle = '#fff';
|
|
589
|
+
ctx.lineWidth = 2;
|
|
590
|
+
ctx.strokeRect(this.x + this.width - previewSize - 10, this.y + this.height - previewSize - 10, previewSize, previewSize);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
598
593
|
|
|
599
|
-
|
|
600
|
-
|
|
594
|
+
// Contrôles bas
|
|
595
|
+
ctx.fillStyle = 'rgba(0,0,0,0.5)';
|
|
596
|
+
ctx.fillRect(this.x, this.y + this.height - 100, this.width, 100);
|
|
597
|
+
|
|
598
|
+
// Bouton capture
|
|
599
|
+
ctx.fillStyle = '#ffffff';
|
|
600
|
+
ctx.beginPath();
|
|
601
|
+
ctx.arc(this.x + this.width/2, this.y + this.height - 50, this.captureButtonRadius, 0, Math.PI * 2);
|
|
602
|
+
ctx.fill();
|
|
603
|
+
|
|
604
|
+
ctx.strokeStyle = '#ff4444';
|
|
605
|
+
ctx.lineWidth = 6;
|
|
606
|
+
ctx.beginPath();
|
|
607
|
+
ctx.arc(this.x + this.width/2, this.y + this.height - 50, this.captureButtonRadius + 10, 0, Math.PI * 2);
|
|
608
|
+
ctx.stroke();
|
|
609
|
+
|
|
610
|
+
// Switch caméra avec icône
|
|
611
|
+
const switchBtnX = this.x + 20;
|
|
612
|
+
const switchBtnY = this.y + 20;
|
|
613
|
+
const switchBtnSize = 50;
|
|
614
|
+
|
|
615
|
+
ctx.fillStyle = 'rgba(0,0,0,0.5)';
|
|
616
|
+
ctx.beginPath();
|
|
617
|
+
ctx.arc(switchBtnX + switchBtnSize/2, switchBtnY + switchBtnSize/2, switchBtnSize/2, 0, Math.PI * 2);
|
|
618
|
+
ctx.fill();
|
|
619
|
+
|
|
620
|
+
this.drawSwitchCameraIcon(ctx, switchBtnX, switchBtnY, switchBtnSize);
|
|
621
|
+
|
|
622
|
+
// Bouton torche (haut droite)
|
|
623
|
+
if (this.torchSupported) {
|
|
624
|
+
const torchBtnX = this.x + this.width - 75;
|
|
625
|
+
const torchBtnY = this.y + 20;
|
|
626
|
+
const torchBtnSize = 50;
|
|
627
|
+
|
|
628
|
+
ctx.fillStyle = this.torchOn ? 'rgba(255,235,59,0.8)' : 'rgba(0,0,0,0.5)';
|
|
601
629
|
ctx.beginPath();
|
|
602
|
-
ctx.arc(
|
|
630
|
+
ctx.arc(torchBtnX + torchBtnSize/2, torchBtnY + torchBtnSize/2, torchBtnSize/2, 0, Math.PI * 2);
|
|
603
631
|
ctx.fill();
|
|
604
632
|
|
|
605
|
-
ctx.
|
|
606
|
-
ctx.
|
|
607
|
-
ctx.
|
|
608
|
-
ctx.
|
|
609
|
-
ctx.
|
|
633
|
+
ctx.fillStyle = '#fff';
|
|
634
|
+
ctx.font = '24px Arial';
|
|
635
|
+
ctx.textAlign = 'center';
|
|
636
|
+
ctx.textBaseline = 'middle';
|
|
637
|
+
ctx.fillText('⚡', torchBtnX + torchBtnSize/2, torchBtnY + torchBtnSize/2);
|
|
638
|
+
}
|
|
610
639
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const switchBtnSize = 50;
|
|
615
|
-
|
|
616
|
-
// Fond semi-transparent
|
|
617
|
-
ctx.fillStyle = 'rgba(0,0,0,0.5)';
|
|
618
|
-
ctx.beginPath();
|
|
619
|
-
ctx.arc(switchBtnX + switchBtnSize/2, switchBtnY + switchBtnSize/2, switchBtnSize/2, 0, Math.PI * 2);
|
|
620
|
-
ctx.fill();
|
|
621
|
-
|
|
622
|
-
// Icône
|
|
623
|
-
this.drawSwitchCameraIcon(ctx, switchBtnX, switchBtnY, switchBtnSize);
|
|
640
|
+
// Bouton switch mode (décalé à gauche de la torche)
|
|
641
|
+
const btnX = this.x + this.width - 130;
|
|
642
|
+
const btnY = this.y + 20;
|
|
624
643
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
ctx.fillStyle = this.torchOn ? '#ffeb3b' : '#ffffff';
|
|
628
|
-
ctx.fillText('⚡', this.x + this.width - 50, this.y + 45);
|
|
629
|
-
}
|
|
644
|
+
ctx.fillStyle = 'rgba(255,255,255,0.9)';
|
|
645
|
+
ctx.fillRect(btnX, btnY, this.modeButtonSize, this.modeButtonSize);
|
|
630
646
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
// Fond du bouton
|
|
636
|
-
ctx.fillStyle = 'rgba(255,255,255,0.9)';
|
|
637
|
-
ctx.fillRect(btnX, btnY, this.modeButtonSize, this.modeButtonSize);
|
|
638
|
-
|
|
639
|
-
// Bordure
|
|
640
|
-
ctx.strokeStyle = '#000';
|
|
641
|
-
ctx.lineWidth = 2;
|
|
642
|
-
ctx.strokeRect(btnX, btnY, this.modeButtonSize, this.modeButtonSize);
|
|
643
|
-
|
|
644
|
-
// Dessiner l'icône appropriée
|
|
645
|
-
if (this.fitMode === 'contain') {
|
|
646
|
-
this.drawContainIcon(ctx, btnX, btnY, this.modeButtonSize);
|
|
647
|
-
} else {
|
|
648
|
-
this.drawCoverIcon(ctx, btnX, btnY, this.modeButtonSize);
|
|
649
|
-
}
|
|
647
|
+
ctx.strokeStyle = '#000';
|
|
648
|
+
ctx.lineWidth = 2;
|
|
649
|
+
ctx.strokeRect(btnX, btnY, this.modeButtonSize, this.modeButtonSize);
|
|
650
650
|
|
|
651
|
-
|
|
651
|
+
if (this.fitMode === 'contain') {
|
|
652
|
+
this.drawContainIcon(ctx, btnX, btnY, this.modeButtonSize);
|
|
653
|
+
} else {
|
|
654
|
+
this.drawCoverIcon(ctx, btnX, btnY, this.modeButtonSize);
|
|
652
655
|
}
|
|
656
|
+
|
|
657
|
+
ctx.restore();
|
|
658
|
+
}
|
|
653
659
|
}
|
|
654
660
|
|
|
655
661
|
export default Camera;
|
|
@@ -2,7 +2,8 @@ import Component from '../core/Component.js';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Composant Camera autonome avec gestion directe des clics/touches
|
|
5
|
-
* Modes : contain (tout visible + bandes), cover (remplit + crop)
|
|
5
|
+
* Modes : contain (tout visible + bandes), cover (remplit + crop)
|
|
6
|
+
* Layout boutons haut : [switch caméra GAUCHE] [torch CENTRE] [mode DROITE]
|
|
6
7
|
*/
|
|
7
8
|
class FloatedCamera extends Component {
|
|
8
9
|
constructor(framework, options = {}) {
|
|
@@ -29,27 +30,28 @@ class FloatedCamera extends Component {
|
|
|
29
30
|
|
|
30
31
|
// Feedback capture
|
|
31
32
|
this.flashTimer = null;
|
|
32
|
-
this.previewPhoto = null;
|
|
33
|
+
this.previewPhoto = null;
|
|
33
34
|
this.previewTimeout = null;
|
|
34
35
|
|
|
35
36
|
this.isStarting = false;
|
|
37
|
+
|
|
38
|
+
// Taille et position des boutons (haut)
|
|
39
|
+
this.topBtnSize = 50;
|
|
40
|
+
this.topBtnMargin = 20;
|
|
36
41
|
}
|
|
37
|
-
|
|
42
|
+
|
|
38
43
|
static cleanupAllCameras() {
|
|
39
44
|
const allVideos = document.querySelectorAll('video');
|
|
40
45
|
allVideos.forEach(video => {
|
|
41
|
-
// Arrêter les streams
|
|
42
46
|
if (video.srcObject) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// Supprimer du DOM
|
|
47
|
+
const stream = video.srcObject;
|
|
48
|
+
if (stream && stream.getTracks) {
|
|
49
|
+
stream.getTracks().forEach(track => track.stop());
|
|
50
|
+
}
|
|
51
|
+
video.srcObject = null;
|
|
52
|
+
}
|
|
51
53
|
if (video.parentNode) {
|
|
52
|
-
|
|
54
|
+
video.parentNode.removeChild(video);
|
|
53
55
|
}
|
|
54
56
|
});
|
|
55
57
|
}
|
|
@@ -57,7 +59,6 @@ class FloatedCamera extends Component {
|
|
|
57
59
|
async _mount() {
|
|
58
60
|
super._mount?.();
|
|
59
61
|
|
|
60
|
-
// ✅ CORRECTION : Ne démarrer que si visible ET pas en navigation
|
|
61
62
|
if (this.visible && this.autoStart && !this.stream && !this.isStarting && !this.framework._isNavigating) {
|
|
62
63
|
this.isStarting = true;
|
|
63
64
|
await this.startCamera();
|
|
@@ -66,7 +67,7 @@ class FloatedCamera extends Component {
|
|
|
66
67
|
|
|
67
68
|
this.setupEventListeners();
|
|
68
69
|
}
|
|
69
|
-
|
|
70
|
+
|
|
70
71
|
onUnmount() {
|
|
71
72
|
this.removeEventListeners();
|
|
72
73
|
this.stopCamera();
|
|
@@ -82,7 +83,6 @@ class FloatedCamera extends Component {
|
|
|
82
83
|
super.destroy?.();
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
// Écoute directe (indépendante du framework)
|
|
86
86
|
setupEventListeners() {
|
|
87
87
|
this.onTouchStart = this.handleTouchStart.bind(this);
|
|
88
88
|
this.onTouchMove = this.handleTouchMove.bind(this);
|
|
@@ -110,7 +110,6 @@ class FloatedCamera extends Component {
|
|
|
110
110
|
canvas.removeEventListener('mouseup', this.onMouseUp);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
// Coordonnées locales
|
|
114
113
|
getLocalPos(clientX, clientY) {
|
|
115
114
|
const rect = this.framework.canvas.getBoundingClientRect();
|
|
116
115
|
const globalX = clientX - rect.left;
|
|
@@ -128,13 +127,8 @@ class FloatedCamera extends Component {
|
|
|
128
127
|
this.handlePress(pos.x, pos.y);
|
|
129
128
|
}
|
|
130
129
|
|
|
131
|
-
handleTouchMove(e) {
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
handleTouchEnd(e) {
|
|
136
|
-
e.preventDefault();
|
|
137
|
-
}
|
|
130
|
+
handleTouchMove(e) { e.preventDefault(); }
|
|
131
|
+
handleTouchEnd(e) { e.preventDefault(); }
|
|
138
132
|
|
|
139
133
|
handleMouseDown(e) {
|
|
140
134
|
const pos = this.getLocalPos(e.clientX, e.clientY);
|
|
@@ -142,11 +136,10 @@ class FloatedCamera extends Component {
|
|
|
142
136
|
}
|
|
143
137
|
|
|
144
138
|
handleMouseMove(e) {}
|
|
145
|
-
|
|
146
139
|
handleMouseUp(e) {}
|
|
147
140
|
|
|
148
141
|
async startCamera() {
|
|
149
|
-
|
|
142
|
+
FloatedCamera.cleanupAllCameras();
|
|
150
143
|
if (this.stream) return;
|
|
151
144
|
|
|
152
145
|
try {
|
|
@@ -253,7 +246,6 @@ class FloatedCamera extends Component {
|
|
|
253
246
|
if (this.onPhoto) this.onPhoto(dataUrl);
|
|
254
247
|
console.log('Photo capturée ! DataURL:', dataUrl.substring(0, 50) + '...');
|
|
255
248
|
|
|
256
|
-
// Feedback : flash + preview 3s
|
|
257
249
|
this.flashTimer = setTimeout(() => {
|
|
258
250
|
this.flashTimer = null;
|
|
259
251
|
this.markDirty();
|
|
@@ -275,257 +267,216 @@ class FloatedCamera extends Component {
|
|
|
275
267
|
this.markDirty();
|
|
276
268
|
}
|
|
277
269
|
|
|
270
|
+
// ─── Zones des boutons ───────────────────────────────────────────────────────
|
|
271
|
+
// Layout haut : [switch caméra GAUCHE] [torch CENTRE] [mode DROITE]
|
|
272
|
+
|
|
273
|
+
_getSwitchBtnBounds() {
|
|
274
|
+
return {
|
|
275
|
+
x: this.topBtnMargin,
|
|
276
|
+
y: this.topBtnMargin,
|
|
277
|
+
size: this.topBtnSize
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
_getTorchBtnBounds() {
|
|
282
|
+
// Centré horizontalement dans le composant
|
|
283
|
+
return {
|
|
284
|
+
x: (this.width - this.topBtnSize) / 2,
|
|
285
|
+
y: this.topBtnMargin,
|
|
286
|
+
size: this.topBtnSize
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
_getModeBtnBounds() {
|
|
291
|
+
// Droite, centré verticalement par rapport aux autres boutons
|
|
292
|
+
const yCenter = this.topBtnMargin + this.topBtnSize / 2;
|
|
293
|
+
return {
|
|
294
|
+
x: this.width - this.topBtnMargin - this.modeButtonSize,
|
|
295
|
+
y: yCenter - this.modeButtonSize / 2,
|
|
296
|
+
size: this.modeButtonSize
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
_getCaptureBtnCenter() {
|
|
301
|
+
return {
|
|
302
|
+
cx: this.width / 2,
|
|
303
|
+
cy: this.height - 50
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ─── Gestion des clics ──────────────────────────────────────────────────────
|
|
308
|
+
|
|
278
309
|
handlePress(relX, relY) {
|
|
310
|
+
// Guard : ignorer tout clic hors du composant
|
|
311
|
+
if (relX < 0 || relX > this.width || relY < 0 || relY > this.height) return;
|
|
279
312
|
|
|
280
|
-
//
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
if (Math.hypot(relX - captureX, relY - captureY) < this.captureButtonRadius + 10) {
|
|
313
|
+
// Bouton capture
|
|
314
|
+
const { cx, cy } = this._getCaptureBtnCenter();
|
|
315
|
+
if (Math.hypot(relX - cx, relY - cy) < this.captureButtonRadius + 10) {
|
|
284
316
|
this.capturePhoto();
|
|
285
317
|
return;
|
|
286
318
|
}
|
|
287
319
|
|
|
288
|
-
// Switch caméra
|
|
289
|
-
|
|
320
|
+
// Switch caméra (GAUCHE)
|
|
321
|
+
const sw = this._getSwitchBtnBounds();
|
|
322
|
+
if (Math.hypot(relX - (sw.x + sw.size / 2), relY - (sw.y + sw.size / 2)) < sw.size / 2 + 5) {
|
|
290
323
|
this.switchCamera();
|
|
291
324
|
return;
|
|
292
325
|
}
|
|
293
326
|
|
|
294
|
-
// Torch
|
|
295
|
-
if (this.torchSupported
|
|
296
|
-
this.
|
|
297
|
-
|
|
327
|
+
// Torch (CENTRE)
|
|
328
|
+
if (this.torchSupported) {
|
|
329
|
+
const tb = this._getTorchBtnBounds();
|
|
330
|
+
if (Math.hypot(relX - (tb.x + tb.size / 2), relY - (tb.y + tb.size / 2)) < tb.size / 2 + 5) {
|
|
331
|
+
this.toggleTorch();
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
298
334
|
}
|
|
299
335
|
|
|
300
|
-
//
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
if (relX > modeButtonX && relX < modeButtonX + this.modeButtonSize &&
|
|
304
|
-
relY > modeButtonY && relY < modeButtonY + this.modeButtonSize) {
|
|
336
|
+
// Mode contain/cover (DROITE)
|
|
337
|
+
const mb = this._getModeBtnBounds();
|
|
338
|
+
if (relX >= mb.x && relX <= mb.x + mb.size && relY >= mb.y && relY <= mb.y + mb.size) {
|
|
305
339
|
this.switchFitMode();
|
|
306
340
|
return;
|
|
307
341
|
}
|
|
308
342
|
}
|
|
309
343
|
|
|
344
|
+
// ─── Icônes ─────────────────────────────────────────────────────────────────
|
|
345
|
+
|
|
310
346
|
drawContainIcon(ctx, x, y, size) {
|
|
311
|
-
// Icône "contain" : rectangle avec flèches vers l'intérieur
|
|
312
347
|
const pad = size * 0.2;
|
|
313
348
|
ctx.strokeStyle = '#000';
|
|
314
349
|
ctx.lineWidth = 2;
|
|
315
|
-
|
|
316
|
-
// Rectangle extérieur
|
|
317
350
|
ctx.strokeRect(x + pad, y + pad, size - pad * 2, size - pad * 2);
|
|
318
|
-
|
|
319
|
-
// Flèches pointant vers l'intérieur
|
|
320
|
-
const arrowSize = size * 0.15;
|
|
321
|
-
ctx.fillStyle = '#000';
|
|
322
|
-
|
|
323
|
-
// Flèche haut
|
|
324
|
-
ctx.beginPath();
|
|
325
|
-
ctx.moveTo(x + size/2, y + pad - 2);
|
|
326
|
-
ctx.lineTo(x + size/2 - arrowSize, y + pad + arrowSize);
|
|
327
|
-
ctx.lineTo(x + size/2 + arrowSize, y + pad + arrowSize);
|
|
328
|
-
ctx.fill();
|
|
329
|
-
|
|
330
|
-
// Flèche bas
|
|
331
|
-
ctx.beginPath();
|
|
332
|
-
ctx.moveTo(x + size/2, y + size - pad + 2);
|
|
333
|
-
ctx.lineTo(x + size/2 - arrowSize, y + size - pad - arrowSize);
|
|
334
|
-
ctx.lineTo(x + size/2 + arrowSize, y + size - pad - arrowSize);
|
|
335
|
-
ctx.fill();
|
|
336
|
-
|
|
337
|
-
// Flèche gauche
|
|
338
|
-
ctx.beginPath();
|
|
339
|
-
ctx.moveTo(x + pad - 2, y + size/2);
|
|
340
|
-
ctx.lineTo(x + pad + arrowSize, y + size/2 - arrowSize);
|
|
341
|
-
ctx.lineTo(x + pad + arrowSize, y + size/2 + arrowSize);
|
|
342
|
-
ctx.fill();
|
|
343
|
-
|
|
344
|
-
// Flèche droite
|
|
345
|
-
ctx.beginPath();
|
|
346
|
-
ctx.moveTo(x + size - pad + 2, y + size/2);
|
|
347
|
-
ctx.lineTo(x + size - pad - arrowSize, y + size/2 - arrowSize);
|
|
348
|
-
ctx.lineTo(x + size - pad - arrowSize, y + size/2 + arrowSize);
|
|
349
|
-
ctx.fill();
|
|
350
|
-
}
|
|
351
351
|
|
|
352
|
-
drawCoverIcon(ctx, x, y, size) {
|
|
353
|
-
// Icône "cover" : rectangle avec flèches vers l'extérieur
|
|
354
|
-
const pad = size * 0.2;
|
|
355
|
-
ctx.strokeStyle = '#000';
|
|
356
|
-
ctx.lineWidth = 2;
|
|
357
|
-
|
|
358
|
-
// Rectangle intérieur
|
|
359
|
-
ctx.strokeRect(x + pad, y + pad, size - pad * 2, size - pad * 2);
|
|
360
|
-
|
|
361
|
-
// Flèches pointant vers l'extérieur
|
|
362
352
|
const arrowSize = size * 0.15;
|
|
363
353
|
ctx.fillStyle = '#000';
|
|
364
|
-
|
|
365
|
-
// Flèche haut
|
|
366
|
-
ctx.beginPath();
|
|
367
|
-
ctx.moveTo(x + size/2, y + 2);
|
|
368
|
-
ctx.lineTo(x + size/2 - arrowSize, y + arrowSize + 2);
|
|
369
|
-
ctx.lineTo(x + size/2 + arrowSize, y + arrowSize + 2);
|
|
370
|
-
ctx.fill();
|
|
371
|
-
|
|
372
|
-
// Flèche bas
|
|
373
|
-
ctx.beginPath();
|
|
374
|
-
ctx.moveTo(x + size/2, y + size - 2);
|
|
375
|
-
ctx.lineTo(x + size/2 - arrowSize, y + size - arrowSize - 2);
|
|
376
|
-
ctx.lineTo(x + size/2 + arrowSize, y + size - arrowSize - 2);
|
|
377
|
-
ctx.fill();
|
|
378
|
-
|
|
379
|
-
// Flèche gauche
|
|
380
|
-
ctx.beginPath();
|
|
381
|
-
ctx.moveTo(x + 2, y + size/2);
|
|
382
|
-
ctx.lineTo(x + arrowSize + 2, y + size/2 - arrowSize);
|
|
383
|
-
ctx.lineTo(x + arrowSize + 2, y + size/2 + arrowSize);
|
|
384
|
-
ctx.fill();
|
|
385
|
-
|
|
386
|
-
// Flèche droite
|
|
387
|
-
ctx.beginPath();
|
|
388
|
-
ctx.moveTo(x + size - 2, y + size/2);
|
|
389
|
-
ctx.lineTo(x + size - arrowSize - 2, y + size/2 - arrowSize);
|
|
390
|
-
ctx.lineTo(x + size - arrowSize - 2, y + size/2 + arrowSize);
|
|
391
|
-
ctx.fill();
|
|
392
|
-
}
|
|
393
354
|
|
|
394
|
-
drawContainIcon(ctx, x, y, size) {
|
|
395
|
-
// Icône "contain" : rectangle avec flèches vers l'intérieur
|
|
396
|
-
const pad = size * 0.2;
|
|
397
|
-
ctx.strokeStyle = '#000';
|
|
398
|
-
ctx.lineWidth = 2;
|
|
399
|
-
|
|
400
|
-
// Rectangle extérieur
|
|
401
|
-
ctx.strokeRect(x + pad, y + pad, size - pad * 2, size - pad * 2);
|
|
402
|
-
|
|
403
|
-
// Flèches pointant vers l'intérieur
|
|
404
|
-
const arrowSize = size * 0.15;
|
|
405
|
-
ctx.fillStyle = '#000';
|
|
406
|
-
|
|
407
|
-
// Flèche haut
|
|
408
355
|
ctx.beginPath();
|
|
409
|
-
ctx.moveTo(x + size/2, y + pad - 2);
|
|
410
|
-
ctx.lineTo(x + size/2 - arrowSize, y + pad + arrowSize);
|
|
411
|
-
ctx.lineTo(x + size/2 + arrowSize, y + pad + arrowSize);
|
|
356
|
+
ctx.moveTo(x + size / 2, y + pad - 2);
|
|
357
|
+
ctx.lineTo(x + size / 2 - arrowSize, y + pad + arrowSize);
|
|
358
|
+
ctx.lineTo(x + size / 2 + arrowSize, y + pad + arrowSize);
|
|
412
359
|
ctx.fill();
|
|
413
|
-
|
|
414
|
-
// Flèche bas
|
|
360
|
+
|
|
415
361
|
ctx.beginPath();
|
|
416
|
-
ctx.moveTo(x + size/2, y + size - pad + 2);
|
|
417
|
-
ctx.lineTo(x + size/2 - arrowSize, y + size - pad - arrowSize);
|
|
418
|
-
ctx.lineTo(x + size/2 + arrowSize, y + size - pad - arrowSize);
|
|
362
|
+
ctx.moveTo(x + size / 2, y + size - pad + 2);
|
|
363
|
+
ctx.lineTo(x + size / 2 - arrowSize, y + size - pad - arrowSize);
|
|
364
|
+
ctx.lineTo(x + size / 2 + arrowSize, y + size - pad - arrowSize);
|
|
419
365
|
ctx.fill();
|
|
420
|
-
|
|
421
|
-
// Flèche gauche
|
|
366
|
+
|
|
422
367
|
ctx.beginPath();
|
|
423
|
-
ctx.moveTo(x + pad - 2, y + size/2);
|
|
424
|
-
ctx.lineTo(x + pad + arrowSize, y + size/2 - arrowSize);
|
|
425
|
-
ctx.lineTo(x + pad + arrowSize, y + size/2 + arrowSize);
|
|
368
|
+
ctx.moveTo(x + pad - 2, y + size / 2);
|
|
369
|
+
ctx.lineTo(x + pad + arrowSize, y + size / 2 - arrowSize);
|
|
370
|
+
ctx.lineTo(x + pad + arrowSize, y + size / 2 + arrowSize);
|
|
426
371
|
ctx.fill();
|
|
427
|
-
|
|
428
|
-
// Flèche droite
|
|
372
|
+
|
|
429
373
|
ctx.beginPath();
|
|
430
|
-
ctx.moveTo(x + size - pad + 2, y + size/2);
|
|
431
|
-
ctx.lineTo(x + size - pad - arrowSize, y + size/2 - arrowSize);
|
|
432
|
-
ctx.lineTo(x + size - pad - arrowSize, y + size/2 + arrowSize);
|
|
374
|
+
ctx.moveTo(x + size - pad + 2, y + size / 2);
|
|
375
|
+
ctx.lineTo(x + size - pad - arrowSize, y + size / 2 - arrowSize);
|
|
376
|
+
ctx.lineTo(x + size - pad - arrowSize, y + size / 2 + arrowSize);
|
|
433
377
|
ctx.fill();
|
|
434
378
|
}
|
|
435
379
|
|
|
436
380
|
drawCoverIcon(ctx, x, y, size) {
|
|
437
|
-
// Icône "cover" : rectangle avec flèches vers l'extérieur
|
|
438
381
|
const pad = size * 0.2;
|
|
439
382
|
ctx.strokeStyle = '#000';
|
|
440
383
|
ctx.lineWidth = 2;
|
|
441
|
-
|
|
442
|
-
// Rectangle intérieur
|
|
443
384
|
ctx.strokeRect(x + pad, y + pad, size - pad * 2, size - pad * 2);
|
|
444
|
-
|
|
445
|
-
// Flèches pointant vers l'extérieur
|
|
385
|
+
|
|
446
386
|
const arrowSize = size * 0.15;
|
|
447
387
|
ctx.fillStyle = '#000';
|
|
448
|
-
|
|
449
|
-
// Flèche haut
|
|
388
|
+
|
|
450
389
|
ctx.beginPath();
|
|
451
|
-
ctx.moveTo(x + size/2, y + 2);
|
|
452
|
-
ctx.lineTo(x + size/2 - arrowSize, y + arrowSize + 2);
|
|
453
|
-
ctx.lineTo(x + size/2 + arrowSize, y + arrowSize + 2);
|
|
390
|
+
ctx.moveTo(x + size / 2, y + 2);
|
|
391
|
+
ctx.lineTo(x + size / 2 - arrowSize, y + arrowSize + 2);
|
|
392
|
+
ctx.lineTo(x + size / 2 + arrowSize, y + arrowSize + 2);
|
|
454
393
|
ctx.fill();
|
|
455
|
-
|
|
456
|
-
// Flèche bas
|
|
394
|
+
|
|
457
395
|
ctx.beginPath();
|
|
458
|
-
ctx.moveTo(x + size/2, y + size - 2);
|
|
459
|
-
ctx.lineTo(x + size/2 - arrowSize, y + size - arrowSize - 2);
|
|
460
|
-
ctx.lineTo(x + size/2 + arrowSize, y + size - arrowSize - 2);
|
|
396
|
+
ctx.moveTo(x + size / 2, y + size - 2);
|
|
397
|
+
ctx.lineTo(x + size / 2 - arrowSize, y + size - arrowSize - 2);
|
|
398
|
+
ctx.lineTo(x + size / 2 + arrowSize, y + size - arrowSize - 2);
|
|
461
399
|
ctx.fill();
|
|
462
|
-
|
|
463
|
-
// Flèche gauche
|
|
400
|
+
|
|
464
401
|
ctx.beginPath();
|
|
465
|
-
ctx.moveTo(x + 2, y + size/2);
|
|
466
|
-
ctx.lineTo(x + arrowSize + 2, y + size/2 - arrowSize);
|
|
467
|
-
ctx.lineTo(x + arrowSize + 2, y + size/2 + arrowSize);
|
|
402
|
+
ctx.moveTo(x + 2, y + size / 2);
|
|
403
|
+
ctx.lineTo(x + arrowSize + 2, y + size / 2 - arrowSize);
|
|
404
|
+
ctx.lineTo(x + arrowSize + 2, y + size / 2 + arrowSize);
|
|
468
405
|
ctx.fill();
|
|
469
|
-
|
|
470
|
-
// Flèche droite
|
|
406
|
+
|
|
471
407
|
ctx.beginPath();
|
|
472
|
-
ctx.moveTo(x + size - 2, y + size/2);
|
|
473
|
-
ctx.lineTo(x + size - arrowSize - 2, y + size/2 - arrowSize);
|
|
474
|
-
ctx.lineTo(x + size - arrowSize - 2, y + size/2 + arrowSize);
|
|
408
|
+
ctx.moveTo(x + size - 2, y + size / 2);
|
|
409
|
+
ctx.lineTo(x + size - arrowSize - 2, y + size / 2 - arrowSize);
|
|
410
|
+
ctx.lineTo(x + size - arrowSize - 2, y + size / 2 + arrowSize);
|
|
475
411
|
ctx.fill();
|
|
476
412
|
}
|
|
477
413
|
|
|
478
414
|
drawSwitchCameraIcon(ctx, x, y, size) {
|
|
479
|
-
// Icône de switch caméra : deux caméras avec flèche circulaire
|
|
480
415
|
const centerX = x + size / 2;
|
|
481
416
|
const centerY = y + size / 2;
|
|
482
417
|
const radius = size * 0.35;
|
|
483
|
-
|
|
418
|
+
|
|
484
419
|
ctx.strokeStyle = '#ffffff';
|
|
485
420
|
ctx.lineWidth = 3;
|
|
486
421
|
ctx.lineCap = 'round';
|
|
487
|
-
|
|
488
|
-
// Arc circulaire (flèche de rotation)
|
|
422
|
+
|
|
489
423
|
ctx.beginPath();
|
|
490
424
|
ctx.arc(centerX, centerY, radius, -Math.PI * 0.7, Math.PI * 0.7);
|
|
491
425
|
ctx.stroke();
|
|
492
|
-
|
|
493
|
-
// Flèche en haut à droite
|
|
426
|
+
|
|
494
427
|
const arrowSize = size * 0.15;
|
|
495
428
|
const arrowAngle = Math.PI * 0.7;
|
|
496
429
|
const arrowX = centerX + radius * Math.cos(arrowAngle);
|
|
497
430
|
const arrowY = centerY + radius * Math.sin(arrowAngle);
|
|
498
|
-
|
|
431
|
+
|
|
499
432
|
ctx.fillStyle = '#ffffff';
|
|
500
433
|
ctx.beginPath();
|
|
501
434
|
ctx.moveTo(arrowX, arrowY);
|
|
502
435
|
ctx.lineTo(arrowX - arrowSize, arrowY - arrowSize * 0.5);
|
|
503
436
|
ctx.lineTo(arrowX - arrowSize * 0.5, arrowY + arrowSize);
|
|
504
437
|
ctx.fill();
|
|
505
|
-
|
|
506
|
-
// Mini caméra au centre
|
|
438
|
+
|
|
507
439
|
const camWidth = size * 0.25;
|
|
508
440
|
const camHeight = size * 0.18;
|
|
509
|
-
const camX = centerX - camWidth / 2;
|
|
510
|
-
const camY = centerY - camHeight / 2;
|
|
511
|
-
|
|
512
441
|
ctx.fillStyle = '#ffffff';
|
|
513
|
-
ctx.fillRect(
|
|
514
|
-
|
|
515
|
-
// Objectif
|
|
442
|
+
ctx.fillRect(centerX - camWidth / 2, centerY - camHeight / 2, camWidth, camHeight);
|
|
443
|
+
|
|
516
444
|
ctx.fillStyle = '#000';
|
|
517
445
|
ctx.beginPath();
|
|
518
446
|
ctx.arc(centerX, centerY, size * 0.08, 0, Math.PI * 2);
|
|
519
447
|
ctx.fill();
|
|
520
448
|
}
|
|
521
449
|
|
|
450
|
+
drawTorchIcon(ctx, x, y, size, torchOn) {
|
|
451
|
+
const cx = x + size / 2;
|
|
452
|
+
const cy = y + size / 2;
|
|
453
|
+
|
|
454
|
+
ctx.fillStyle = torchOn ? 'rgba(255, 235, 59, 0.9)' : 'rgba(0, 0, 0, 0.55)';
|
|
455
|
+
ctx.beginPath();
|
|
456
|
+
ctx.arc(cx, cy, size / 2, 0, Math.PI * 2);
|
|
457
|
+
ctx.fill();
|
|
458
|
+
|
|
459
|
+
ctx.strokeStyle = torchOn ? '#f9a825' : 'rgba(255,255,255,0.3)';
|
|
460
|
+
ctx.lineWidth = 1.5;
|
|
461
|
+
ctx.beginPath();
|
|
462
|
+
ctx.arc(cx, cy, size / 2, 0, Math.PI * 2);
|
|
463
|
+
ctx.stroke();
|
|
464
|
+
|
|
465
|
+
ctx.font = `${Math.round(size * 0.48)}px Arial`;
|
|
466
|
+
ctx.textAlign = 'center';
|
|
467
|
+
ctx.textBaseline = 'middle';
|
|
468
|
+
ctx.fillStyle = torchOn ? '#000000' : '#ffffff';
|
|
469
|
+
ctx.fillText('⚡', cx, cy);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// ─── Rendu ──────────────────────────────────────────────────────────────────
|
|
473
|
+
|
|
522
474
|
draw(ctx) {
|
|
523
475
|
ctx.save();
|
|
524
476
|
|
|
525
477
|
ctx.fillStyle = '#000';
|
|
526
478
|
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
527
479
|
|
|
528
|
-
// Flash blanc après capture
|
|
529
480
|
if (this.flashTimer) {
|
|
530
481
|
ctx.fillStyle = 'rgba(255,255,255,0.6)';
|
|
531
482
|
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
@@ -536,13 +487,15 @@ class FloatedCamera extends Component {
|
|
|
536
487
|
ctx.font = '16px Arial';
|
|
537
488
|
ctx.textAlign = 'center';
|
|
538
489
|
ctx.textBaseline = 'middle';
|
|
539
|
-
ctx.fillText(this.error, this.x + this.width/2, this.y + this.height/2);
|
|
490
|
+
ctx.fillText(this.error, this.x + this.width / 2, this.y + this.height / 2);
|
|
491
|
+
|
|
540
492
|
} else if (!this.loaded) {
|
|
541
493
|
ctx.fillStyle = '#fff';
|
|
542
494
|
ctx.font = '16px Arial';
|
|
543
495
|
ctx.textAlign = 'center';
|
|
544
496
|
ctx.textBaseline = 'middle';
|
|
545
|
-
ctx.fillText('Démarrage caméra...', this.x + this.width/2, this.y + this.height/2);
|
|
497
|
+
ctx.fillText('Démarrage caméra...', this.x + this.width / 2, this.y + this.height / 2);
|
|
498
|
+
|
|
546
499
|
} else if (this.video && this.loaded) {
|
|
547
500
|
const videoRatio = this.video.videoWidth / this.video.videoHeight;
|
|
548
501
|
const canvasRatio = this.width / this.height;
|
|
@@ -572,76 +525,68 @@ class FloatedCamera extends Component {
|
|
|
572
525
|
drawWidth = drawHeight * videoRatio;
|
|
573
526
|
offsetX = (this.width - drawWidth) / 2;
|
|
574
527
|
}
|
|
575
|
-
}
|
|
528
|
+
}
|
|
576
529
|
|
|
577
530
|
ctx.drawImage(this.video, this.x + offsetX, this.y + offsetY, drawWidth, drawHeight);
|
|
578
531
|
|
|
579
|
-
// Mini preview dernière photo (bas droite, 3s)
|
|
580
532
|
if (this.previewPhoto) {
|
|
581
533
|
const previewSize = 80;
|
|
582
534
|
const img = new Image();
|
|
583
535
|
img.src = this.previewPhoto;
|
|
584
|
-
|
|
536
|
+
const px = this.x + this.width - previewSize - 10;
|
|
537
|
+
const py = this.y + this.height - previewSize - 10;
|
|
538
|
+
ctx.drawImage(img, px, py, previewSize, previewSize);
|
|
585
539
|
ctx.strokeStyle = '#fff';
|
|
586
540
|
ctx.lineWidth = 2;
|
|
587
|
-
ctx.strokeRect(
|
|
541
|
+
ctx.strokeRect(px, py, previewSize, previewSize);
|
|
588
542
|
}
|
|
589
543
|
}
|
|
590
544
|
|
|
591
|
-
//
|
|
545
|
+
// ── Barre bas ───────────────────────────────────────────────────────────
|
|
592
546
|
ctx.fillStyle = 'rgba(0,0,0,0.5)';
|
|
593
547
|
ctx.fillRect(this.x, this.y + this.height - 100, this.width, 100);
|
|
594
548
|
|
|
595
549
|
// Bouton capture
|
|
550
|
+
const { cx, cy } = this._getCaptureBtnCenter();
|
|
596
551
|
ctx.fillStyle = '#ffffff';
|
|
597
552
|
ctx.beginPath();
|
|
598
|
-
ctx.arc(this.x +
|
|
553
|
+
ctx.arc(this.x + cx, this.y + cy, this.captureButtonRadius, 0, Math.PI * 2);
|
|
599
554
|
ctx.fill();
|
|
600
555
|
|
|
601
556
|
ctx.strokeStyle = '#ff4444';
|
|
602
557
|
ctx.lineWidth = 6;
|
|
603
558
|
ctx.beginPath();
|
|
604
|
-
ctx.arc(this.x +
|
|
559
|
+
ctx.arc(this.x + cx, this.y + cy, this.captureButtonRadius + 10, 0, Math.PI * 2);
|
|
605
560
|
ctx.stroke();
|
|
606
561
|
|
|
607
|
-
//
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
// Fond semi-transparent
|
|
562
|
+
// ── Boutons haut ────────────────────────────────────────────────────────
|
|
563
|
+
// Ordre de dessin : switch → mode → torch (torch EN DERNIER = jamais caché)
|
|
564
|
+
|
|
565
|
+
// 1. Switch caméra (GAUCHE)
|
|
566
|
+
const sw = this._getSwitchBtnBounds();
|
|
613
567
|
ctx.fillStyle = 'rgba(0,0,0,0.5)';
|
|
614
568
|
ctx.beginPath();
|
|
615
|
-
ctx.arc(
|
|
569
|
+
ctx.arc(this.x + sw.x + sw.size / 2, this.y + sw.y + sw.size / 2, sw.size / 2, 0, Math.PI * 2);
|
|
616
570
|
ctx.fill();
|
|
617
|
-
|
|
618
|
-
// Icône
|
|
619
|
-
this.drawSwitchCameraIcon(ctx, switchBtnX, switchBtnY, switchBtnSize);
|
|
571
|
+
this.drawSwitchCameraIcon(ctx, this.x + sw.x, this.y + sw.y, sw.size);
|
|
620
572
|
|
|
621
|
-
//
|
|
622
|
-
|
|
623
|
-
ctx.fillStyle = this.torchOn ? '#ffeb3b' : '#ffffff';
|
|
624
|
-
ctx.fillText('⚡', this.x + this.width - 50, this.y + 45);
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
// Bouton switch mode avec icône
|
|
628
|
-
const btnX = this.x + this.width - 80;
|
|
629
|
-
const btnY = this.y + 20;
|
|
630
|
-
|
|
631
|
-
// Fond du bouton
|
|
573
|
+
// 2. Mode contain/cover (DROITE)
|
|
574
|
+
const mb = this._getModeBtnBounds();
|
|
632
575
|
ctx.fillStyle = 'rgba(255,255,255,0.9)';
|
|
633
|
-
ctx.fillRect(
|
|
634
|
-
|
|
635
|
-
// Bordure
|
|
576
|
+
ctx.fillRect(this.x + mb.x, this.y + mb.y, mb.size, mb.size);
|
|
636
577
|
ctx.strokeStyle = '#000';
|
|
637
578
|
ctx.lineWidth = 2;
|
|
638
|
-
ctx.strokeRect(
|
|
639
|
-
|
|
640
|
-
// Dessiner l'icône appropriée
|
|
579
|
+
ctx.strokeRect(this.x + mb.x, this.y + mb.y, mb.size, mb.size);
|
|
641
580
|
if (this.fitMode === 'contain') {
|
|
642
|
-
this.drawContainIcon(ctx,
|
|
581
|
+
this.drawContainIcon(ctx, this.x + mb.x, this.y + mb.y, mb.size);
|
|
643
582
|
} else {
|
|
644
|
-
this.drawCoverIcon(ctx,
|
|
583
|
+
this.drawCoverIcon(ctx, this.x + mb.x, this.y + mb.y, mb.size);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// 3. Torch (CENTRE) — dessiné en dernier, jamais caché par les autres
|
|
587
|
+
if (this.torchSupported) {
|
|
588
|
+
const tb = this._getTorchBtnBounds();
|
|
589
|
+
this.drawTorchIcon(ctx, this.x + tb.x, this.y + tb.y, tb.size, this.torchOn);
|
|
645
590
|
}
|
|
646
591
|
|
|
647
592
|
ctx.restore();
|