canvasframework 0.3.6
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/README.md +554 -0
- package/components/Accordion.js +252 -0
- package/components/AndroidDatePickerDialog.js +398 -0
- package/components/AppBar.js +225 -0
- package/components/Avatar.js +202 -0
- package/components/BottomNavigationBar.js +205 -0
- package/components/BottomSheet.js +374 -0
- package/components/Button.js +225 -0
- package/components/Card.js +193 -0
- package/components/Checkbox.js +180 -0
- package/components/Chip.js +212 -0
- package/components/CircularProgress.js +143 -0
- package/components/ContextMenu.js +116 -0
- package/components/DatePicker.js +257 -0
- package/components/Dialog.js +367 -0
- package/components/Divider.js +125 -0
- package/components/Drawer.js +261 -0
- package/components/FAB.js +270 -0
- package/components/FileUpload.js +315 -0
- package/components/IOSDatePickerWheel.js +268 -0
- package/components/ImageCarousel.js +193 -0
- package/components/ImageComponent.js +223 -0
- package/components/Input.js +309 -0
- package/components/List.js +94 -0
- package/components/ListItem.js +223 -0
- package/components/Modal.js +364 -0
- package/components/MultiSelectDialog.js +206 -0
- package/components/NumberInput.js +271 -0
- package/components/ProgressBar.js +88 -0
- package/components/RadioButton.js +142 -0
- package/components/SearchInput.js +315 -0
- package/components/SegmentedControl.js +202 -0
- package/components/Select.js +199 -0
- package/components/SelectDialog.js +255 -0
- package/components/Slider.js +113 -0
- package/components/Snackbar.js +243 -0
- package/components/Stepper.js +281 -0
- package/components/SwipeableListItem.js +179 -0
- package/components/Switch.js +147 -0
- package/components/Table.js +492 -0
- package/components/Tabs.js +125 -0
- package/components/Text.js +141 -0
- package/components/TextField.js +331 -0
- package/components/Toast.js +236 -0
- package/components/TreeView.js +420 -0
- package/components/Video.js +397 -0
- package/components/View.js +140 -0
- package/components/VirtualList.js +120 -0
- package/core/CanvasFramework.js +1271 -0
- package/core/CanvasWork.js +32 -0
- package/core/Component.js +153 -0
- package/core/LogicWorker.js +25 -0
- package/core/WebGLCanvasAdapter.js +1369 -0
- package/features/Column.js +43 -0
- package/features/Grid.js +47 -0
- package/features/LayoutComponent.js +43 -0
- package/features/OpenStreetMap.js +310 -0
- package/features/Positioned.js +33 -0
- package/features/PullToRefresh.js +328 -0
- package/features/Row.js +40 -0
- package/features/SignaturePad.js +257 -0
- package/features/Skeleton.js +84 -0
- package/features/Stack.js +21 -0
- package/index.js +101 -0
- package/manager/AccessibilityManager.js +107 -0
- package/manager/ErrorHandler.js +59 -0
- package/manager/FeatureFlags.js +60 -0
- package/manager/MemoryManager.js +107 -0
- package/manager/PerformanceMonitor.js +84 -0
- package/manager/SecurityManager.js +54 -0
- package/package.json +28 -0
- package/utils/AnimationEngine.js +428 -0
- package/utils/DataStore.js +403 -0
- package/utils/EventBus.js +407 -0
- package/utils/FetchClient.js +74 -0
- package/utils/FormValidator.js +355 -0
- package/utils/GeoLocationService.js +62 -0
- package/utils/I18n.js +207 -0
- package/utils/IndexedDBManager.js +273 -0
- package/utils/OfflineSyncManager.js +342 -0
- package/utils/QueryBuilder.js +478 -0
- package/utils/SafeArea.js +64 -0
- package/utils/SecureStorage.js +289 -0
- package/utils/StateManager.js +207 -0
- package/utils/WebSocketClient.js +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
<img width="1536" height="1024" alt="image" src="https://github.com/user-attachments/assets/feae14e9-8d68-41f9-9e33-e444f1a6f360" />
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Canvas UI Engine (working name)
|
|
5
|
+
|
|
6
|
+
> **Canvas-based UI Engine for Mobile & Embedded Apps**
|
|
7
|
+
> A high-performance UI engine rendered with Canvas/WebGL, running inside a WebView runtime (Capacitor / Cordova), without DOM, HTML or CSS.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 🚀 Overview
|
|
12
|
+
|
|
13
|
+
**Canvas UI Engine** is a **low-level UI engine** that renders the entire user interface using **Canvas 2D or WebGL**, instead of the DOM.
|
|
14
|
+
|
|
15
|
+
Although it runs inside a WebView, it **does not rely on HTML, CSS, or the browser layout engine**.
|
|
16
|
+
The WebView is used **only as a JavaScript runtime**, not as a UI system.
|
|
17
|
+
|
|
18
|
+
> Think **Flutter’s rendering model**, but implemented in **JavaScript**, running inside a WebView.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## ❌ What this engine is NOT
|
|
23
|
+
|
|
24
|
+
- ❌ Not a web framework
|
|
25
|
+
- ❌ Not DOM-based
|
|
26
|
+
- ❌ Not Ionic / React / Vue
|
|
27
|
+
- ❌ Not HTML/CSS driven
|
|
28
|
+
- ❌ Not designed for SEO or websites
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## ✅ What this engine IS
|
|
33
|
+
|
|
34
|
+
- ✅ A **Canvas-first UI engine**
|
|
35
|
+
- ✅ A **custom rendering pipeline**
|
|
36
|
+
- ✅ A **custom layout system**
|
|
37
|
+
- ✅ A **custom input & gesture system**
|
|
38
|
+
- ✅ A **deterministic, app-like UI**
|
|
39
|
+
- ✅ Designed for **mobile & embedded apps**
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 🧠 Architecture
|
|
44
|
+
|
|
45
|
+
<img width="1536" height="1024" alt="image" src="https://github.com/user-attachments/assets/da04de5b-0223-4241-86bc-ccb6b7ac0eaa" />
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
> **The WebView is only a runtime shell.**
|
|
49
|
+
> **The UI is fully controlled by the engine (no DOM, no HTML layout).**
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
### Key point
|
|
53
|
+
|
|
54
|
+
> **The browser does not manage UI.
|
|
55
|
+
> The engine does.**
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 🎨 Rendering
|
|
60
|
+
|
|
61
|
+
- Canvas 2D (CPU)
|
|
62
|
+
- Optional WebGL backend (GPU accelerated)
|
|
63
|
+
- Device Pixel Ratio aware
|
|
64
|
+
- Dirty-region rendering
|
|
65
|
+
- Scene graph based
|
|
66
|
+
|
|
67
|
+
Rendering performance depends **entirely on the engine**, not on the browser DOM.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 🧩 UI Model
|
|
72
|
+
|
|
73
|
+
- Every component is **drawn**, not mounted
|
|
74
|
+
- No HTML nodes
|
|
75
|
+
- No CSS
|
|
76
|
+
- No reflow
|
|
77
|
+
- No repaint cost from the browser
|
|
78
|
+
|
|
79
|
+
Example components:
|
|
80
|
+
- Button
|
|
81
|
+
- Text
|
|
82
|
+
- Input
|
|
83
|
+
- Slider
|
|
84
|
+
- Switch
|
|
85
|
+
- List / Card
|
|
86
|
+
- Modal / Dialog / BottomSheet
|
|
87
|
+
- Navigation & routing system
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 🧭 Navigation
|
|
92
|
+
|
|
93
|
+
- Internal routing system
|
|
94
|
+
- Stack-based navigation
|
|
95
|
+
- Animated transitions (slide / fade)
|
|
96
|
+
- Independent from browser routing logic
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 🧵 Multithreading
|
|
101
|
+
|
|
102
|
+
- **UI Worker** → layout & scroll inertia
|
|
103
|
+
- **Logic Worker** → business logic
|
|
104
|
+
- Main thread → rendering only
|
|
105
|
+
|
|
106
|
+
This allows smoother UI and better separation of concerns.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 🔌 Native capabilities
|
|
111
|
+
|
|
112
|
+
Native features are accessed via the **host runtime** (Capacitor / Cordova):
|
|
113
|
+
|
|
114
|
+
- Camera
|
|
115
|
+
- Filesystem
|
|
116
|
+
- Secure storage
|
|
117
|
+
- Geolocation
|
|
118
|
+
- WebSocket
|
|
119
|
+
- Network
|
|
120
|
+
|
|
121
|
+
⚠️ Plugins that **require DOM access are not supported**.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 📦 Data & Networking
|
|
126
|
+
|
|
127
|
+
The engine provides its own service layer:
|
|
128
|
+
- Cached fetch service
|
|
129
|
+
- WebSocket manager
|
|
130
|
+
- Geolocation abstraction
|
|
131
|
+
- Offline-first data handling
|
|
132
|
+
|
|
133
|
+
All APIs are **engine-controlled**, not browser-controlled.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 🆚 Comparison
|
|
138
|
+
|
|
139
|
+
### vs React Native
|
|
140
|
+
|
|
141
|
+
| React Native | Canvas UI Engine |
|
|
142
|
+
|-------------|-----------------|
|
|
143
|
+
| Native views | Custom rendering |
|
|
144
|
+
| Bridge overhead | Direct rendering |
|
|
145
|
+
| Platform UI | Engine UI |
|
|
146
|
+
| DOM-like model | Scene graph |
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
### vs Flutter
|
|
151
|
+
|
|
152
|
+
| Flutter | Canvas UI Engine |
|
|
153
|
+
|--------|------------------|
|
|
154
|
+
| Native engine | WebView runtime |
|
|
155
|
+
| Skia | Canvas / WebGL |
|
|
156
|
+
| Dart | JavaScript |
|
|
157
|
+
| Compiled | Interpreted |
|
|
158
|
+
| UI engine | UI engine |
|
|
159
|
+
|
|
160
|
+
👉 **Same architecture philosophy**, different runtime constraints.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## ⚠️ Known limitations
|
|
165
|
+
|
|
166
|
+
- No DOM access
|
|
167
|
+
- No HTML rendering
|
|
168
|
+
- No SEO
|
|
169
|
+
- No accessibility (yet)
|
|
170
|
+
- Text shaping is basic (LTR focused)
|
|
171
|
+
|
|
172
|
+
These are **intentional design decisions**.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 🎯 Target use cases
|
|
177
|
+
|
|
178
|
+
- Mobile applications
|
|
179
|
+
- Embedded systems
|
|
180
|
+
- Kiosk interfaces
|
|
181
|
+
- Medical / industrial apps
|
|
182
|
+
- Offline-first apps
|
|
183
|
+
- UI where **control > compatibility**
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 🛣️ Roadmap (simplified)
|
|
188
|
+
|
|
189
|
+
### Phase 1 – Core engine (current)
|
|
190
|
+
- Canvas/WebGL rendering
|
|
191
|
+
- Input & layout
|
|
192
|
+
- Core components
|
|
193
|
+
|
|
194
|
+
### Phase 2 – Performance & UX
|
|
195
|
+
- Gesture system
|
|
196
|
+
- Animation engine
|
|
197
|
+
- Advanced scrolling
|
|
198
|
+
- Asset caching
|
|
199
|
+
|
|
200
|
+
### Phase 3 – Ecosystem
|
|
201
|
+
- Plugin bridge
|
|
202
|
+
- Devtools
|
|
203
|
+
- Theming system
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 🧪 Philosophy
|
|
208
|
+
|
|
209
|
+
> **The WebView is just a runtime.
|
|
210
|
+
> The UI is owned by the engine.**
|
|
211
|
+
|
|
212
|
+
If the engine is fast → the app is fast.
|
|
213
|
+
If the engine is slow → nothing can save it.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Exemples
|
|
218
|
+
# Exemple – Accordion
|
|
219
|
+
|
|
220
|
+
Afficher des sections d’informations extensibles (FAQ, paramètres, détails).
|
|
221
|
+
```
|
|
222
|
+
import { Accordion } from './framework/index.js';
|
|
223
|
+
|
|
224
|
+
const accordion = new Accordion(app, {
|
|
225
|
+
x: 16,
|
|
226
|
+
y: 80,
|
|
227
|
+
width: app.width - 32,
|
|
228
|
+
title: 'Informations du compte',
|
|
229
|
+
icon: 'ℹ️',
|
|
230
|
+
content: 'Votre compte vous permet de gérer vos préférences, votre sécurité et vos informations personnelles.',
|
|
231
|
+
expanded: false,
|
|
232
|
+
onToggle: (expanded) => {
|
|
233
|
+
console.log('Accordion ouvert ?', expanded);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
app.add(accordion);
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
# Button (Material / Cupertino auto)
|
|
241
|
+
|
|
242
|
+
Action principale (submit, navigation, confirmation).
|
|
243
|
+
```
|
|
244
|
+
import { Button } from './framework/index.js';
|
|
245
|
+
|
|
246
|
+
const button = new Button(app, {
|
|
247
|
+
x: 16,
|
|
248
|
+
y: 300,
|
|
249
|
+
width: app.width - 32,
|
|
250
|
+
height: 48,
|
|
251
|
+
text: 'Continuer',
|
|
252
|
+
elevation: 4
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
button.onClick = () => {
|
|
256
|
+
console.log('Bouton cliqué');
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
app.add(button);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
# Exemple – BottomNavigationBar
|
|
263
|
+
|
|
264
|
+
Navigation principale d’application mobile.
|
|
265
|
+
```
|
|
266
|
+
import { BottomNavigationBar } from './framework/index.js';
|
|
267
|
+
|
|
268
|
+
const bottomNav = new BottomNavigationBar(app, {
|
|
269
|
+
items: [
|
|
270
|
+
{ icon: 'home', label: 'Accueil' },
|
|
271
|
+
{ icon: 'search', label: 'Recherche' },
|
|
272
|
+
{ icon: 'favorite', label: 'Favoris' },
|
|
273
|
+
{ icon: 'person', label: 'Profil' }
|
|
274
|
+
],
|
|
275
|
+
onChange: (index, item) => {
|
|
276
|
+
console.log('Onglet sélectionné:', index, item.label);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
app.add(bottomNav);
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
# Exemple – Dialog (alert / confirmation)
|
|
284
|
+
|
|
285
|
+
Alerte, confirmation, choix utilisateur.
|
|
286
|
+
```
|
|
287
|
+
import { Dialog, Button } from './framework/index.js';
|
|
288
|
+
|
|
289
|
+
const showDialogBtn = new Button(app, {
|
|
290
|
+
x: 16,
|
|
291
|
+
y: 380,
|
|
292
|
+
width: app.width - 32,
|
|
293
|
+
height: 48,
|
|
294
|
+
text: 'Supprimer le compte'
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
showDialogBtn.onClick = () => {
|
|
298
|
+
const dialog = new Dialog(app, {
|
|
299
|
+
title: 'Confirmation',
|
|
300
|
+
message: 'Êtes-vous sûr de vouloir supprimer votre compte ? Cette action est irréversible.',
|
|
301
|
+
buttons: ['Annuler', 'Supprimer'],
|
|
302
|
+
onButtonClick: (index, label) => {
|
|
303
|
+
console.log('Bouton dialog:', label);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
app.add(dialog);
|
|
308
|
+
dialog.show();
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
app.add(showDialogBtn);
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
# Exemple – Combinaison réelle (mini écran)
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
app.add(new Text(app, {
|
|
319
|
+
x: 16,
|
|
320
|
+
y: 24,
|
|
321
|
+
text: 'Paramètres',
|
|
322
|
+
fontSize: 22,
|
|
323
|
+
bold: true
|
|
324
|
+
}));
|
|
325
|
+
|
|
326
|
+
// Accordéon
|
|
327
|
+
app.add(new Accordion(app, {
|
|
328
|
+
x: 16,
|
|
329
|
+
y: 80,
|
|
330
|
+
width: app.width - 32,
|
|
331
|
+
title: 'Sécurité',
|
|
332
|
+
content: 'Changez votre mot de passe, activez la double authentification.'
|
|
333
|
+
}));
|
|
334
|
+
|
|
335
|
+
// Bouton
|
|
336
|
+
app.add(new Button(app, {
|
|
337
|
+
x: 16,
|
|
338
|
+
y: 240,
|
|
339
|
+
width: app.width - 32,
|
|
340
|
+
height: 48,
|
|
341
|
+
text: 'Déconnexion'
|
|
342
|
+
}));
|
|
343
|
+
|
|
344
|
+
// Navigation
|
|
345
|
+
app.add(new BottomNavigationBar(app, {
|
|
346
|
+
items: [
|
|
347
|
+
{ icon: 'home', label: 'Accueil' },
|
|
348
|
+
{ icon: 'settings', label: 'Paramètres' }
|
|
349
|
+
]
|
|
350
|
+
}));
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
# Stack – superposition (équivalent Flutter Stack)
|
|
354
|
+
|
|
355
|
+
Exemple : carte avec image + titre + bouton flottant
|
|
356
|
+
|
|
357
|
+
```
|
|
358
|
+
import Stack from './layout/Stack.js';
|
|
359
|
+
import Text from './components/Text.js';
|
|
360
|
+
import Button from './components/Button.js';
|
|
361
|
+
import Image from './components/Image.js';
|
|
362
|
+
|
|
363
|
+
const card = new Stack(app, {
|
|
364
|
+
x: 16,
|
|
365
|
+
y: 40,
|
|
366
|
+
width: app.width - 32,
|
|
367
|
+
height: 200
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
card.add(new Image(app, {
|
|
371
|
+
src: 'cover.jpg',
|
|
372
|
+
width: card.width,
|
|
373
|
+
height: 200
|
|
374
|
+
}));
|
|
375
|
+
|
|
376
|
+
card.add(new Text(app, {
|
|
377
|
+
x: 16,
|
|
378
|
+
y: 16,
|
|
379
|
+
text: 'Titre de la carte',
|
|
380
|
+
fontSize: 20,
|
|
381
|
+
color: '#FFFFFF'
|
|
382
|
+
}));
|
|
383
|
+
|
|
384
|
+
card.add(new Button(app, {
|
|
385
|
+
x: card.width - 72,
|
|
386
|
+
y: card.height - 56,
|
|
387
|
+
width: 56,
|
|
388
|
+
height: 56,
|
|
389
|
+
text: '+'
|
|
390
|
+
}));
|
|
391
|
+
|
|
392
|
+
app.add(card);
|
|
393
|
+
card.layoutRecursive();
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
# Column – layout vertical (Flutter Column)
|
|
397
|
+
|
|
398
|
+
Exemple : écran de paramètres
|
|
399
|
+
|
|
400
|
+
```
|
|
401
|
+
import Column from './layout/Column.js';
|
|
402
|
+
import Text from './components/Text.js';
|
|
403
|
+
import Button from './components/Button.js';
|
|
404
|
+
|
|
405
|
+
const column = new Column(app, {
|
|
406
|
+
x: 16,
|
|
407
|
+
y: 40,
|
|
408
|
+
width: app.width - 32,
|
|
409
|
+
spacing: 12,
|
|
410
|
+
align: 'stretch'
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
column.add(new Text(app, {
|
|
414
|
+
text: 'Paramètres',
|
|
415
|
+
fontSize: 22,
|
|
416
|
+
height: 32
|
|
417
|
+
}));
|
|
418
|
+
|
|
419
|
+
column.add(new Button(app, {
|
|
420
|
+
height: 48,
|
|
421
|
+
text: 'Compte'
|
|
422
|
+
}));
|
|
423
|
+
|
|
424
|
+
column.add(new Button(app, {
|
|
425
|
+
height: 48,
|
|
426
|
+
text: 'Sécurité'
|
|
427
|
+
}));
|
|
428
|
+
|
|
429
|
+
column.add(new Button(app, {
|
|
430
|
+
height: 48,
|
|
431
|
+
text: 'Notifications'
|
|
432
|
+
}));
|
|
433
|
+
|
|
434
|
+
app.add(column);
|
|
435
|
+
column.layout();
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
# Row – layout horizontal (Flutter Row)
|
|
439
|
+
|
|
440
|
+
Exemple : barre d’actions
|
|
441
|
+
|
|
442
|
+
```
|
|
443
|
+
import Row from './layout/Row.js';
|
|
444
|
+
import Button from './components/Button.js';
|
|
445
|
+
|
|
446
|
+
const actions = new Row(app, {
|
|
447
|
+
x: 16,
|
|
448
|
+
y: 120,
|
|
449
|
+
height: 48,
|
|
450
|
+
spacing: 12,
|
|
451
|
+
align: 'center'
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
actions.add(new Button(app, {
|
|
455
|
+
width: 100,
|
|
456
|
+
height: 40,
|
|
457
|
+
text: 'Annuler'
|
|
458
|
+
}));
|
|
459
|
+
|
|
460
|
+
actions.add(new Button(app, {
|
|
461
|
+
width: 120,
|
|
462
|
+
height: 40,
|
|
463
|
+
text: 'Valider'
|
|
464
|
+
}));
|
|
465
|
+
|
|
466
|
+
app.add(actions);
|
|
467
|
+
actions.layout();
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
# Grid – grille adaptative (Flutter GridView.count)
|
|
471
|
+
|
|
472
|
+
Exemple : grille d’options
|
|
473
|
+
|
|
474
|
+
```
|
|
475
|
+
import Grid from './layout/Grid.js';
|
|
476
|
+
import Card from './components/Card.js';
|
|
477
|
+
|
|
478
|
+
const grid = new Grid(app, {
|
|
479
|
+
x: 16,
|
|
480
|
+
y: 200,
|
|
481
|
+
width: app.width - 32,
|
|
482
|
+
columns: 3,
|
|
483
|
+
spacing: 12
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
for (let i = 0; i < 6; i++) {
|
|
487
|
+
grid.add(new Card(app, {
|
|
488
|
+
height: 100,
|
|
489
|
+
title: `Item ${i + 1}`
|
|
490
|
+
}));
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
app.add(grid);
|
|
494
|
+
grid.layout();
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
# Composition réelle (comme Flutter 😏)
|
|
498
|
+
|
|
499
|
+
```
|
|
500
|
+
const screen = new Column(app, {
|
|
501
|
+
x: 0,
|
|
502
|
+
y: 0,
|
|
503
|
+
width: app.width,
|
|
504
|
+
spacing: 24,
|
|
505
|
+
align: 'stretch'
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
// Header
|
|
509
|
+
screen.add(new Text(app, {
|
|
510
|
+
text: 'Accueil',
|
|
511
|
+
fontSize: 24,
|
|
512
|
+
height: 40
|
|
513
|
+
}));
|
|
514
|
+
|
|
515
|
+
// Hero
|
|
516
|
+
const hero = new Stack(app, {
|
|
517
|
+
width: app.width,
|
|
518
|
+
height: 180
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
hero.add(new Image(app, {
|
|
522
|
+
src: 'hero.jpg',
|
|
523
|
+
width: app.width,
|
|
524
|
+
height: 180
|
|
525
|
+
}));
|
|
526
|
+
|
|
527
|
+
hero.add(new Text(app, {
|
|
528
|
+
x: 16,
|
|
529
|
+
y: 120,
|
|
530
|
+
text: 'Bienvenue',
|
|
531
|
+
fontSize: 20,
|
|
532
|
+
color: '#FFF'
|
|
533
|
+
}));
|
|
534
|
+
|
|
535
|
+
screen.add(hero);
|
|
536
|
+
|
|
537
|
+
// Actions
|
|
538
|
+
const row = new Row(app, {
|
|
539
|
+
height: 48,
|
|
540
|
+
spacing: 12
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
row.add(new Button(app, { width: 120, height: 40, text: 'Explorer' }));
|
|
544
|
+
row.add(new Button(app, { width: 120, height: 40, text: 'Profil' }));
|
|
545
|
+
|
|
546
|
+
screen.add(row);
|
|
547
|
+
|
|
548
|
+
app.add(screen);
|
|
549
|
+
screen.layoutRecursive();
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
## 📄 License
|
|
553
|
+
|
|
554
|
+
MIT
|