canvasframework 0.3.10 → 0.3.12
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/AppBar.js +129 -50
- package/components/BottomNavigationBar.js +156 -65
- package/components/InputDatalist.js +723 -0
- package/core/CanvasFramework.js +6 -4
- package/core/UIBuilder.js +236 -0
- package/index.js +3 -1
- package/package.json +1 -1
package/components/AppBar.js
CHANGED
|
@@ -50,12 +50,92 @@ class AppBar extends Component {
|
|
|
50
50
|
|
|
51
51
|
// Ripple effect (Material uniquement)
|
|
52
52
|
this.ripples = [];
|
|
53
|
+
this.animationFrame = null;
|
|
54
|
+
this.lastAnimationTime = 0;
|
|
55
|
+
|
|
56
|
+
// États pressed pour iOS
|
|
53
57
|
this.leftPressed = false;
|
|
54
58
|
this.rightPressed = false;
|
|
55
59
|
|
|
56
60
|
this.onPress = this.handlePress.bind(this);
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Démarrer l'animation des ripples
|
|
65
|
+
* @private
|
|
66
|
+
*/
|
|
67
|
+
startRippleAnimation() {
|
|
68
|
+
const animate = (timestamp) => {
|
|
69
|
+
if (!this.lastAnimationTime) this.lastAnimationTime = timestamp;
|
|
70
|
+
const deltaTime = timestamp - this.lastAnimationTime;
|
|
71
|
+
this.lastAnimationTime = timestamp;
|
|
72
|
+
|
|
73
|
+
let needsUpdate = false;
|
|
74
|
+
|
|
75
|
+
// Mettre à jour chaque ripple
|
|
76
|
+
for (let i = this.ripples.length - 1; i >= 0; i--) {
|
|
77
|
+
const ripple = this.ripples[i];
|
|
78
|
+
|
|
79
|
+
// Animer le rayon (expansion)
|
|
80
|
+
if (ripple.radius < ripple.maxRadius) {
|
|
81
|
+
ripple.radius += (ripple.maxRadius / 250) * deltaTime;
|
|
82
|
+
needsUpdate = true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Animer l'opacité (fade out) - commencer plus tôt
|
|
86
|
+
if (ripple.radius >= ripple.maxRadius * 0.4) {
|
|
87
|
+
ripple.opacity -= (0.003 * deltaTime);
|
|
88
|
+
if (ripple.opacity < 0) ripple.opacity = 0;
|
|
89
|
+
needsUpdate = true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Supprimer les ripples terminés
|
|
93
|
+
if (ripple.opacity <= 0 && ripple.radius >= ripple.maxRadius * 0.95) {
|
|
94
|
+
this.ripples.splice(i, 1);
|
|
95
|
+
needsUpdate = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Redessiner si nécessaire
|
|
100
|
+
if (needsUpdate) {
|
|
101
|
+
this.requestRender();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Continuer l'animation
|
|
105
|
+
if (this.ripples.length > 0) {
|
|
106
|
+
this.animationFrame = requestAnimationFrame(animate);
|
|
107
|
+
} else {
|
|
108
|
+
this.animationFrame = null;
|
|
109
|
+
this.lastAnimationTime = 0;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (this.ripples.length > 0 && !this.animationFrame) {
|
|
114
|
+
this.animationFrame = requestAnimationFrame(animate);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Demander un redessin
|
|
120
|
+
* @private
|
|
121
|
+
*/
|
|
122
|
+
requestRender() {
|
|
123
|
+
if (this.framework && this.framework.requestRender) {
|
|
124
|
+
this.framework.requestRender();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Nettoyer l'animation lors de la destruction
|
|
130
|
+
*/
|
|
131
|
+
destroy() {
|
|
132
|
+
if (this.animationFrame) {
|
|
133
|
+
cancelAnimationFrame(this.animationFrame);
|
|
134
|
+
this.animationFrame = null;
|
|
135
|
+
}
|
|
136
|
+
super.destroy();
|
|
137
|
+
}
|
|
138
|
+
|
|
59
139
|
/**
|
|
60
140
|
* Dessine l'AppBar
|
|
61
141
|
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
@@ -98,13 +178,13 @@ class AppBar extends Component {
|
|
|
98
178
|
if (this.leftPressed && this.leftIcon) {
|
|
99
179
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
|
|
100
180
|
ctx.beginPath();
|
|
101
|
-
ctx.arc(28, this.y + this.height / 2, 20, 0, Math.PI * 2);
|
|
181
|
+
ctx.arc(this.x + 28, this.y + this.height / 2, 20, 0, Math.PI * 2);
|
|
102
182
|
ctx.fill();
|
|
103
183
|
}
|
|
104
184
|
if (this.rightPressed && this.rightIcon) {
|
|
105
185
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
|
|
106
186
|
ctx.beginPath();
|
|
107
|
-
ctx.arc(this.width - 28, this.y + this.height / 2, 20, 0, Math.PI * 2);
|
|
187
|
+
ctx.arc(this.x + this.width - 28, this.y + this.height / 2, 20, 0, Math.PI * 2);
|
|
108
188
|
ctx.fill();
|
|
109
189
|
}
|
|
110
190
|
}
|
|
@@ -112,7 +192,7 @@ class AppBar extends Component {
|
|
|
112
192
|
// Titre
|
|
113
193
|
ctx.fillStyle = this.textColor;
|
|
114
194
|
const titleAlign = this.platform === 'material' && this.leftIcon ? 'left' : 'center';
|
|
115
|
-
const titleX = titleAlign === 'left' ? 72 : this.width / 2;
|
|
195
|
+
const titleX = titleAlign === 'left' ? this.x + 72 : this.x + this.width / 2;
|
|
116
196
|
ctx.font = `${this.platform === 'material' ? 'bold ' : ''}20px -apple-system, Roboto, sans-serif`;
|
|
117
197
|
ctx.textAlign = titleAlign;
|
|
118
198
|
ctx.textBaseline = 'middle';
|
|
@@ -122,9 +202,9 @@ class AppBar extends Component {
|
|
|
122
202
|
if (this.leftIcon) {
|
|
123
203
|
const iconColor = this.platform === 'cupertino' ? '#007AFF' : this.textColor;
|
|
124
204
|
if (this.leftIcon === 'menu') {
|
|
125
|
-
this.drawMenuIcon(ctx, 16, this.y + this.height / 2, iconColor);
|
|
205
|
+
this.drawMenuIcon(ctx, this.x + 16, this.y + this.height / 2, iconColor);
|
|
126
206
|
} else if (this.leftIcon === 'back') {
|
|
127
|
-
this.drawBackIcon(ctx, 16, this.y + this.height / 2, iconColor);
|
|
207
|
+
this.drawBackIcon(ctx, this.x + 16, this.y + this.height / 2, iconColor);
|
|
128
208
|
}
|
|
129
209
|
}
|
|
130
210
|
|
|
@@ -132,9 +212,9 @@ class AppBar extends Component {
|
|
|
132
212
|
if (this.rightIcon) {
|
|
133
213
|
const iconColor = this.platform === 'cupertino' ? '#007AFF' : this.textColor;
|
|
134
214
|
if (this.rightIcon === 'search') {
|
|
135
|
-
this.drawSearchIcon(ctx, this.width - 36, this.y + this.height / 2, iconColor);
|
|
215
|
+
this.drawSearchIcon(ctx, this.x + this.width - 36, this.y + this.height / 2, iconColor);
|
|
136
216
|
} else if (this.rightIcon === 'more') {
|
|
137
|
-
this.drawMoreIcon(ctx, this.width - 36, this.y + this.height / 2, iconColor);
|
|
217
|
+
this.drawMoreIcon(ctx, this.x + this.width - 36, this.y + this.height / 2, iconColor);
|
|
138
218
|
}
|
|
139
219
|
}
|
|
140
220
|
|
|
@@ -157,35 +237,6 @@ class AppBar extends Component {
|
|
|
157
237
|
}
|
|
158
238
|
}
|
|
159
239
|
|
|
160
|
-
/**
|
|
161
|
-
* Anime les effets ripple
|
|
162
|
-
* @private
|
|
163
|
-
*/
|
|
164
|
-
animateRipple() {
|
|
165
|
-
const animate = () => {
|
|
166
|
-
let hasActiveRipples = false;
|
|
167
|
-
|
|
168
|
-
for (let ripple of this.ripples) {
|
|
169
|
-
if (ripple.radius < ripple.maxRadius) {
|
|
170
|
-
ripple.radius += ripple.maxRadius / 15;
|
|
171
|
-
hasActiveRipples = true;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (ripple.radius >= ripple.maxRadius * 0.5) {
|
|
175
|
-
ripple.opacity -= 0.05;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
this.ripples = this.ripples.filter(r => r.opacity > 0);
|
|
180
|
-
|
|
181
|
-
if (hasActiveRipples) {
|
|
182
|
-
requestAnimationFrame(animate);
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
animate();
|
|
187
|
-
}
|
|
188
|
-
|
|
189
240
|
/**
|
|
190
241
|
* Dessine l'icône menu (hamburger)
|
|
191
242
|
* @private
|
|
@@ -253,10 +304,14 @@ class AppBar extends Component {
|
|
|
253
304
|
* Vérifie si un point est dans les zones cliquables
|
|
254
305
|
*/
|
|
255
306
|
isPointInside(x, y) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
307
|
+
// Les coordonnées x, y sont absolues, on les compare avec nos coordonnées absolues
|
|
308
|
+
const inY = y >= this.y && y <= this.y + this.height;
|
|
309
|
+
|
|
310
|
+
if (!inY) return false;
|
|
311
|
+
|
|
312
|
+
if (this.leftIcon && x >= this.x && x <= this.x + 56) return true;
|
|
313
|
+
if (this.rightIcon && x >= this.x + this.width - 56 && x <= this.x + this.width) return true;
|
|
314
|
+
|
|
260
315
|
return false;
|
|
261
316
|
}
|
|
262
317
|
|
|
@@ -269,46 +324,70 @@ class AppBar extends Component {
|
|
|
269
324
|
|
|
270
325
|
if (adjustedY >= this.y && adjustedY <= this.y + this.height) {
|
|
271
326
|
// Bouton gauche
|
|
272
|
-
if (this.leftIcon && x >=
|
|
327
|
+
if (this.leftIcon && x >= this.x && x <= this.x + 56) {
|
|
273
328
|
// Ripple effect (Material)
|
|
274
329
|
if (this.platform === 'material') {
|
|
275
330
|
this.ripples.push({
|
|
276
|
-
x: 28,
|
|
331
|
+
x: this.x + 28,
|
|
277
332
|
y: this.y + this.height / 2,
|
|
278
333
|
radius: 0,
|
|
279
334
|
maxRadius: 28,
|
|
280
|
-
opacity: 1
|
|
335
|
+
opacity: 1,
|
|
336
|
+
createdAt: performance.now()
|
|
281
337
|
});
|
|
282
|
-
|
|
338
|
+
|
|
339
|
+
// Démarrer l'animation si elle n'est pas en cours
|
|
340
|
+
if (!this.animationFrame) {
|
|
341
|
+
this.startRippleAnimation();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Forcer un redessin
|
|
345
|
+
this.requestRender();
|
|
283
346
|
} else {
|
|
284
347
|
// iOS pressed state
|
|
285
348
|
this.leftPressed = true;
|
|
286
|
-
setTimeout(() => {
|
|
349
|
+
setTimeout(() => {
|
|
350
|
+
this.leftPressed = false;
|
|
351
|
+
this.requestRender();
|
|
352
|
+
}, 150);
|
|
287
353
|
}
|
|
288
354
|
|
|
289
355
|
if (this.onLeftClick) this.onLeftClick();
|
|
356
|
+
this.requestRender();
|
|
290
357
|
return true;
|
|
291
358
|
}
|
|
292
359
|
|
|
293
360
|
// Bouton droit
|
|
294
|
-
if (this.rightIcon && x >= this.width - 56 && x <= this.width) {
|
|
361
|
+
if (this.rightIcon && x >= this.x + this.width - 56 && x <= this.x + this.width) {
|
|
295
362
|
// Ripple effect (Material)
|
|
296
363
|
if (this.platform === 'material') {
|
|
297
364
|
this.ripples.push({
|
|
298
|
-
x: this.width - 28,
|
|
365
|
+
x: this.x + this.width - 28,
|
|
299
366
|
y: this.y + this.height / 2,
|
|
300
367
|
radius: 0,
|
|
301
368
|
maxRadius: 28,
|
|
302
|
-
opacity: 1
|
|
369
|
+
opacity: 1,
|
|
370
|
+
createdAt: performance.now()
|
|
303
371
|
});
|
|
304
|
-
|
|
372
|
+
|
|
373
|
+
// Démarrer l'animation si elle n'est pas en cours
|
|
374
|
+
if (!this.animationFrame) {
|
|
375
|
+
this.startRippleAnimation();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Forcer un redessin
|
|
379
|
+
this.requestRender();
|
|
305
380
|
} else {
|
|
306
381
|
// iOS pressed state
|
|
307
382
|
this.rightPressed = true;
|
|
308
|
-
setTimeout(() => {
|
|
383
|
+
setTimeout(() => {
|
|
384
|
+
this.rightPressed = false;
|
|
385
|
+
this.requestRender();
|
|
386
|
+
}, 150);
|
|
309
387
|
}
|
|
310
388
|
|
|
311
389
|
if (this.onRightClick) this.onRightClick();
|
|
390
|
+
this.requestRender();
|
|
312
391
|
return true;
|
|
313
392
|
}
|
|
314
393
|
}
|
|
@@ -49,6 +49,8 @@ class BottomNavigationBar extends Component {
|
|
|
49
49
|
|
|
50
50
|
// Ripple effect (Material)
|
|
51
51
|
this.ripples = [];
|
|
52
|
+
this.animationFrame = null;
|
|
53
|
+
this.lastAnimationTime = 0;
|
|
52
54
|
|
|
53
55
|
// Animation de l'indicateur (iOS)
|
|
54
56
|
this.indicatorX = 0;
|
|
@@ -61,6 +63,82 @@ class BottomNavigationBar extends Component {
|
|
|
61
63
|
this.updateIndicatorPosition();
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Démarrer l'animation des ripples
|
|
68
|
+
* @private
|
|
69
|
+
*/
|
|
70
|
+
startRippleAnimation() {
|
|
71
|
+
const animate = (timestamp) => {
|
|
72
|
+
if (!this.lastAnimationTime) this.lastAnimationTime = timestamp;
|
|
73
|
+
const deltaTime = timestamp - this.lastAnimationTime;
|
|
74
|
+
this.lastAnimationTime = timestamp;
|
|
75
|
+
|
|
76
|
+
let needsUpdate = false;
|
|
77
|
+
|
|
78
|
+
// Mettre à jour chaque ripple
|
|
79
|
+
for (let i = this.ripples.length - 1; i >= 0; i--) {
|
|
80
|
+
const ripple = this.ripples[i];
|
|
81
|
+
|
|
82
|
+
// Animer le rayon (expansion)
|
|
83
|
+
if (ripple.radius < ripple.maxRadius) {
|
|
84
|
+
ripple.radius += (ripple.maxRadius / 300) * deltaTime;
|
|
85
|
+
needsUpdate = true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Animer l'opacité (fade out)
|
|
89
|
+
if (ripple.radius >= ripple.maxRadius * 0.4) {
|
|
90
|
+
ripple.opacity -= (0.003 * deltaTime);
|
|
91
|
+
if (ripple.opacity < 0) ripple.opacity = 0;
|
|
92
|
+
needsUpdate = true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Supprimer les ripples terminés
|
|
96
|
+
if (ripple.opacity <= 0 && ripple.radius >= ripple.maxRadius * 0.95) {
|
|
97
|
+
this.ripples.splice(i, 1);
|
|
98
|
+
needsUpdate = true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Redessiner si nécessaire
|
|
103
|
+
if (needsUpdate) {
|
|
104
|
+
this.requestRender();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Continuer l'animation
|
|
108
|
+
if (this.ripples.length > 0) {
|
|
109
|
+
this.animationFrame = requestAnimationFrame(animate);
|
|
110
|
+
} else {
|
|
111
|
+
this.animationFrame = null;
|
|
112
|
+
this.lastAnimationTime = 0;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
if (this.ripples.length > 0 && !this.animationFrame) {
|
|
117
|
+
this.animationFrame = requestAnimationFrame(animate);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Demander un redessin
|
|
123
|
+
* @private
|
|
124
|
+
*/
|
|
125
|
+
requestRender() {
|
|
126
|
+
if (this.framework && this.framework.requestRender) {
|
|
127
|
+
this.framework.requestRender();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Nettoyer l'animation lors de la destruction
|
|
133
|
+
*/
|
|
134
|
+
destroy() {
|
|
135
|
+
if (this.animationFrame) {
|
|
136
|
+
cancelAnimationFrame(this.animationFrame);
|
|
137
|
+
this.animationFrame = null;
|
|
138
|
+
}
|
|
139
|
+
super.destroy();
|
|
140
|
+
}
|
|
141
|
+
|
|
64
142
|
/**
|
|
65
143
|
* Met à jour la position de l'indicateur iOS
|
|
66
144
|
* @private
|
|
@@ -80,17 +158,30 @@ class BottomNavigationBar extends Component {
|
|
|
80
158
|
*/
|
|
81
159
|
animateIndicator() {
|
|
82
160
|
this.animatingIndicator = true;
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
161
|
+
const startTime = performance.now();
|
|
162
|
+
const duration = 300; // 300ms d'animation
|
|
163
|
+
const startX = this.indicatorX;
|
|
164
|
+
const endX = this.targetIndicatorX;
|
|
165
|
+
|
|
166
|
+
const animate = (currentTime) => {
|
|
167
|
+
const elapsed = currentTime - startTime;
|
|
168
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
169
|
+
|
|
170
|
+
// Easing function (easeOutCubic)
|
|
171
|
+
const easeProgress = 1 - Math.pow(1 - progress, 3);
|
|
172
|
+
this.indicatorX = startX + (endX - startX) * easeProgress;
|
|
173
|
+
|
|
174
|
+
if (progress < 1) {
|
|
87
175
|
requestAnimationFrame(animate);
|
|
176
|
+
this.requestRender();
|
|
88
177
|
} else {
|
|
89
|
-
this.indicatorX =
|
|
178
|
+
this.indicatorX = endX;
|
|
90
179
|
this.animatingIndicator = false;
|
|
180
|
+
this.requestRender();
|
|
91
181
|
}
|
|
92
182
|
};
|
|
93
|
-
|
|
183
|
+
|
|
184
|
+
requestAnimationFrame(animate);
|
|
94
185
|
}
|
|
95
186
|
|
|
96
187
|
/**
|
|
@@ -120,11 +211,6 @@ class BottomNavigationBar extends Component {
|
|
|
120
211
|
ctx.stroke();
|
|
121
212
|
}
|
|
122
213
|
|
|
123
|
-
// Ripples (Material)
|
|
124
|
-
if (this.platform === 'material') {
|
|
125
|
-
this.drawRipples(ctx);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
214
|
// Items
|
|
129
215
|
const itemWidth = this.width / this.items.length;
|
|
130
216
|
|
|
@@ -161,6 +247,11 @@ class BottomNavigationBar extends Component {
|
|
|
161
247
|
ctx.fillText(item.label, itemX + itemWidth / 2, labelY);
|
|
162
248
|
}
|
|
163
249
|
|
|
250
|
+
// Ripples (Material) - DESSINER APRÈS LES ÉLÉMENTS
|
|
251
|
+
if (this.platform === 'material') {
|
|
252
|
+
this.drawRipples(ctx);
|
|
253
|
+
}
|
|
254
|
+
|
|
164
255
|
ctx.restore();
|
|
165
256
|
}
|
|
166
257
|
|
|
@@ -169,44 +260,24 @@ class BottomNavigationBar extends Component {
|
|
|
169
260
|
* @private
|
|
170
261
|
*/
|
|
171
262
|
drawRipples(ctx) {
|
|
263
|
+
// Sauvegarder le contexte
|
|
264
|
+
ctx.save();
|
|
265
|
+
|
|
266
|
+
// Créer un masque de clipping pour limiter les ripples à la barre
|
|
267
|
+
ctx.beginPath();
|
|
268
|
+
ctx.rect(this.x, this.y, this.width, this.height);
|
|
269
|
+
ctx.clip();
|
|
270
|
+
|
|
172
271
|
for (let ripple of this.ripples) {
|
|
173
|
-
ctx.save();
|
|
174
272
|
ctx.globalAlpha = ripple.opacity;
|
|
175
273
|
ctx.fillStyle = this.rippleColor;
|
|
176
274
|
ctx.beginPath();
|
|
177
275
|
ctx.arc(ripple.x, ripple.y, ripple.radius, 0, Math.PI * 2);
|
|
178
276
|
ctx.fill();
|
|
179
|
-
ctx.restore();
|
|
180
277
|
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Anime les effets ripple
|
|
185
|
-
* @private
|
|
186
|
-
*/
|
|
187
|
-
animateRipple() {
|
|
188
|
-
const animate = () => {
|
|
189
|
-
let hasActiveRipples = false;
|
|
190
|
-
|
|
191
|
-
for (let ripple of this.ripples) {
|
|
192
|
-
if (ripple.radius < ripple.maxRadius) {
|
|
193
|
-
ripple.radius += ripple.maxRadius / 12;
|
|
194
|
-
hasActiveRipples = true;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (ripple.radius >= ripple.maxRadius * 0.5) {
|
|
198
|
-
ripple.opacity -= 0.05;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
this.ripples = this.ripples.filter(r => r.opacity > 0);
|
|
203
|
-
|
|
204
|
-
if (hasActiveRipples) {
|
|
205
|
-
requestAnimationFrame(animate);
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
278
|
|
|
209
|
-
|
|
279
|
+
// Restaurer le contexte
|
|
280
|
+
ctx.restore();
|
|
210
281
|
}
|
|
211
282
|
|
|
212
283
|
/**
|
|
@@ -308,32 +379,52 @@ class BottomNavigationBar extends Component {
|
|
|
308
379
|
* @private
|
|
309
380
|
*/
|
|
310
381
|
handlePress(x, y) {
|
|
311
|
-
|
|
312
|
-
const
|
|
382
|
+
// Convertir les coordonnées absolues en coordonnées relatives à la barre
|
|
383
|
+
const relativeX = x - this.x;
|
|
384
|
+
const relativeY = y - this.y;
|
|
313
385
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
x: (index + 0.5) * itemWidth,
|
|
319
|
-
y: this.y + this.height / 2,
|
|
320
|
-
radius: 0,
|
|
321
|
-
maxRadius: itemWidth / 2,
|
|
322
|
-
opacity: 1
|
|
323
|
-
});
|
|
324
|
-
this.animateRipple();
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
this.selectedIndex = index;
|
|
328
|
-
this.updateIndicatorPosition();
|
|
386
|
+
// Vérifier si on est dans la barre
|
|
387
|
+
if (relativeY >= 0 && relativeY <= this.height) {
|
|
388
|
+
const itemWidth = this.width / this.items.length;
|
|
389
|
+
const index = Math.floor(relativeX / itemWidth);
|
|
329
390
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
this.
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
391
|
+
if (index >= 0 && index < this.items.length && index !== this.selectedIndex) {
|
|
392
|
+
// Ripple effect (Material)
|
|
393
|
+
if (this.platform === 'material') {
|
|
394
|
+
// Calculer la taille maximale du ripple (ne pas dépasser la hauteur de la barre)
|
|
395
|
+
const maxRippleRadius = Math.min(itemWidth * 0.6, this.height * 0.8);
|
|
396
|
+
|
|
397
|
+
this.ripples.push({
|
|
398
|
+
x: this.x + (index + 0.5) * itemWidth, // Coordonnée absolue
|
|
399
|
+
y: this.y + this.height / 2, // Coordonnée absolue
|
|
400
|
+
radius: 0,
|
|
401
|
+
maxRadius: maxRippleRadius,
|
|
402
|
+
opacity: 1,
|
|
403
|
+
createdAt: performance.now()
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Démarrer l'animation si elle n'est pas en cours
|
|
407
|
+
if (!this.animationFrame) {
|
|
408
|
+
this.startRippleAnimation();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Forcer un redessin
|
|
412
|
+
this.requestRender();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
this.selectedIndex = index;
|
|
416
|
+
this.updateIndicatorPosition();
|
|
417
|
+
|
|
418
|
+
// Animer l'indicateur (iOS)
|
|
419
|
+
if (this.platform === 'cupertino') {
|
|
420
|
+
this.animateIndicator();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (this.onChange) {
|
|
424
|
+
this.onChange(index, this.items[index]);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
this.requestRender();
|
|
337
428
|
}
|
|
338
429
|
}
|
|
339
430
|
}
|