; ******************************************************************** ; 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 ; Structure to hold cache statistics / Structure pour contenir les statistiques du cache <--- MODIFIED Structure CacheStats TotalItems.i ; Total items in cache (loaded or not) / Nombre total d'éléments (chargés ou non) LoadedInMemory.i ; Number of images currently in memory / Nombre d'images actuellement en mémoire WaitingToLoad.i ; Number of images in the loading queue / Nombre d'images dans la file d'attente MemoryUsed.q ; Total memory used by loaded images in bytes / Mémoire totale utilisée par les images chargées en octets EndStructure ; 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. TotalMemoryUsed.q ; Running total of memory used by images / Total de la mémoire utilisée par les images 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. Declare GetStats(*Stats.CacheStats) ; Get current cache statistics / Récupère les statistiques actuelles du cache 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) Param\TotalMemoryUsed = 0 ; Initialize memory counter / Initialise le compteur mémoire 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é". ; Securely add the new image's memory size to the total / Ajoute de manière sécurisée la taille mémoire de la nouvelle image au total LockMutex(Param\CacheListMutex) Param\TotalMemoryUsed + (result\Width * result\Height * (ImageDepth(*Ptr\Image) / 8)) UnlockMutex(Param\CacheListMutex) ; 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) ; Subtract the image's memory size from the total before freeing it / Soustrait la taille mémoire de l'image du total avant de la libérer Param\TotalMemoryUsed - (ImageWidth(*Ptr\Image) * ImageHeight(*Ptr\Image) * (ImageDepth(*Ptr\Image) / 8)) 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 ; GetStats - Collects and returns the current statistics of the cache. / Collecte et retourne les statistiques actuelles du cache. Procedure GetStats(*Stats.CacheStats) If *Stats LockMutex(Param\CacheListMutex) *Stats\TotalItems = MapSize(Param\CacheList()) *Stats\MemoryUsed = Param\TotalMemoryUsed ; Read the pre-calculated total / Lit le total pré-calculé Protected LoadedCount.i = 0 ForEach Param\CacheList() If Param\CacheList()\State > 0 LoadedCount + 1 EndIf Next *Stats\LoadedInMemory = LoadedCount UnlockMutex(Param\CacheListMutex) LockMutex(Param\LoadListMutex) *Stats\WaitingToLoad = ListSize(Param\LoadList()) UnlockMutex(Param\LoadListMutex) EndIf 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. Declare SetThumbsSize(GadgetId.i, Size.l) ; Change Thumb size / Change la taille des vignettes Declare SetThumbsDisplayStyle(GadgetId.i, Style.l) ; Change fill style image / change le style de rempissage de l'image 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. DisplayStyle.l ; style (Fit, Fill, Stretch) 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, *Gdt\DisplayStyle) 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. *Gdt\DisplayStyle = ImgTools::#Image_Style_Fit ; default fit style 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 ; Procédure : SetThumbsSize - Permet de changer la taille des vignettes dynamiquement. Procedure SetThumbsSize(GadgetId.i, Size.l) Protected *Gdt.gdt *Gdt = GetGadgetData(GadgetId) If *Gdt *Gdt\Size = Size ; Met à jour la taille de base ; Recalcule toutes les dimensions du gadget en fonction de la nouvelle taille InitGadgetValue(GadgetId) ; Demande le rechargement des images pour la nouvelle disposition UpdateIndexs(GadgetId) ; Force le redessin du gadget pour afficher le changement SignalSemaphore(*Gdt\DrawSemaphore) EndIf EndProcedure ; Procédure : SetThumbsDisplayStyle - Permet de changer le style d'affichage des vignettes. Procedure SetThumbsDisplayStyle(GadgetId.i, Style.l) Protected *Gdt.gdt *Gdt = GetGadgetData(GadgetId) If *Gdt ; Met à jour la variable de style dans la structure du gadget *Gdt\DisplayStyle = Style ; Force le redessin du gadget pour que le changement soit visible immédiatement SignalSemaphore(*Gdt\DrawSemaphore) EndIf 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 ; Procedure: FormatBytes - Converts a byte count into a human-readable string (KB, MB, GB) / Convertit un nombre d'octets en une chaîne lisible (Ko, Mo, Go). Procedure.s FormatBytes(bytes.q) If bytes >= 1024 * 1024 * 1024 ; GB / Go ProcedureReturn StrD(bytes / (1024.0 * 1024.0 * 1024.0), 2) + " GB" ElseIf bytes >= 1024 * 1024 ; MB / Mo ProcedureReturn StrD(bytes / (1024.0 * 1024.0), 2) + " MB" ElseIf bytes >= 1024 ; KB / Ko ProcedureReturn StrD(bytes / 1024.0, 2) + " KB" Else ; Bytes / Octets ProcedureReturn Str(bytes) + " B" EndIf EndProcedure ; 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 #Gdt_ThumbSize #Gdt_FrameStyle #Gdt_OptionFit #Gdt_OptionFill #Gdt_OptionStretch #StatusBar_Main 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, 10, 10, 120, 25, "Choose Folder") TrackBarGadget(#Gdt_ThumbSize, 140, 10, 200, 25, 64, 256) ; Min: 64px, Max: 256px SetGadgetState(#Gdt_ThumbSize, 128) ; On règle la taille initiale à 128px FrameGadget(#Gdt_FrameStyle, 350, 5, 280, 35, "Display Style") OptionGadget(#Gdt_OptionFit, 360, 20, 80, 15, "Fit") OptionGadget(#Gdt_OptionFill, 450, 20, 80, 15, "Fill") OptionGadget(#Gdt_OptionStretch, 540, 20, 80, 15, "Stretch") SetGadgetState(#Gdt_OptionFit, #True) ; On coche "Fit" par défaut 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()) If CreateStatusBar(#StatusBar_Main, WindowID(#Win_main)) AddStatusBarField(400) EndIf ; On ajoute un timer qui se déclenchera toutes les 500ms (2 fois par seconde) AddWindowTimer(#Win_main, 1, 500) Repeat Event = WaitWindowEvent() If Event = #PB_Event_Gadget Select EventGadget() ; <--- On utilise un Select pour plus de clarté Case #Gdt_Folder Repertoire$ = PathRequester("Chose Directory", "G:\Documents\Photos\Photos a trier") If Repertoire$ <> "" If ExamineDirectory(0, Repertoire$, "*.jpg") ; ... (le reste du code reste inchangé) 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())) Thumbs::LimitIndex(#Gdt_ThumbA, ListSize(CurrentList())) Thumbs::ForceUpdate(#Gdt_ThumbA) EndIf EndIf Case #Gdt_ThumbSize ; <--- Voici notre nouvelle gestion d'événement ;If EventType() = #PB_EventType_Change Debug "coucou" Define NewSize.l = GetGadgetState(#Gdt_ThumbSize) ; On appelle notre nouvelle fonction pour mettre à jour la taille ! Thumbs::SetThumbsSize(#Gdt_ThumbA, NewSize) Debug NewSize ;EndIf Case #Gdt_OptionFit Thumbs::SetThumbsDisplayStyle(#Gdt_ThumbA, ImgTools::#Image_Style_Fit) Case #Gdt_OptionFill Thumbs::SetThumbsDisplayStyle(#Gdt_ThumbA, ImgTools::#Image_Style_Fill) Case #Gdt_OptionStretch Thumbs::SetThumbsDisplayStyle(#Gdt_ThumbA, ImgTools::#Image_Style_Stretch) EndSelect EndIf If Event = #PB_Event_SizeWindow Debug "coucou" ResizeGadget(#Gdt_ThumbA, 0, 50, WindowWidth(#Win_main), WindowHeight(#Win_main) - 50) EndIf If Event = #PB_Event_Timer And EventTimer() = 1 Define Stats.Cache::CacheStats Cache::GetStats(@Stats) ; Format the text to display, now including memory usage ; Formate le texte à afficher, en incluant maintenant l'utilisation mémoire Define StatText.s = "Cache Items: " + Stats\TotalItems + " | In Memory: " + Stats\LoadedInMemory + " | Queue: " + Stats\WaitingToLoad StatText + " | Mem. Used: " + FormatBytes(Stats\MemoryUsed) StatusBarText(#StatusBar_Main, 0, StatText) 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 = 441 ; FirstLine = 434 ; Folding = -------- ; EnableThread ; EnableXP ; DPIAware