canvasframework 0.5.40 → 0.5.41
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/Cards.js +232 -0
- package/core/CanvasFramework.js +4 -3
- package/core/UIBuilder.js +2 -2
- package/index.js +1 -1
- package/package.json +1 -1
- package/components/Card.js +0 -534
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import Component from '../core/Component.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Container avec système de layout et élévation
|
|
5
|
+
* @class
|
|
6
|
+
* @extends Component
|
|
7
|
+
*/
|
|
8
|
+
class Cards extends Component {
|
|
9
|
+
constructor(framework, options = {}, children = []) {
|
|
10
|
+
super(framework, options);
|
|
11
|
+
this.children = [];
|
|
12
|
+
this.padding = options.padding || 0;
|
|
13
|
+
this.gap = options.gap || 0;
|
|
14
|
+
this.direction = options.direction || 'column';
|
|
15
|
+
this.align = options.align || 'start';
|
|
16
|
+
this.bgColor = options.bgColor || 'transparent';
|
|
17
|
+
this.borderRadius = options.borderRadius || 0;
|
|
18
|
+
this.elevation = options.elevation || 0;
|
|
19
|
+
this.shadowColor = options.shadowColor || 'rgba(0,0,0,0.15)';
|
|
20
|
+
this._applyElevationStyles();
|
|
21
|
+
|
|
22
|
+
// Mode de positionnement
|
|
23
|
+
this.positionMode = options.positionMode || 'flow';
|
|
24
|
+
|
|
25
|
+
// Stocker les positions relatives des enfants
|
|
26
|
+
this.childRelativePositions = new Map();
|
|
27
|
+
|
|
28
|
+
// Ajouter les enfants passés dans le constructeur
|
|
29
|
+
if (children && children.length > 0) {
|
|
30
|
+
this.addChildren(children);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_applyElevationStyles() {
|
|
35
|
+
const elevationStyles = {
|
|
36
|
+
0: { blur: 0, offsetY: 0, spread: 0, opacity: 0 },
|
|
37
|
+
1: { blur: 2, offsetY: 1, spread: 0, opacity: 0.1 },
|
|
38
|
+
2: { blur: 4, offsetY: 2, spread: 1, opacity: 0.15 },
|
|
39
|
+
3: { blur: 8, offsetY: 4, spread: 2, opacity: 0.2 },
|
|
40
|
+
4: { blur: 16, offsetY: 8, spread: 3, opacity: 0.25 },
|
|
41
|
+
5: { blur: 24, offsetY: 12, spread: 4, opacity: 0.3 }
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const style = elevationStyles[Math.min(this.elevation, 5)] || elevationStyles[0];
|
|
45
|
+
|
|
46
|
+
this.shadowBlur = style.blur;
|
|
47
|
+
this.shadowOffsetY = style.offsetY;
|
|
48
|
+
this.shadowSpread = style.spread;
|
|
49
|
+
this.shadowOpacity = style.opacity;
|
|
50
|
+
|
|
51
|
+
this._updateShadowColor();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_updateShadowColor() {
|
|
55
|
+
const rgbMatch = this.shadowColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
56
|
+
if (rgbMatch) {
|
|
57
|
+
const r = rgbMatch[1];
|
|
58
|
+
const g = rgbMatch[2];
|
|
59
|
+
const b = rgbMatch[3];
|
|
60
|
+
this._computedShadowColor = `rgba(${r}, ${g}, ${b}, ${this.shadowOpacity})`;
|
|
61
|
+
} else {
|
|
62
|
+
this._computedShadowColor = this.shadowColor;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Ajoute plusieurs enfants
|
|
68
|
+
* @param {Component[]} children - Tableau d'enfants
|
|
69
|
+
*/
|
|
70
|
+
addChildren(children) {
|
|
71
|
+
for (const child of children) {
|
|
72
|
+
this.add(child);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Ajoute un enfant
|
|
78
|
+
* @param {Component} child - Composant enfant
|
|
79
|
+
* @returns {Component} L'enfant ajouté
|
|
80
|
+
*/
|
|
81
|
+
add(child) {
|
|
82
|
+
this.children.push(child);
|
|
83
|
+
|
|
84
|
+
// Si l'enfant a des coordonnées x,y, on les interprète comme relatives au container
|
|
85
|
+
if (child.x !== undefined && child.y !== undefined) {
|
|
86
|
+
// Stocker les positions RELATIVES par rapport au container
|
|
87
|
+
this.childRelativePositions.set(child, {
|
|
88
|
+
relativeX: child.x, // x relatif au container
|
|
89
|
+
relativeY: child.y // y relatif au container
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Calculer la position absolue immédiatement
|
|
93
|
+
this.updateChildPosition(child);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return child;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Met à jour la position d'un enfant en fonction de la position du container
|
|
101
|
+
* @param {Component} child - L'enfant à positionner
|
|
102
|
+
* @private
|
|
103
|
+
*/
|
|
104
|
+
updateChildPosition(child) {
|
|
105
|
+
const relativePos = this.childRelativePositions.get(child);
|
|
106
|
+
|
|
107
|
+
if (relativePos) {
|
|
108
|
+
// Position ABSOLUE = position container + position relative + padding
|
|
109
|
+
child.x = this.x + this.padding + relativePos.relativeX;
|
|
110
|
+
child.y = this.y + this.padding + relativePos.relativeY;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Met à jour la position de tous les enfants quand le container bouge
|
|
116
|
+
* @override
|
|
117
|
+
*/
|
|
118
|
+
setPosition(x, y) {
|
|
119
|
+
super.setPosition(x, y);
|
|
120
|
+
this.updateAllChildrenPositions();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Met à jour toutes les positions des enfants
|
|
125
|
+
* @private
|
|
126
|
+
*/
|
|
127
|
+
updateAllChildrenPositions() {
|
|
128
|
+
for (const child of this.children) {
|
|
129
|
+
this.updateChildPosition(child);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Change la position relative d'un enfant dans le container
|
|
135
|
+
* @param {Component} child - L'enfant à repositionner
|
|
136
|
+
* @param {number} relativeX - Nouvelle position X relative
|
|
137
|
+
* @param {number} relativeY - Nouvelle position Y relative
|
|
138
|
+
*/
|
|
139
|
+
setChildPosition(child, relativeX, relativeY) {
|
|
140
|
+
this.childRelativePositions.set(child, {
|
|
141
|
+
relativeX: relativeX,
|
|
142
|
+
relativeY: relativeY
|
|
143
|
+
});
|
|
144
|
+
this.updateChildPosition(child);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
draw(ctx) {
|
|
148
|
+
ctx.save();
|
|
149
|
+
|
|
150
|
+
// Dessiner l'ombre
|
|
151
|
+
if (this.elevation > 0 && this.bgColor !== 'transparent') {
|
|
152
|
+
this.drawShadow(ctx);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Dessiner le fond
|
|
156
|
+
if (this.bgColor !== 'transparent') {
|
|
157
|
+
ctx.fillStyle = this.bgColor;
|
|
158
|
+
if (this.borderRadius > 0) {
|
|
159
|
+
ctx.beginPath();
|
|
160
|
+
this.roundRect(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
|
|
161
|
+
ctx.fill();
|
|
162
|
+
} else {
|
|
163
|
+
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Dessiner les enfants
|
|
168
|
+
for (let child of this.children) {
|
|
169
|
+
if (child.visible) child.draw(ctx);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
ctx.restore();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
drawShadow(ctx) {
|
|
176
|
+
ctx.save();
|
|
177
|
+
|
|
178
|
+
ctx.shadowColor = this._computedShadowColor;
|
|
179
|
+
ctx.shadowBlur = this.shadowBlur;
|
|
180
|
+
ctx.shadowOffsetX = 0;
|
|
181
|
+
ctx.shadowOffsetY = this.shadowOffsetY;
|
|
182
|
+
|
|
183
|
+
ctx.fillStyle = this.bgColor;
|
|
184
|
+
|
|
185
|
+
if (this.borderRadius > 0) {
|
|
186
|
+
ctx.beginPath();
|
|
187
|
+
const spread = this.shadowSpread;
|
|
188
|
+
this.roundRect(
|
|
189
|
+
ctx,
|
|
190
|
+
this.x - spread/2,
|
|
191
|
+
this.y - spread/2,
|
|
192
|
+
this.width + spread,
|
|
193
|
+
this.height + spread,
|
|
194
|
+
this.borderRadius + spread/2
|
|
195
|
+
);
|
|
196
|
+
ctx.fill();
|
|
197
|
+
} else {
|
|
198
|
+
const spread = this.shadowSpread;
|
|
199
|
+
ctx.fillRect(
|
|
200
|
+
this.x - spread/2,
|
|
201
|
+
this.y - spread/2,
|
|
202
|
+
this.width + spread,
|
|
203
|
+
this.height + spread
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
ctx.restore();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
roundRect(ctx, x, y, width, height, radius) {
|
|
211
|
+
ctx.beginPath();
|
|
212
|
+
ctx.moveTo(x + radius, y);
|
|
213
|
+
ctx.lineTo(x + width - radius, y);
|
|
214
|
+
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
|
215
|
+
ctx.lineTo(x + width, y + height - radius);
|
|
216
|
+
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
|
217
|
+
ctx.lineTo(x + radius, y + height);
|
|
218
|
+
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
|
219
|
+
ctx.lineTo(x, y + radius);
|
|
220
|
+
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
221
|
+
ctx.closePath();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
isPointInside(x, y) {
|
|
225
|
+
return x >= this.x &&
|
|
226
|
+
x <= this.x + this.width &&
|
|
227
|
+
y >= this.y &&
|
|
228
|
+
y <= this.y + this.height;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export default Cards;
|
package/core/CanvasFramework.js
CHANGED
|
@@ -4,7 +4,8 @@ import Input from '../components/Input.js';
|
|
|
4
4
|
import Slider from '../components/Slider.js';
|
|
5
5
|
import Text from '../components/Text.js';
|
|
6
6
|
import View from '../components/View.js';
|
|
7
|
-
import Card from '../components/Card.js';
|
|
7
|
+
//import Card from '../components/Card.js';
|
|
8
|
+
import Cards from '../components/Cards.js';
|
|
8
9
|
import FAB from '../components/FAB.js';
|
|
9
10
|
import SpeedDialFAB from '../components/SpeedDialFAB.js';
|
|
10
11
|
import MorphingFAB from '../components/MorphingFAB.js';
|
|
@@ -2233,7 +2234,7 @@ class CanvasFramework {
|
|
|
2233
2234
|
if (comp.visible) {
|
|
2234
2235
|
const adjustedY = isFixedComponent(comp) ? y : y - this.scrollOffset;
|
|
2235
2236
|
|
|
2236
|
-
|
|
2237
|
+
/* if (comp instanceof Card && comp.clickableChildren && comp.children && comp.children.length > 0) {
|
|
2237
2238
|
if (comp.isPointInside(x, adjustedY)) {
|
|
2238
2239
|
const cardAdjustedY = adjustedY - comp.y - comp.padding;
|
|
2239
2240
|
const cardAdjustedX = x - comp.x - comp.padding;
|
|
@@ -2300,7 +2301,7 @@ class CanvasFramework {
|
|
|
2300
2301
|
}
|
|
2301
2302
|
}
|
|
2302
2303
|
}
|
|
2303
|
-
}
|
|
2304
|
+
}*/
|
|
2304
2305
|
|
|
2305
2306
|
if (comp.isPointInside(x, adjustedY)) {
|
|
2306
2307
|
switch (eventType) {
|
package/core/UIBuilder.js
CHANGED
|
@@ -5,7 +5,7 @@ import Input from '../components/Input.js';
|
|
|
5
5
|
import Slider from '../components/Slider.js';
|
|
6
6
|
import Text from '../components/Text.js';
|
|
7
7
|
import View from '../components/View.js';
|
|
8
|
-
import
|
|
8
|
+
import Cards from '../components/Cards.js';
|
|
9
9
|
import FAB from '../components/FAB.js';
|
|
10
10
|
import SpeedDialFAB from '../components/SpeedDialFAB.js';
|
|
11
11
|
import MorphingFAB from '../components/MorphingFAB.js';
|
|
@@ -82,7 +82,7 @@ const Components = {
|
|
|
82
82
|
Slider,
|
|
83
83
|
Text,
|
|
84
84
|
View,
|
|
85
|
-
|
|
85
|
+
Cards,
|
|
86
86
|
FAB,
|
|
87
87
|
SpeedDialFAB,
|
|
88
88
|
MorphingFAB,
|
package/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export { default as Input } from './components/Input.js';
|
|
|
12
12
|
export { default as Slider } from './components/Slider.js';
|
|
13
13
|
export { default as Text } from './components/Text.js';
|
|
14
14
|
export { default as View } from './components/View.js';
|
|
15
|
-
export { default as
|
|
15
|
+
export { default as Cards } from './components/Cards.js';
|
|
16
16
|
export { default as FAB } from './components/FAB.js';
|
|
17
17
|
export { default as SpeedDialFAB } from './components/SpeedDialFAB.js';
|
|
18
18
|
export { default as MorphingFAB } from './components/MorphingFAB.js';
|
package/package.json
CHANGED
package/components/Card.js
DELETED
|
@@ -1,534 +0,0 @@
|
|
|
1
|
-
import Component from '../core/Component.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Container avec système de layout et effet d'élévation
|
|
5
|
-
* @class
|
|
6
|
-
* @extends Component
|
|
7
|
-
* @property {Component[]} children - Enfants
|
|
8
|
-
* @property {number} padding - Padding interne
|
|
9
|
-
* @property {number} gap - Espacement constant entre enfants
|
|
10
|
-
* @property {string} direction - Direction ('column' ou 'row')
|
|
11
|
-
* @property {string} align - Alignement ('start', 'center', 'end', 'stretch')
|
|
12
|
-
* @property {string} bgColor - Couleur de fond
|
|
13
|
-
* @property {number} borderRadius - Rayon des coins
|
|
14
|
-
* @property {number} elevation - Niveau d'élévation (ombres)
|
|
15
|
-
* @property {boolean} autoLayout - Active le layout automatique
|
|
16
|
-
*/
|
|
17
|
-
class Card extends Component {
|
|
18
|
-
/**
|
|
19
|
-
* Crée une instance de Card
|
|
20
|
-
* @param {CanvasFramework} framework - Framework parent
|
|
21
|
-
* @param {Object} [options={}] - Options de configuration
|
|
22
|
-
* @param {number} [options.padding=0] - Padding interne
|
|
23
|
-
* @param {number} [options.gap=0] - Espacement constant entre enfants
|
|
24
|
-
* @param {string} [options.direction='column'] - Direction
|
|
25
|
-
* @param {string} [options.align='start'] - Alignement
|
|
26
|
-
* @param {string} [options.bgColor='transparent'] - Couleur de fond
|
|
27
|
-
* @param {number} [options.borderRadius=0] - Rayon des coins
|
|
28
|
-
* @param {number} [options.elevation=0] - Niveau d'élévation (0-5)
|
|
29
|
-
* @param {boolean} [options.autoLayout=true] - Active le layout automatique
|
|
30
|
-
*/
|
|
31
|
-
constructor(framework, options = {}) {
|
|
32
|
-
super(framework, options);
|
|
33
|
-
this.children = [];
|
|
34
|
-
this.padding = options.padding || 0;
|
|
35
|
-
this.gap = options.gap || 0;
|
|
36
|
-
this.direction = options.direction || 'column';
|
|
37
|
-
this.align = options.align || 'start';
|
|
38
|
-
this.bgColor = options.bgColor || 'transparent';
|
|
39
|
-
this.borderRadius = options.borderRadius || 0;
|
|
40
|
-
this.elevation = options.elevation || 0;
|
|
41
|
-
this.autoLayout = options.autoLayout !== undefined ? options.autoLayout : true;
|
|
42
|
-
|
|
43
|
-
// Stocker les positions relatives des enfants
|
|
44
|
-
this.childPositions = new Map();
|
|
45
|
-
|
|
46
|
-
// NOUVEAU: Flag pour forcer les positions absolues
|
|
47
|
-
this._positionsCorrected = false;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Ajoute un enfant (position relative convertie en absolue)
|
|
52
|
-
* @param {Component} child - Composant enfant
|
|
53
|
-
* @returns {Component} L'enfant ajouté
|
|
54
|
-
*/
|
|
55
|
-
add(child) {
|
|
56
|
-
this.children.push(child);
|
|
57
|
-
|
|
58
|
-
// SOLUTION: Stocker les coordonnées relatives AVANT conversion
|
|
59
|
-
const relativeX = child.x || 0;
|
|
60
|
-
const relativeY = child.y || 0;
|
|
61
|
-
|
|
62
|
-
// Stocker la position relative originale
|
|
63
|
-
this.childPositions.set(child, {
|
|
64
|
-
x: relativeX,
|
|
65
|
-
y: relativeY
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// CONVERTIR IMMÉDIATEMENT en positions absolues
|
|
69
|
-
child.x = this.x + relativeX;
|
|
70
|
-
child.y = this.y + relativeY;
|
|
71
|
-
|
|
72
|
-
// Si autoLayout est activé, organiser automatiquement
|
|
73
|
-
if (this.autoLayout) {
|
|
74
|
-
// Utiliser setTimeout pour éviter les conflits avec Vite Hot Reload
|
|
75
|
-
setTimeout(() => this.layout(), 0);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return child;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Organise les enfants selon le layout
|
|
83
|
-
* @private
|
|
84
|
-
*/
|
|
85
|
-
layout() {
|
|
86
|
-
if (this.children.length === 0 || !this.autoLayout) return;
|
|
87
|
-
|
|
88
|
-
if (this.direction === 'column') {
|
|
89
|
-
let currentY = this.padding;
|
|
90
|
-
|
|
91
|
-
for (let i = 0; i < this.children.length; i++) {
|
|
92
|
-
const child = this.children[i];
|
|
93
|
-
|
|
94
|
-
// Calculer la position X selon l'alignement
|
|
95
|
-
let childX = this.padding;
|
|
96
|
-
if (this.align === 'center') {
|
|
97
|
-
childX = (this.width - child.width) / 2;
|
|
98
|
-
} else if (this.align === 'end') {
|
|
99
|
-
childX = this.width - child.width - this.padding;
|
|
100
|
-
} else if (this.align === 'stretch') {
|
|
101
|
-
childX = this.padding;
|
|
102
|
-
child.width = this.width - (this.padding * 2);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Positionner l'enfant en COORDONNÉES ABSOLUES
|
|
106
|
-
child.x = this.x + childX;
|
|
107
|
-
child.y = this.y + currentY;
|
|
108
|
-
|
|
109
|
-
// Stocker la position relative
|
|
110
|
-
this.childPositions.set(child, { x: childX, y: currentY });
|
|
111
|
-
|
|
112
|
-
// Mettre à jour la position Y pour l'enfant suivant
|
|
113
|
-
currentY += child.height;
|
|
114
|
-
|
|
115
|
-
// Ajouter le gap seulement si ce n'est pas le dernier enfant
|
|
116
|
-
if (i < this.children.length - 1) {
|
|
117
|
-
currentY += this.gap;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
} else {
|
|
121
|
-
// Direction 'row'
|
|
122
|
-
let currentX = this.padding;
|
|
123
|
-
|
|
124
|
-
for (let i = 0; i < this.children.length; i++) {
|
|
125
|
-
const child = this.children[i];
|
|
126
|
-
|
|
127
|
-
// Calculer la position Y selon l'alignement
|
|
128
|
-
let childY = this.padding;
|
|
129
|
-
if (this.align === 'center') {
|
|
130
|
-
childY = (this.height - child.height) / 2;
|
|
131
|
-
} else if (this.align === 'end') {
|
|
132
|
-
childY = this.height - child.height - this.padding;
|
|
133
|
-
} else if (this.align === 'stretch') {
|
|
134
|
-
childY = this.padding;
|
|
135
|
-
child.height = this.height - (this.padding * 2);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Positionner l'enfant en COORDONNÉES ABSOLUES
|
|
139
|
-
child.x = this.x + currentX;
|
|
140
|
-
child.y = this.y + childY;
|
|
141
|
-
|
|
142
|
-
// Stocker la position relative
|
|
143
|
-
this.childPositions.set(child, { x: currentX, y: childY });
|
|
144
|
-
|
|
145
|
-
// Mettre à jour la position X pour l'enfant suivant
|
|
146
|
-
currentX += child.width;
|
|
147
|
-
|
|
148
|
-
// Ajouter le gap seulement si ce n'est pas le dernier enfant
|
|
149
|
-
if (i < this.children.length - 1) {
|
|
150
|
-
currentX += this.gap;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Marquer que les positions sont corrigées
|
|
156
|
-
this._positionsCorrected = true;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Dessine la carte et ses enfants
|
|
161
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
162
|
-
*/
|
|
163
|
-
draw(ctx) {
|
|
164
|
-
ctx.save();
|
|
165
|
-
|
|
166
|
-
// SOLUTION: Vérifier et corriger les positions AVANT de dessiner
|
|
167
|
-
// Ceci garantit que les enfants sont en coordonnées absolues
|
|
168
|
-
if (this._positionsCorrected) {
|
|
169
|
-
this._ensureAbsolutePositions();
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Dessiner l'ombre si elevation > 0
|
|
173
|
-
if (this.elevation > 0) {
|
|
174
|
-
this.drawShadow(ctx);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Dessiner le fond de la carte
|
|
178
|
-
if (this.bgColor !== 'transparent') {
|
|
179
|
-
ctx.fillStyle = this.bgColor;
|
|
180
|
-
if (this.borderRadius > 0) {
|
|
181
|
-
ctx.beginPath();
|
|
182
|
-
this.roundRect(ctx, this.x, this.y, this.width, this.height, this.borderRadius);
|
|
183
|
-
ctx.fill();
|
|
184
|
-
} else {
|
|
185
|
-
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Dessiner les enfants
|
|
190
|
-
for (let child of this.children) {
|
|
191
|
-
if (child.visible) child.draw(ctx);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
ctx.restore();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* S'assure que tous les enfants ont des coordonnées absolues
|
|
199
|
-
* @private
|
|
200
|
-
*/
|
|
201
|
-
_ensureAbsolutePositions() {
|
|
202
|
-
for (let child of this.children) {
|
|
203
|
-
const relativePos = this.childPositions.get(child);
|
|
204
|
-
if (relativePos) {
|
|
205
|
-
// Recalculer la position absolue basée sur la position de la carte
|
|
206
|
-
const expectedX = this.x + relativePos.x;
|
|
207
|
-
const expectedY = this.y + relativePos.y;
|
|
208
|
-
|
|
209
|
-
// Corriger si nécessaire (important pour Vite Hot Reload)
|
|
210
|
-
if (Math.abs(child.x - expectedX) > 1 || Math.abs(child.y - expectedY) > 1) {
|
|
211
|
-
child.x = expectedX;
|
|
212
|
-
child.y = expectedY;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Met à jour la position de la carte et ajuste les enfants
|
|
220
|
-
* @param {number} x - Nouvelle position X
|
|
221
|
-
* @param {number} y - Nouvelle position Y
|
|
222
|
-
*/
|
|
223
|
-
setPosition(x, y) {
|
|
224
|
-
const deltaX = x - this.x;
|
|
225
|
-
const deltaY = y - this.y;
|
|
226
|
-
|
|
227
|
-
super.setPosition(x, y);
|
|
228
|
-
|
|
229
|
-
// Déplacer tous les enfants avec la carte
|
|
230
|
-
for (let child of this.children) {
|
|
231
|
-
child.x += deltaX;
|
|
232
|
-
child.y += deltaY;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Mettre à jour les positions relatives stockées
|
|
236
|
-
this._updateRelativePositions();
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Met à jour les positions relatives stockées
|
|
241
|
-
* @private
|
|
242
|
-
*/
|
|
243
|
-
_updateRelativePositions() {
|
|
244
|
-
for (let [child, pos] of this.childPositions.entries()) {
|
|
245
|
-
// Les positions relatives ne changent pas quand la carte bouge
|
|
246
|
-
// On garde juste la référence
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Supprime un enfant
|
|
252
|
-
* @param {Component} child - Composant enfant à supprimer
|
|
253
|
-
* @returns {boolean} True si l'enfant a été supprimé
|
|
254
|
-
*/
|
|
255
|
-
remove(child) {
|
|
256
|
-
const index = this.children.indexOf(child);
|
|
257
|
-
if (index > -1) {
|
|
258
|
-
this.children.splice(index, 1);
|
|
259
|
-
this.childPositions.delete(child);
|
|
260
|
-
if (this.autoLayout) this.layout();
|
|
261
|
-
return true;
|
|
262
|
-
}
|
|
263
|
-
return false;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Supprime tous les enfants
|
|
268
|
-
*/
|
|
269
|
-
clear() {
|
|
270
|
-
this.children = [];
|
|
271
|
-
this.childPositions.clear();
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Définit la position d'un enfant dans le système de coordonnées de la Card
|
|
276
|
-
* @param {Component} child - L'enfant à positionner
|
|
277
|
-
* @param {number} relativeX - Position X relative à la Card
|
|
278
|
-
* @param {number} relativeY - Position Y relative à la Card
|
|
279
|
-
*/
|
|
280
|
-
setChildPosition(child, relativeX, relativeY) {
|
|
281
|
-
if (this.children.includes(child)) {
|
|
282
|
-
// Définir en coordonnées absolues
|
|
283
|
-
child.x = this.x + relativeX;
|
|
284
|
-
child.y = this.y + relativeY;
|
|
285
|
-
this.childPositions.set(child, { x: relativeX, y: relativeY });
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Active/désactive le layout automatique
|
|
291
|
-
* @param {boolean} enabled - True pour activer le layout automatique
|
|
292
|
-
*/
|
|
293
|
-
setAutoLayout(enabled) {
|
|
294
|
-
this.autoLayout = enabled;
|
|
295
|
-
if (enabled) this.layout();
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Force un recalcul du layout
|
|
300
|
-
*/
|
|
301
|
-
updateLayout() {
|
|
302
|
-
this.layout();
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Génère les paramètres d'ombre selon le niveau d'élévation
|
|
307
|
-
* @param {number} elevation - Niveau d'élévation (0-5)
|
|
308
|
-
* @returns {Object} Configuration de l'ombre
|
|
309
|
-
* @private
|
|
310
|
-
*/
|
|
311
|
-
getShadowConfig(elevation) {
|
|
312
|
-
const shadows = [
|
|
313
|
-
{ blur: 0, offsetY: 0, color: 'transparent', spread: 0 },
|
|
314
|
-
{ blur: 2, offsetY: 1, color: 'rgba(0,0,0,0.12)', spread: 0 },
|
|
315
|
-
{ blur: 3, offsetY: 1, color: 'rgba(0,0,0,0.14)', spread: 0 },
|
|
316
|
-
{ blur: 4, offsetY: 2, color: 'rgba(0,0,0,0.16)', spread: 0 },
|
|
317
|
-
{ blur: 6, offsetY: 3, color: 'rgba(0,0,0,0.18)', spread: 0 },
|
|
318
|
-
{ blur: 8, offsetY: 4, color: 'rgba(0,0,0,0.20)', spread: 0 },
|
|
319
|
-
];
|
|
320
|
-
|
|
321
|
-
return shadows[Math.min(elevation, shadows.length - 1)];
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Dessine l'effet d'ombre selon l'élévation
|
|
326
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
327
|
-
* @private
|
|
328
|
-
*/
|
|
329
|
-
drawShadow(ctx) {
|
|
330
|
-
if (this.elevation <= 0) return;
|
|
331
|
-
|
|
332
|
-
const shadow = this.getShadowConfig(this.elevation);
|
|
333
|
-
|
|
334
|
-
ctx.save();
|
|
335
|
-
|
|
336
|
-
ctx.shadowColor = shadow.color;
|
|
337
|
-
ctx.shadowBlur = shadow.blur;
|
|
338
|
-
ctx.shadowOffsetX = 0;
|
|
339
|
-
ctx.shadowOffsetY = shadow.offsetY;
|
|
340
|
-
|
|
341
|
-
if (this.borderRadius > 0) {
|
|
342
|
-
ctx.beginPath();
|
|
343
|
-
this.roundRect(ctx, this.x, this.y + shadow.offsetY, this.width, this.height, this.borderRadius);
|
|
344
|
-
ctx.fillStyle = this.bgColor === 'transparent' ? 'white' : this.bgColor;
|
|
345
|
-
ctx.fill();
|
|
346
|
-
} else {
|
|
347
|
-
ctx.fillStyle = this.bgColor === 'transparent' ? 'white' : this.bgColor;
|
|
348
|
-
ctx.fillRect(this.x, this.y + shadow.offsetY, this.width, this.height);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
ctx.restore();
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Dessine un rectangle avec coins arrondis
|
|
356
|
-
* @param {CanvasRenderingContext2D} ctx - Contexte de dessin
|
|
357
|
-
* @param {number} x - Position X
|
|
358
|
-
* @param {number} y - Position Y
|
|
359
|
-
* @param {number} width - Largeur
|
|
360
|
-
* @param {number} height - Hauteur
|
|
361
|
-
* @param {number} radius - Rayon des coins
|
|
362
|
-
* @private
|
|
363
|
-
*/
|
|
364
|
-
roundRect(ctx, x, y, width, height, radius) {
|
|
365
|
-
if (radius === 0) {
|
|
366
|
-
ctx.rect(x, y, width, height);
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
ctx.beginPath();
|
|
371
|
-
ctx.moveTo(x + radius, y);
|
|
372
|
-
ctx.lineTo(x + width - radius, y);
|
|
373
|
-
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
|
374
|
-
ctx.lineTo(x + width, y + height - radius);
|
|
375
|
-
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
|
376
|
-
ctx.lineTo(x + radius, y + height);
|
|
377
|
-
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
|
378
|
-
ctx.lineTo(x, y + radius);
|
|
379
|
-
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
380
|
-
ctx.closePath();
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Vérifie si un point est dans les limites
|
|
385
|
-
* @param {number} x - Coordonnée X
|
|
386
|
-
* @param {number} y - Coordonnée Y
|
|
387
|
-
* @returns {boolean} True si le point est dans la vue
|
|
388
|
-
*/
|
|
389
|
-
isPointInside(x, y) {
|
|
390
|
-
return x >= this.x &&
|
|
391
|
-
x <= this.x + this.width &&
|
|
392
|
-
y >= this.y &&
|
|
393
|
-
y <= this.y + this.height;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
/**
|
|
397
|
-
* Définit le niveau d'élévation
|
|
398
|
-
* @param {number} elevation - Nouveau niveau d'élévation (0-5)
|
|
399
|
-
*/
|
|
400
|
-
setElevation(elevation) {
|
|
401
|
-
this.elevation = Math.max(0, Math.min(elevation, 5));
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Augmente le niveau d'élévation
|
|
406
|
-
*/
|
|
407
|
-
raise() {
|
|
408
|
-
this.setElevation(this.elevation + 1);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Réduit le niveau d'élévation
|
|
413
|
-
*/
|
|
414
|
-
lower() {
|
|
415
|
-
this.setElevation(this.elevation - 1);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Définit l'espacement entre enfants
|
|
420
|
-
* @param {number} gap - Nouvel espacement
|
|
421
|
-
*/
|
|
422
|
-
setGap(gap) {
|
|
423
|
-
this.gap = Math.max(0, gap);
|
|
424
|
-
if (this.autoLayout) this.layout();
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* Définit le padding
|
|
429
|
-
* @param {number} padding - Nouveau padding
|
|
430
|
-
*/
|
|
431
|
-
setPadding(padding) {
|
|
432
|
-
this.padding = Math.max(0, padding);
|
|
433
|
-
if (this.autoLayout) this.layout();
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Définit la direction du layout
|
|
438
|
-
* @param {string} direction - 'column' ou 'row'
|
|
439
|
-
*/
|
|
440
|
-
setDirection(direction) {
|
|
441
|
-
if (direction === 'column' || direction === 'row') {
|
|
442
|
-
this.direction = direction;
|
|
443
|
-
if (this.autoLayout) this.layout();
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/**
|
|
448
|
-
* Définit l'alignement
|
|
449
|
-
* @param {string} align - 'start', 'center', 'end' ou 'stretch'
|
|
450
|
-
*/
|
|
451
|
-
setAlign(align) {
|
|
452
|
-
if (['start', 'center', 'end', 'stretch'].includes(align)) {
|
|
453
|
-
this.align = align;
|
|
454
|
-
if (this.autoLayout) this.layout();
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* Calcule la hauteur totale nécessaire pour contenir tous les enfants
|
|
460
|
-
* @returns {number} Hauteur totale nécessaire
|
|
461
|
-
*/
|
|
462
|
-
getTotalHeight() {
|
|
463
|
-
if (this.children.length === 0) return 0;
|
|
464
|
-
|
|
465
|
-
if (this.direction === 'column') {
|
|
466
|
-
const totalChildrenHeight = this.children.reduce((sum, child) => sum + child.height, 0);
|
|
467
|
-
const totalGapHeight = this.gap * Math.max(0, this.children.length - 1);
|
|
468
|
-
return totalChildrenHeight + totalGapHeight + (this.padding * 2);
|
|
469
|
-
}
|
|
470
|
-
return this.height;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
* Calcule la largeur totale nécessaire pour contenir tous les enfants
|
|
475
|
-
* @returns {number} Largeur totale nécessaire
|
|
476
|
-
*/
|
|
477
|
-
getTotalWidth() {
|
|
478
|
-
if (this.children.length === 0) return 0;
|
|
479
|
-
|
|
480
|
-
if (this.direction === 'row') {
|
|
481
|
-
const totalChildrenWidth = this.children.reduce((sum, child) => sum + child.width, 0);
|
|
482
|
-
const totalGapWidth = this.gap * Math.max(0, this.children.length - 1);
|
|
483
|
-
return totalChildrenWidth + totalGapWidth + (this.padding * 2);
|
|
484
|
-
}
|
|
485
|
-
return this.width;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* Ajuste automatiquement la hauteur de la carte pour contenir tous les enfants
|
|
490
|
-
*/
|
|
491
|
-
fitHeight() {
|
|
492
|
-
if (this.direction === 'column') {
|
|
493
|
-
this.height = this.getTotalHeight();
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Ajuste automatiquement la largeur de la carte pour contenir tous les enfants
|
|
499
|
-
*/
|
|
500
|
-
fitWidth() {
|
|
501
|
-
if (this.direction === 'row') {
|
|
502
|
-
this.width = this.getTotalWidth();
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* Ajuste automatiquement les dimensions de la carte pour contenir tous les enfants
|
|
508
|
-
*/
|
|
509
|
-
fitSize() {
|
|
510
|
-
this.fitWidth();
|
|
511
|
-
this.fitHeight();
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
/**
|
|
515
|
-
* Met à jour les dimensions et relayout si autoLayout est activé
|
|
516
|
-
* @param {number} width - Nouvelle largeur
|
|
517
|
-
* @param {number} height - Nouvelle hauteur
|
|
518
|
-
*/
|
|
519
|
-
setSize(width, height) {
|
|
520
|
-
super.setSize(width, height);
|
|
521
|
-
if (this.autoLayout) this.layout();
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/**
|
|
525
|
-
* Obtient la position relative d'un enfant
|
|
526
|
-
* @param {Component} child - L'enfant
|
|
527
|
-
* @returns {Object|null} Position relative {x, y} ou null si non trouvé
|
|
528
|
-
*/
|
|
529
|
-
getChildPosition(child) {
|
|
530
|
-
return this.childPositions.get(child) || null;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
export default Card;
|