canvasframework 0.3.8 → 0.3.10

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.
@@ -1,17 +1,13 @@
1
1
  import Component from '../core/Component.js';
2
2
 
3
3
  /**
4
- * Bouton cliquable
4
+ * Bouton cliquable avec variantes Material et Cupertino
5
5
  * @class
6
6
  * @extends Component
7
- * @property {string} text - Texte du bouton
8
- * @property {number} fontSize - Taille de la police
9
- * @property {string} platform - Plateforme
10
- * @property {string} bgColor - Couleur de fond
11
- * @property {string} textColor - Couleur du texte
12
- * @property {string} rippleColor - Couleur du ripple
13
- * @property {number} elevation - Élévation (ombre)
14
- * @property {Array} ripples - Effets ripple
7
+ *
8
+ * Types Material: 'filled', 'outlined', 'text', 'elevated', 'tonal'
9
+ * Types Cupertino: 'filled', 'gray', 'tinted', 'bordered', 'plain'
10
+ * Shapes: 'rounded', 'square', 'pill' (très arrondi)
15
11
  */
16
12
  class Button extends Component {
17
13
  /**
@@ -20,35 +16,135 @@ class Button extends Component {
20
16
  * @param {Object} [options={}] - Options de configuration
21
17
  * @param {string} [options.text='Button'] - Texte
22
18
  * @param {number} [options.fontSize=16] - Taille de police
23
- * @param {string} [options.bgColor] - Couleur de fond (auto selon platform)
24
- * @param {string} [options.textColor] - Couleur du texte (auto selon platform)
25
- * @param {number} [options.elevation=2] - Élévation (Material)
19
+ * @param {string} [options.type] - Type de bouton (auto selon platform)
20
+ * @param {string} [options.shape='rounded'] - Forme: 'rounded', 'square', 'pill'
21
+ * @param {string} [options.bgColor] - Couleur personnalisée
22
+ * @param {string} [options.textColor] - Couleur du texte personnalisée
23
+ * @param {number} [options.elevation=2] - Élévation (Material elevated)
26
24
  */
27
25
  constructor(framework, options = {}) {
28
26
  super(framework, options);
29
27
  this.text = options.text || 'Button';
30
28
  this.fontSize = options.fontSize || 16;
31
29
  this.platform = framework.platform;
30
+ this.shape = options.shape || 'rounded';
32
31
 
33
- // Couleurs selon la plateforme
32
+ // Définir le type de bouton selon la plateforme
34
33
  if (this.platform === 'material') {
35
- this.bgColor = options.bgColor || '#6200EE';
36
- this.textColor = options.textColor || '#FFFFFF';
37
- this.rippleColor = 'rgba(255, 255, 255, 0.3)';
38
- this.elevation = options.elevation || 2;
34
+ this.type = options.type || 'filled';
35
+ this.setupMaterialStyle(options);
39
36
  } else {
40
- this.bgColor = options.bgColor || '#007AFF';
41
- this.textColor = options.textColor || '#FFFFFF';
42
- this.borderRadius = 10;
37
+ this.type = options.type || 'filled';
38
+ this.setupCupertinoStyle(options);
43
39
  }
44
40
 
45
- // Effet Ripple
41
+ // Effets ripple (Material uniquement)
46
42
  this.ripples = [];
47
43
 
48
- // Bind des méthodes
44
+ // Bind
49
45
  this.onPress = this.handlePress.bind(this);
50
46
  }
51
47
 
48
+ /**
49
+ * Configure le style Material Design
50
+ * @private
51
+ */
52
+ setupMaterialStyle(options) {
53
+ const baseColor = options.bgColor || '#6200EE';
54
+ this.elevation = options.elevation || 2;
55
+
56
+ switch (this.type) {
57
+ case 'filled':
58
+ this.bgColor = baseColor;
59
+ this.textColor = options.textColor || '#FFFFFF';
60
+ this.borderWidth = 0;
61
+ this.rippleColor = 'rgba(255, 255, 255, 0.3)';
62
+ break;
63
+
64
+ case 'outlined':
65
+ this.bgColor = 'transparent';
66
+ this.textColor = options.textColor || baseColor;
67
+ this.borderColor = baseColor;
68
+ this.borderWidth = 1;
69
+ this.rippleColor = this.hexToRgba(baseColor, 0.2);
70
+ this.elevation = 0;
71
+ break;
72
+
73
+ case 'text':
74
+ this.bgColor = 'transparent';
75
+ this.textColor = options.textColor || baseColor;
76
+ this.borderWidth = 0;
77
+ this.rippleColor = this.hexToRgba(baseColor, 0.2);
78
+ this.elevation = 0;
79
+ break;
80
+
81
+ case 'elevated':
82
+ this.bgColor = options.bgColor || '#FFFFFF';
83
+ this.textColor = options.textColor || baseColor;
84
+ this.borderWidth = 0;
85
+ this.rippleColor = this.hexToRgba(baseColor, 0.2);
86
+ this.elevation = options.elevation || 4;
87
+ break;
88
+
89
+ case 'tonal':
90
+ this.bgColor = this.hexToRgba(baseColor, 0.3);
91
+ this.textColor = options.textColor || baseColor;
92
+ this.borderWidth = 0;
93
+ this.rippleColor = this.hexToRgba(baseColor, 0.3);
94
+ this.elevation = 0;
95
+ break;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Configure le style Cupertino (iOS)
101
+ * @private
102
+ */
103
+ setupCupertinoStyle(options) {
104
+ const baseColor = options.bgColor || '#007AFF';
105
+
106
+ switch (this.type) {
107
+ case 'filled':
108
+ this.bgColor = baseColor;
109
+ this.textColor = options.textColor || '#FFFFFF';
110
+ this.borderWidth = 0;
111
+ break;
112
+
113
+ case 'gray':
114
+ this.bgColor = 'rgba(120, 120, 128, 0.16)';
115
+ this.textColor = options.textColor || baseColor;
116
+ this.borderWidth = 0;
117
+ break;
118
+
119
+ case 'tinted':
120
+ this.bgColor = this.hexToRgba(baseColor, 0.2);
121
+ this.textColor = options.textColor || baseColor;
122
+ this.borderWidth = 0;
123
+ break;
124
+
125
+ case 'plain':
126
+ this.bgColor = 'transparent';
127
+ this.textColor = options.textColor || baseColor;
128
+ this.borderWidth = 0;
129
+ break;
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Retourne le rayon des coins selon la forme
135
+ * @returns {number} Rayon en pixels
136
+ * @private
137
+ */
138
+ getBorderRadius() {
139
+ switch (this.shape) {
140
+ case 'square':
141
+ return 0;
142
+ case 'rounded':
143
+ default:
144
+ return this.platform === 'material' ? 4 : 10;
145
+ }
146
+ }
147
+
52
148
  /**
53
149
  * Gère la pression sur le bouton
54
150
  * @param {number} x - Coordonnée X
@@ -56,7 +152,6 @@ class Button extends Component {
56
152
  * @private
57
153
  */
58
154
  handlePress(x, y) {
59
- // Créer un ripple au point de clic
60
155
  if (this.platform === 'material') {
61
156
  const adjustedY = y - this.framework.scrollOffset;
62
157
  this.ripples.push({
@@ -89,7 +184,6 @@ class Button extends Component {
89
184
  }
90
185
  }
91
186
 
92
- // Nettoyer les ripples terminés
93
187
  this.ripples = this.ripples.filter(r => r.opacity > 0);
94
188
 
95
189
  if (hasActiveRipples) {
@@ -107,31 +201,44 @@ class Button extends Component {
107
201
  draw(ctx) {
108
202
  ctx.save();
109
203
 
110
- if (this.platform === 'material') {
111
- // Material Design
112
- // Ombre (elevation)
113
- if (this.elevation > 0 && !this.pressed) {
114
- ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
115
- ctx.shadowBlur = this.elevation * 2;
116
- ctx.shadowOffsetY = this.elevation;
117
- }
118
-
204
+ const radius = this.getBorderRadius();
205
+
206
+ // Ombre Material (elevated/filled)
207
+ if (this.platform === 'material' && this.elevation > 0 && !this.pressed) {
208
+ ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
209
+ ctx.shadowBlur = this.elevation * 2;
210
+ ctx.shadowOffsetY = this.elevation;
211
+ }
212
+
213
+ // Background
214
+ if (this.bgColor !== 'transparent') {
119
215
  ctx.fillStyle = this.pressed ? this.darkenColor(this.bgColor) : this.bgColor;
120
216
  ctx.beginPath();
121
- this.roundRect(ctx, this.x, this.y, this.width, this.height, 4);
217
+ this.roundRect(ctx, this.x, this.y, this.width, this.height, radius);
122
218
  ctx.fill();
123
-
124
- ctx.shadowColor = 'transparent';
125
- ctx.shadowBlur = 0;
126
- ctx.shadowOffsetY = 0;
127
-
128
- // Clipping pour les ripples
219
+ }
220
+
221
+ // Réinitialiser l'ombre
222
+ ctx.shadowColor = 'transparent';
223
+ ctx.shadowBlur = 0;
224
+ ctx.shadowOffsetY = 0;
225
+
226
+ // Bordure
227
+ if (this.borderWidth > 0) {
228
+ ctx.strokeStyle = this.borderColor;
229
+ ctx.lineWidth = this.borderWidth;
230
+ ctx.beginPath();
231
+ this.roundRect(ctx, this.x, this.y, this.width, this.height, radius);
232
+ ctx.stroke();
233
+ }
234
+
235
+ // Ripple effect (Material)
236
+ if (this.platform === 'material') {
129
237
  ctx.save();
130
238
  ctx.beginPath();
131
- this.roundRect(ctx, this.x, this.y, this.width, this.height, 4);
239
+ this.roundRect(ctx, this.x, this.y, this.width, this.height, radius);
132
240
  ctx.clip();
133
241
 
134
- // Dessiner les ripples
135
242
  for (let ripple of this.ripples) {
136
243
  ctx.globalAlpha = ripple.opacity;
137
244
  ctx.fillStyle = this.rippleColor;
@@ -141,17 +248,20 @@ class Button extends Component {
141
248
  }
142
249
 
143
250
  ctx.restore();
144
-
145
- } else {
146
- // Cupertino (iOS)
147
- ctx.fillStyle = this.pressed ? this.darkenColor(this.bgColor) : this.bgColor;
251
+ }
252
+
253
+ // Effet pressed pour iOS (overlay sombre)
254
+ if (this.platform === 'cupertino' && this.pressed && this.bgColor !== 'transparent') {
255
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
148
256
  ctx.beginPath();
149
- this.roundRect(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
257
+ this.roundRect(ctx, this.x, this.y, this.width, this.height, radius);
150
258
  ctx.fill();
151
259
  }
152
260
 
153
261
  // Texte
154
- ctx.fillStyle = this.textColor;
262
+ ctx.fillStyle = this.pressed && this.platform === 'cupertino'
263
+ ? this.darkenColor(this.textColor)
264
+ : this.textColor;
155
265
  ctx.font = `${this.fontSize}px -apple-system, BlinkMacSystemFont, 'Roboto', sans-serif`;
156
266
  ctx.textAlign = 'center';
157
267
  ctx.textBaseline = 'middle';
@@ -162,15 +272,14 @@ class Button extends Component {
162
272
 
163
273
  /**
164
274
  * Dessine un rectangle avec coins arrondis
165
- * @param {CanvasRenderingContext2D} ctx - Contexte de dessin
166
- * @param {number} x - Position X
167
- * @param {number} y - Position Y
168
- * @param {number} width - Largeur
169
- * @param {number} height - Hauteur
170
- * @param {number} radius - Rayon des coins
171
275
  * @private
172
276
  */
173
277
  roundRect(ctx, x, y, width, height, radius) {
278
+ if (radius === 0) {
279
+ ctx.rect(x, y, width, height);
280
+ return;
281
+ }
282
+
174
283
  ctx.moveTo(x + radius, y);
175
284
  ctx.lineTo(x + width - radius, y);
176
285
  ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
@@ -184,19 +293,24 @@ class Button extends Component {
184
293
 
185
294
  /**
186
295
  * Assombrit une couleur
187
- * @param {string} color - Couleur hexadécimale
188
- * @returns {string} Couleur assombrie
189
296
  * @private
190
297
  */
191
298
  darkenColor(color) {
299
+ if (color === 'transparent') return 'rgba(0, 0, 0, 0.1)';
300
+
301
+ if (color.startsWith('rgba') || color.startsWith('rgb')) {
302
+ return color.replace(/[\d.]+\)$/g, match => {
303
+ const val = parseFloat(match);
304
+ return `${Math.max(0, val - 0.1)})`;
305
+ });
306
+ }
307
+
192
308
  const rgb = this.hexToRgb(color);
193
309
  return `rgb(${Math.max(0, rgb.r - 30)}, ${Math.max(0, rgb.g - 30)}, ${Math.max(0, rgb.b - 30)})`;
194
310
  }
195
311
 
196
312
  /**
197
- * Convertit une couleur hex en RGB
198
- * @param {string} hex - Couleur hexadécimale
199
- * @returns {{r: number, g: number, b: number}} Objet RGB
313
+ * Convertit hex en RGB
200
314
  * @private
201
315
  */
202
316
  hexToRgb(hex) {
@@ -208,11 +322,17 @@ class Button extends Component {
208
322
  } : { r: 0, g: 0, b: 0 };
209
323
  }
210
324
 
325
+ /**
326
+ * Convertit hex en RGBA avec alpha
327
+ * @private
328
+ */
329
+ hexToRgba(hex, alpha) {
330
+ const rgb = this.hexToRgb(hex);
331
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`;
332
+ }
333
+
211
334
  /**
212
335
  * Vérifie si un point est dans les limites
213
- * @param {number} x - Coordonnée X
214
- * @param {number} y - Coordonnée Y
215
- * @returns {boolean} True si le point est dans le bouton
216
336
  */
217
337
  isPointInside(x, y) {
218
338
  return x >= this.x &&