Files
Thumbnails/Thumbnails.pb
2025-09-14 19:41:57 +02:00

1189 lines
73 KiB
Plaintext

; ********************************************************************
; 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.
Declare SetThumbsSize(GadgetId.i, Size.l) ; Change Thumb size / Change la taille des vignettes
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
; 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
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
#Gdt_ThumbSize
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
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
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
EndSelect
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 = 1158
; FirstLine = 1126
; Folding = -------
; EnableThread
; EnableXP
; DPIAware