canvasframework 0.5.9 → 0.5.11

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 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 (Text rendering), running inside a WebView runtime (Capacitor / Cordova), without DOM, HTML or CSS.
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
 
@@ -34,6 +34,25 @@ class Camera extends Component {
34
34
 
35
35
  this.isStarting = false;
36
36
  }
37
+
38
+ static cleanupAllCameras() {
39
+ const allVideos = document.querySelectorAll('video');
40
+ allVideos.forEach(video => {
41
+ // Arrêter les streams
42
+ if (video.srcObject) {
43
+ const stream = video.srcObject;
44
+ if (stream && stream.getTracks) {
45
+ stream.getTracks().forEach(track => track.stop());
46
+ }
47
+ video.srcObject = null;
48
+ }
49
+
50
+ // Supprimer du DOM
51
+ if (video.parentNode) {
52
+ video.parentNode.removeChild(video);
53
+ }
54
+ });
55
+ }
37
56
 
38
57
  async _mount() {
39
58
  super._mount?.();
@@ -120,6 +139,7 @@ class Camera extends Component {
120
139
  handleMouseUp(e) {}
121
140
 
122
141
  async startCamera() {
142
+ Camera.cleanupAllCameras();
123
143
  if (this.stream) return;
124
144
 
125
145
  try {
@@ -1,7 +1,7 @@
1
1
  import Component from '../core/Component.js';
2
2
 
3
3
  /**
4
- * Composant FloatedCamera autonome avec gestion directe des clics/touches
4
+ * Composant Camera autonome avec gestion directe des clics/touches
5
5
  * Modes : contain (tout visible + bandes), cover (remplit + crop), fit (centre sans crop)
6
6
  */
7
7
  class FloatedCamera extends Component {
@@ -34,6 +34,25 @@ class FloatedCamera extends Component {
34
34
 
35
35
  this.isStarting = false;
36
36
  }
37
+
38
+ static cleanupAllCameras() {
39
+ const allVideos = document.querySelectorAll('video');
40
+ allVideos.forEach(video => {
41
+ // Arrêter les streams
42
+ if (video.srcObject) {
43
+ const stream = video.srcObject;
44
+ if (stream && stream.getTracks) {
45
+ stream.getTracks().forEach(track => track.stop());
46
+ }
47
+ video.srcObject = null;
48
+ }
49
+
50
+ // Supprimer du DOM
51
+ if (video.parentNode) {
52
+ video.parentNode.removeChild(video);
53
+ }
54
+ });
55
+ }
37
56
 
38
57
  async _mount() {
39
58
  super._mount?.();
@@ -120,6 +139,7 @@ class FloatedCamera extends Component {
120
139
  handleMouseUp(e) {}
121
140
 
122
141
  async startCamera() {
142
+ FloatedCamera.cleanupAllCameras();
123
143
  if (this.stream) return;
124
144
 
125
145
  try {
@@ -1781,6 +1781,7 @@ class CanvasFramework {
1781
1781
  * @private
1782
1782
  */
1783
1783
  async navigateTo(path, options = {}) {
1784
+
1784
1785
  const {
1785
1786
  replace = false,
1786
1787
  animate = true,
@@ -1803,7 +1804,7 @@ class CanvasFramework {
1803
1804
  } = match;
1804
1805
 
1805
1806
  // ===== LIFECYCLE: AVANT DE QUITTER L'ANCIENNE ROUTE =====
1806
-
1807
+
1807
1808
  // Hook beforeLeave de la route actuelle (peut bloquer la navigation)
1808
1809
  const currentRouteData = this.routes.get(this.currentRoute);
1809
1810
  if (currentRouteData?.beforeLeave) {
@@ -1903,9 +1904,10 @@ class CanvasFramework {
1903
1904
  // ===== LANCER L'ANIMATION DE TRANSITION =====
1904
1905
 
1905
1906
  if (animate && !this.transitionState.isTransitioning) {
1906
- const transitionType = transition || route.transition || 'slide';
1907
- this.startTransition(oldComponents, this.components, transitionType, direction);
1908
- }
1907
+ const transitionType = transition || route.transition || 'slide';
1908
+ this.startTransition(oldComponents, this.components, transitionType, direction);
1909
+ // Pas besoin d'appeler renderFull() ici, la boucle de rendu s'en chargera
1910
+ }
1909
1911
 
1910
1912
  // ===== LIFECYCLE: APRÈS ÊTRE ENTRÉ DANS LA NOUVELLE ROUTE =====
1911
1913
 
@@ -1929,6 +1931,17 @@ class CanvasFramework {
1929
1931
 
1930
1932
  // ✅ OPTIONNEL : Marquer les composants comme "dirty" pour forcer le rendu
1931
1933
  this._maxScrollDirty = true;
1934
+
1935
+ this.components.forEach(comp => {
1936
+ // Vérifier si c'est un composant caméra
1937
+ if (comp.constructor.name === 'FloatedCamera' || comp.constructor.name === 'Camera') {
1938
+ // Arrêter le stream vidéo
1939
+ if (comp.stopCamera && typeof comp.stopCamera === 'function') {
1940
+ comp.stopCamera();
1941
+ console.log(`🎥 Caméra ${comp.constructor.name} arrêtée avant navigation`);
1942
+ }
1943
+ }
1944
+ });
1932
1945
  }
1933
1946
 
1934
1947
  /**
@@ -2661,7 +2674,6 @@ class CanvasFramework {
2661
2674
 
2662
2675
  startRenderLoop() {
2663
2676
  let lastScrollOffset = this.scrollOffset;
2664
- let lastRenderMode = 'full';
2665
2677
 
2666
2678
  const render = () => {
2667
2679
  if (!this._splashFinished) {
@@ -2669,33 +2681,31 @@ class CanvasFramework {
2669
2681
  return;
2670
2682
  }
2671
2683
 
2672
- // Vérifier le scroll
2673
- const scrollChanged = Math.abs(this.scrollOffset - lastScrollOffset) > 0.1;
2674
- lastScrollOffset = this.scrollOffset;
2675
-
2676
- // Décider du mode de rendu
2677
- let renderMode = 'full';
2678
-
2679
- if (this.optimizationEnabled &&
2680
- this.dirtyComponents.size > 0 &&
2681
- !this.isDragging &&
2682
- Math.abs(this.scrollVelocity) < 0.5 &&
2683
- !scrollChanged &&
2684
- this.dirtyComponents.size < 3) {
2685
- renderMode = 'dirty';
2686
- }
2684
+ // TOUJOURS effacer l'écran en premier
2685
+ this.ctx.fillStyle = this.backgroundColor || '#ffffff';
2686
+ this.ctx.fillRect(0, 0, this.width, this.height);
2687
2687
 
2688
- if (renderMode === 'full' || lastRenderMode !== renderMode) {
2689
- this.ctx.fillStyle = this.backgroundColor || '#ffffff';
2690
- this.ctx.fillRect(0, 0, this.width, this.height);
2688
+ // Si une transition est en cours
2689
+ if (this.transitionState.isTransitioning) {
2690
+ // Mettre à jour la progression
2691
+ const elapsed = Date.now() - this.transitionState.startTime;
2692
+ this.transitionState.progress = Math.min(elapsed / this.transitionState.duration, 1);
2693
+
2694
+ // Rendu spécial pour la transition
2695
+ this.renderSimpleTransition();
2696
+
2697
+ // Si la transition est terminée
2698
+ if (this.transitionState.progress >= 1) {
2699
+ this.transitionState.isTransitioning = false;
2700
+ this.transitionState.oldComponents = [];
2701
+ }
2702
+ }
2703
+ // Sinon, rendu normal
2704
+ else {
2691
2705
  this.renderFull();
2692
- } else {
2693
- this._renderDirtyComponents();
2694
2706
  }
2695
2707
 
2696
- lastRenderMode = renderMode;
2697
-
2698
- // ✅ AJOUTER : Calcul et affichage du FPS
2708
+ // Calcul FPS (optionnel)
2699
2709
  this._frames++;
2700
2710
  const now = performance.now();
2701
2711
  const elapsed = now - this._lastFpsTime;
@@ -2706,7 +2716,6 @@ class CanvasFramework {
2706
2716
  this._lastFpsTime = now;
2707
2717
  }
2708
2718
 
2709
- // ✅ AJOUTER : Afficher le FPS si activé
2710
2719
  if (this.showFps) {
2711
2720
  this.ctx.save();
2712
2721
  this.ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
@@ -2719,17 +2728,7 @@ class CanvasFramework {
2719
2728
  this.ctx.restore();
2720
2729
  }
2721
2730
 
2722
- // AJOUTER : Indicateurs de débogage si activés
2723
- if (this.debbug) {
2724
- this.drawOverflowIndicators();
2725
- }
2726
-
2727
- // ✅ AJOUTER : Marquer le premier rendu
2728
- if (!this._firstRenderDone) {
2729
- this._markFirstRender();
2730
- }
2731
-
2732
- // ✅ AJOUTER : Mettre à jour l'inertie si nécessaire
2731
+ // Mettre à jour l'inertie du scroll
2733
2732
  if (Math.abs(this.scrollVelocity) > 0.1 && !this.isDragging) {
2734
2733
  this.scrollWorker.postMessage({ type: 'UPDATE_INERTIA' });
2735
2734
  }
@@ -2740,53 +2739,186 @@ class CanvasFramework {
2740
2739
  render();
2741
2740
  }
2742
2741
 
2743
- // 3. Ajoutez une méthode renderFull() optimisée
2744
- renderFull() {
2745
- // Sauvegarder le contexte
2746
- this.ctx.save();
2747
-
2748
- // Séparer les composants
2749
- const scrollableComponents = [];
2750
- const fixedComponents = [];
2751
-
2752
- for (let comp of this.components) {
2753
- if (this.isFixedComponent(comp)) {
2754
- fixedComponents.push(comp);
2755
- } else {
2756
- scrollableComponents.push(comp);
2757
- }
2758
- }
2759
-
2760
- // ✅ OPTIMISATION : Dessiner les composants scrollables avec translation
2761
- if (scrollableComponents.length > 0) {
2762
- this.ctx.save();
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
- }
2742
+ /**
2743
+ * Rendu ultra simple pour la transition slide
2744
+ */
2745
+ renderSimpleTransition() {
2746
+ const { progress, type, direction, oldComponents, newComponents } = this.transitionState;
2747
+
2748
+ // Calculer la position de décalage
2749
+ const slideOffset = this.width * (1 - progress);
2750
+ const directionMultiplier = direction === 'forward' ? 1 : -1;
2751
+
2752
+ // Pour l'animation slide, dessiner seulement la nouvelle vue à la bonne position
2753
+ if (type === 'slide') {
2754
+ // Sauvegarder le contexte
2755
+ this.ctx.save();
2756
+
2757
+ // Appliquer la translation pour l'animation
2758
+ this.ctx.translate(slideOffset * directionMultiplier, 0);
2759
+
2760
+ // Dessiner UNIQUEMENT les nouveaux composants
2761
+ for (let comp of newComponents) {
2762
+ if (comp && comp.visible) {
2763
+ const isFixed = this.isFixedComponent(comp);
2764
+
2765
+ if (isFixed) {
2766
+ comp.draw(this.ctx);
2767
+ } else {
2768
+ // Pour les composants scrollables, ajouter le décalage
2769
+ this.ctx.save();
2770
+ this.ctx.translate(0, this.scrollOffset);
2771
+ comp.draw(this.ctx);
2772
+ this.ctx.restore();
2773
+ }
2774
+ }
2775
+ }
2776
+
2777
+ this.ctx.restore();
2778
+ }
2779
+ // Pour fade, dessiner la nouvelle vue avec alpha
2780
+ else if (type === 'fade') {
2781
+ this.ctx.save();
2782
+ this.ctx.globalAlpha = progress;
2783
+
2784
+ for (let comp of newComponents) {
2785
+ if (comp && comp.visible) {
2786
+ const isFixed = this.isFixedComponent(comp);
2787
+
2788
+ if (isFixed) {
2789
+ comp.draw(this.ctx);
2790
+ } else {
2791
+ this.ctx.save();
2792
+ this.ctx.translate(0, this.scrollOffset);
2793
+ comp.draw(this.ctx);
2794
+ this.ctx.restore();
2795
+ }
2796
+ }
2797
+ }
2798
+
2799
+ this.ctx.restore();
2800
+ }
2801
+ // Pour 'none', dessiner normalement
2802
+ else {
2803
+ this.renderFull();
2804
+ }
2805
+ }
2776
2806
 
2777
- this.ctx.restore();
2778
- }
2807
+ /**
2808
+ * Rendu normal (sans transition)
2809
+ */
2810
+ renderFull() {
2811
+ this.ctx.save();
2812
+
2813
+ // Séparer les composants fixes et scrollables
2814
+ const scrollableComponents = [];
2815
+ const fixedComponents = [];
2816
+
2817
+ for (let comp of this.components) {
2818
+ if (this.isFixedComponent(comp)) {
2819
+ fixedComponents.push(comp);
2820
+ } else {
2821
+ scrollableComponents.push(comp);
2822
+ }
2823
+ }
2824
+
2825
+ // Dessiner les composants scrollables
2826
+ if (scrollableComponents.length > 0) {
2827
+ this.ctx.save();
2828
+ this.ctx.translate(0, this.scrollOffset);
2829
+
2830
+ for (let comp of scrollableComponents) {
2831
+ if (comp.visible) {
2832
+ comp.draw(this.ctx);
2833
+ }
2834
+ }
2835
+
2836
+ this.ctx.restore();
2837
+ }
2838
+
2839
+ // Dessiner les composants fixes
2840
+ for (let comp of fixedComponents) {
2841
+ if (comp.visible) {
2842
+ comp.draw(this.ctx);
2843
+ }
2844
+ }
2845
+
2846
+ this.ctx.restore();
2847
+ }
2848
+
2849
+ /**
2850
+ * Mettre à jour la progression de la transition
2851
+ * @private
2852
+ */
2853
+ updateTransition() {
2854
+ if (!this.transitionState.isTransitioning) return;
2855
+
2856
+ const elapsed = Date.now() - this.transitionState.startTime;
2857
+ this.transitionState.progress = Math.min(elapsed / this.transitionState.duration, 1);
2858
+
2859
+ // Si la transition est terminée
2860
+ if (this.transitionState.progress >= 1) {
2861
+ this.transitionState.isTransitioning = false;
2862
+
2863
+ // Marquer tous les nouveaux composants comme sales pour le prochain rendu
2864
+ this.transitionState.newComponents.forEach(comp => {
2865
+ this.markComponentDirty(comp);
2866
+ });
2867
+ }
2868
+ }
2779
2869
 
2780
- // Dessiner les composants fixes
2781
- for (let comp of fixedComponents) {
2782
- if (comp.visible) {
2783
- comp.draw(this.ctx);
2784
- }
2785
- }
2870
+ /**
2871
+ * Rendu complet normal (sans transition)
2872
+ * @private
2873
+ */
2874
+ renderFull() {
2875
+ // Sauvegarder le contexte
2876
+ this.ctx.save();
2877
+
2878
+ // Séparer les composants
2879
+ const scrollableComponents = [];
2880
+ const fixedComponents = [];
2881
+
2882
+ for (let comp of this.components) {
2883
+ if (this.isFixedComponent(comp)) {
2884
+ fixedComponents.push(comp);
2885
+ } else {
2886
+ scrollableComponents.push(comp);
2887
+ }
2888
+ }
2889
+
2890
+ // Dessiner les composants scrollables avec translation
2891
+ if (scrollableComponents.length > 0) {
2892
+ this.ctx.save();
2893
+ this.ctx.translate(0, this.scrollOffset);
2894
+
2895
+ for (let comp of scrollableComponents) {
2896
+ if (comp.visible) {
2897
+ // Viewport culling
2898
+ const screenY = comp.y + this.scrollOffset;
2899
+ const isInViewport = screenY + comp.height >= -100 && screenY <= this.height + 100;
2900
+
2901
+ if (isInViewport) {
2902
+ comp.draw(this.ctx);
2903
+ }
2904
+ }
2905
+ }
2906
+
2907
+ this.ctx.restore();
2908
+ }
2909
+
2910
+ // Dessiner les composants fixes
2911
+ for (let comp of fixedComponents) {
2912
+ if (comp.visible) {
2913
+ comp.draw(this.ctx);
2914
+ }
2915
+ }
2916
+
2917
+ // Restaurer le contexte
2918
+ this.ctx.restore();
2919
+ }
2786
2920
 
2787
- // Restaurer le contexte
2788
- this.ctx.restore();
2789
- }
2921
+
2790
2922
 
2791
2923
  /**
2792
2924
  * Fait défiler à une position spécifique
@@ -2901,8 +3033,4 @@ class CanvasFramework {
2901
3033
  }
2902
3034
  }
2903
3035
 
2904
- export default CanvasFramework;
2905
-
2906
-
2907
-
2908
-
3036
+ export default CanvasFramework;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canvasframework",
3
- "version": "0.5.9",
3
+ "version": "0.5.11",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/beyons/CanvasFramework.git"