canvasframework 0.6.3 → 0.7.1
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 +159 -99
- package/components/Avatar.js +142 -143
- package/components/BottomSheet.js +124 -106
- package/components/Button.js +207 -254
- package/components/Checkbox.js +98 -91
- package/components/Chip.js +137 -106
- package/components/Dialog.js +161 -146
- package/components/FAB.js +122 -195
- package/components/Switch.js +146 -112
- package/components/Text.js +104 -103
- package/components/Toast.js +132 -156
- package/components/VirtualList.js +88 -59
- package/core/CanvasFramework.js +115 -44
- package/core/CanvasUtils.js +141 -0
- package/core/Component.js +124 -66
- package/core/ThemeManager.js +162 -176
- package/core/WebGLCanvasAdapter.js +88 -39
- package/package.json +1 -1
package/components/Switch.js
CHANGED
|
@@ -1,146 +1,180 @@
|
|
|
1
1
|
import Component from '../core/Component.js';
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
|
-
* Interrupteur (toggle)
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* @property {Function} onChange - Callback au changement
|
|
4
|
+
* Interrupteur (toggle) — Material et Cupertino.
|
|
5
|
+
*
|
|
6
|
+
* Corrections par rapport à la version précédente :
|
|
7
|
+
* - setInterval remplacé par requestAnimationFrame (plus précis, respecte le display rate)
|
|
8
|
+
* - `adjustedY` mort supprimé
|
|
9
|
+
* - Support `disabled` ajouté
|
|
10
|
+
* - destroy() annule le RAF en cours
|
|
11
11
|
*/
|
|
12
12
|
class Switch extends Component {
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
* @param {
|
|
16
|
-
* @param {
|
|
17
|
-
* @param {boolean} [options.
|
|
18
|
-
* @param {Function} [options.onChange]
|
|
14
|
+
* @param {CanvasFramework} framework
|
|
15
|
+
* @param {Object} [options={}]
|
|
16
|
+
* @param {boolean} [options.checked=false]
|
|
17
|
+
* @param {boolean} [options.disabled=false]
|
|
18
|
+
* @param {Function} [options.onChange]
|
|
19
19
|
*/
|
|
20
20
|
constructor(framework, options = {}) {
|
|
21
21
|
super(framework, options);
|
|
22
|
-
|
|
22
|
+
|
|
23
|
+
this.checked = options.checked || false;
|
|
24
|
+
this.disabled = options.disabled || false;
|
|
23
25
|
this.platform = framework.platform;
|
|
24
|
-
|
|
26
|
+
|
|
27
|
+
// Dimensions fixes Material / Cupertino
|
|
28
|
+
this.width = 51;
|
|
25
29
|
this.height = 31;
|
|
30
|
+
|
|
26
31
|
this.onChange = options.onChange;
|
|
32
|
+
|
|
33
|
+
// Progression d'animation [0 = off, 1 = on]
|
|
27
34
|
this.animProgress = this.checked ? 1 : 0;
|
|
28
|
-
this.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
this.onClick = this.
|
|
35
|
+
this._animating = false;
|
|
36
|
+
this._rafId = null;
|
|
37
|
+
|
|
38
|
+
this.onClick = this._handleClick.bind(this);
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
// ─────────────────────────────────────────
|
|
42
|
+
// INTERACTIONS
|
|
43
|
+
// ─────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
/** @private */
|
|
46
|
+
_handleClick() {
|
|
47
|
+
if (this.disabled) return;
|
|
48
|
+
|
|
40
49
|
this.checked = !this.checked;
|
|
41
|
-
|
|
42
|
-
this.
|
|
50
|
+
this.onChange?.(this.checked);
|
|
51
|
+
this._startAnimation();
|
|
43
52
|
}
|
|
44
53
|
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (this.isAnimating) return;
|
|
51
|
-
|
|
52
|
-
this.isAnimating = true;
|
|
54
|
+
/** @private */
|
|
55
|
+
_startAnimation() {
|
|
56
|
+
if (this._animating) return;
|
|
57
|
+
this._animating = true;
|
|
58
|
+
|
|
53
59
|
const target = this.checked ? 1 : 0;
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
60
|
+
|
|
61
|
+
const step = () => {
|
|
62
|
+
if (this._destroyed) { this._rafId = null; this._animating = false; return; }
|
|
63
|
+
|
|
64
|
+
const diff = target - this.animProgress;
|
|
65
|
+
|
|
66
|
+
if (Math.abs(diff) < 0.01) {
|
|
57
67
|
this.animProgress = target;
|
|
58
|
-
|
|
59
|
-
this.
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
this._animating = false;
|
|
69
|
+
this._rafId = null;
|
|
70
|
+
this.markDirty();
|
|
71
|
+
return;
|
|
62
72
|
}
|
|
63
|
-
|
|
73
|
+
|
|
74
|
+
// Lerp fluide (~150 ms pour un écran 60 Hz)
|
|
75
|
+
this.animProgress += diff * 0.18;
|
|
76
|
+
this.markDirty();
|
|
77
|
+
this._rafId = requestAnimationFrame(step);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
this._rafId = requestAnimationFrame(step);
|
|
64
81
|
}
|
|
65
82
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
83
|
+
// ─────────────────────────────────────────
|
|
84
|
+
// DESSIN
|
|
85
|
+
// ─────────────────────────────────────────
|
|
86
|
+
|
|
70
87
|
draw(ctx) {
|
|
71
88
|
ctx.save();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
89
|
+
|
|
90
|
+
if (this.disabled) ctx.globalAlpha = 0.38;
|
|
91
|
+
|
|
76
92
|
if (this.platform === 'material') {
|
|
77
|
-
|
|
78
|
-
const trackColor = this.checked ? 'rgba(98, 0, 238, 0.5)' : 'rgba(0, 0, 0, 0.38)';
|
|
79
|
-
const thumbColor = this.checked ? '#6200EE' : '#FAFAFA';
|
|
80
|
-
|
|
81
|
-
// Track
|
|
82
|
-
ctx.fillStyle = trackColor;
|
|
83
|
-
ctx.beginPath();
|
|
84
|
-
ctx.arc(this.x + 15.5, adjustedY + 15.5, 7, Math.PI / 2, Math.PI * 1.5);
|
|
85
|
-
ctx.arc(this.x + 35.5, adjustedY + 15.5, 7, Math.PI * 1.5, Math.PI / 2);
|
|
86
|
-
ctx.closePath();
|
|
87
|
-
ctx.fill();
|
|
88
|
-
|
|
89
|
-
// Thumb
|
|
90
|
-
const thumbX = this.x + 15.5 + (this.animProgress * 20);
|
|
91
|
-
ctx.fillStyle = thumbColor;
|
|
92
|
-
ctx.beginPath();
|
|
93
|
-
ctx.arc(thumbX, adjustedY + 15.5, 10, 0, Math.PI * 2);
|
|
94
|
-
ctx.fill();
|
|
95
|
-
|
|
96
|
-
// Shadow for unchecked state
|
|
97
|
-
if (!this.checked) {
|
|
98
|
-
ctx.strokeStyle = 'rgba(0, 0, 0, 0.12)';
|
|
99
|
-
ctx.lineWidth = 1;
|
|
100
|
-
ctx.stroke();
|
|
101
|
-
}
|
|
93
|
+
this._drawMaterial(ctx);
|
|
102
94
|
} else {
|
|
103
|
-
|
|
104
|
-
const bgColor = this.checked ? '#34C759' : '#E9E9EA';
|
|
105
|
-
|
|
106
|
-
// Track
|
|
107
|
-
ctx.fillStyle = bgColor;
|
|
108
|
-
ctx.beginPath();
|
|
109
|
-
ctx.arc(this.x + 15.5, adjustedY + 15.5, 15.5, Math.PI / 2, Math.PI * 1.5);
|
|
110
|
-
ctx.arc(this.x + 35.5, adjustedY + 15.5, 15.5, Math.PI * 1.5, Math.PI / 2);
|
|
111
|
-
ctx.closePath();
|
|
112
|
-
ctx.fill();
|
|
113
|
-
|
|
114
|
-
// Thumb with shadow
|
|
115
|
-
const thumbX = this.x + 15.5 + (this.animProgress * 20);
|
|
116
|
-
ctx.fillStyle = '#FFFFFF';
|
|
117
|
-
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
|
|
118
|
-
ctx.shadowBlur = 4;
|
|
119
|
-
ctx.shadowOffsetY = 2;
|
|
120
|
-
ctx.beginPath();
|
|
121
|
-
ctx.arc(thumbX, adjustedY + 15.5, 13.5, 0, Math.PI * 2);
|
|
122
|
-
ctx.fill();
|
|
123
|
-
|
|
124
|
-
// Reset shadow
|
|
125
|
-
ctx.shadowColor = 'transparent';
|
|
126
|
-
ctx.shadowBlur = 0;
|
|
127
|
-
ctx.shadowOffsetY = 0;
|
|
95
|
+
this._drawCupertino(ctx);
|
|
128
96
|
}
|
|
129
|
-
|
|
97
|
+
|
|
130
98
|
ctx.restore();
|
|
131
99
|
}
|
|
132
100
|
|
|
133
|
-
/**
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
101
|
+
/** @private */
|
|
102
|
+
_drawMaterial(ctx) {
|
|
103
|
+
const trackColor = this.checked ? 'rgba(98,0,238,0.5)' : 'rgba(0,0,0,0.38)';
|
|
104
|
+
const thumbColor = this.checked ? '#6200EE' : '#FAFAFA';
|
|
105
|
+
|
|
106
|
+
// Track
|
|
107
|
+
ctx.fillStyle = trackColor;
|
|
108
|
+
ctx.beginPath();
|
|
109
|
+
ctx.arc(this.x + 15.5, this.y + 15.5, 7, Math.PI / 2, Math.PI * 1.5);
|
|
110
|
+
ctx.arc(this.x + 35.5, this.y + 15.5, 7, Math.PI * 1.5, Math.PI / 2);
|
|
111
|
+
ctx.closePath();
|
|
112
|
+
ctx.fill();
|
|
113
|
+
|
|
114
|
+
// Thumb
|
|
115
|
+
const thumbX = this.x + 15.5 + this.animProgress * 20;
|
|
116
|
+
ctx.fillStyle = thumbColor;
|
|
117
|
+
ctx.beginPath();
|
|
118
|
+
ctx.arc(thumbX, this.y + 15.5, 10, 0, Math.PI * 2);
|
|
119
|
+
ctx.fill();
|
|
120
|
+
|
|
121
|
+
if (!this.checked) {
|
|
122
|
+
ctx.strokeStyle = 'rgba(0,0,0,0.12)';
|
|
123
|
+
ctx.lineWidth = 1;
|
|
124
|
+
ctx.stroke();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** @private */
|
|
129
|
+
_drawCupertino(ctx) {
|
|
130
|
+
const bgColor = this.checked ? '#34C759' : '#E9E9EA';
|
|
131
|
+
|
|
132
|
+
// Track
|
|
133
|
+
ctx.fillStyle = bgColor;
|
|
134
|
+
ctx.beginPath();
|
|
135
|
+
ctx.arc(this.x + 15.5, this.y + 15.5, 15.5, Math.PI / 2, Math.PI * 1.5);
|
|
136
|
+
ctx.arc(this.x + 35.5, this.y + 15.5, 15.5, Math.PI * 1.5, Math.PI / 2);
|
|
137
|
+
ctx.closePath();
|
|
138
|
+
ctx.fill();
|
|
139
|
+
|
|
140
|
+
// Thumb avec ombre
|
|
141
|
+
const thumbX = this.x + 15.5 + this.animProgress * 20;
|
|
142
|
+
ctx.fillStyle = '#FFFFFF';
|
|
143
|
+
ctx.shadowColor = 'rgba(0,0,0,0.3)';
|
|
144
|
+
ctx.shadowBlur = 4;
|
|
145
|
+
ctx.shadowOffsetY = 2;
|
|
146
|
+
ctx.beginPath();
|
|
147
|
+
ctx.arc(thumbX, this.y + 15.5, 13.5, 0, Math.PI * 2);
|
|
148
|
+
ctx.fill();
|
|
149
|
+
|
|
150
|
+
// Reset ombre
|
|
151
|
+
ctx.shadowColor = 'transparent';
|
|
152
|
+
ctx.shadowBlur = 0;
|
|
153
|
+
ctx.shadowOffsetY = 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ─────────────────────────────────────────
|
|
157
|
+
// HIT TEST
|
|
158
|
+
// ─────────────────────────────────────────
|
|
159
|
+
|
|
139
160
|
isPointInside(x, y) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
161
|
+
if (this.disabled) return false;
|
|
162
|
+
return (
|
|
163
|
+
x >= this.x && x <= this.x + this.width &&
|
|
164
|
+
y >= this.y && y <= this.y + this.height
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ─────────────────────────────────────────
|
|
169
|
+
// DESTROY
|
|
170
|
+
// ─────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
destroy() {
|
|
173
|
+
if (this._rafId) {
|
|
174
|
+
cancelAnimationFrame(this._rafId);
|
|
175
|
+
this._rafId = null;
|
|
176
|
+
}
|
|
177
|
+
super.destroy();
|
|
144
178
|
}
|
|
145
179
|
}
|
|
146
180
|
|
package/components/Text.js
CHANGED
|
@@ -1,141 +1,142 @@
|
|
|
1
1
|
import Component from '../core/Component.js';
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* @property {string} align - Alignement ('left', 'center', 'right')
|
|
10
|
-
* @property {boolean} bold - Gras
|
|
11
|
-
* @property {number|null} maxWidth - Largeur maximale
|
|
12
|
-
* @property {boolean} wrap - Retour à la ligne
|
|
13
|
-
* @property {number} lineHeight - Hauteur de ligne
|
|
14
|
-
* @property {string[]|null} wrappedLines - Lignes après wrap
|
|
4
|
+
* Text — composant texte avec support du wrap et de la troncature.
|
|
5
|
+
*
|
|
6
|
+
* Corrections :
|
|
7
|
+
* - Calcul de x aligné pour tous les modes (center / right / left)
|
|
8
|
+
* - isPointInside retourne false sauf si onClick est défini
|
|
9
|
+
* - destroy() propre
|
|
15
10
|
*/
|
|
16
11
|
class Text extends Component {
|
|
17
12
|
/**
|
|
18
|
-
*
|
|
19
|
-
* @param {
|
|
20
|
-
* @param {
|
|
21
|
-
* @param {
|
|
22
|
-
* @param {
|
|
23
|
-
* @param {string}
|
|
24
|
-
* @param {
|
|
25
|
-
* @param {
|
|
26
|
-
* @param {
|
|
27
|
-
* @param {
|
|
28
|
-
* @param {number} [options.lineHeight] - Hauteur de ligne
|
|
13
|
+
* @param {CanvasFramework} framework
|
|
14
|
+
* @param {Object} [options={}]
|
|
15
|
+
* @param {string} [options.text='']
|
|
16
|
+
* @param {number} [options.fontSize=16]
|
|
17
|
+
* @param {string} [options.color='#000000']
|
|
18
|
+
* @param {string} [options.align='left'] - 'left' | 'center' | 'right'
|
|
19
|
+
* @param {boolean} [options.bold=false]
|
|
20
|
+
* @param {number} [options.maxWidth] - Largeur maximale (troncature ou wrap)
|
|
21
|
+
* @param {boolean} [options.wrap=false] - Retour à la ligne automatique
|
|
22
|
+
* @param {number} [options.lineHeight] - Hauteur de ligne (défaut : fontSize × 1.2)
|
|
29
23
|
*/
|
|
30
24
|
constructor(framework, options = {}) {
|
|
31
25
|
super(framework, options);
|
|
32
|
-
|
|
33
|
-
this.
|
|
34
|
-
this.
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
37
|
-
this.
|
|
38
|
-
this.
|
|
26
|
+
|
|
27
|
+
this.text = options.text || '';
|
|
28
|
+
this.fontSize = options.fontSize || 16;
|
|
29
|
+
this.color = options.color || '#000000';
|
|
30
|
+
this.align = options.align || 'left';
|
|
31
|
+
this.bold = options.bold || false;
|
|
32
|
+
this.maxWidth = options.maxWidth || null;
|
|
33
|
+
this.wrap = options.wrap || false;
|
|
39
34
|
this.lineHeight = options.lineHeight || this.fontSize * 1.2;
|
|
40
|
-
|
|
41
|
-
// Calculer la hauteur en fonction du texte
|
|
42
|
-
if (this.wrap && this.maxWidth && this.text) {
|
|
43
|
-
this.calculateWrappedHeight();
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Calcule la hauteur avec wrap
|
|
49
|
-
* @private
|
|
50
|
-
*/
|
|
51
|
-
calculateWrappedHeight() {
|
|
52
|
-
// Cette méthode sera appelée dans draw quand on a le contexte
|
|
53
|
-
// Pour l'instant, on initialise juste
|
|
54
|
-
this.wrappedLines = null;
|
|
55
35
|
}
|
|
56
36
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
37
|
+
// ─────────────────────────────────────────
|
|
38
|
+
// DESSIN
|
|
39
|
+
// ─────────────────────────────────────────
|
|
40
|
+
|
|
61
41
|
draw(ctx) {
|
|
62
42
|
ctx.save();
|
|
63
|
-
|
|
64
|
-
ctx.
|
|
65
|
-
ctx.
|
|
43
|
+
|
|
44
|
+
ctx.fillStyle = this.color;
|
|
45
|
+
ctx.font = `${this.bold ? 'bold ' : ''}${this.fontSize}px -apple-system, BlinkMacSystemFont, 'Roboto', sans-serif`;
|
|
46
|
+
ctx.textAlign = this.align;
|
|
66
47
|
ctx.textBaseline = 'top';
|
|
67
|
-
|
|
48
|
+
|
|
49
|
+
// Calcul de l'ancre X selon l'alignement
|
|
50
|
+
const contentWidth = this.maxWidth || this.width;
|
|
51
|
+
let anchorX;
|
|
52
|
+
if (this.align === 'center') {
|
|
53
|
+
anchorX = this.x + contentWidth / 2;
|
|
54
|
+
} else if (this.align === 'right') {
|
|
55
|
+
anchorX = this.x + contentWidth;
|
|
56
|
+
} else {
|
|
57
|
+
anchorX = this.x;
|
|
58
|
+
}
|
|
59
|
+
|
|
68
60
|
let lines = [this.text];
|
|
69
|
-
|
|
70
|
-
// Si wrap est activé et on a une largeur max, on divise le texte
|
|
61
|
+
|
|
71
62
|
if (this.wrap && this.maxWidth && this.text) {
|
|
72
|
-
lines = this.
|
|
63
|
+
lines = this._wrapText(ctx, this.text, this.maxWidth);
|
|
73
64
|
} else if (this.maxWidth && this.text) {
|
|
74
|
-
|
|
75
|
-
const ellipsis = '...';
|
|
76
|
-
let text = this.text;
|
|
77
|
-
while (ctx.measureText(text).width > this.maxWidth && text.length > 3) {
|
|
78
|
-
text = text.substring(0, text.length - 1);
|
|
79
|
-
}
|
|
80
|
-
if (text !== this.text && text.length > 3) {
|
|
81
|
-
text = text.substring(0, text.length - 3) + ellipsis;
|
|
82
|
-
}
|
|
83
|
-
lines = [text];
|
|
65
|
+
lines = [this._truncateText(ctx, this.text, this.maxWidth)];
|
|
84
66
|
}
|
|
85
|
-
|
|
86
|
-
// Calculer la position x en fonction de l'alignement
|
|
87
|
-
const x = this.align === 'center' ? this.x + (this.maxWidth || this.width) / 2 :
|
|
88
|
-
this.align === 'right' ? this.x + (this.maxWidth || this.width) : this.x;
|
|
89
|
-
|
|
90
|
-
// Dessiner chaque ligne
|
|
67
|
+
|
|
91
68
|
for (let i = 0; i < lines.length; i++) {
|
|
92
|
-
|
|
93
|
-
const y = this.y + (i * this.lineHeight);
|
|
94
|
-
ctx.fillText(line, x, y);
|
|
69
|
+
ctx.fillText(lines[i], anchorX, this.y + i * this.lineHeight);
|
|
95
70
|
}
|
|
96
|
-
|
|
97
|
-
//
|
|
71
|
+
|
|
72
|
+
// Mise à jour de la hauteur si multilignes
|
|
98
73
|
if (lines.length > 1) {
|
|
99
74
|
this.height = lines.length * this.lineHeight;
|
|
100
75
|
}
|
|
101
|
-
|
|
76
|
+
|
|
102
77
|
ctx.restore();
|
|
103
78
|
}
|
|
104
79
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const lines = [];
|
|
116
|
-
let currentLine = words[0];
|
|
117
|
-
|
|
80
|
+
// ─────────────────────────────────────────
|
|
81
|
+
// UTILITAIRES
|
|
82
|
+
// ─────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
/** @private */
|
|
85
|
+
_wrapText(ctx, text, maxWidth) {
|
|
86
|
+
const words = text.split(' ');
|
|
87
|
+
const lines = [];
|
|
88
|
+
let current = words[0] || '';
|
|
89
|
+
|
|
118
90
|
for (let i = 1; i < words.length; i++) {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
currentLine += " " + word;
|
|
91
|
+
const test = `${current} ${words[i]}`;
|
|
92
|
+
if (ctx.measureText(test).width <= maxWidth) {
|
|
93
|
+
current = test;
|
|
123
94
|
} else {
|
|
124
|
-
lines.push(
|
|
125
|
-
|
|
95
|
+
lines.push(current);
|
|
96
|
+
current = words[i];
|
|
126
97
|
}
|
|
127
98
|
}
|
|
128
|
-
lines.push(
|
|
99
|
+
lines.push(current);
|
|
129
100
|
return lines;
|
|
130
101
|
}
|
|
131
102
|
|
|
103
|
+
/** @private */
|
|
104
|
+
_truncateText(ctx, text, maxWidth) {
|
|
105
|
+
if (ctx.measureText(text).width <= maxWidth) return text;
|
|
106
|
+
|
|
107
|
+
const ellipsis = '...';
|
|
108
|
+
let truncated = text;
|
|
109
|
+
|
|
110
|
+
while (truncated.length > 0 && ctx.measureText(truncated + ellipsis).width > maxWidth) {
|
|
111
|
+
truncated = truncated.slice(0, -1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return truncated + ellipsis;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ─────────────────────────────────────────
|
|
118
|
+
// HIT TEST
|
|
119
|
+
// ─────────────────────────────────────────
|
|
120
|
+
|
|
132
121
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
122
|
+
* Retourne false par défaut (le texte n'est pas cliquable).
|
|
123
|
+
* Retourne true uniquement si un onClick a été défini.
|
|
135
124
|
*/
|
|
136
|
-
isPointInside() {
|
|
137
|
-
return false;
|
|
125
|
+
isPointInside(x, y) {
|
|
126
|
+
if (!this.onClick) return false;
|
|
127
|
+
return (
|
|
128
|
+
x >= this.x && x <= this.x + this.width &&
|
|
129
|
+
y >= this.y && y <= this.y + this.height
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ─────────────────────────────────────────
|
|
134
|
+
// DESTROY
|
|
135
|
+
// ─────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
destroy() {
|
|
138
|
+
super.destroy();
|
|
138
139
|
}
|
|
139
140
|
}
|
|
140
141
|
|
|
141
|
-
export default Text;
|
|
142
|
+
export default Text;
|