canvasframework 0.5.0 → 0.5.2
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/Button.js +1 -0
- package/components/ListItem.js +135 -89
- package/components/SwipeableListItem.js +0 -20
- package/core/CanvasFramework.js +179 -148
- package/features/PullToRefresh.js +1 -1
- package/package.json +1 -1
package/components/Button.js
CHANGED
package/components/ListItem.js
CHANGED
|
@@ -53,16 +53,35 @@ class ListItem extends Component {
|
|
|
53
53
|
* @param {number} y - Coordonnée Y
|
|
54
54
|
* @private
|
|
55
55
|
*/
|
|
56
|
-
|
|
56
|
+
handlePress(x, y) {
|
|
57
57
|
if (this.platform === 'material') {
|
|
58
|
-
|
|
58
|
+
// ✅ CORRECTION : Calculer les coordonnées LOCALES du composant
|
|
59
|
+
// x et y sont déjà les coordonnées écran après ajustement par le framework
|
|
60
|
+
|
|
61
|
+
// Calculer les coordonnées relatives au composant
|
|
62
|
+
const localX = x - this.x;
|
|
63
|
+
const localY = y - this.y;
|
|
64
|
+
|
|
65
|
+
// ✅ AJOUTER : Vérifier si le point est vraiment dans le composant
|
|
66
|
+
if (localX < 0 || localX > this.width || localY < 0 || localY > this.height) {
|
|
67
|
+
return; // Le clic n'est pas dans le composant
|
|
68
|
+
}
|
|
69
|
+
|
|
59
70
|
this.ripples.push({
|
|
60
|
-
x:
|
|
61
|
-
y:
|
|
71
|
+
x: localX, // ✅ Coordonnée X relative au composant
|
|
72
|
+
y: localY, // ✅ Coordonnée Y relative au composant
|
|
62
73
|
radius: 0,
|
|
63
74
|
maxRadius: Math.max(this.width, this.height) * 1.5,
|
|
64
|
-
opacity: 1
|
|
75
|
+
opacity: 1,
|
|
76
|
+
startTime: Date.now() // Pour une animation plus précise
|
|
65
77
|
});
|
|
78
|
+
// ✅ TEMPORAIREMENT mettre pressed à true pour le feedback visuel immédiat
|
|
79
|
+
this.pressed = true;
|
|
80
|
+
|
|
81
|
+
// ✅ MAIS le remettre à false après un court délai
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
this.pressed = false;
|
|
84
|
+
}, 150); // 150ms de feedback tactile
|
|
66
85
|
this.animateRipple();
|
|
67
86
|
}
|
|
68
87
|
}
|
|
@@ -72,28 +91,54 @@ class ListItem extends Component {
|
|
|
72
91
|
* @private
|
|
73
92
|
*/
|
|
74
93
|
animateRipple() {
|
|
94
|
+
let animationId = null;
|
|
95
|
+
const startTime = Date.now();
|
|
96
|
+
const duration = 600; // 600ms pour l'animation complète
|
|
97
|
+
|
|
75
98
|
const animate = () => {
|
|
99
|
+
const elapsed = Date.now() - startTime;
|
|
100
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
101
|
+
|
|
76
102
|
let hasActiveRipples = false;
|
|
77
103
|
|
|
78
104
|
for (let ripple of this.ripples) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
105
|
+
// ✅ Animation plus fluide avec easing
|
|
106
|
+
const easedProgress = this.easeOutCubic(progress);
|
|
107
|
+
ripple.radius = easedProgress * ripple.maxRadius;
|
|
108
|
+
|
|
109
|
+
// Fade out à partir de 50% de progression
|
|
110
|
+
if (progress > 0.5) {
|
|
111
|
+
const fadeProgress = (progress - 0.5) / 0.5;
|
|
112
|
+
ripple.opacity = 1 - fadeProgress;
|
|
82
113
|
}
|
|
83
114
|
|
|
84
|
-
if (
|
|
85
|
-
|
|
115
|
+
if (progress < 1) {
|
|
116
|
+
hasActiveRipples = true;
|
|
86
117
|
}
|
|
87
118
|
}
|
|
88
119
|
|
|
120
|
+
// Filtrer les ripples terminés
|
|
89
121
|
this.ripples = this.ripples.filter(r => r.opacity > 0);
|
|
90
122
|
|
|
91
|
-
if (hasActiveRipples) {
|
|
92
|
-
requestAnimationFrame(animate);
|
|
123
|
+
if (hasActiveRipples && this.ripples.length > 0) {
|
|
124
|
+
animationId = requestAnimationFrame(animate);
|
|
125
|
+
} else {
|
|
126
|
+
// ✅ Nettoyer quand l'animation est terminée
|
|
127
|
+
if (animationId) {
|
|
128
|
+
cancelAnimationFrame(animationId);
|
|
129
|
+
}
|
|
130
|
+
this.ripples = [];
|
|
93
131
|
}
|
|
94
132
|
};
|
|
95
133
|
|
|
96
|
-
animate
|
|
134
|
+
animationId = requestAnimationFrame(animate);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Fonction d'easing pour animation fluide
|
|
139
|
+
*/
|
|
140
|
+
easeOutCubic(t) {
|
|
141
|
+
return 1 - Math.pow(1 - t, 3);
|
|
97
142
|
}
|
|
98
143
|
|
|
99
144
|
/**
|
|
@@ -103,108 +148,109 @@ class ListItem extends Component {
|
|
|
103
148
|
draw(ctx) {
|
|
104
149
|
ctx.save();
|
|
105
150
|
|
|
106
|
-
// Background
|
|
151
|
+
// 1. Background (toujours opaque)
|
|
107
152
|
ctx.fillStyle = this.pressed ? '#F5F5F5' : this.bgColor;
|
|
108
153
|
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
109
154
|
|
|
110
|
-
//
|
|
155
|
+
// 2. Ripples (si présents)
|
|
111
156
|
if (this.platform === 'material' && this.ripples.length > 0) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
ctx.clip();
|
|
116
|
-
|
|
117
|
-
for (let ripple of this.ripples) {
|
|
118
|
-
ctx.globalAlpha = ripple.opacity;
|
|
119
|
-
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
|
|
157
|
+
ctx.save();
|
|
158
|
+
|
|
159
|
+
// Clip pour contenir les ripples
|
|
120
160
|
ctx.beginPath();
|
|
121
|
-
ctx.
|
|
122
|
-
ctx.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
161
|
+
ctx.rect(this.x, this.y, this.width, this.height);
|
|
162
|
+
ctx.clip();
|
|
163
|
+
|
|
164
|
+
// Dessiner tous les ripples
|
|
165
|
+
for (let ripple of this.ripples) {
|
|
166
|
+
// Utiliser fillStyle avec alpha intégré au lieu de globalAlpha
|
|
167
|
+
ctx.fillStyle = `rgba(0, 0, 0, ${0.1 * ripple.opacity})`;
|
|
168
|
+
ctx.beginPath();
|
|
169
|
+
ctx.arc(this.x + ripple.x, this.y + ripple.y, ripple.radius, 0, Math.PI * 2);
|
|
170
|
+
ctx.fill();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
ctx.restore();
|
|
126
174
|
}
|
|
127
175
|
|
|
176
|
+
// 3. Contenu (texte, icônes, etc.) - toujours avec alpha = 1
|
|
177
|
+
this.drawContent(ctx);
|
|
178
|
+
|
|
179
|
+
ctx.restore();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Dessine le contenu du ListItem (séparé pour plus de clarté)
|
|
184
|
+
*/
|
|
185
|
+
drawContent(ctx) {
|
|
128
186
|
let leftOffset = 16;
|
|
129
187
|
|
|
130
|
-
// Left Icon ou Image
|
|
131
188
|
if (this.leftIcon) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
189
|
+
ctx.fillStyle = '#757575';
|
|
190
|
+
ctx.font = '24px sans-serif';
|
|
191
|
+
ctx.textAlign = 'left';
|
|
192
|
+
ctx.textBaseline = 'middle';
|
|
193
|
+
ctx.fillText(this.leftIcon, this.x + leftOffset, this.y + this.height / 2);
|
|
194
|
+
leftOffset += 48;
|
|
138
195
|
} else if (this.leftImage) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
ctx.fillText('👤', this.x + leftOffset + 20, this.y + this.height / 2);
|
|
151
|
-
|
|
152
|
-
leftOffset += 56;
|
|
196
|
+
ctx.fillStyle = '#E0E0E0';
|
|
197
|
+
ctx.beginPath();
|
|
198
|
+
ctx.arc(this.x + leftOffset + 20, this.y + this.height / 2, 20, 0, Math.PI * 2);
|
|
199
|
+
ctx.fill();
|
|
200
|
+
|
|
201
|
+
ctx.fillStyle = '#757575';
|
|
202
|
+
ctx.font = '14px sans-serif';
|
|
203
|
+
ctx.textAlign = 'center';
|
|
204
|
+
ctx.textBaseline = 'middle';
|
|
205
|
+
ctx.fillText('👤', this.x + leftOffset + 20, this.y + this.height / 2);
|
|
206
|
+
leftOffset += 56;
|
|
153
207
|
}
|
|
154
208
|
|
|
155
|
-
// Title et Subtitle
|
|
156
209
|
const textX = this.x + leftOffset;
|
|
157
210
|
const centerY = this.y + this.height / 2;
|
|
158
211
|
|
|
159
212
|
if (this.subtitle) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
ctx.textBaseline = 'top';
|
|
171
|
-
ctx.fillText(this.subtitle, textX, centerY + 2);
|
|
213
|
+
ctx.fillStyle = '#000000';
|
|
214
|
+
ctx.font = '16px -apple-system, Roboto, sans-serif';
|
|
215
|
+
ctx.textAlign = 'left';
|
|
216
|
+
ctx.textBaseline = 'bottom';
|
|
217
|
+
ctx.fillText(this.title, textX, centerY - 2);
|
|
218
|
+
|
|
219
|
+
ctx.fillStyle = '#757575';
|
|
220
|
+
ctx.font = '14px -apple-system, Roboto, sans-serif';
|
|
221
|
+
ctx.textBaseline = 'top';
|
|
222
|
+
ctx.fillText(this.subtitle, textX, centerY + 2);
|
|
172
223
|
} else {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
ctx.fillText(this.title, textX, centerY);
|
|
224
|
+
ctx.fillStyle = '#000000';
|
|
225
|
+
ctx.font = '16px -apple-system, Roboto, sans-serif';
|
|
226
|
+
ctx.textAlign = 'left';
|
|
227
|
+
ctx.textBaseline = 'middle';
|
|
228
|
+
ctx.fillText(this.title, textX, centerY);
|
|
179
229
|
}
|
|
180
230
|
|
|
181
|
-
// Right Text ou Icon
|
|
182
231
|
if (this.rightText) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
232
|
+
ctx.fillStyle = '#757575';
|
|
233
|
+
ctx.font = '14px -apple-system, Roboto, sans-serif';
|
|
234
|
+
ctx.textAlign = 'right';
|
|
235
|
+
ctx.textBaseline = 'middle';
|
|
236
|
+
ctx.fillText(this.rightText, this.x + this.width - 16, centerY);
|
|
188
237
|
} else if (this.rightIcon) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
238
|
+
ctx.fillStyle = '#757575';
|
|
239
|
+
ctx.font = '20px sans-serif';
|
|
240
|
+
ctx.textAlign = 'right';
|
|
241
|
+
ctx.textBaseline = 'middle';
|
|
242
|
+
ctx.fillText(this.rightIcon, this.x + this.width - 16, centerY);
|
|
194
243
|
}
|
|
195
244
|
|
|
196
|
-
// Divider
|
|
197
245
|
if (this.divider) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
246
|
+
ctx.strokeStyle = '#E0E0E0';
|
|
247
|
+
ctx.lineWidth = 1;
|
|
248
|
+
ctx.beginPath();
|
|
249
|
+
ctx.moveTo(this.x + leftOffset, this.y + this.height);
|
|
250
|
+
ctx.lineTo(this.x + this.width, this.y + this.height);
|
|
251
|
+
ctx.stroke();
|
|
204
252
|
}
|
|
205
|
-
|
|
206
|
-
ctx.restore();
|
|
207
|
-
}
|
|
253
|
+
}
|
|
208
254
|
|
|
209
255
|
/**
|
|
210
256
|
* Vérifie si un point est dans les limites
|
|
@@ -60,7 +60,6 @@ class SwipeableListItem extends Component {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
const canvas = this.framework.canvas;
|
|
63
|
-
console.log('📡 Configuration des événements sur le canvas');
|
|
64
63
|
|
|
65
64
|
// Événements souris
|
|
66
65
|
canvas.addEventListener('mousedown', this.handleMouseDown);
|
|
@@ -123,7 +122,6 @@ class SwipeableListItem extends Component {
|
|
|
123
122
|
*/
|
|
124
123
|
handleMouseDown(event) {
|
|
125
124
|
const coords = this.getCoordinates(event);
|
|
126
|
-
console.log('🖱️ MouseDown', coords, 'isInside:', this.isPointInside(coords.x, coords.y));
|
|
127
125
|
this.onDragStart(coords.x, coords.y);
|
|
128
126
|
}
|
|
129
127
|
|
|
@@ -134,7 +132,6 @@ class SwipeableListItem extends Component {
|
|
|
134
132
|
handleMouseMove(event) {
|
|
135
133
|
if (!this.dragging) return;
|
|
136
134
|
const coords = this.getCoordinates(event);
|
|
137
|
-
console.log('🖱️ MouseMove', coords, 'dragging:', this.dragging);
|
|
138
135
|
this.onDragMove(coords.x, coords.y);
|
|
139
136
|
this.requestRender();
|
|
140
137
|
}
|
|
@@ -145,7 +142,6 @@ class SwipeableListItem extends Component {
|
|
|
145
142
|
*/
|
|
146
143
|
handleMouseUp(event) {
|
|
147
144
|
if (!this.dragging) return;
|
|
148
|
-
console.log('🖱️ MouseUp');
|
|
149
145
|
this.onDragEnd();
|
|
150
146
|
this.requestRender();
|
|
151
147
|
}
|
|
@@ -156,7 +152,6 @@ class SwipeableListItem extends Component {
|
|
|
156
152
|
*/
|
|
157
153
|
handleTouchStart(event) {
|
|
158
154
|
const coords = this.getCoordinates(event);
|
|
159
|
-
console.log('👆 TouchStart', coords, 'isInside:', this.isPointInside(coords.x, coords.y));
|
|
160
155
|
if (this.isPointInside(coords.x, coords.y)) {
|
|
161
156
|
event.preventDefault();
|
|
162
157
|
this.onDragStart(coords.x, coords.y);
|
|
@@ -171,7 +166,6 @@ class SwipeableListItem extends Component {
|
|
|
171
166
|
if (!this.dragging) return;
|
|
172
167
|
event.preventDefault();
|
|
173
168
|
const coords = this.getCoordinates(event);
|
|
174
|
-
console.log('👆 TouchMove', coords);
|
|
175
169
|
this.onDragMove(coords.x, coords.y);
|
|
176
170
|
this.requestRender();
|
|
177
171
|
}
|
|
@@ -183,7 +177,6 @@ class SwipeableListItem extends Component {
|
|
|
183
177
|
handleTouchEnd(event) {
|
|
184
178
|
if (!this.dragging) return;
|
|
185
179
|
event.preventDefault();
|
|
186
|
-
console.log('👆 TouchEnd');
|
|
187
180
|
this.onDragEnd();
|
|
188
181
|
this.requestRender();
|
|
189
182
|
}
|
|
@@ -205,14 +198,12 @@ class SwipeableListItem extends Component {
|
|
|
205
198
|
*/
|
|
206
199
|
onDragStart(x, y) {
|
|
207
200
|
const inside = this.isPointInside(x, y);
|
|
208
|
-
console.log('🟢 onDragStart', { x, y, inside, bounds: { x: this.x, y: this.y, w: this.width, h: this.height } });
|
|
209
201
|
|
|
210
202
|
if (inside) {
|
|
211
203
|
this.dragging = true;
|
|
212
204
|
this.startX = x;
|
|
213
205
|
this.startY = y;
|
|
214
206
|
this.hasMoved = false;
|
|
215
|
-
console.log('✅ Swipe démarré');
|
|
216
207
|
} else {
|
|
217
208
|
console.log('❌ Point en dehors des limites');
|
|
218
209
|
}
|
|
@@ -228,7 +219,6 @@ class SwipeableListItem extends Component {
|
|
|
228
219
|
const deltaX = x - this.startX;
|
|
229
220
|
const deltaY = Math.abs(y - this.startY);
|
|
230
221
|
|
|
231
|
-
console.log('🔄 onDragMove', { deltaX, deltaY, hasMoved: this.hasMoved });
|
|
232
222
|
|
|
233
223
|
// Swipe horizontal seulement si déplacement > 5px
|
|
234
224
|
if (Math.abs(deltaX) > 5 || this.hasMoved) {
|
|
@@ -242,7 +232,6 @@ class SwipeableListItem extends Component {
|
|
|
242
232
|
);
|
|
243
233
|
this.dragOffset = Math.min(Math.max(this.dragOffset, -maxOffset), maxOffset);
|
|
244
234
|
|
|
245
|
-
console.log('↔️ Offset mis à jour:', this.dragOffset);
|
|
246
235
|
}
|
|
247
236
|
}
|
|
248
237
|
}
|
|
@@ -252,14 +241,11 @@ class SwipeableListItem extends Component {
|
|
|
252
241
|
*/
|
|
253
242
|
onDragEnd() {
|
|
254
243
|
if (this.dragging) {
|
|
255
|
-
console.log('🔴 onDragEnd', { offset: this.dragOffset, hasMoved: this.hasMoved });
|
|
256
244
|
|
|
257
245
|
if (this.hasMoved) {
|
|
258
246
|
if (this.dragOffset > 80 && this.leftActions[0]) {
|
|
259
|
-
console.log('✅ Action gauche déclenchée');
|
|
260
247
|
this.leftActions[0].onClick?.();
|
|
261
248
|
} else if (this.dragOffset < -80 && this.rightActions[0]) {
|
|
262
|
-
console.log('✅ Action droite déclenchée');
|
|
263
249
|
this.rightActions[0].onClick?.();
|
|
264
250
|
}
|
|
265
251
|
}
|
|
@@ -334,12 +320,6 @@ class SwipeableListItem extends Component {
|
|
|
334
320
|
const inside = x >= this.x && x <= this.x + this.width &&
|
|
335
321
|
y >= this.y && y <= this.y + this.height;
|
|
336
322
|
|
|
337
|
-
console.log('🎯 isPointInside check', {
|
|
338
|
-
point: { x, y },
|
|
339
|
-
bounds: { x: this.x, y: this.y, width: this.width, height: this.height },
|
|
340
|
-
inside
|
|
341
|
-
});
|
|
342
|
-
|
|
343
323
|
return inside;
|
|
344
324
|
}
|
|
345
325
|
}
|
package/core/CanvasFramework.js
CHANGED
|
@@ -155,14 +155,24 @@ class CanvasFramework {
|
|
|
155
155
|
constructor(canvasId, options = {}) {
|
|
156
156
|
// ✅ AJOUTER: Démarrer le chronomètre
|
|
157
157
|
const startTime = performance.now();
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
158
|
+
|
|
159
|
+
this.metrics = {
|
|
160
|
+
initTime: 0,
|
|
161
|
+
firstRenderTime: null,
|
|
162
|
+
firstInteractionTime: null,
|
|
163
|
+
totalStartupTime: null
|
|
164
|
+
};
|
|
165
|
+
this._firstRenderDone = false;
|
|
166
|
+
this._startupStartTime = startTime;
|
|
167
|
+
|
|
168
|
+
// ✅ Créer automatiquement le canvas
|
|
169
|
+
this.canvas = document.createElement('canvas');
|
|
170
|
+
this.canvas.id = canvasId || `canvas-${Date.now()}`;
|
|
171
|
+
this.canvas.style.display = 'block';
|
|
172
|
+
this.canvas.style.touchAction = 'none';
|
|
173
|
+
this.canvas.style.userSelect = 'none';
|
|
174
|
+
document.body.appendChild(this.canvas);
|
|
175
|
+
|
|
166
176
|
// NOUVELLE OPTION: choisir entre Canvas 2D et WebGL
|
|
167
177
|
this.useWebGL = options.useWebGL ?? false; // utilise la valeur si fournie, sinon false
|
|
168
178
|
|
|
@@ -999,71 +1009,73 @@ class CanvasFramework {
|
|
|
999
1009
|
* ✅ OPTIMISATION OPTION 2: Rendu partiel (seulement les composants sales)
|
|
1000
1010
|
* @private
|
|
1001
1011
|
*/
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1012
|
+
_renderDirtyComponents() {
|
|
1013
|
+
const ctx = this.optimizations.useDoubleBuffering ? this._bufferCtx : this.ctx;
|
|
1014
|
+
|
|
1015
|
+
if (!ctx || this.dirtyComponents.size === 0) return;
|
|
1016
|
+
|
|
1017
|
+
// ✅ CORRECTION : Ne pas nettoyer avec fillRect !
|
|
1018
|
+
// À la place, utiliser le clipping pour redessiner proprement
|
|
1019
|
+
|
|
1020
|
+
// Copier dans un tableau
|
|
1021
|
+
const dirtyArray = Array.from(this.dirtyComponents);
|
|
1022
|
+
|
|
1023
|
+
// Vider immédiatement
|
|
1024
|
+
this.dirtyComponents.clear();
|
|
1025
|
+
|
|
1026
|
+
// Séparer les composants
|
|
1027
|
+
const fixedComps = [];
|
|
1028
|
+
const scrollableComps = [];
|
|
1029
|
+
|
|
1030
|
+
dirtyArray.forEach(comp => {
|
|
1031
|
+
if (!comp.visible) return;
|
|
1032
|
+
|
|
1033
|
+
if (this.isFixedComponent(comp)) {
|
|
1034
|
+
fixedComps.push(comp);
|
|
1035
|
+
} else {
|
|
1036
|
+
scrollableComps.push(comp);
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
// ✅ APPROCHE CORRECTE : Redessiner les composants sales
|
|
1041
|
+
// sans effacer leur zone d'abord
|
|
1042
|
+
|
|
1043
|
+
// 1. Dessiner les scrollables
|
|
1044
|
+
if (scrollableComps.length > 0) {
|
|
1045
|
+
ctx.save();
|
|
1046
|
+
ctx.translate(0, this.scrollOffset);
|
|
1047
|
+
|
|
1048
|
+
for (let comp of scrollableComps) {
|
|
1049
|
+
// ✅ NE PAS faire de fillRect !
|
|
1050
|
+
// Juste dessiner le composant
|
|
1051
|
+
if (comp.draw) {
|
|
1052
|
+
comp.draw(ctx);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
if (comp.markClean) {
|
|
1056
|
+
comp.markClean();
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
ctx.restore();
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// 2. Dessiner les fixes
|
|
1064
|
+
for (let comp of fixedComps) {
|
|
1065
|
+
if (comp.draw) {
|
|
1066
|
+
comp.draw(ctx);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
if (comp.markClean) {
|
|
1070
|
+
comp.markClean();
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// Flush si double buffering
|
|
1075
|
+
if (this.optimizations.useDoubleBuffering) {
|
|
1076
|
+
this.flush();
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1067
1079
|
|
|
1068
1080
|
/**
|
|
1069
1081
|
* Active/désactive une optimisation spécifique
|
|
@@ -2467,10 +2479,20 @@ class CanvasFramework {
|
|
|
2467
2479
|
|
|
2468
2480
|
// 4. Modifiez markComponentDirty() pour éviter de marquer pendant le scroll
|
|
2469
2481
|
markComponentDirty(component) {
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2482
|
+
// Vérifications basiques
|
|
2483
|
+
if (!component || !component.visible) return;
|
|
2484
|
+
|
|
2485
|
+
// ✅ TOUJOURS ajouter au set des composants sales
|
|
2486
|
+
// Les conditions de rendu seront vérifiées dans _renderDirtyComponents
|
|
2487
|
+
if (this.optimizationEnabled) {
|
|
2488
|
+
this.dirtyComponents.add(component);
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
// ✅ Optionnel : Marquer aussi le composant lui-même
|
|
2492
|
+
if (component._dirty !== undefined) {
|
|
2493
|
+
component._dirty = true;
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2474
2496
|
|
|
2475
2497
|
enableOptimization() {
|
|
2476
2498
|
this.optimizationEnabled = true;
|
|
@@ -2636,77 +2658,86 @@ class CanvasFramework {
|
|
|
2636
2658
|
}
|
|
2637
2659
|
}
|
|
2638
2660
|
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2661
|
+
startRenderLoop() {
|
|
2662
|
+
let lastScrollOffset = this.scrollOffset;
|
|
2663
|
+
let lastRenderMode = 'full';
|
|
2664
|
+
|
|
2665
|
+
const render = () => {
|
|
2666
|
+
if (!this._splashFinished) {
|
|
2667
|
+
requestAnimationFrame(render);
|
|
2668
|
+
return;
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
// Vérifier le scroll
|
|
2672
|
+
const scrollChanged = Math.abs(this.scrollOffset - lastScrollOffset) > 0.1;
|
|
2673
|
+
lastScrollOffset = this.scrollOffset;
|
|
2674
|
+
|
|
2675
|
+
// Décider du mode de rendu
|
|
2676
|
+
let renderMode = 'full';
|
|
2677
|
+
|
|
2678
|
+
if (this.optimizationEnabled &&
|
|
2679
|
+
this.dirtyComponents.size > 0 &&
|
|
2680
|
+
!this.isDragging &&
|
|
2681
|
+
Math.abs(this.scrollVelocity) < 0.5 &&
|
|
2682
|
+
!scrollChanged &&
|
|
2683
|
+
this.dirtyComponents.size < 3) {
|
|
2684
|
+
renderMode = 'dirty';
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
if (renderMode === 'full' || lastRenderMode !== renderMode) {
|
|
2688
|
+
this.ctx.fillStyle = this.backgroundColor || '#ffffff';
|
|
2689
|
+
this.ctx.fillRect(0, 0, this.width, this.height);
|
|
2690
|
+
this.renderFull();
|
|
2691
|
+
} else {
|
|
2692
|
+
this._renderDirtyComponents();
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
lastRenderMode = renderMode;
|
|
2696
|
+
|
|
2697
|
+
// ✅ AJOUTER : Calcul et affichage du FPS
|
|
2698
|
+
this._frames++;
|
|
2699
|
+
const now = performance.now();
|
|
2700
|
+
const elapsed = now - this._lastFpsTime;
|
|
2701
|
+
|
|
2702
|
+
if (elapsed >= 1000) {
|
|
2703
|
+
this.fps = Math.round((this._frames * 1000) / elapsed);
|
|
2704
|
+
this._frames = 0;
|
|
2705
|
+
this._lastFpsTime = now;
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
// ✅ AJOUTER : Afficher le FPS si activé
|
|
2709
|
+
if (this.showFps) {
|
|
2710
|
+
this.ctx.save();
|
|
2711
|
+
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
|
|
2712
|
+
this.ctx.fillRect(10, 10, 100, 40);
|
|
2713
|
+
this.ctx.fillStyle = '#00ff00';
|
|
2714
|
+
this.ctx.font = 'bold 20px monospace';
|
|
2715
|
+
this.ctx.textAlign = 'left';
|
|
2716
|
+
this.ctx.textBaseline = 'top';
|
|
2717
|
+
this.ctx.fillText(`FPS: ${this.fps}`, 20, 20);
|
|
2718
|
+
this.ctx.restore();
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
// ✅ AJOUTER : Indicateurs de débogage si activés
|
|
2722
|
+
if (this.debbug) {
|
|
2723
|
+
this.drawOverflowIndicators();
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
// ✅ AJOUTER : Marquer le premier rendu
|
|
2727
|
+
if (!this._firstRenderDone) {
|
|
2728
|
+
this._markFirstRender();
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
// ✅ AJOUTER : Mettre à jour l'inertie si nécessaire
|
|
2732
|
+
if (Math.abs(this.scrollVelocity) > 0.1 && !this.isDragging) {
|
|
2733
|
+
this.scrollWorker.postMessage({ type: 'UPDATE_INERTIA' });
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
requestAnimationFrame(render);
|
|
2737
|
+
};
|
|
2738
|
+
|
|
2739
|
+
render();
|
|
2740
|
+
}
|
|
2710
2741
|
|
|
2711
2742
|
// 3. Ajoutez une méthode renderFull() optimisée
|
|
2712
2743
|
renderFull() {
|
|
@@ -247,7 +247,7 @@ class PullToRefresh extends Component {
|
|
|
247
247
|
ctx.save();
|
|
248
248
|
|
|
249
249
|
const progress = Math.min(1, this.pullDistance / this.refreshThreshold);
|
|
250
|
-
const displayHeight = Math.min(this.pullDistance,
|
|
250
|
+
const displayHeight = Math.min(this.pullDistance, 230);
|
|
251
251
|
|
|
252
252
|
// Background
|
|
253
253
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.95)';
|