From bf412f28c745f2e8c10e81c3157cb92d6d00d9d9 Mon Sep 17 00:00:00 2001 From: Yann Lebrun Date: Sun, 14 Sep 2025 19:21:40 +0200 Subject: [PATCH] =?UTF-8?q?T=C3=A9l=C3=A9verser=20les=20fichiers=20vers=20?= =?UTF-8?q?"/"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 287 ++++++------ Thumbnails.pb | 1152 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1295 insertions(+), 144 deletions(-) create mode 100644 Thumbnails.pb diff --git a/README.md b/README.md index cde040d..7edc4ed 100644 --- a/README.md +++ b/README.md @@ -1,144 +1,143 @@ -# Visionneuse de Vignettes en PureBasic / Thumbnail Viewer in PureBasic - -Ce projet est un composant d'interface utilisateur avancé pour PureBasic qui permet d'afficher des centaines ou des milliers de vignettes d'images de manière fluide et efficace. Il est conçu pour être facilement intégré dans n'importe quelle application nécessitant une galerie d'images performante. - -*This project is an advanced UI component for PureBasic that allows displaying hundreds or thousands of image thumbnails smoothly and efficiently. It is designed to be easily integrated into any application requiring a high-performance image gallery.* - -## Fonctionnalités / Features ✨ - - * **Chargement Asynchrone :** Les images sont chargées en arrière-plan grâce au multithreading, ce qui évite de figer l'application. - - * ***Asynchronous Loading:*** *Images are loaded in the background using multithreading, which prevents the application from freezing.* - - * **Système de Cache :** Un cache intelligent conserve les images chargées en mémoire pour un réaffichage instantané et gère la libération de la mémoire pour les images qui ne sont plus visibles. - - * ***Caching System:*** *An intelligent cache keeps loaded images in memory for instant redisplay and manages memory release for images that are no longer visible.* - - * **Défilement Fluide :** Le défilement vertical est optimisé pour une expérience utilisateur agréable, même avec de nombreuses vignettes. - - * ***Smooth Scrolling:*** *Vertical scrolling is optimized for a pleasant user experience, even with many thumbnails.* - - * **Modes d'Affichage :** Les images peuvent être ajustées pour s'adapter, remplir ou s'étirer dans l'espace de la vignette (`#Image_Style_Fit`, `#Image_Style_Fill`, `#Image_Style_Stretch`). - - * ***Display Modes:*** *Images can be adjusted to fit, fill, or stretch within the thumbnail area (`#Image_Style_Fit`, `#Image_Style_Fill`, `#Image_Style_Stretch`).* - - * **Dessin Vectoriel :** L'utilisation du sous-système `VectorDrawing` assure un rendu de haute qualité et une bonne compatibilité avec les écrans à haute résolution (HiDPI). - - * ***Vector Drawing:*** *The use of the `VectorDrawing` subsystem ensures high-quality rendering and compatibility with high-resolution (HiDPI) displays.* - - * **Sélection Multiple :** Support de la sélection d'une ou plusieurs vignettes (y compris la sélection de plage avec la touche **Majuscule**). - - * ***Multiple Selection:*** *Supports selecting one or more thumbnails (including range selection with the **Shift** key).* - ------ - -## Structure du Projet / Project Structure 🏗️ - -Le code est organisé en plusieurs modules logiques pour une meilleure clarté et maintenance. -*The code is organized into several logical modules for better clarity and maintainability.* - - * **`Core`** : Définit les structures de données de base utilisées dans le projet. / *Defines the basic data structures used in the project.* - * **`ImgTools`** : Fournit des fonctions utilitaires pour le redimensionnement et le positionnement des images. / *Provides utility functions for image resizing and positioning.* - * **`Cache`** : Gère toute la logique de chargement en arrière-plan et de mise en cache des images. / *Manages all the background loading and image caching logic.* - * **`Thumbs`** : Contient le gadget principal (`ThumbsGadget`) et gère l'affichage, les événements et les interactions utilisateur. / *Contains the main gadget (`ThumbsGadget`) and handles display, events, and user interactions.* - ------ - -## Prérequis et Compilation / Prerequisites and Compilation ⚙️ - -Pour compiler et utiliser ce projet, vous aurez besoin de : -*To compile and use this project, you will need:* - - * **PureBasic 6.04 LTS (x64)** ou une version plus récente. / *or a more recent version.* - * **Système d'exploitation / OS :** Windows (testé principalement sur cette plateforme / *tested mainly on this platform*). - -### Options du Compilateur / Compiler Options - -Assurez-vous que les options suivantes sont activées dans les paramètres de votre compilateur. -*Make sure the following options are enabled in your compiler settings.* - - * ✅ **Activer le support des threads (Thread-Safe)** : Essentiel pour le système de cache. / *Essential for the caching system.* - * ✅ **Activer la prise en charge DPI (DPI-Aware)** : Pour un affichage correct sur les écrans modernes. / *For correct display on modern screens.* - ------ - -## Comment l'utiliser ? / How to Use It? 🚀 - -L'intégration du gadget dans votre propre fenêtre est simple. Voici un exemple de base. -*Integrating the gadget into your own window is simple. Here is a basic example.* - -```purebasic -;- Incorporez les modules du projet ici / Include the project's modules here - -Global NewList CurrentList.s() -Global CurrentListMutex.i = CreateMutex() - -; 1. Créez une fonction de rappel (callback) pour charger les chemins d'images -; 1. Create a callback function to load the image paths -Procedure CallBackLoadFiles(GadgetId.i, Index.i, Length.l) - Protected n.l, TmpIndex.i - Protected *Ptr.Core::FileData - - LockMutex(CurrentListMutex) - For n = 1 To Length - TmpIndex = Index + n - 1 - If TmpIndex >= 0 And TmpIndex < ListSize(CurrentList()) - SelectElement(CurrentList(), TmpIndex) - - ; On demande au cache de préparer le fichier / Ask the cache to prepare the file - *Ptr = Cache::GetFileDataFromCache(CurrentList()) - ; On lie le pointeur de données à l'index de la vignette / Link the data pointer to the thumbnail index - Thumbs::AddImageToThumb(GadgetId, TmpIndex, *Ptr) - EndIf - Next - Thumbs::LimitIndex(GadgetId, ListSize(CurrentList())) - UnlockMutex(CurrentListMutex) -EndProcedure - - -; 2. Créez votre fenêtre et le gadget / Create your window and the gadget -If OpenWindow(0, 0, 0, 800, 600, "Exemple de Vignettes / Thumbnail Example", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_SizeGadget) - - ; Créez le gadget en spécifiant la taille des vignettes et la fonction de rappel - ; Create the gadget, specifying the thumbnail size and the callback function - Thumbs::ThumbsGadget(1, 0, 0, WindowWidth(0), WindowHeight(0), 128, @CallBackLoadFiles()) - - ; 3. Remplissez votre liste de chemins d'images / Populate your list of image paths - LockMutex(CurrentListMutex) - ExamineDirectory(0, "C:\Your\Images", "*.jpg") - While NextDirectoryEntry(0) - AddElement(CurrentList()) - CurrentList() = "C:\Your\Images\" + DirectoryEntryName(0) - Wend - FinishDirectory(0) - UnlockMutex(CurrentListMutex) - - ; 4. Forcez la mise à jour pour afficher les nouvelles images / Force an update to display the new images - Thumbs::ForceUpdate(1) - - ; Boucle d'événements classique / Classic event loop - Repeat - Select WaitWindowEvent() - Case #PB_Event_CloseWindow - Quit = 1 - Case #PB_Event_SizeWindow - ResizeGadget(1, 0, 0, WindowWidth(0), WindowHeight(0)) - EndSelect - Until Quit = 1 - - Thumbs::FreeThumbsGadget(1) -EndIf -``` - ------ - -## Auteur / Author ✍️ - - * **Thyphoon** - -## Licence / License 📜 - -Ce projet est distribué sous une licence libre et non restrictive. Vous pouvez l'utiliser, le modifier et le distribuer sans contrainte. Un crédit est apprécié mais non obligatoire. - -*This project is distributed under a free and unrestricted license. You can use, modify, and distribute it without constraints. Credit is appreciated but not required.* - +# Visionneuse de Vignettes en PureBasic / Thumbnail Viewer in PureBasic + +Ce projet est un composant d'interface utilisateur avancé pour PureBasic qui permet d'afficher des centaines ou des milliers de vignettes d'images de manière fluide et efficace. Il est conçu pour être facilement intégré dans n'importe quelle application nécessitant une galerie d'images performante. + +*This project is an advanced UI component for PureBasic that allows displaying hundreds or thousands of image thumbnails smoothly and efficiently. It is designed to be easily integrated into any application requiring a high-performance image gallery.* + +## Fonctionnalités / Features ✨ + + * **Chargement Asynchrone :** Les images sont chargées en arrière-plan grâce au multithreading, ce qui évite de figer l'application. + + * ***Asynchronous Loading:*** *Images are loaded in the background using multithreading, which prevents the application from freezing.* + + * **Système de Cache :** Un cache intelligent conserve les images chargées en mémoire pour un réaffichage instantané et gère la libération de la mémoire pour les images qui ne sont plus visibles. + + * ***Caching System:*** *An intelligent cache keeps loaded images in memory for instant redisplay and manages memory release for images that are no longer visible.* + + * **Défilement Fluide :** Le défilement vertical est optimisé pour une expérience utilisateur agréable, même avec de nombreuses vignettes. + + * ***Smooth Scrolling:*** *Vertical scrolling is optimized for a pleasant user experience, even with many thumbnails.* + + * **Modes d'Affichage :** Les images peuvent être ajustées pour s'adapter, remplir ou s'étirer dans l'espace de la vignette (`#Image_Style_Fit`, `#Image_Style_Fill`, `#Image_Style_Stretch`). + + * ***Display Modes:*** *Images can be adjusted to fit, fill, or stretch within the thumbnail area (`#Image_Style_Fit`, `#Image_Style_Fill`, `#Image_Style_Stretch`).* + + * **Dessin Vectoriel :** L'utilisation du sous-système `VectorDrawing` assure un rendu de haute qualité et une bonne compatibilité avec les écrans à haute résolution (HiDPI). + + * ***Vector Drawing:*** *The use of the `VectorDrawing` subsystem ensures high-quality rendering and compatibility with high-resolution (HiDPI) displays.* + + * **Sélection Multiple :** Support de la sélection d'une ou plusieurs vignettes (y compris la sélection de plage avec la touche **Majuscule**). + + * ***Multiple Selection:*** *Supports selecting one or more thumbnails (including range selection with the **Shift** key).* + +----- + +## Structure du Projet / Project Structure 🏗️ + +Le code est organisé en plusieurs modules logiques pour une meilleure clarté et maintenance. +*The code is organized into several logical modules for better clarity and maintainability.* + + * **`Core`** : Définit les structures de données de base utilisées dans le projet. / *Defines the basic data structures used in the project.* + * **`ImgTools`** : Fournit des fonctions utilitaires pour le redimensionnement et le positionnement des images. / *Provides utility functions for image resizing and positioning.* + * **`Cache`** : Gère toute la logique de chargement en arrière-plan et de mise en cache des images. / *Manages all the background loading and image caching logic.* + * **`Thumbs`** : Contient le gadget principal (`ThumbsGadget`) et gère l'affichage, les événements et les interactions utilisateur. / *Contains the main gadget (`ThumbsGadget`) and handles display, events, and user interactions.* + +----- + +## Prérequis et Compilation / Prerequisites and Compilation ⚙️ + +Pour compiler et utiliser ce projet, vous aurez besoin de : +*To compile and use this project, you will need:* + + * **PureBasic 6.04 LTS (x64)** ou une version plus récente. / *or a more recent version.* + * **Système d'exploitation / OS :** Windows (testé principalement sur cette plateforme / *tested mainly on this platform*). + +### Options du Compilateur / Compiler Options + +Assurez-vous que les options suivantes sont activées dans les paramètres de votre compilateur. +*Make sure the following options are enabled in your compiler settings.* + + * ✅ **Activer le support des threads (Thread-Safe)** : Essentiel pour le système de cache. / *Essential for the caching system.* + * ✅ **Activer la prise en charge DPI (DPI-Aware)** : Pour un affichage correct sur les écrans modernes. / *For correct display on modern screens.* + +----- + +## Comment l'utiliser ? / How to Use It? 🚀 + +L'intégration du gadget dans votre propre fenêtre est simple. Voici un exemple de base. +*Integrating the gadget into your own window is simple. Here is a basic example.* + +```purebasic +;- Incorporez les modules du projet ici / Include the project's modules here + +Global NewList CurrentList.s() +Global CurrentListMutex.i = CreateMutex() + +; 1. Créez une fonction de rappel (callback) pour charger les chemins d'images +; 1. Create a callback function to load the image paths +Procedure CallBackLoadFiles(GadgetId.i, Index.i, Length.l) + Protected n.l, TmpIndex.i + Protected *Ptr.Core::FileData + + LockMutex(CurrentListMutex) + For n = 1 To Length + TmpIndex = Index + n - 1 + If TmpIndex >= 0 And TmpIndex < ListSize(CurrentList()) + SelectElement(CurrentList(), TmpIndex) + + ; On demande au cache de préparer le fichier / Ask the cache to prepare the file + *Ptr = Cache::GetFileDataFromCache(CurrentList()) + ; On lie le pointeur de données à l'index de la vignette / Link the data pointer to the thumbnail index + Thumbs::AddImageToThumb(GadgetId, TmpIndex, *Ptr) + EndIf + Next + Thumbs::LimitIndex(GadgetId, ListSize(CurrentList())) + UnlockMutex(CurrentListMutex) +EndProcedure + + +; 2. Créez votre fenêtre et le gadget / Create your window and the gadget +If OpenWindow(0, 0, 0, 800, 600, "Exemple de Vignettes / Thumbnail Example", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_SizeGadget) + + ; Créez le gadget en spécifiant la taille des vignettes et la fonction de rappel + ; Create the gadget, specifying the thumbnail size and the callback function + Thumbs::ThumbsGadget(1, 0, 0, WindowWidth(0), WindowHeight(0), 128, @CallBackLoadFiles()) + + ; 3. Remplissez votre liste de chemins d'images / Populate your list of image paths + LockMutex(CurrentListMutex) + ExamineDirectory(0, "C:\Your\Images", "*.jpg") + While NextDirectoryEntry(0) + AddElement(CurrentList()) + CurrentList() = "C:\Your\Images\" + DirectoryEntryName(0) + Wend + FinishDirectory(0) + UnlockMutex(CurrentListMutex) + + ; 4. Forcez la mise à jour pour afficher les nouvelles images / Force an update to display the new images + Thumbs::ForceUpdate(1) + + ; Boucle d'événements classique / Classic event loop + Repeat + Select WaitWindowEvent() + Case #PB_Event_CloseWindow + Quit = 1 + Case #PB_Event_SizeWindow + ResizeGadget(1, 0, 0, WindowWidth(0), WindowHeight(0)) + EndSelect + Until Quit = 1 + + Thumbs::FreeThumbsGadget(1) +EndIf +``` + +----- + +## Auteur / Author ✍️ + + * **Thyphoon** + +## Licence / License 📜 + +Ce projet est distribué sous une licence libre et non restrictive. Vous pouvez l'utiliser, le modifier et le distribuer sans contrainte. Un crédit est apprécié mais non obligatoire. + +*This project is distributed under a free and unrestricted license. You can use, modify, and distribute it without constraints. Credit is appreciated but not required.* \ No newline at end of file diff --git a/Thumbnails.pb b/Thumbnails.pb new file mode 100644 index 0000000..7cfbf01 --- /dev/null +++ b/Thumbnails.pb @@ -0,0 +1,1152 @@ +; ******************************************************************** +; Program: Thumbnails +; Description: add a Thumbnails to select image / Ajoute un visualiseur de vignettes pour sélectionner une image +; Version: 8.5 +; Author: Thyphoon +; Date: September, 2025 +; License: Free, unrestricted, credit appreciated but not required. / Gratuit, sans restriction, un crédit est apprécié mais non requis. +; Note: Please share improvement ! / N'hésitez pas à partager les améliorations ! +; +; ******************************************************************** + +; Check if the thread-safe option is enabled in the compiler. / Vérifie si l'option "thread-safe" est activée dans le compilateur. +; This is crucial because the program uses multiple threads for loading images. / C'est crucial car le programme utilise plusieurs threads pour le chargement des images. +CompilerIf #PB_Compiler_Thread = #False + CompilerError("You must enable Compiler threadsafe") ; English: You must enable Compiler threadsafe / Français: Vous devez activer l'option "threadsafe" du compilateur + End +CompilerEndIf + +; ================================================= +;- Module Helpers +; +; Contains various utility functions. / Contient des fonctions utilitaires diverses. +; ================================================= +DeclareModule Helpers + Declare.i Min(a.i, b.i) ; Declaration of the Min function. / Déclaration de la fonction Min. + Declare.i Max(a.i, b.i) ; Declaration of the Max function. / Déclaration de la fonction Max. +EndDeclareModule + +Module Helpers + ; Procedure: Min - Returns the smaller of two integers. / Procédure : Min - Retourne le plus petit de deux entiers. + Procedure.i Min(a.i, b.i) + If a < b + ProcedureReturn a + Else + ProcedureReturn b + EndIf + EndProcedure + + ; Procedure: Max - Returns the larger of two integers. / Procédure : Max - Retourne le plus grand de deux entiers. + Procedure.i Max(a.i, b.i) + If a > b + ProcedureReturn a + Else + ProcedureReturn b + EndIf + EndProcedure +EndModule + +; This block ensures the Core module is defined only once. / Ce bloc s'assure que le module Core n'est défini qu'une seule fois. +CompilerIf Not Defined(Core, #PB_Module) + DeclareModule Core + ; Structure to hold data for each file/image. / Structure pour contenir les données de chaque fichier/image. + Structure FileData + FilePath.s ; Full path to the image file. / Chemin complet du fichier image. + Selected.b ; Flag to indicate if the thumbnail is selected (True/False). / Indicateur pour savoir si la vignette est sélectionnée (Vrai/Faux). + State.b ; Loading state of the image: / État de chargement de l'image : + ; 0 = Not Loaded / Non chargée + ; 1 = Loaded into memory but not displayed / Chargée en mémoire mais pas affichée + ; 2 = Currently displayed on screen / Actuellement affichée à l'écran + Image.i ; The PureBasic image handle (ID). / L'identifiant (handle) de l'image PureBasic. + ; You can add any other custom data below. / Vous pouvez ajouter d'autres données personnalisées ci-dessous. + Map MetaData.s() ; A map to store additional metadata (e.g., EXIF info). / Une map pour stocker des métadonnées additionnelles (ex: infos EXIF). + EndStructure + EndDeclareModule + + Module Core + ; The module itself is empty, it's just used for defining the shared structure. / Le module est vide, il sert juste à définir la structure partagée. + EndModule +CompilerEndIf + +; ================================================= +;- Module ImgTools +; +; Contains tools for image manipulation. / Contient des outils pour la manipulation d'images. +; ================================================= +DeclareModule ImgTools + ; Structure to define the position and dimensions for displaying an image. / Structure pour définir la position et les dimensions d'affichage d'une image. + Structure DefDisplayImage + X.l + Y.l + Width.l + Height.l + EndStructure + + ; Enumeration for different image scaling styles. / Énumération pour les différents styles de mise à l'échelle de l'image. + Enumeration + #Image_Style_Fit ; The image is resized to fit entirely within the container, preserving aspect ratio. May leave empty bands. / L'image est redimensionnée pour être entièrement visible, en conservant les proportions. Peut laisser des bandes vides. + #Image_Style_Fill ; The image is resized to completely cover the container, preserving aspect ratio. May be cropped. / L'image est redimensionnée pour couvrir entièrement le conteneur, en conservant les proportions. Peut être rognée. + #Image_Style_Stretch ; The image is stretched to fill the container exactly, ignoring aspect ratio. / L'image est étirée pour remplir exactement le conteneur, sans conserver les proportions. + EndEnumeration + + ; Declaration of the function that calculates how to fit an image into a container. / Déclaration de la fonction qui calcule comment ajuster une image dans un conteneur. + Declare ImageToContainer(*result.DefDisplayImage, Image, ContainerWidth.l, ContainerHeight.l, Style.l = #Image_Style_Fit) +EndDeclareModule + +Module ImgTools + ; Procedure: ImageToContainer - Calculates the optimal dimensions and position for an image within a given container size and style. / Procédure : ImageToContainer - Calcule les dimensions et la position optimales d'une image dans un conteneur donné, selon un style précis. + Procedure ImageToContainer(*result.DefDisplayImage, Image.i, ContainerWidth.l, ContainerHeight.l, Style.l = #Image_Style_Fit) + + If IsImage(Image) And ImageHeight(Image) > 0 And ContainerHeight > 0 + Protected ImgRatio.f + Protected ContRatio.f + + ; Using floating-point numbers (.f) for more precise ratio calculation. / Utilisation de nombres à virgule flottante (.f) pour un calcul de ratio plus précis. + ImgRatio.f = ImageWidth(Image) / ImageHeight(Image) + ContRatio.f = ContainerWidth / ContainerHeight + + Select Style + Case #Image_Style_Fit + ; The image must be fully visible, potentially leaving empty bands. / L'image doit être entièrement visible, quitte à laisser des bandes vides. + If ImgRatio > ContRatio ; The image is "wider" than the container. / L'image est plus "large" que le conteneur. + *result\Width = ContainerWidth + *result\Height = ImageHeight(Image) * ContainerWidth / ImageWidth(Image) + *result\X = 0 + *result\Y = (ContainerHeight - *result\Height) / 2 ; Center vertically. / Centrer verticalement. + Else ; The image is "taller" than the container. / L'image est plus "haute" que le conteneur. + *result\Width = ImageWidth(Image) * ContainerHeight / ImageHeight(Image) + *result\Height = ContainerHeight + *result\X = (ContainerWidth - *result\Width) / 2 ; Center horizontally. / Centrer horizontalement. + *result\Y = 0 + EndIf + + Case #Image_Style_Fill + ; The image must completely cover the container, potentially being cropped. / L'image doit couvrir entièrement le conteneur, quitte à être rognée. + If ImgRatio > ContRatio ; The image is "wider" than the container. / L'image est plus "large" que le conteneur. + *result\Width = ImageWidth(Image) * ContainerHeight / ImageHeight(Image) + *result\Height = ContainerHeight + *result\X = (ContainerWidth - *result\Width) / 2 ; Center horizontally. / Centrer horizontalement. + *result\Y = 0 + Else ; The image is "taller" than the container. / L'image est plus "haute" que le conteneur. + *result\Width = ContainerWidth + *result\Height = ImageHeight(Image) * ContainerWidth / ImageWidth(Image) + *result\X = 0 + *result\Y = (ContainerHeight - *result\Height) / 2 ; Center vertically. / Centrer verticalement. + EndIf + + Case #Image_Style_Stretch + ; The image is stretched to fill the container exactly, without preserving proportions. / L'image est étirée pour remplir exactement le conteneur, sans conserver les proportions. + *result\X = 0 + *result\Y = 0 + *result\Width = ContainerWidth + *result\Height = ContainerHeight + EndSelect + EndIf + EndProcedure +EndModule + +; ================================================= +;- Module Cache +; +; Manages asynchronous loading and caching of images. / Gère le chargement asynchrone et la mise en cache des images. +; ================================================= +DeclareModule Cache + EnableExplicit + + ; A prototype for a callback function to load media. Allows custom loading logic. / Prototype pour une fonction de callback pour charger un média. Permet une logique de chargement personnalisée. + Prototype.i CallBackLoadMedia(*Ptr.Core::FileData) + + ; Main structure for cache parameters. / Structure principale pour les paramètres du cache. + Structure Param + CallBackLoadMedia.CallBackLoadMedia ; Pointer to the custom media loading function. / Pointeur vers la fonction de chargement de média personnalisée. + LoadListMutex.i ; Mutex to protect access to the LoadList. / Mutex pour protéger l'accès à la LoadList. + List LoadList.i() ; List of pointers to FileData that need to be loaded. / Liste des pointeurs vers les FileData à charger. + NewTaskSemaphore.i ; Semaphore to signal worker threads that new tasks are available. / Sémaphore pour signaler aux threads de travail que de nouvelles tâches sont disponibles. + CacheListMutex.i ; Mutex to protect access to the CacheList map. / Mutex pour protéger l'accès à la map CacheList. + Map CacheList.Core::FileData() ; Map storing all FileData, indexed by file path. This is the main cache. / Map stockant tous les FileData, indexés par chemin de fichier. C'est le cache principal. + Array WorkerThreads.i(1) ; Array to hold thread handles for the worker pool. / Tableau pour conserver les handles des threads du pool de travailleurs. + SignalMutex.i ; Mutex to protect access to the Signal flag. / Mutex pour protéger l'accès à l'indicateur Signal. + Signal.b ; A flag that is set to True when an image has been loaded, to notify the main thread. / Un indicateur mis à Vrai quand une image a été chargée, pour notifier le thread principal. + QuitMutex.i ; Mutex to protect access to the Quit flag. / Mutex pour protéger l'accès à l'indicateur Quit. + Quit.b ; A flag to signal all worker threads to terminate. / Un indicateur pour signaler à tous les threads de travail de se terminer. + EndStructure + + Global Param.Param ; Global instance of the parameter structure. / Instance globale de la structure de paramètres. + + Declare InitCache() ; Initializes the cache system. / Initialise le système de cache. + Declare SetCallBackLoadMedia(CallBackLoadMedia.i) ; Sets a custom media loading function. / Définit une fonction de chargement de média personnalisée. + Declare AddFileToLoadList(FilePath.s) ; Adds a file to the loading queue. / Ajoute un fichier à la file de chargement. + Declare CacheClean() ; Cleans the cache to free up memory. / Nettoie le cache pour libérer de la mémoire. + Declare.i GetFileDataFromCache(FilePath.s, Image.i = 0) ; Retrieves file data from the cache, or queues it for loading if not present. / Récupère les données d'un fichier depuis le cache, ou le met en file d'attente de chargement s'il n'est pas présent. + Declare.b GetSignalAndReset() ; Checks if a new image is ready and resets the signal. / Vérifie si une nouvelle image est prête et réinitialise le signal. + Declare QuitCache() ; Properly shuts down the cache system and all worker threads. / Arrête proprement le système de cache et tous les threads de travail. +EndDeclareModule + +Module Cache + EnableExplicit + + ; The constant is now private to the module, without 'Global'. / La constante est maintenant privée au module, sans 'Global'. + #WORKER_THREADS = 4 ; Defines the number of threads that will load images in the background. / Définit le nombre de threads qui chargeront les images en arrière-plan. + + ; Procedure: InitCache - Initializes all synchronization objects and the worker thread array. / Procédure : InitCache - Initialise tous les objets de synchronisation et le tableau des threads de travail. + Procedure InitCache() + Param\LoadListMutex = CreateMutex() + Param\CacheListMutex = CreateMutex() + Param\SignalMutex = CreateMutex() + Param\QuitMutex = CreateMutex() + Param\NewTaskSemaphore = CreateSemaphore(0) + Dim Param\WorkerThreads(#WORKER_THREADS - 1) + EndProcedure + + ; Procedure: SetCallBackLoadMedia - Allows setting a custom function for loading media. / Procédure : SetCallBackLoadMedia - Permet de définir une fonction personnalisée pour le chargement des médias. + Procedure SetCallBackLoadMedia(CallBackLoadMedia.i) + Param\CallBackLoadMedia = CallBackLoadMedia + EndProcedure + + ; Procedure: CacheWorkerThread - The main function for each worker thread. It waits for tasks and processes them. / Procédure : CacheWorkerThread - La fonction principale de chaque thread de travail. Il attend des tâches et les traite. + Procedure CacheWorkerThread(ThreadID.i) + Protected Quit.b = #False + Protected *Ptr.Core::FileData + + Repeat + ; Wait until the semaphore signals that a new task is available. This is very efficient as the thread sleeps until needed. / Attend que le sémaphore signale qu'une nouvelle tâche est disponible. C'est très efficace car le thread est en veille jusqu'à ce qu'il soit nécessaire. + WaitSemaphore(Param\NewTaskSemaphore) + + ; Safely check the global Quit flag. / Vérifie en toute sécurité l'indicateur global Quit. + LockMutex(Param\QuitMutex) + Quit = Param\Quit + UnlockMutex(Param\QuitMutex) + + If Quit = #False + ; Safely get the next file to load from the list. / Récupère de manière sécurisée le prochain fichier à charger de la liste. + LockMutex(Param\LoadListMutex) + If ListSize(Param\LoadList()) > 0 + FirstElement(Param\LoadList()) + *Ptr = Param\LoadList() + DeleteElement(Param\LoadList()) + Else + *Ptr = 0 + EndIf + UnlockMutex(Param\LoadListMutex) + + If *Ptr + ; Check if the image needs loading and the file exists. / Vérifie si l'image a besoin d'être chargée et si le fichier existe. + If *Ptr\Image = 0 And FileSize(*Ptr\FilePath) > 0 + Debug "Worker " + ThreadID + " loads: " + GetFilePart(*Ptr\FilePath) ; English: Worker X loads: Y / Français: Le travailleur X charge : Y + + If Param\CallBackLoadMedia <> 0 + ; Use the custom callback if it's provided. / Utilise le callback personnalisé s'il est fourni. + Param\CallBackLoadMedia(*Ptr) + Else + ; Otherwise, use the standard LoadImage function. / Sinon, utilise la fonction standard LoadImage. + *Ptr\Image = LoadImage(#PB_Any, *Ptr\FilePath) + EndIf + + If IsImage(*Ptr\Image) + Protected result.ImgTools::DefDisplayImage + ; Resize the loaded image to a thumbnail size to save memory and improve drawing speed. / Redimensionne l'image chargée à une taille de vignette pour économiser la mémoire et améliorer la vitesse de dessin. + ImgTools::ImageToContainer(@result, *Ptr\Image, 256, 256, ImgTools::#Image_Style_Fit) + ResizeImage(*Ptr\Image, result\Width, result\Height, #PB_Image_Smooth) + *Ptr\State = 1 ; State = 1 means "Loaded". / État = 1 signifie "Chargé". + + ; Safely set the signal to notify the main thread that a new image is ready for display. / Met à jour de manière sécurisée le signal pour notifier le thread principal qu'une nouvelle image est prête à être affichée. + LockMutex(Param\SignalMutex) + Param\Signal = #True + UnlockMutex(Param\SignalMutex) + Else + Debug "Loading ERROR: " + *Ptr\FilePath ; English: Loading ERROR / Français: ERREUR de chargement + ; Create a red placeholder image to indicate a loading error. / Crée une image de remplacement rouge pour indiquer une erreur de chargement. + *Ptr\Image = CreateImage(#PB_Any, 256, 256, 24, RGB(255, 0, 0)) + *Ptr\State = 1 + EndIf + EndIf + EndIf + EndIf + Until Quit = #True + + Debug "Cache Worker " + ThreadID + " finished." ; English: Cache Worker X finished. / Français: Le travailleur du cache X a terminé. + EndProcedure + + ; Procedure: StartWorkers - Creates and starts the pool of worker threads if they are not already running. / Procédure : StartWorkers - Crée et démarre le pool de threads de travail s'ils ne sont pas déjà en cours d'exécution. + Procedure StartWorkers() + If IsThread(Param\WorkerThreads(0)) = #False + Debug "Starting pool of " + #WORKER_THREADS + " threads." ; English: Starting pool of X threads. / Français: Démarrage du pool de X threads. + Protected i + For i = 0 To #WORKER_THREADS - 1 + Param\WorkerThreads(i) = CreateThread(@CacheWorkerThread(), i) + Next + EndIf + EndProcedure + + ; Procedure: AddFileToLoadList - Adds a file to the queue to be loaded by a worker thread. / Procédure : AddFileToLoadList - Ajoute un fichier à la file d'attente pour être chargé par un thread de travail. + Procedure AddFileToLoadList(FilePath.s) + Protected *Ptr + LockMutex(Param\CacheListMutex) + ; Check if the file is not already in our cache list. / Vérifie si le fichier n'est pas déjà dans notre liste de cache. + If FindMapElement(Param\CacheList(), FilePath) = #False + ; If not, add it to the main cache map. / Sinon, l'ajoute à la map principale du cache. + *Ptr = AddMapElement(Param\CacheList(), FilePath) + Param\CacheList()\FilePath = FilePath + Param\CacheList()\State = 0 ; State = "Not Loaded" / État = "Non chargé" + UnlockMutex(Param\CacheListMutex) + + ; Then, add a pointer to this new entry to the loading list. / Ensuite, ajoute un pointeur vers cette nouvelle entrée dans la liste de chargement. + LockMutex(Param\LoadListMutex) + AddElement(Param\LoadList()) + Param\LoadList() = *Ptr + UnlockMutex(Param\LoadListMutex) + + ; Signal one sleeping worker thread that a new job is available. / Signale à un thread de travail en attente qu'un nouveau travail est disponible. + SignalSemaphore(Param\NewTaskSemaphore) + Else + ; If it was already in the map, do nothing. / S'il était déjà dans la map, ne rien faire. + UnlockMutex(Param\CacheListMutex) + EndIf + EndProcedure + + ; Procedure: GetFileDataFromCache - Main interface to get image data. / Procédure : GetFileDataFromCache - Interface principale pour obtenir les données d'une image. + Procedure.i GetFileDataFromCache(FilePath.s, Image.i = 0) + Protected *Ptr.core::FileData + + ; Safely check if the file data already exists in the cache. / Vérifie de manière sécurisée si les données du fichier existent déjà dans le cache. + LockMutex(Param\CacheListMutex) + *Ptr = FindMapElement(Param\CacheList(), FilePath) + UnlockMutex(Param\CacheListMutex) + + If *Ptr = 0 + ; If it doesn't exist, we need to create it and queue it for loading. / S'il n'existe pas, nous devons le créer et le mettre en file d'attente de chargement. + StartWorkers() ; Make sure the workers are running. / S'assure que les travailleurs sont en cours d'exécution. + + LockMutex(Param\CacheListMutex) + *Ptr = AddMapElement(Param\CacheList(), FilePath) + *Ptr\FilePath = FilePath + + If Image = 0 ; If no pre-existing image is provided. / Si aucune image préexistante n'est fournie. + *Ptr\State = 0 ; "Not Loaded" / "Non chargé" + *Ptr\Image = 0 + + ; Add the new entry to the loading queue. / Ajoute la nouvelle entrée à la file de chargement. + LockMutex(Param\LoadListMutex) + AddElement(Param\LoadList()) + Param\LoadList() = *Ptr + UnlockMutex(Param\LoadListMutex) + SignalSemaphore(Param\NewTaskSemaphore) ; Wake up a worker. / Réveille un travailleur. + Else + ; If an image is provided directly, just store it. / Si une image est fournie directement, il suffit de la stocker. + *Ptr\State = 1 ; "Loaded" / "Chargé" + *Ptr\Image = Image + EndIf + UnlockMutex(Param\CacheListMutex) + EndIf + + ProcedureReturn *Ptr ; Return the pointer to the FileData structure. / Retourne le pointeur vers la structure FileData. + EndProcedure + + ; Procedure: QuitCache - Gracefully shuts down all worker threads and frees resources. / Procédure : QuitCache - Arrête proprement tous les threads de travail et libère les ressources. + Procedure QuitCache() + Debug "Stopping the cache..." ; English: Stopping the cache... / Français: Arrêt du cache... + If IsThread(Param\WorkerThreads(0)) + ; Set the Quit flag so threads know they should exit. / Met l'indicateur Quit à vrai pour que les threads sachent qu'ils doivent se terminer. + LockMutex(Param\QuitMutex) + Param\Quit = #True + UnlockMutex(Param\QuitMutex) + + ; Signal all worker threads to wake them up from WaitSemaphore. / Signale tous les threads de travail pour les réveiller de leur WaitSemaphore. + Protected i + For i = 0 To #WORKER_THREADS - 1 + SignalSemaphore(Param\NewTaskSemaphore) + Next + + ; Wait for each thread to finish its execution. / Attend que chaque thread ait terminé son exécution. + For i = 0 To #WORKER_THREADS - 1 + If IsThread(Param\WorkerThreads(i)) + WaitThread(Param\WorkerThreads(i)) + EndIf + Next + Debug "All workers have been stopped." ; English: All workers have been stopped. / Français: Tous les workers sont arrêtés. + EndIf + + ; Free all synchronization objects. / Libère tous les objets de synchronisation. + FreeMutex(Param\LoadListMutex) + FreeMutex(Param\CacheListMutex) + FreeMutex(Param\SignalMutex) + FreeMutex(Param\QuitMutex) + FreeSemaphore(Param\NewTaskSemaphore) + EndProcedure + + ; Procedure: GetSignalAndReset - Atomically gets the value of the Signal flag and resets it to False. / Procédure : GetSignalAndReset - Obtient de manière atomique la valeur de l'indicateur Signal et le remet à Faux. + Procedure.b GetSignalAndReset() + Protected Signal.b + LockMutex(Param\SignalMutex) + Signal = Param\Signal + Param\Signal = #False + UnlockMutex(Param\SignalMutex) + ProcedureReturn Signal + EndProcedure + + ; Procedure: Free - (Not used in the corrected code) - Frees memory associated with a FileData structure. / Procédure : Free - (Non utilisée dans le code corrigé) - Libère la mémoire associée à une structure FileData. + Procedure Free(*Ptr.core::FileData) + If IsImage(*Ptr\Image): FreeImage(*Ptr\Image): EndIf + FreeMap(*Ptr\MetaData()) + EndProcedure + + ; Procedure: CacheClean - Iterates through the cache and unloads images that are no longer needed to save memory. / Procédure : CacheClean - Parcourt le cache et décharge les images qui ne sont plus nécessaires pour économiser la mémoire. + Procedure CacheClean() + Protected *Ptr.core::FileData + LockMutex(Param\CacheListMutex) + ForEach Param\CacheList() + If MapSize(Param\CacheList()) < 500 ; Limit to start cleaning / Limite pour commencer le nettoyage + Break ; Stop cleaning if the cache size is below the threshold. / Arrête le nettoyage si la taille du cache est en dessous du seuil. + Else + *Ptr = Param\CacheList() + ; We only clean images that are loaded (State=1) and not selected. / On ne nettoie que les images chargées (State=1) et non sélectionnées. + If *Ptr And *Ptr\State = 1 And *Ptr\Selected = #False + Debug "Cleaning image: " + GetFilePart(*Ptr\FilePath) ; English: Cleaning image: / Français: Nettoyage image : + + ; --- CORRECTION --- + ; Instead of destroying the structure, we only free the image / Au lieu de détruire la structure, on libère seulement l'image + ; and reset the state. The pointer remains valid. / et on réinitialise l'état. Le pointeur reste valide. + If IsImage(*Ptr\Image) + FreeImage(*Ptr\Image) + *Ptr\Image = 0 + *Ptr\State = 0 ; State = "Not Loaded" / État = "Non chargé" + EndIf + ; The old "Free(*Ptr)" and "DeleteMapElement()" lines are removed / Les anciennes lignes "Free(*Ptr)" et "DeleteMapElement()" sont supprimées + ; because they caused the crash. / car elles causaient le crash. + EndIf + EndIf + Next + UnlockMutex(Param\CacheListMutex) + EndProcedure +EndModule + +; ================================================= +;- Module Thumbs +; +; Manages the thumbnail display gadget, including drawing and event handling. / Gère le gadget d'affichage des vignettes, y compris le dessin et la gestion des événements. +; ================================================= +DeclareModule Thumbs + Declare SetCallBackLoadFromIndex(GadgetId.i, CallBackLoadFromIndex.i) ; Sets the callback function used to request image data. / Définit la fonction de callback utilisée pour demander les données des images. + Declare AddImageToThumb(GadgetId.i, Index.i, *Ptr) ; Associates image data with a specific thumbnail index. / Associe des données d'image à un index de vignette spécifique. + Declare LimitIndex(GadgetId.i, IndexMax.i = -1) ; Sets the maximum scrollable index. / Définit l'index maximal de défilement. + Declare ThumbsGadget(GadgetId.i, X.l, Y.l, Width.l, Height.l, Size.l, CallBack.i = 0) ; Creates the thumbnail gadget. / Crée le gadget de vignettes. + Declare FreeThumbsGadget(GadgetId.i) ; Frees all resources associated with the gadget. / Libère toutes les ressources associées au gadget. + Declare ForceUpdate(GadgetId.i) ; Forces a complete refresh of the gadget. / Force un rafraîchissement complet du gadget. +EndDeclareModule + +Module Thumbs + EnableExplicit + UseModule Helpers + + ; Prototype for the callback function that loads data based on an index. / Prototype de la fonction de callback qui charge les données à partir d'un index. + Prototype CallBackLoadFromIndex(GadgetId.i, Index.i, Length.l) + + ; Structure to hold all data and state for a single thumbnail gadget instance. / Structure pour contenir toutes les données et l'état d'une instance unique du gadget de vignettes. + Structure Gdt + GadgetId.i ; The Canvas Gadget ID. / L'ID du gadget Canvas. + ; Unused buffer variables / Variables de buffer non utilisées + BufferCanvasImage.i[1] + SelectedBufferCanvasImage.b + + Size.l ; The base size (width and height) for each thumbnail. / La taille de base (largeur et hauteur) de chaque vignette. + Index.i ; The top-left thumbnail index currently being displayed. / L'index de la vignette en haut à gauche actuellement affichée. + OldIndex.i ; The last index, used to clean up old items. / Le dernier index, utilisé pour nettoyer les anciens éléments. + IndexMax.i ; -1 for infinity, otherwise the maximum index to limit scrolling. / -1 pour infini, sinon l'index maximum pour limiter le défilement. + NbH.l ; Number of horizontal thumbnails. / Nombre de vignettes horizontales. + NbV.l ; Number of vertical thumbnails. / Nombre de vignettes verticales. + + ; Scroll related variables / Variables liées au défilement + StartScroll.b ; #True if the user is currently dragging the scrollbar. / #True si l'utilisateur est en train de faire glisser la barre de défilement. + CursorStartY.l ; The initial Y position of the cursor when scrolling starts. / La position Y initiale du curseur lorsque le défilement commence. + CursorDeltaY.l ; The difference between the current and starting cursor Y position. / La différence entre la position Y actuelle et de départ du curseur. + ThumbsDeltaY.l ; The pixel offset for smooth vertical scrolling of thumbnails. / Le décalage en pixels pour un défilement vertical fluide des vignettes. + ZoneClick.l ; Area where the user clicked: 1 = ScrollBar, 2 = Thumbs. / Zone où l'utilisateur a cliqué : 1 = Barre de défilement, 2 = Vignettes. + LastIndexSelected.i ; The index of the last item that was clicked/selected. / L'index du dernier élément qui a été cliqué/sélectionné. + + ; DPI Aware Values (scaled for high-resolution displays) / Valeurs compatibles DPI (mises à l'échelle pour les écrans haute résolution) + _GadgetWidth.l ; Scaled gadget width. / Largeur du gadget mise à l'échelle. + _GadgetHeight.l ; Scaled gadget height. / Hauteur du gadget mise à l'échelle. + _Size.l ; Scaled thumbnail size. / Taille de la vignette mise à l'échelle. + _ScrollWidth.l ; Scaled scrollbar width. / Largeur de la barre de défilement mise à l'échelle. + _ScrollHeight.l ; Scaled scrollbar handle height. / Hauteur de la poignée de la barre de défilement mise à l'échelle. + _ThumbsWidth.l ; Scaled width of the thumbnail area. / Largeur de la zone des vignettes mise à l'échelle. + _ThumbsHeight.l ; Scaled height of the thumbnail area. / Hauteur de la zone des vignettes mise à l'échelle. + _MarginH.l ; Scaled horizontal margin between thumbnails. / Marge horizontale mise à l'échelle entre les vignettes. + _MarginV.l ; Scaled vertical margin between thumbnails. / Marge verticale mise à l'échelle entre les vignettes. + _TimeLineWidth.l ; Scaled width of the timeline area on the right. / Largeur de la zone de la timeline à droite mise à l'échelle. + + ThumbPointerlistMutex.i ; Mutex to protect access to the ThumbPointerList map. / Mutex pour protéger l'accès à la map ThumbPointerList. + Map ThumbPointerList.i() ; Map associating an index (as a string) to a pointer of its FileData. / Map associant un index (en chaîne de caractères) à un pointeur vers son FileData. + + ; Callback related variables / Variables liées au callback + LoadFromIndexInitialized.b; #True if the callback is set, otherwise #False. / #True si le callback est défini, sinon #False. + CallBackLoadFromIndex.CallBackLoadFromIndex ; Pointer to the callback function. / Pointeur vers la fonction de callback. + CallBackUpdateThread.i ; Thread handle for the callback updater thread. / Handle du thread pour le thread de mise à jour du callback. + CallBackMutex.i ; Mutex to protect callback-related variables. / Mutex pour protéger les variables liées au callback. + CallBackIndex.i ; The starting index for the next data load request. / L'index de départ pour la prochaine demande de chargement de données. + CallBackNbThumbs.l ; The number of thumbnails to request. / Le nombre de vignettes à demander. + CallBackNeedReload.b ; Flag indicating that new data needs to be loaded. / Indicateur signalant que de nouvelles données doivent être chargées. + + DrawSemaphore.i ; Semaphore to signal the drawing thread to redraw the canvas. / Sémaphore pour signaler au thread de dessin de redessiner le canvas. + Quit.b ; Flag to signal the background threads to terminate. / Indicateur pour signaler aux threads d'arrière-plan de se terminer. + ThreadDrawCanvasImage.i ; Thread handle for the canvas drawing thread. / Handle du thread pour le thread de dessin du canvas. + EndStructure + + ; Global structure for the Thumbs module. / Structure globale pour le module Thumbs. + Structure param + Map Gdt.Gdt() ; A map to store Gdt data for each gadget, indexed by gadget ID. / Une map pour stocker les données Gdt de chaque gadget, indexée par l'ID du gadget. + DrawAlphaImageMutex.i ; (Unused in current logic) Mutex for drawing. / (Inutilisé dans la logique actuelle) Mutex pour le dessin. + EndStructure + + Global param.param + param\DrawAlphaImageMutex = CreateMutex() + + ; Procedure: InitGadgetValue - Recalculates all DPI-aware dimensions and layout values for the gadget. / Procédure : InitGadgetValue - Recalcule toutes les dimensions et valeurs de disposition compatibles DPI pour le gadget. + Procedure InitGadgetValue(GadgetId.i) + Protected *Gdt.Gdt + *Gdt = GetGadgetData(GadgetId) + *Gdt\_GadgetWidth = DesktopScaledX(GadgetWidth(GadgetId)) + *Gdt\_GadgetHeight = DesktopScaledY(GadgetHeight(GadgetId)) + *Gdt\_Size = DesktopScaledX(*Gdt\Size) + *Gdt\_ScrollWidth = DesktopScaledX(25) + *Gdt\_ScrollHeight = *Gdt\_ScrollWidth * 2 + *Gdt\_TimeLineWidth = DesktopScaledX(50) + *Gdt\_ThumbsWidth.l = *Gdt\_GadgetWidth - *Gdt\_ScrollWidth - *Gdt\_TimeLineWidth + *Gdt\_ThumbsHeight.l = *Gdt\_GadgetHeight + *Gdt\NbH.l = Int(*Gdt\_ThumbsWidth / *Gdt\_Size) + *Gdt\NbV.l = Int(*Gdt\_GadgetHeight / *Gdt\_Size) + *Gdt\_MarginH.l = (*Gdt\_ThumbsWidth - *Gdt\NbH * *Gdt\_Size) / (*Gdt\NbH + 1) + *Gdt\_MarginV.l = (*Gdt\_GadgetHeight - *Gdt\NbV * *Gdt\_Size) / (*Gdt\NbV + 1) + EndProcedure + + ; Procedure: AddImageToThumb - Adds or updates the pointer for a given index in the thumbnail list. / Procédure : AddImageToThumb - Ajoute ou met à jour le pointeur pour un index donné dans la liste des vignettes. + Procedure AddImageToThumb(GadgetId.i, Index.i, *Ptr.core::FileData) + If *Ptr > 0 + Debug "Add " + Str(Index) + " " + GetFilePart(*Ptr\FilePath) ; English: Add index path / Français: Ajoute index chemin + Else + Debug "Add " + Str(Index) + " - - - " + EndIf + Protected *Gdt.gdt + *Gdt = GetGadgetData(GadgetId) + LockMutex(*Gdt\ThumbPointerlistMutex) + AddMapElement(*Gdt\ThumbPointerlist(), Str(Index)) + *Gdt\ThumbPointerlist() = *Ptr + UnlockMutex(*Gdt\ThumbPointerlistMutex) + EndProcedure + + ; Procedure: UpdateIndexs - Called when scrolling occurs. It calculates which new thumbnails need to be loaded. / Procédure : UpdateIndexs - Appelée lors du défilement. Elle calcule quelles nouvelles vignettes doivent être chargées. + Procedure UpdateIndexs(GadgetId.i) + Protected *Gdt.gdt + Protected *Ptr.core::FileData + Static counter.l + *Gdt = GetGadgetData(GadgetId) + If *Gdt\LoadFromIndexInitialized = #True + Debug "COUNTER=" + Str(counter) + counter = counter + 1 + Protected Index.i = *Gdt\Index - *Gdt\Nbh ; Calculate the starting index for loading (one row above visible area). / Calcule l'index de départ pour le chargement (une rangée au-dessus de la zone visible). + Protected NThumbs.l = (*Gdt\NbV + 2) * *Gdt\Nbh ; Number of thumbs that must be loaded (visible rows + one above and one below). / Nombre de vignettes qui doivent être chargées (rangées visibles + une au-dessus et une en dessous). + + ; Clean up indexes that are no longer needed (this part of the logic seems complex and might be simplified). / Nettoie les index qui ne sont plus nécessaires (cette partie de la logique semble complexe et pourrait être simplifiée). + LockMutex(*Gdt\ThumbPointerlistMutex) + Protected.l DeltaIndex = Index - *Gdt\OldIndex + *Gdt\OldIndex = Index + Protected n.l + For n = 0 To Abs(DeltaIndex) + If DeltaIndex > 0 ; Scrolling down. / Défilement vers le bas. + If FindMapElement(*Gdt\ThumbPointerList(), Str(*Gdt\OldIndex + DeltaIndex)) + DeleteMapElement(*Gdt\ThumbPointerList()) + EndIf + ElseIf DeltaIndex < 0 ; Scrolling up. / Défilement vers le haut. + If FindMapElement(*Gdt\ThumbPointerList(), Str(*Gdt\OldIndex + NThumbs - DeltaIndex)) + DeleteMapElement(*Gdt\ThumbPointerList()) + EndIf + EndIf + Next + UnlockMutex(*Gdt\ThumbPointerlistMutex) + + ; Set parameters for the callback thread to load new data. / Définit les paramètres pour que le thread de callback charge de nouvelles données. + LockMutex(*Gdt\CallBackMutex) + *Gdt\CallBackIndex = Index + DeltaIndex + *Gdt\CallBackNbThumbs = NThumbs - DeltaIndex + *Gdt\CallBackNeedReload = #True + UnlockMutex(*Gdt\CallBackMutex) + EndIf + EndProcedure + + ; Procedure: UpdateImage2Index - A background thread that waits for a reload signal and then calls the main callback function. / Procédure : UpdateImage2Index - Un thread d'arrière-plan qui attend un signal de rechargement puis appelle la fonction de callback principale. + Procedure UpdateImage2Index(GadgetId.i) + Protected Index.i, Thumbs.l + Protected NeedReload.b = #False + Protected *Gdt.gdt + *Gdt = GetGadgetData(GadgetId) + Repeat + ; Safely check if a reload is needed. / Vérifie de manière sécurisée si un rechargement est nécessaire. + LockMutex(*Gdt\CallBackMutex) + If *Gdt\CallBackNeedReload = #True + Index.i = *Gdt\CallBackIndex + Thumbs.l = *Gdt\CallBackNbThumbs + *Gdt\CallBackNeedReload = #False + NeedReload = #True + Else + NeedReload = #False + EndIf + UnlockMutex(*Gdt\CallBackMutex) + + If NeedReload = #True + If *Gdt\CallBackLoadFromIndex > 0 + ; Call the user-provided function to load the required range of thumbnails. / Appelle la fonction fournie par l'utilisateur pour charger la plage de vignettes requise. + *Gdt\CallBackLoadFromIndex(*Gdt\GadgetId, *Gdt\CallBackIndex, *Gdt\CallBackNbThumbs) + Debug "param\CallBackLoadFromIndex(" + Str(*Gdt\CallBackIndex) + "," + Str(*Gdt\CallBackNbThumbs) + ")" + Else + Delay(10) + Debug "No Set CallBackLoadFromIndex" ; English: No callback function set. / Français: Pas de fonction de callback définie. + EndIf + Else + Delay(50) ; Wait if there is nothing to do. / Attend s'il n'y a rien à faire. + EndIf + Until *Gdt\Quit = #True + EndProcedure + + ; Procedure: LimitIndex - Sets the maximum index for scrolling, based on the total number of items. / Procédure : LimitIndex - Définit l'index maximal pour le défilement, en fonction du nombre total d'éléments. + Procedure LimitIndex(GadgetId.i, IndexMax.i = -1) + Protected *Gdt.gdt + *Gdt = GetGadgetData(GadgetId) + If IndexMax >= 0 + *Gdt = GetGadgetData(GadgetId) + ; Calculate the maximum top-left index so the last row of thumbnails is visible at the bottom. / Calcule l'index maximal en haut à gauche pour que la dernière rangée de vignettes soit visible en bas. + IndexMax = Round((IndexMax / *Gdt\NbH), #PB_Round_Up) * *Gdt\NbH - (*Gdt\NbH * (*Gdt\NbV)) + *Gdt\IndexMax = IndexMax + If *Gdt\IndexMax < 0 + *Gdt\IndexMax = 0 + EndIf + Debug "IndexMax:" + Str(*Gdt\IndexMax) + ElseIf IndexMax = -1 + *Gdt\IndexMax = -1 ; No limit. / Pas de limite. + EndIf + EndProcedure + + ; Procedure: DrawCanvasImage - The main drawing thread. It runs in a loop to draw the gadget content. / Procédure : DrawCanvasImage - Le thread de dessin principal. Il s'exécute en boucle pour dessiner le contenu du gadget. + Procedure DrawCanvasImage(GadgetId.i) + Protected *Gdt.gdt + Protected CursorY.l + *Gdt = GetGadgetData(GadgetId) + Repeat + ; Apply smooth scrolling effect if the user is dragging. / Applique l'effet de défilement fluide si l'utilisateur fait un glisser-déposer. + If *Gdt\StartScroll = #True + *Gdt\ThumbsDeltaY = *Gdt\ThumbsDeltaY + (*Gdt\CursorDeltaY / 10) + EndIf + + ; Calculate the Y position of the scrollbar handle. / Calcule la position Y de la poignée de la barre de défilement. + CursorY = *Gdt\_GadgetHeight / 2 - *Gdt\_ScrollHeight - *Gdt\CursorDeltaY + ; Limit cursor position to top boundary. / Limite la position du curseur à la bordure supérieure. + If CursorY < 0 + CursorY = 0 + *Gdt\ThumbsDeltaY = *Gdt\_Size ; Fast scroll mode when hitting the boundary. / Mode de défilement rapide en atteignant la limite. + EndIf + + ; Limit cursor position to bottom boundary. / Limite la position du curseur à la bordure inférieure. + If CursorY > *Gdt\_GadgetHeight - *Gdt\_ScrollHeight + CursorY = *Gdt\_GadgetHeight - *Gdt\_ScrollHeight + *Gdt\ThumbsDeltaY = -*Gdt\_Size ; Fast scroll mode. / Mode de défilement rapide. + EndIf + + ; Check if the scroll delta has exceeded the size of a thumbnail, indicating we need to shift the index. / Vérifie si le delta de défilement a dépassé la taille d'une vignette, indiquant qu'il faut changer d'index. + Protected DeltaIndex.l + If *Gdt\ThumbsDeltaY >= *Gdt\_Size + DeltaIndex = Int(*Gdt\ThumbsDeltaY / *Gdt\_Size) * *Gdt\NbH + *Gdt\Index = *Gdt\Index - DeltaIndex ; Scrolling down, index decreases conceptually (this seems reversed, might be a logic choice). / Défilement vers le bas, l'index diminue conceptuellement (cela semble inversé, peut être un choix logique). + *Gdt\ThumbsDeltaY = *Gdt\ThumbsDeltaY % *Gdt\_Size + UpdateIndexs(GadgetId) ; Request new images. / Demande de nouvelles images. + EndIf + + If *Gdt\ThumbsDeltaY <= -*Gdt\_Size + DeltaIndex = Abs(Int(*Gdt\ThumbsDeltaY / *Gdt\_Size) * *Gdt\NbH) + *Gdt\Index = *Gdt\Index + DeltaIndex ; Scrolling up, index increases. / Défilement vers le haut, l'index augmente. + *Gdt\ThumbsDeltaY = *Gdt\ThumbsDeltaY % *Gdt\_Size + UpdateIndexs(GadgetId) + EndIf + + ; Limit scrolling to the boundaries (0 and IndexMax). / Limite le défilement aux bordures (0 et IndexMax). + If *Gdt\Index <= 0 + *Gdt\Index = 0 + If *Gdt\ThumbsDeltaY > 0: *Gdt\ThumbsDeltaY = 0: EndIf + EndIf + + If *Gdt\IndexMax > -1 + If *Gdt\Index >= *Gdt\IndexMax + *Gdt\Index = *Gdt\IndexMax + If *Gdt\ThumbsDeltaY < 0: *Gdt\ThumbsDeltaY = 0: EndIf + EndIf + EndIf + + Protected *Ptr.core::FileData + Protected State.l, Image.i, Selected.b, FileName.s + + ; Redraw the canvas only if signaled by the Cache module (new image ready) or by user interaction. / Redessine le canvas uniquement sur signal du module Cache (nouvelle image prête) ou par interaction de l'utilisateur. + If (Cache::GetSignalAndReset() = #True Or TrySemaphore(*Gdt\DrawSemaphore)) And StartVectorDrawing(CanvasVectorOutput(*Gdt\GadgetId)) + Debug "DRAW IMAGE" + VectorSourceColor(RGBA(128, 128, 128, 255)) + FillVectorOutput() ; Fill background. / Remplit l'arrière-plan. + + Protected ListIndex.l = -1 + Protected.l nx, ny, x, y + Protected i.i + ; Loop through all visible rows (-1 to NbV+1 to draw partial rows at top/bottom). / Boucle sur toutes les rangées visibles (-1 à NbV+1 pour dessiner les rangées partielles en haut/bas). + For ny = -1 To *Gdt\NbV + 1 + For nx = 0 To *Gdt\NbH - 1 + ListIndex = ListIndex + 1 + ; Calculate the position of the current thumbnail. / Calcule la position de la vignette actuelle. + x = nx * *Gdt\_Size + *Gdt\_MarginH * nx + (*Gdt\_MarginH) + y = ny * *Gdt\_Size + *Gdt\_MarginV * ny + (*Gdt\_MarginV) + *Gdt\ThumbsDeltaY + i = nx + ny * *Gdt\NbH + *Gdt\Index + + AddPathBox(x, y, *Gdt\_Size, *Gdt\_Size) ; Define the thumbnail area. / Définit la zone de la vignette. + VectorSourceColor(RGBA(100, 100, 100, 255)) ; Placeholder color. / Couleur de remplissage. + SaveVectorState() + ClipPath(#PB_Path_Preserve) ; Clip drawing to this box. / Limite le dessin à cette boîte. + FillPath() + + Selected = 0 + State = 0 + Image = -1 + FileName = "" + ; Get the data for this index from our local map. / Récupère les données pour cet index depuis notre map locale. + LockMutex(*Gdt\ThumbPointerlistMutex) + If FindMapElement(*Gdt\ThumbPointerList(), Str(i)) + *Ptr = *Gdt\ThumbPointerList() + Else + *Ptr = 0 + EndIf + UnlockMutex(*Gdt\ThumbPointerlistMutex) + + If *Ptr > 0 + If *Ptr\State > 0 And IsImage(*Ptr\Image) ; If image is loaded. / Si l'image est chargée. + *Ptr\State = 2 ; Set state to "Displayed". / Met l'état à "Affiché". + State = 2 + Image.i = *Ptr\Image + Selected = *Ptr\Selected + FileName.s = GetFilePart(*Ptr\FilePath) + + Protected result.ImgTools::DefDisplayImage + ImgTools::ImageToContainer(@result, Image, *Gdt\_Size, *Gdt\_Size, ImgTools::#Image_Style_Fit) + + Protected _Border.l, _BorderX2.l + ; Draw a green border if the item is selected. / Dessine une bordure verte si l'élément est sélectionné. + If Selected = 1 + AddPathBox(result\X + x, result\Y + y, result\Width, result\Height) + VectorSourceColor(RGBA(0, 255, 0, 255)) + FillPath() + _Border = DesktopScaledX(2) + _BorderX2 = _Border * 2 + Else + _Border = 0 + _BorderX2 = 0 + EndIf + + ; Draw the actual image inside the border. / Dessine l'image réelle à l'intérieur de la bordure. + AddPathBox(result\X + x + _Border, result\Y + y + _Border, result\Width - _BorderX2, result\Height - _BorderX2) + VectorSourceColor(RGBA(0, 0, 0, 255)) + FillPath() + MovePathCursor(result\X + x + _Border, result\Y + y + _Border) + DrawVectorImage(ImageID(Image), 255, result\Width - _BorderX2, result\Height - _BorderX2) + Else ; If image is not loaded yet, do nothing (just the placeholder background). / Si l'image n'est pas encore chargée, ne rien faire (juste le fond de remplissage). + EndIf + Else ; No data for this index yet. / Pas encore de données pour cet index. + EndIf + + ; Draw text overlay (index number and filename). / Dessine le texte en superposition (numéro d'index et nom de fichier). + VectorSourceColor(RGBA(255, 255, 255, 255)) + MovePathCursor(x + 5, y + 5) + DrawVectorText(Str(i) + " " + Filename) + RestoreVectorState() ; Restore clipping. / Restaure le clipping. + Next + Next + + ; Draw the scrollbar background and handle. / Dessine le fond et la poignée de la barre de défilement. + AddPathBox(*Gdt\_ThumbsWidth, 0, *Gdt\_ScrollWidth, *Gdt\_GadgetHeight): VectorSourceColor(RGBA(100, 100, 100, 255)): FillPath() + AddPathBox(*Gdt\_ThumbsWidth, CursorY, *Gdt\_ScrollWidth, *Gdt\_ScrollHeight): VectorSourceColor(RGBA(200, 200, 200, 255)): FillPath() + + ; Draw the timeline on the right side (static in this version). / Dessine la timeline sur le côté droit (statique dans cette version). + If *Gdt\_TimeLineWidth > 0 + Protected.l Date_Start = 1921 + Protected.l Date_End = 2021 + Protected.l TH = VectorTextHeight("Ty") + Protected.l Nb = *Gdt\_GadgetHeight / (Date_End - Date_Start) + Protected.l z, zy = 0 + For z = 0 To (Date_End - Date_Start) + zy = zy + TH + If zy >= TH * 2 + zy = 0 + VectorSourceColor(RGBA(255, 255, 255, 255)) + Protected date.s = Str(Date_End - z) + MovePathCursor(*Gdt\_GadgetWidth - VectorTextWidth(Date), z * TH) + DrawVectorText(date) + EndIf + Next + EndIf + + StopVectorDrawing() + EndIf + Delay(50) ; Wait before the next drawing cycle to avoid high CPU usage. / Attend avant le prochain cycle de dessin pour éviter une utilisation élevée du CPU. + Until *Gdt\Quit = #True + Debug "DrawCanvasImage " + Str(*Gdt\GadgetId) + " Say Bye Bye !" + EndProcedure + + ; Procedure: ThumbsEvent - The main event handler for the thumbnail gadget. / Procédure : ThumbsEvent - Le gestionnaire d'événements principal pour le gadget de vignettes. + Procedure ThumbsEvent() + Protected *Gdt.gdt + *Gdt = GetGadgetData(EventGadget()) + Protected.l Mx, My + Mx = GetGadgetAttribute(*Gdt\GadgetId, #PB_Canvas_MouseX) + My = GetGadgetAttribute(*Gdt\GadgetId, #PB_Canvas_MouseY) + + Select EventType() + Case #PB_EventType_KeyDown + Select GetGadgetAttribute(*Gdt\GadgetId, #PB_Canvas_Key) + Case #PB_Shortcut_Down ; Scroll down slightly. / Défilement léger vers le bas. + If *Gdt\Index < *Gdt\IndexMax + *Gdt\ThumbsDeltaY = *Gdt\ThumbsDeltaY - DesktopScaledY(5) + SignalSemaphore(*Gdt\DrawSemaphore) + EndIf + Case #PB_Shortcut_Up ; Scroll up slightly. / Défilement léger vers le haut. + If *Gdt\Index > 0 + *Gdt\ThumbsDeltaY = *Gdt\ThumbsDeltaY + DesktopScaledY(5) + SignalSemaphore(*Gdt\DrawSemaphore) + EndIf + Case #PB_Shortcut_PageDown ; Scroll down one page. / Défilement d'une page vers le bas. + If *Gdt\Index < *Gdt\IndexMax + *Gdt\ThumbsDeltaY = 0 + *Gdt\Index = *Gdt\Index + *Gdt\NbH + UpdateIndexs(*Gdt\GadgetId) + SignalSemaphore(*Gdt\DrawSemaphore) + EndIf + Case #PB_Shortcut_PageUp ; Scroll up one page. / Défilement d'une page vers le haut. + If *Gdt\Index > 0 + *Gdt\ThumbsDeltaY = 0 + *Gdt\Index = *Gdt\Index - *Gdt\NbH + UpdateIndexs(*Gdt\GadgetId) + SignalSemaphore(*Gdt\DrawSemaphore) + EndIf + EndSelect + Case #PB_EventType_Resize ; Window was resized. / La fenêtre a été redimensionnée. + InitGadgetValue(*Gdt\GadgetId) ; Recalculate dimensions. / Recalcule les dimensions. + UpdateIndexs(*Gdt\GadgetId) ; Reload data for the new layout. / Recharge les données pour la nouvelle disposition. + SignalSemaphore(*Gdt\DrawSemaphore) + Case #PB_EventType_LostFocus ; The gadget lost focus. / Le gadget a perdu le focus. + *Gdt\StartScroll = #False ; Stop scrolling. / Arrête le défilement. + *Gdt\CursorDeltaY = 0 + SignalSemaphore(*Gdt\DrawSemaphore) + Case #PB_EventType_MouseMove + If Mx > *Gdt\_ThumbsWidth ; If cursor is over the scrollbar area. / Si le curseur est sur la zone de la barre de défilement. + SetGadgetAttribute(*Gdt\GadgetId, #PB_Canvas_Cursor, #PB_Cursor_UpDown) + Else + SetGadgetAttribute(*Gdt\GadgetId, #PB_Canvas_Cursor, #PB_Cursor_Default) + EndIf + + If *Gdt\StartScroll = #True ; If dragging the scrollbar. / Si on fait glisser la barre de défilement. + *Gdt\CursorDeltaY = *Gdt\CursorStartY - GetGadgetAttribute(*Gdt\GadgetId, #PB_Canvas_MouseY) + SignalSemaphore(*Gdt\DrawSemaphore) + EndIf + Case #PB_EventType_LeftButtonDown + If Mx > *Gdt\_ThumbsWidth + *Gdt\ZoneClick = 1 ; Click in the scroll area. / Clic dans la zone de défilement. + If *Gdt\StartScroll = #False + *Gdt\CursorStartY = My + *Gdt\StartScroll = #True + EndIf + Else + *Gdt\ZoneClick = 2 ; Click in the thumbnail area. / Clic dans la zone des vignettes. + EndIf + Case #PB_EventType_LeftButtonUp + If *Gdt\StartScroll = #True + *Gdt\CursorStartY = 0 + *Gdt\StartScroll = #False + *Gdt\CursorDeltaY = 0 + SignalSemaphore(*Gdt\DrawSemaphore) + EndIf + Case #PB_EventType_LeftClick + If *Gdt\ZoneClick = 2 And *Gdt\StartScroll = #False + ; Calculate which thumbnail was clicked. / Calcule sur quelle vignette le clic a eu lieu. + Protected *Ptr.core::FileData + Protected nx.l = (mx - *Gdt\_MarginH) / (*Gdt\_Size + *Gdt\_MarginH) + Protected ny.l = (my - *Gdt\_MarginV - *Gdt\ThumbsDeltaY) / (*Gdt\_Size + *Gdt\_MarginV) + Protected index.l = nx + ny * *Gdt\NbH + *Gdt\Index + Protected i.i + LockMutex(*Gdt\ThumbPointerlistMutex) + + Protected Modifiers = GetGadgetAttribute(*Gdt\GadgetId, #PB_Canvas_Modifiers) + + Select Modifiers + Case #PB_Canvas_Shift ; Shift key is pressed for range selection. / La touche Maj est enfoncée pour une sélection multiple. + If *Gdt\LastIndexSelected <> -1 + Protected start.i = Helpers::Min(index, *Gdt\LastIndexSelected) + Protected endIndex.i = Helpers::Max(index, *Gdt\LastIndexSelected) + + ; First, deselect all. / D'abord, tout désélectionner. + ForEach *Gdt\ThumbPointerList() + *Ptr = *Gdt\ThumbPointerList() + If *Ptr > 0 : *Ptr\Selected = #False : EndIf + Next + + ; Then, select the entire range. / Ensuite, sélectionner toute la plage. + For i.i = start To endIndex + If FindMapElement(*Gdt\ThumbPointerList(), Str(i)) + *Ptr = *Gdt\ThumbPointerList() + If *Ptr > 0 : *Ptr\Selected = #True : EndIf + EndIf + Next + EndIf + Case #PB_Canvas_Control ; Control key is pressed to toggle selection. / La touche Ctrl est enfoncée pour basculer la sélection. + If FindMapElement(*Gdt\ThumbPointerList(), Str(index)) + *Ptr = *Gdt\ThumbPointerList() + If *Ptr > 0 + *Ptr\Selected = 1 - *Ptr\Selected ; Toggle 0 to 1 or 1 to 0. / Bascule de 0 à 1 ou de 1 à 0. + *Gdt\LastIndexSelected = index + EndIf + EndIf + Default ; Simple click: select one, deselect all others. / Clic simple : sélectionne un élément, désélectionne tous les autres. + ForEach *Gdt\ThumbPointerList() + *Ptr = *Gdt\ThumbPointerList() + If *Ptr > 0 : *Ptr\Selected = #False : EndIf + Next + + If FindMapElement(*Gdt\ThumbPointerList(), Str(index)) + *Ptr = *Gdt\ThumbPointerList() + If *Ptr > 0 + *Ptr\Selected = #True + *Gdt\LastIndexSelected = index + EndIf + EndIf + EndSelect + + UnlockMutex(*Gdt\ThumbPointerlistMutex) + SignalSemaphore(*Gdt\DrawSemaphore) + EndIf + EndSelect + EndProcedure + + ; Procedure: SetCallBackLoadFromIndex - Assigns the data loading callback function to the gadget. / Procédure : SetCallBackLoadFromIndex - Assigne la fonction de callback de chargement des données au gadget. + Procedure SetCallBackLoadFromIndex(GadgetId.i, CallBackLoadFromIndex.i) + If IsGadget(GadgetId) And GadgetType(GadgetId) = #PB_GadgetType_Canvas + Protected *Gdt.gdt + *Gdt = GetGadgetData(GadgetId) + *Gdt\CallBackLoadFromIndex = CallBackLoadFromIndex + *Gdt\LoadFromIndexInitialized = #True + Else + Debug "Gadget " + Str(GadgetId) + " Not Initialized Or Wrong Type Thumbs::SetCallBackLoadFromIndex()" + EndIf + EndProcedure + + ; Procedure: ThumbsGadget - The constructor for creating a new thumbnail gadget. / Procédure : ThumbsGadget - Le constructeur pour créer un nouveau gadget de vignettes. + Procedure ThumbsGadget(GadgetId.i, X.l, Y.l, Width.l, Height.l, Size.l, CallBack.i = 0) + Protected newGadgetId.i + + newGadgetId = CanvasGadget(GadgetId.i, X.l, Y.l, Width.l, Height.l, #PB_Canvas_Keyboard | #PB_Canvas_Border) + + If GadgetId = #PB_Any + GadgetId = newGadgetId + EndIf + Protected *Gdt.Gdt + *Gdt = AddMapElement(param\Gdt(), Str(GadgetId)) + + Debug *Gdt + If *Gdt + ; Initialize all resources for this gadget instance. / Initialise toutes les ressources pour cette instance de gadget. + *Gdt\DrawSemaphore = CreateSemaphore(1) + *Gdt\CallBackMutex = CreateMutex() + *Gdt\ThumbPointerlistMutex = CreateMutex() + *Gdt\GadgetId = GadgetId + *Gdt\Size = Size + *Gdt\LastIndexSelected = -1 ; Initialize with no selection. / Initialise sans sélection. + Debug *Gdt\Size + SetGadgetData(GadgetId, *Gdt) + InitGadgetValue(GadgetId) + If CallBack + SetCallBackLoadFromIndex(GadgetId, CallBack) + UpdateIndexs(GadgetId) ; Trigger initial load. / Déclenche le chargement initial. + EndIf + ; Start the two background threads for this gadget. / Démarre les deux threads d'arrière-plan pour ce gadget. + *Gdt\CallBackUpdateThread = CreateThread(@UpdateImage2Index(), GadgetId) + *Gdt\ThreadDrawCanvasImage = CreateThread(@DrawCanvasImage(), GadgetId) + BindGadgetEvent(GadgetId, @ThumbsEvent(), #PB_All) + Else + Debug "Error to Init ThumbsGadget" + EndIf + EndProcedure + + ; Procedure: FreeThumbsGadget - The destructor to clean up a gadget instance. / Procédure : FreeThumbsGadget - Le destructeur pour nettoyer une instance de gadget. + Procedure FreeThumbsGadget(GadgetId.i) + Protected *Gdt.gdt + If IsGadget(GadgetID) + *Gdt = GetGadgetData(GadgetId) + *Gdt\Quit = #True ; Signal threads to quit. / Signale aux threads de se terminer. + WaitThread(*Gdt\ThreadDrawCanvasImage) ; Wait for the drawing thread to finish. / Attend que le thread de dessin se termine. + FreeMutex(*Gdt\ThumbPointerListMutex) + WaitThread(*Gdt\CallBackUpdateThread) ; Wait for the callback thread to finish. / Attend que le thread de callback se termine. + FreeMutex(*Gdt\CallBackMutex) + FreeSemaphore(*Gdt\DrawSemaphore) + FreeMap(*Gdt\ThumbPointerList()) + DeleteMapElement(param\Gdt(), Str(GadgetId)) + EndIf + EndProcedure + + ; This procedure is commented out and not used in the final code. / Cette procédure est commentée et non utilisée dans le code final. + ; Procedure RemoveThumbPointerList(GadgetId.i,Index.i,Number.i) + ; Protected *Gdt.gdt + ; Protected *Ptr.core::FileData + ; Protected n.l + ; *Gdt=GetGadgetData(GadgetId) + ; LockMutex(*Gdt\ThumbPointerMutex) + ; For n=0 To Number + ; If SelectElement(*Gdt\ThumbPointerList(),Index) + ; *Ptr=*Gdt\ThumbPointer() + ; If *Ptr>0 + ; If *Ptr\State=2:*Ptr\State=1:EndIf + ; EndIf + ; DeleteElement(*Gdt\ThumbPointerList()) + ; EndIf + ; Next + ; LockMutex(*Gdt\ThumbPointerMutex) + ; EndProcedure + + ; Procedure: ForceUpdate - Clears the current list and forces a full reload of data. / Procédure : ForceUpdate - Vide la liste actuelle et force un rechargement complet des données. + Procedure ForceUpdate(GadgetId.i) + Protected *Gdt.gdt + Protected *Ptr.core::FileData + *Gdt = GetGadgetData(GadgetId) + *Gdt\Index = 0 ; Reset scroll position. / Réinitialise la position de défilement. + LockMutex(*Gdt\ThumbPointerlistMutex) + ForEach *Gdt\ThumbPointerList() + *Ptr = *Gdt\ThumbPointerList() + If *Ptr > 0 + If *Ptr\State = 2: *Ptr\State = 1: EndIf ; Image not Displayed / Image non affichée + DeleteMapElement(*Gdt\ThumbPointerList()) + EndIf + Next + UnlockMutex(*Gdt\ThumbPointerlistMutex) + UpdateIndexs(GadgetId) ; Trigger a reload from index 0. / Déclenche un rechargement à partir de l'index 0. + EndProcedure +EndModule + +; ================================================= +;- TEST PART +; +; This section contains example code to demonstrate how to use the Thumbs gadget. / Cette section contient un exemple de code pour démontrer comment utiliser le gadget Thumbs. +; It only runs when this file is the main compiled file. / Elle ne s'exécute que si ce fichier est le fichier principal compilé. +; ================================================= +CompilerIf #PB_Compiler_IsMainFile + + ; Enumeration for the main window and gadgets. / Énumération pour la fenêtre principale et les gadgets. + Enumeration + #Win_main + #Gdt_Nav + #Gdt_Folder + #Gdt_ThumbA + #Gdt_ThumbB + EndEnumeration + + Global NewList CurrentList.s() ; A global list to hold the file paths of the images to be displayed. / Une liste globale pour contenir les chemins des fichiers des images à afficher. + Global CurrentListMutex.i ; A mutex to protect access to this global list. / Un mutex pour protéger l'accès à cette liste globale. + + ; Procedure: CallBackLoadFromIndexB - This is the implementation of the callback function. / Procédure : CallBackLoadFromIndexB - C'est l'implémentation de la fonction de callback. + ; The Thumbs gadget calls this function when it needs image data for a specific range of indexes. / Le gadget Thumbs appelle cette fonction lorsqu'il a besoin des données d'image pour une plage d'index spécifique. + Procedure CallBackLoadFromIndexB(GadgetId.i, Index.i, Length.l) + Protected n.l + Protected TmpIndex.i + Protected relativeIndex.l + Protected *Ptr.Core::FileData + + Debug "CallBackLoadFromIndexB(" + Str(Index) + "," + Str(Length) + ")" + LockMutex(CurrentListMutex) + For n = 1 To Length + TmpIndex = Index + n - 1 + If TmpIndex >= 0 And TmpIndex < ListSize(CurrentList()) + SelectElement(CurrentList(), TmpIndex) + relativeIndex = relativeIndex + 1 + + ; For each requested index, get the file path from our list and ask the Cache module to load it. / Pour chaque index demandé, obtient le chemin du fichier depuis notre liste et demande au module Cache de le charger. + *Ptr = Cache::GetFileDataFromCache(CurrentList()) + If *Ptr + ; Then, associate the returned FileData pointer with the index in the Thumbs gadget. / Ensuite, associe le pointeur FileData retourné avec l'index dans le gadget Thumbs. + Thumbs::AddImageToThumb(GadgetId, TmpIndex, *Ptr) + Else + Thumbs::AddImageToThumb(GadgetId, TmpIndex, 0) + EndIf + Else + ; Provide a null pointer for indexes that are out of bounds. / Fournit un pointeur nul pour les index hors limites. + Thumbs::AddImageToThumb(GadgetId, TmpIndex, -1) + EndIf + Next + Thumbs::LimitIndex(GadgetId, ListSize(CurrentList())) ; Update the maximum scroll limit. / Met à jour la limite maximale de défilement. + UnlockMutex(CurrentListMutex) + EndProcedure + + Define Repertoire$ + Define Event.i + + ; Enable image decoders. / Active les décodeurs d'image. + UseJPEGImageDecoder() + UsePNGImageDecoder() + UseMD5Fingerprint() + + CurrentListMutex = CreateMutex() + + If OpenWindow(#Win_main, 0, 0, 1024, 600, "Thumbnails", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_SizeGadget) + ButtonGadget(#Gdt_Folder, 0, 0, 100, 25, "Choose Folder") + Cache::InitCache() ; Initialize the caching system. / Initialise le système de cache. + ; Create the thumbnail gadget, passing our callback function. / Crée le gadget de vignettes, en passant notre fonction de callback. + Thumbs::ThumbsGadget(#Gdt_ThumbA, 0, 50, WindowWidth(#Win_main), WindowHeight(#Win_main) - 50, 128, @CallBackLoadFromIndexB()) + + Repeat + Event = WaitWindowEvent() + If Event = #PB_Event_Gadget + If EventGadget() = #Gdt_Folder + Repertoire$ = PathRequester("Chose Directory", "G:\Documents\Photos\Photos a trier") + If Repertoire$ <> "" + If ExamineDirectory(0, Repertoire$, "*.jpg") + ; Safely clear the old list and fill it with new file paths. / Vide de manière sécurisée l'ancienne liste et la remplit avec les nouveaux chemins de fichiers. + LockMutex(CurrentListMutex) + ClearList(CurrentList()) + While NextDirectoryEntry(0) + If DirectoryEntryType(0) = #PB_DirectoryEntry_File + AddElement(CurrentList()) + CurrentList() = Repertoire$ + DirectoryEntryName(0) + EndIf + Wend + FinishDirectory(0) + UnlockMutex(CurrentListMutex) + + Debug "LISTSIZE=" + Str(ListSize(CurrentList())) + ; Update the gadget with the new list size and force it to reload. / Met à jour le gadget avec la nouvelle taille de liste et force son rechargement. + Thumbs::LimitIndex(#Gdt_ThumbA, ListSize(CurrentList())) + Thumbs::ForceUpdate(#Gdt_ThumbA) + EndIf + EndIf + EndIf + EndIf + + If Event = #PB_Event_SizeWindow + Debug "coucou" + ResizeGadget(#Gdt_ThumbA, 0, 50, WindowWidth(#Win_main), WindowHeight(#Win_main) - 50) + EndIf + + Until Event = #PB_Event_CloseWindow + + ; Clean up all resources before exiting. / Nettoie toutes les ressources avant de quitter. + Thumbs::FreeThumbsGadget(#Gdt_ThumbA) + Cache::QuitCache() + EndIf +CompilerEndIf +; IDE Options = PureBasic 6.21 (Windows - x64) +; CursorPosition = 810 +; FirstLine = 801 +; Folding = ------- +; EnableThread +; EnableXP +; DPIAware +; IDE Options = PureBasic 6.21 (Windows - x64) +; CursorPosition = 3 +; Folding = ------- +; EnableThread +; EnableXP +; DPIAware \ No newline at end of file