canvasframework 0.5.8 → 0.5.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.
- package/README.md +1 -1
- package/core/CanvasFramework.js +240 -58
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Canvas UI Engine (UI engine inspired by Flutter, built for the Web)
|
|
5
5
|
|
|
6
6
|
> **Canvas-based UI Engine for Mobile & Embedded Apps**
|
|
7
|
-
> A high-performance UI engine rendered with Canvas/WebGL
|
|
7
|
+
> A high-performance UI engine rendered with Canvas/WebGL, running inside a WebView runtime (Capacitor / Cordova), without DOM, HTML or CSS.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
package/core/CanvasFramework.js
CHANGED
|
@@ -1903,9 +1903,10 @@ class CanvasFramework {
|
|
|
1903
1903
|
// ===== LANCER L'ANIMATION DE TRANSITION =====
|
|
1904
1904
|
|
|
1905
1905
|
if (animate && !this.transitionState.isTransitioning) {
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1906
|
+
const transitionType = transition || route.transition || 'slide';
|
|
1907
|
+
this.startTransition(oldComponents, this.components, transitionType, direction);
|
|
1908
|
+
// Pas besoin d'appeler renderFull() ici, la boucle de rendu s'en chargera
|
|
1909
|
+
}
|
|
1909
1910
|
|
|
1910
1911
|
// ===== LIFECYCLE: APRÈS ÊTRE ENTRÉ DANS LA NOUVELLE ROUTE =====
|
|
1911
1912
|
|
|
@@ -2662,21 +2663,39 @@ class CanvasFramework {
|
|
|
2662
2663
|
startRenderLoop() {
|
|
2663
2664
|
let lastScrollOffset = this.scrollOffset;
|
|
2664
2665
|
let lastRenderMode = 'full';
|
|
2666
|
+
let lastFrameTime = performance.now();
|
|
2665
2667
|
|
|
2666
|
-
const render = () => {
|
|
2668
|
+
const render = (currentTime) => {
|
|
2669
|
+
// Calculer le delta time
|
|
2670
|
+
const deltaTime = currentTime - lastFrameTime;
|
|
2671
|
+
lastFrameTime = currentTime;
|
|
2672
|
+
|
|
2667
2673
|
if (!this._splashFinished) {
|
|
2668
2674
|
requestAnimationFrame(render);
|
|
2669
2675
|
return;
|
|
2670
2676
|
}
|
|
2671
2677
|
|
|
2672
|
-
//
|
|
2678
|
+
// 1. Mettre à jour l'animation de transition si active
|
|
2679
|
+
if (this.transitionState.isTransitioning) {
|
|
2680
|
+
this.updateTransition();
|
|
2681
|
+
|
|
2682
|
+
// Si la transition est terminée, nettoyer
|
|
2683
|
+
if (this.transitionState.progress >= 1) {
|
|
2684
|
+
this.transitionState.isTransitioning = false;
|
|
2685
|
+
this.transitionState.oldComponents = [];
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
// 2. Vérifier le scroll
|
|
2673
2690
|
const scrollChanged = Math.abs(this.scrollOffset - lastScrollOffset) > 0.1;
|
|
2674
2691
|
lastScrollOffset = this.scrollOffset;
|
|
2675
2692
|
|
|
2676
|
-
// Décider du mode de rendu
|
|
2693
|
+
// 3. Décider du mode de rendu
|
|
2677
2694
|
let renderMode = 'full';
|
|
2678
2695
|
|
|
2696
|
+
// Conditions pour utiliser le rendu optimisé (dirty components)
|
|
2679
2697
|
if (this.optimizationEnabled &&
|
|
2698
|
+
!this.transitionState.isTransitioning && // ← Pas pendant les transitions
|
|
2680
2699
|
this.dirtyComponents.size > 0 &&
|
|
2681
2700
|
!this.isDragging &&
|
|
2682
2701
|
Math.abs(this.scrollVelocity) < 0.5 &&
|
|
@@ -2685,17 +2704,23 @@ class CanvasFramework {
|
|
|
2685
2704
|
renderMode = 'dirty';
|
|
2686
2705
|
}
|
|
2687
2706
|
|
|
2688
|
-
|
|
2707
|
+
// 4. Exécuter le rendu
|
|
2708
|
+
if (this.transitionState.isTransitioning) {
|
|
2709
|
+
// Pendant les transitions, toujours utiliser un rendu complet spécial
|
|
2710
|
+
this.renderTransition();
|
|
2711
|
+
} else if (renderMode === 'full' || lastRenderMode !== renderMode) {
|
|
2712
|
+
// Rendu complet normal
|
|
2689
2713
|
this.ctx.fillStyle = this.backgroundColor || '#ffffff';
|
|
2690
2714
|
this.ctx.fillRect(0, 0, this.width, this.height);
|
|
2691
2715
|
this.renderFull();
|
|
2692
2716
|
} else {
|
|
2717
|
+
// Rendu optimisé (seulement les composants sales)
|
|
2693
2718
|
this._renderDirtyComponents();
|
|
2694
2719
|
}
|
|
2695
2720
|
|
|
2696
2721
|
lastRenderMode = renderMode;
|
|
2697
2722
|
|
|
2698
|
-
//
|
|
2723
|
+
// 5. Calculer et afficher le FPS
|
|
2699
2724
|
this._frames++;
|
|
2700
2725
|
const now = performance.now();
|
|
2701
2726
|
const elapsed = now - this._lastFpsTime;
|
|
@@ -2706,7 +2731,7 @@ class CanvasFramework {
|
|
|
2706
2731
|
this._lastFpsTime = now;
|
|
2707
2732
|
}
|
|
2708
2733
|
|
|
2709
|
-
//
|
|
2734
|
+
// Afficher le FPS si activé
|
|
2710
2735
|
if (this.showFps) {
|
|
2711
2736
|
this.ctx.save();
|
|
2712
2737
|
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
|
|
@@ -2719,74 +2744,231 @@ class CanvasFramework {
|
|
|
2719
2744
|
this.ctx.restore();
|
|
2720
2745
|
}
|
|
2721
2746
|
|
|
2722
|
-
//
|
|
2747
|
+
// Indicateurs de débogage si activés
|
|
2723
2748
|
if (this.debbug) {
|
|
2724
2749
|
this.drawOverflowIndicators();
|
|
2725
2750
|
}
|
|
2726
2751
|
|
|
2727
|
-
//
|
|
2752
|
+
// Marquer le premier rendu
|
|
2728
2753
|
if (!this._firstRenderDone) {
|
|
2729
2754
|
this._markFirstRender();
|
|
2730
2755
|
}
|
|
2731
2756
|
|
|
2732
|
-
//
|
|
2757
|
+
// Mettre à jour l'inertie du scroll si nécessaire
|
|
2733
2758
|
if (Math.abs(this.scrollVelocity) > 0.1 && !this.isDragging) {
|
|
2734
2759
|
this.scrollWorker.postMessage({ type: 'UPDATE_INERTIA' });
|
|
2735
2760
|
}
|
|
2736
2761
|
|
|
2762
|
+
// Nettoyer les composants sales après le rendu
|
|
2763
|
+
if (renderMode === 'full') {
|
|
2764
|
+
this.dirtyComponents.clear();
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2737
2767
|
requestAnimationFrame(render);
|
|
2738
2768
|
};
|
|
2739
2769
|
|
|
2740
|
-
render();
|
|
2770
|
+
render(lastFrameTime);
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
|
|
2774
|
+
/**
|
|
2775
|
+
* Rendu spécial pour les transitions
|
|
2776
|
+
* @private
|
|
2777
|
+
*/
|
|
2778
|
+
renderTransition() {
|
|
2779
|
+
const ctx = this.ctx;
|
|
2780
|
+
|
|
2781
|
+
// Effacer l'écran
|
|
2782
|
+
ctx.fillStyle = this.backgroundColor || '#ffffff';
|
|
2783
|
+
ctx.fillRect(0, 0, this.width, this.height);
|
|
2784
|
+
|
|
2785
|
+
// Dessiner l'animation de transition
|
|
2786
|
+
const progress = this.transitionState.progress;
|
|
2787
|
+
const { type, direction, oldComponents, newComponents } = this.transitionState;
|
|
2788
|
+
const directionMultiplier = direction === 'forward' ? 1 : -1;
|
|
2789
|
+
|
|
2790
|
+
// Calculer le facteur d'easing
|
|
2791
|
+
const eased = this.easeInOutCubic(progress);
|
|
2792
|
+
|
|
2793
|
+
switch (type) {
|
|
2794
|
+
case 'slide':
|
|
2795
|
+
// 1. Ancienne vue qui sort
|
|
2796
|
+
ctx.save();
|
|
2797
|
+
ctx.translate(-this.width * eased * directionMultiplier, 0);
|
|
2798
|
+
ctx.globalAlpha = 1 - eased * 0.3;
|
|
2799
|
+
|
|
2800
|
+
for (let comp of oldComponents) {
|
|
2801
|
+
if (comp && comp.visible) {
|
|
2802
|
+
const isFixed = this.isFixedComponent(comp);
|
|
2803
|
+
|
|
2804
|
+
if (isFixed) {
|
|
2805
|
+
// Composants fixes : dessiner normalement
|
|
2806
|
+
comp.draw(ctx);
|
|
2807
|
+
} else {
|
|
2808
|
+
// Composants scrollables : appliquer le décalage de scroll
|
|
2809
|
+
ctx.save();
|
|
2810
|
+
ctx.translate(0, this.scrollOffset);
|
|
2811
|
+
comp.draw(ctx);
|
|
2812
|
+
ctx.restore();
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
ctx.restore();
|
|
2817
|
+
|
|
2818
|
+
// 2. Nouvelle vue qui entre
|
|
2819
|
+
ctx.save();
|
|
2820
|
+
ctx.translate(this.width * (1 - eased) * directionMultiplier, 0);
|
|
2821
|
+
|
|
2822
|
+
for (let comp of newComponents) {
|
|
2823
|
+
if (comp && comp.visible) {
|
|
2824
|
+
const isFixed = this.isFixedComponent(comp);
|
|
2825
|
+
|
|
2826
|
+
if (isFixed) {
|
|
2827
|
+
comp.draw(ctx);
|
|
2828
|
+
} else {
|
|
2829
|
+
ctx.save();
|
|
2830
|
+
ctx.translate(0, this.scrollOffset);
|
|
2831
|
+
comp.draw(ctx);
|
|
2832
|
+
ctx.restore();
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
ctx.restore();
|
|
2837
|
+
break;
|
|
2838
|
+
|
|
2839
|
+
case 'fade':
|
|
2840
|
+
// Ancienne vue qui disparaît
|
|
2841
|
+
ctx.save();
|
|
2842
|
+
ctx.globalAlpha = 1 - eased;
|
|
2843
|
+
for (let comp of oldComponents) {
|
|
2844
|
+
if (comp && comp.visible) {
|
|
2845
|
+
const isFixed = this.isFixedComponent(comp);
|
|
2846
|
+
|
|
2847
|
+
if (isFixed) {
|
|
2848
|
+
comp.draw(ctx);
|
|
2849
|
+
} else {
|
|
2850
|
+
ctx.save();
|
|
2851
|
+
ctx.translate(0, this.scrollOffset);
|
|
2852
|
+
comp.draw(ctx);
|
|
2853
|
+
ctx.restore();
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
ctx.restore();
|
|
2858
|
+
|
|
2859
|
+
// Nouvelle vue qui apparaît
|
|
2860
|
+
ctx.save();
|
|
2861
|
+
ctx.globalAlpha = eased;
|
|
2862
|
+
for (let comp of newComponents) {
|
|
2863
|
+
if (comp && comp.visible) {
|
|
2864
|
+
const isFixed = this.isFixedComponent(comp);
|
|
2865
|
+
|
|
2866
|
+
if (isFixed) {
|
|
2867
|
+
comp.draw(ctx);
|
|
2868
|
+
} else {
|
|
2869
|
+
ctx.save();
|
|
2870
|
+
ctx.translate(0, this.scrollOffset);
|
|
2871
|
+
comp.draw(ctx);
|
|
2872
|
+
ctx.restore();
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
ctx.restore();
|
|
2877
|
+
break;
|
|
2878
|
+
|
|
2879
|
+
case 'none':
|
|
2880
|
+
// Pas d'animation, juste dessiner la nouvelle vue
|
|
2881
|
+
for (let comp of newComponents) {
|
|
2882
|
+
if (comp && comp.visible) {
|
|
2883
|
+
const isFixed = this.isFixedComponent(comp);
|
|
2884
|
+
|
|
2885
|
+
if (isFixed) {
|
|
2886
|
+
comp.draw(ctx);
|
|
2887
|
+
} else {
|
|
2888
|
+
ctx.save();
|
|
2889
|
+
ctx.translate(0, this.scrollOffset);
|
|
2890
|
+
comp.draw(ctx);
|
|
2891
|
+
ctx.restore();
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
break;
|
|
2896
|
+
}
|
|
2741
2897
|
}
|
|
2742
2898
|
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
this.ctx.translate(0, this.scrollOffset);
|
|
2764
|
-
|
|
2765
|
-
for (let comp of scrollableComponents) {
|
|
2766
|
-
if (comp.visible) {
|
|
2767
|
-
// Viewport culling
|
|
2768
|
-
const screenY = comp.y + this.scrollOffset;
|
|
2769
|
-
const isInViewport = screenY + comp.height >= -100 && screenY <= this.height + 100;
|
|
2770
|
-
|
|
2771
|
-
if (isInViewport) {
|
|
2772
|
-
comp.draw(this.ctx);
|
|
2773
|
-
}
|
|
2774
|
-
}
|
|
2775
|
-
}
|
|
2776
|
-
|
|
2777
|
-
this.ctx.restore();
|
|
2778
|
-
}
|
|
2899
|
+
/**
|
|
2900
|
+
* Mettre à jour la progression de la transition
|
|
2901
|
+
* @private
|
|
2902
|
+
*/
|
|
2903
|
+
updateTransition() {
|
|
2904
|
+
if (!this.transitionState.isTransitioning) return;
|
|
2905
|
+
|
|
2906
|
+
const elapsed = Date.now() - this.transitionState.startTime;
|
|
2907
|
+
this.transitionState.progress = Math.min(elapsed / this.transitionState.duration, 1);
|
|
2908
|
+
|
|
2909
|
+
// Si la transition est terminée
|
|
2910
|
+
if (this.transitionState.progress >= 1) {
|
|
2911
|
+
this.transitionState.isTransitioning = false;
|
|
2912
|
+
|
|
2913
|
+
// Marquer tous les nouveaux composants comme sales pour le prochain rendu
|
|
2914
|
+
this.transitionState.newComponents.forEach(comp => {
|
|
2915
|
+
this.markComponentDirty(comp);
|
|
2916
|
+
});
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2779
2919
|
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2920
|
+
/**
|
|
2921
|
+
* Rendu complet normal (sans transition)
|
|
2922
|
+
* @private
|
|
2923
|
+
*/
|
|
2924
|
+
renderFull() {
|
|
2925
|
+
// Sauvegarder le contexte
|
|
2926
|
+
this.ctx.save();
|
|
2927
|
+
|
|
2928
|
+
// Séparer les composants
|
|
2929
|
+
const scrollableComponents = [];
|
|
2930
|
+
const fixedComponents = [];
|
|
2931
|
+
|
|
2932
|
+
for (let comp of this.components) {
|
|
2933
|
+
if (this.isFixedComponent(comp)) {
|
|
2934
|
+
fixedComponents.push(comp);
|
|
2935
|
+
} else {
|
|
2936
|
+
scrollableComponents.push(comp);
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2940
|
+
// Dessiner les composants scrollables avec translation
|
|
2941
|
+
if (scrollableComponents.length > 0) {
|
|
2942
|
+
this.ctx.save();
|
|
2943
|
+
this.ctx.translate(0, this.scrollOffset);
|
|
2944
|
+
|
|
2945
|
+
for (let comp of scrollableComponents) {
|
|
2946
|
+
if (comp.visible) {
|
|
2947
|
+
// Viewport culling
|
|
2948
|
+
const screenY = comp.y + this.scrollOffset;
|
|
2949
|
+
const isInViewport = screenY + comp.height >= -100 && screenY <= this.height + 100;
|
|
2950
|
+
|
|
2951
|
+
if (isInViewport) {
|
|
2952
|
+
comp.draw(this.ctx);
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
this.ctx.restore();
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
// Dessiner les composants fixes
|
|
2961
|
+
for (let comp of fixedComponents) {
|
|
2962
|
+
if (comp.visible) {
|
|
2963
|
+
comp.draw(this.ctx);
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
// Restaurer le contexte
|
|
2968
|
+
this.ctx.restore();
|
|
2969
|
+
}
|
|
2786
2970
|
|
|
2787
|
-
|
|
2788
|
-
this.ctx.restore();
|
|
2789
|
-
}
|
|
2971
|
+
|
|
2790
2972
|
|
|
2791
2973
|
/**
|
|
2792
2974
|
* Fait défiler à une position spécifique
|
package/package.json
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canvasframework",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.10",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/beyons/CanvasFramework.git"
|
|
7
|
+
},
|
|
4
8
|
"description": "Canvas-based cross-platform UI framework (Material & Cupertino)",
|
|
5
9
|
"type": "module",
|
|
6
10
|
"main": "./index.js",
|