* the fully-functional code for this screenshot (including the sample images) can be found in the second post of this thread (linked).

Gadget Features
- blackboxed module design with dynamically-created child gadgets and bound event handling - no risk of namespace clashes
- canvas-based and lightweight, utilising only 2 canvas gadgets and 1 window with 1 image gadget for previews
- multiple instances of the gadget can be implemented simultaneously within the same namespace/application
- supported containers include windows, container gadgets, canvas gadgets (as container), and panel gadgets
- supported images types include BMP, JPG, PNG, GIF, TIFF - image libraries loaded dynamically only as required
- the maximum number of images that can be loaded is limited by system resources and canvas limitations (32767 pixels)
- the gadget can be dropped onto any panel or multiple panels of the panel gadget, each with variable settings
- two modes of implementation - basic (with only 3 parameters) and advanced (with 3 + 10 optional parameters)
- the thumbnails can be sized and spaced as desired, including options for border & background colours and title display
- selected images can be displayed with the built-in previewer (auto-orientation) or returned to the calling application
- natively-drawn sizeable scroll bar - requires no additional image assets (supports vertical scrolling only)
- features a busy notification window when loading big or large number of images
- scrolling can be effected by:
- the mouse wheel
- dragging the scroll bar button
- clicking on any part of the scroll bar to jump to selection
- touching & dragging on any part of the container (touch-responsive)
ImagePickerGadget.pbi Module
Here's the module for the image picker gadget (ImagePickerGadget.pbi):
Code: Select all
;====================================================================
;
; Cross-Platform Canvas-Based Image Picker Module
;
; features:
; 1. blackboxed module design
; 2. canvas-based and lightweight
; 3. fully-contained event handling
; 4. multiple instances simultaneously
; 5. basic implementation mode (3 parameters only)
; 6. advanced implementation (3 + 10 optional parameters)
; 7. supports BMP, JPG, PNG, GIF, TIFF image types
; 8. supports different sized images and orientations
; 9. image libraries loaded dynamically as required
; 10. loadable image count contingent on canvas limitations
; 11. busy notification window when images are still loading
; 12. custom-sizable thumbnails with border, background, title
; 13. custom-sizable image preview window (auto-orientation)
; 14. option to display or return selected images
; 15. custom-drawn scroll bar w/sizable width
; 16. scroll functions (supports vertical scrolling only):
; - mouse wheel
; - scroll bar button
; - jump-to on scroll bar
; - touch & drag on container
; 17. can be implemented onto these containers:
; - windows
; - container gadgets
; - canvas gadgets (with container flag)
; - panel gadgets (on multiple panels)
;
; tested & working with PureBasic v6.12 LTS (x64) on:
; 1. Windows 8.1, 10 (Intel)
; 2. Windows 11 (Intel & ARM)
; 3. Ubuntu Linux 20.04, 24.10 (Intel & ARM)
; 4. macOS High Sierra, Catalina (Intel)
; 5. macOS Sonoma (Silicon M1)
;
; special thanks to PureBasic forum users:
; 1. @deseven for providing a workaround for an image-clipping
; issue affecting later versions of macOS (line 220)
; 2. @HeXOR for identifying an error that occurs when the
; canvas size limit of 32767 is exceeded (line 343)
; 3. @AZJIO for identifying an error that occurs when the image
; folder is missing a trailing path separator (line 159)
;
; by TI-994A - free to use, improve, share...
;
; 22nd November 2024 - Singapore
;
;====================================================================
DeclareModule ImagePicker
#ImageSelected = #PB_Event_FirstCustomValue
Define parent, panelIndex, scrollerWidth, selectedImage, previewSelectedImage, previewImageSize, thumbnailSize, thumbnailSpacing, thumbnailTitle
Define thumbnailBorder = -1, thumbnailBackground = -1, gadgetBackground = -1
Define.s selectedImageName, imagesFolder
Declare initImagePicker()
Declare ImagePicker(parent, imagesFolder.s, previewSelectedImage)
EndDeclareModule
Module ImagePicker
EnableExplicit
Structure coords
x.d
y.d
EndStructure
Structure img
imgName.s
imgNum.i
imgX1.d
imgX2.d
imgY1.d
imgY2.d
EndStructure
Structure imagePickerInstance
click.coords
scroll.coords
canvas.i
scroller.i
scrollerWidth.i
scrollerVisible.i
scrollSpeed.i
scrollFactor.d
parent.i
parentWidth.i
parentHeight.i
panelIndex.i
previewImageWindow.i
previewImageViewer.i
thumbnailSize.i
thumbnailSpacing.i
thumbnailTitle.i
thumbnailBorder.i
thumbnailBackground.i
previewSelectedImage.i
previewImageSize.i
gadgetBackground.i
imagesFolder.s
Array images.img(0)
EndStructure
Global Dim picker.imagePickerInstance(0)
Global index
Declare ImagePicker(x, y.s, z)
Declare initImagePicker()
Declare windowHandler()
Declare gadgetHandler()
Declare loadImages()
Declare hourGlass()
Declare displayImage(x)
Declare scrollCanvas(x, y)
Declare drawScroller(*x.coords)
Declare checkClickPoint(*x.coords)
Declare validateDirectory(x.s)
Macro IIf(a, b, c)
If Bool(a):b:Else:c:EndIf
EndMacro
#canvasMaxSize = 32767
Procedure ImagePicker(_parent, _imagesFolder.s, _previewSelectedImage)
Shared parent, imagesFolder, previewSelectedImage
parent = _parent
imagesFolder = _imagesFolder
previewSelectedImage = _previewSelectedImage
initImagePicker()
EndProcedure
Procedure initImagePicker()
Shared parent, panelIndex, scrollerWidth, previewSelectedImage, previewImageSize, gadgetBackground, thumbnailSize, thumbnailSpacing, thumbnailTitle, thumbnailBorder, thumbnailBackground, imagesFolder.s
Protected parentIsWindow, selectedPanel, invalidParameters.s
Static init
CompilerIf #PB_Compiler_Version => 603
If Not init
CompilerIf #PB_Compiler_DPIAware
MessageRequester("Image Picker Gadget:", "The gadget does not support DPI awareness. Please turn off the option for best results.")
CompilerEndIf
EndIf
CompilerEndIf
If parent = #Null Or (Not IsWindow(parent) And Not IsGadget(parent))
invalidParameters = "parent value"
EndIf
If Right(imagesFolder, 1) <> #PS$
imagesFolder + #PS$
EndIf
If imagesFolder = #Null$ Or Not validateDirectory(imagesFolder)
If invalidParameters <> #Null$
invalidParameters + ", "
EndIf
invalidParameters + "images folder"
EndIf
If Trim(invalidParameters) <> #Null$
MessageRequester("Image Picker Gadget:", "The following parameters are invalid: " + invalidParameters)
CloseGadgetList()
ProcedureReturn
EndIf
If init
index + 1
EndIf
init = #True
ReDim picker(index)
picker(index)\parent = parent
picker(index)\panelIndex = panelIndex
picker(index)\imagesFolder = imagesFolder
picker(index)\thumbnailTitle = thumbnailTitle
picker(index)\previewSelectedImage = previewSelectedImage
IIf(scrollerWidth <= 0, picker(index)\scrollerWidth = 10, picker(index)\scrollerWidth = scrollerWidth)
IIF(thumbnailSize <= 0, picker(index)\thumbnailSize = 100, picker(index)\thumbnailSize = thumbnailSize)
IIF(thumbnailSpacing <= 0, picker(index)\thumbnailSpacing = 10, picker(index)\thumbnailSpacing = thumbnailSpacing)
IIF(thumbnailBorder < 0, picker(index)\thumbnailBorder = #White, picker(index)\thumbnailBorder = thumbnailBorder)
IIF(thumbnailBackground < 0, picker(index)\thumbnailBackground = #Black, picker(index)\thumbnailBackground = thumbnailBackground)
IIF(gadgetBackground < 0, picker(index)\gadgetBackground = #Black, picker(index)\gadgetBackground = gadgetBackground)
IIf(previewImageSize < 100, picker(index)\previewImageSize = 500, picker(index)\previewImageSize = previewImageSize)
hourglass()
If IsWindow(parent)
parentIsWindow = #True
picker(index)\parentWidth = WindowWidth(picker(index)\parent)
picker(index)\parentHeight = WindowHeight(picker(index)\parent)
UseGadgetList(WindowID(picker(index)\parent))
Else
picker(index)\parentWidth = GadgetWidth(picker(index)\parent)
picker(index)\parentHeight = GadgetHeight(picker(index)\parent)
If GadgetType(picker(index)\parent) = #PB_GadgetType_Panel
OpenGadgetList(picker(index)\parent, picker(index)\panelIndex)
CompilerIf #PB_Compiler_OS = #PB_OS_Linux
picker(index)\parentWidth - 2
picker(index)\parentHeight - 30
CompilerElse
picker(index)\parentWidth = GetGadgetAttribute(picker(index)\parent, #PB_Panel_ItemWidth)
picker(index)\parentHeight = GetGadgetAttribute(picker(index)\parent, #PB_Panel_ItemHeight)
CompilerEndIf
Else
OpenGadgetList(picker(index)\parent)
EndIf
EndIf
CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
If Not parentIsWindow
If GadgetType(picker(index)\parent) = #PB_GadgetType_Panel
SetGadgetState(picker(index)\parent, picker(index)\panelIndex)
selectedPanel = CocoaMessage(0, GadgetID(picker(index)\parent), "selectedTabViewItem")
CocoaMessage(0, CocoaMessage(0, selectedPanel, "view"), "setClipsToBounds:", #True)
SetGadgetState(picker(index)\parent, 0)
Else
CocoaMessage(0, GadgetID(picker(index)\parent), "setClipsToBounds:", #True)
EndIf
EndIf
picker(index)\scrollSpeed = 1
CompilerElse
picker(index)\scrollSpeed = 50
CompilerEndIf
picker(index)\canvas = CanvasGadget(#PB_Any, 0, 0, picker(index)\parentWidth - picker(index)\scrollerWidth, picker(index)\parentHeight)
picker(index)\scroller = CanvasGadget(#PB_Any, picker(index)\parentWidth - picker(index)\scrollerWidth, 0, picker(index)\scrollerWidth, picker(index)\parentHeight)
SetGadgetData(picker(index)\canvas, index)
SetGadgetData(picker(index)\scroller, index)
loadImages()
If picker(index)\scrollerVisible
drawScroller(@picker(index)\scroll)
EndIf
If parentIsWindow
BindEvent(#PB_Event_Gadget, @gadgetHandler(), picker(index)\parent, picker(index)\canvas, #PB_All)
BindEvent(#PB_Event_Gadget, @gadgetHandler(), picker(index)\parent, picker(index)\scroller, #PB_All)
Else
BindGadgetEvent(picker(index)\canvas, @gadgetHandler(), #PB_All)
BindGadgetEvent(picker(index)\scroller, @gadgetHandler(), #PB_All)
CloseGadgetList()
CloseGadgetList()
EndIf
panelIndex = #Null
scrollerWidth = #Null
imagesFolder = #Null$
thumbnailSize = #Null
thumbnailSize = #Null
thumbnailSpacing = #Null
thumbnailTitle = #False
thumbnailBorder = -1
thumbnailBackground = -1
gadgetBackground = -1
previewImageSize = #Null
previewSelectedImage = #False
hourglass()
EndProcedure
Procedure drawScroller(*scroll.coords)
Protected scrollerBarColour, scrollerButtonColour, scrollerButtonRadius, scrollY.d
scrollY = *scroll\y
scrollerButtonColour = #Blue
scrollerBarColour = RGB(222, 222, 222)
scrollerButtonRadius = picker(index)\scrollerWidth / 2
If scrollY < scrollerButtonRadius
scrollY = scrollerButtonRadius
ElseIf scrollY > picker(index)\parentHeight - (scrollerButtonRadius)
scrollY = picker(index)\parentHeight - (scrollerButtonRadius)
EndIf
StartDrawing(CanvasOutput(picker(index)\scroller))
RoundBox(0, 0, picker(index)\scrollerWidth, GadgetHeight(picker(index)\scroller), scrollerButtonRadius, scrollerButtonRadius, scrollerBarColour)
Circle(scrollerButtonRadius, scrollY, scrollerButtonRadius, scrollerButtonColour)
StopDrawing()
EndProcedure
Procedure loadImages()
Protected directory, titleFontID, titleWidth, titleHeight, canvasNewRow, canvasNewHeight, drawX, drawY, image, imgCount, imgWidth, imgHeight, imgAspectRatio.d
Protected.s fileName, fileExtension, canvasPos.coords
Static jpgDecoderLoaded, pngDecoderLoaded, gifDecoderLoaded, tifDecoderLoaded
canvasPos\x = picker(index)\thumbnailSpacing
canvasPos\y = picker(index)\thumbnailSpacing
titleHeight = Int(picker(index)\thumbnailSize / 15)
If titleHeight < 10 : titleHeight = 10 : EndIf
titleFontID = FontID(LoadFont(#PB_Any, "Arial", titleHeight))
directory = ExamineDirectory(#PB_Any, picker(index)\imagesFolder, "*.*")
If directory
While NextDirectoryEntry(directory)
If DirectoryEntryType(directory) = #PB_DirectoryEntry_File
fileName = DirectoryEntryName(directory)
fileExtension = LCase(GetExtensionPart(fileName))
If fileExtension = "bmp" Or fileExtension = "jpg" Or fileExtension = "jpeg" Or fileExtension = "png" Or fileExtension = "gif" Or fileExtension = "tif" Or fileExtension = "tiff"
Select fileExtension
Case "jpg", "jpeg"
If Not jpgDecoderLoaded
UseJPEGImageDecoder()
jpgDecoderLoaded = #True
EndIf
Case "png"
If Not pngDecoderLoaded
UsePNGImageDecoder()
pngDecoderLoaded = #True
EndIf
Case "gif"
If Not gifDecoderLoaded
UseGIFImageDecoder()
gifDecoderLoaded = #True
EndIf
Case "tif", "tiff"
If Not tifDecoderLoaded
UseTIFFImageDecoder()
tifDecoderLoaded = #True
EndIf
EndSelect
If canvasPos\x + picker(index)\thumbnailSize + picker(index)\thumbnailSpacing > GadgetWidth(picker(index)\canvas)
canvasPos\x = picker(index)\thumbnailSpacing
canvasPos\y + picker(index)\thumbnailSize + picker(index)\thumbnailSpacing
canvasNewHeight = canvasPos\y + picker(index)\thumbnailSize + picker(index)\thumbnailSpacing
canvasNewRow = #True
If canvasNewHeight > GadgetHeight(picker(index)\canvas)
If canvasNewHeight < #canvasMaxSize
ResizeGadget(picker(index)\canvas, #PB_Ignore, #PB_Ignore, #PB_Ignore, canvasNewHeight)
picker(index)\scrollFactor = (canvasNewHeight - picker(index)\parentHeight) / picker(index)\parentHeight
picker(index)\scrollerVisible = #True
Else
MessageRequester("Image Picker Gadget:", "Maximum display limit reached. " + Str(imgCount - 1) + " images loaded.")
Break
EndIf
EndIf
EndIf
image = LoadImage(#PB_Any, picker(index)\imagesFolder + fileName)
If IsImage(image)
imgCount + 1
ReDim picker(index)\images(imgCount)
picker(index)\images(imgCount)\imgNum = image
picker(index)\images(imgCount)\imgName = fileName
imgWidth = ImageWidth(picker(index)\images(imgCount)\imgNum)
imgHeight = ImageHeight(picker(index)\images(imgCount)\imgNum)
If imgWidth > imgHeight
imgAspectRatio = imgHeight / imgWidth
imgWidth = picker(index)\thumbnailSize
imgHeight = imgWidth * imgAspectRatio
drawX = canvasPos\x
drawY = canvasPos\y + ((picker(index)\thumbnailSize - imgHeight) / 2)
Else
imgAspectRatio = imgWidth / imgHeight
imgHeight = picker(index)\thumbnailSize
imgWidth = imgHeight * imgAspectRatio
drawY = canvasPos\y
drawX = canvasPos\x + ((picker(index)\thumbnailSize - imgWidth) / 2)
EndIf
StartDrawing(CanvasOutput(picker(index)\canvas))
If imgCount = 1 Or canvasNewRow
Box(0, canvasPos\y - picker(index)\thumbnailSpacing, GadgetWidth(picker(index)\canvas), picker(index)\thumbnailSize + (picker(index)\thumbnailSpacing * 2), picker(index)\gadgetBackground)
canvasNewRow = #False
EndIf
If picker(index)\thumbnailBackground > -1
Box(canvasPos\x, canvasPos\y, picker(index)\thumbnailSize, picker(index)\thumbnailSize, picker(index)\thumbnailBackground)
EndIf
DrawImage(ImageID(picker(index)\images(imgCount)\imgNum), drawX, drawY, imgWidth, imgHeight)
If picker(index)\thumbnailTitle
DrawingMode(#PB_2DDrawing_AlphaBlend)
ClipOutput(canvasPos\x, canvasPos\y, picker(index)\thumbnailSize, titleHeight + 10)
Box(canvasPos\x, canvasPos\y, picker(index)\thumbnailSize, titleHeight + 10 , RGBA(0, 0, 0, 100))
DrawingFont(titleFontID)
DrawingMode(#PB_2DDrawing_Transparent)
titleWidth = TextWidth(fileName)
If titleWidth => picker(index)\thumbnailSize
While titleWidth > picker(index)\thumbnailSize - 40
fileName = Left(fileName, Len(fileName) - 1)
titleWidth = TextWidth(fileName)
Wend
fileName + "..."
titleWidth = TextWidth(fileName)
EndIf
DrawText(canvasPos\x + picker(index)\thumbnailSize / 2 - titleWidth / 2, canvasPos\y + 5, fileName, #White)
UnclipOutput()
EndIf
If picker(index)\thumbnailBorder > -1
DrawingMode(#PB_2DDrawing_Outlined)
Box(canvasPos\x, canvasPos\y, picker(index)\thumbnailSize, picker(index)\thumbnailSize, picker(index)\thumbnailBorder)
EndIf
picker(index)\images(imgCount)\imgX1 = canvasPos\x : picker(index)\images(imgCount)\imgX2 = canvasPos\x + picker(index)\thumbnailSize
picker(index)\images(imgCount)\imgY1 = canvasPos\y : picker(index)\images(imgCount)\imgY2 = canvasPos\y + picker(index)\thumbnailSize
StopDrawing()
canvasPos\x + picker(index)\thumbnailSize + picker(index)\thumbnailSpacing
EndIf
EndIf
EndIf
Wend
FinishDirectory(directory)
EndIf
EndProcedure
Procedure hourglass()
Static hourglassWindow
If IsWindow(hourglassWindow)
CloseWindow(hourglassWindow)
Else
hourglassWindow = OpenWindow(#PB_Any, 0, 0, 200, 50, "ImagePickerGadget()", #PB_Window_BorderLess | #PB_Window_WindowCentered)
SetWindowColor(hourglassWindow, RGB(250, 200, 210))
TextGadget(#PB_Any, 0, 15, 200, 30, "Loading images...", #PB_Text_Center)
EndIf
EndProcedure
Procedure displayImage(selectedImageIndex)
Protected image, tempImage, imgWidth, imgHeight, imgAspectRatio.d, imageName.s
image = picker(index)\images(selectedImageIndex)\imgNum
imageName = picker(index)\images(selectedImageIndex)\imgName
CopyImage(image, tempImage)
imgWidth = ImageWidth(tempImage)
imgHeight = ImageHeight(tempImage)
If imgWidth > imgHeight
imgAspectRatio = imgHeight / imgWidth
imgWidth = picker(index)\previewImageSize
imgHeight = imgWidth * imgAspectRatio
Else
imgAspectRatio = imgWidth / imgHeight
imgHeight = picker(index)\previewImageSize
imgWidth = imgHeight * imgAspectRatio
EndIf
ResizeImage(tempImage, imgWidth, imgHeight)
If Not IsWindow(picker(index)\previewImageWindow)
picker(index)\previewImageWindow = OpenWindow(#PB_Any, 0, 0, imgWidth, imgHeight, imageName, #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
picker(index)\previewImageViewer = ImageGadget(#PB_Any, 0, 0, imgWidth, imgHeight, ImageID(tempImage))
BindEvent(#PB_Event_CloseWindow, @windowHandler(), picker(index)\previewImageWindow)
Else
SetWindowTitle(picker(index)\previewImageWindow, imageName)
If WindowWidth(picker(index)\previewImageWindow) <> imgWidth Or WindowHeight(picker(index)\previewImageWindow) <> imgHeight
ResizeWindow(picker(index)\previewImageWindow, #PB_Ignore, #PB_Ignore, imgWidth, imgHeight)
ResizeGadget(picker(index)\previewImageViewer, #PB_Ignore, #PB_Ignore, imgWidth, imgHeight)
EndIf
SetGadgetState(picker(index)\previewImageViewer, ImageID(tempImage))
EndIf
EndProcedure
Procedure validateDirectory(imagesFolder.s)
Protected directory, directoryValid, fileExtension.s
directory = ExamineDirectory(#PB_Any, imagesFolder, "*.*")
If directory
While NextDirectoryEntry(directory)
If DirectoryEntryType(directory) = #PB_DirectoryEntry_File
fileExtension = LCase(GetExtensionPart(DirectoryEntryName(directory)))
If DirectoryEntrySize(directory) > 0 And (fileExtension = "bmp" Or fileExtension = "jpg" Or fileExtension = "jpeg" Or fileExtension = "png" Or fileExtension = "gif" Or fileExtension = "tif" Or fileExtension = "tiff")
directoryValid = #True
Break
EndIf
EndIf
Wend
FinishDirectory(directory)
EndIf
ProcedureReturn directoryValid
EndProcedure
Procedure scrollCanvas(deltaY, scrollbar)
Protected canvasY, newCanvasY
canvasY = GadgetY(picker(index)\canvas)
If scrollbar
newCanvasY = deltaY
Else
newCanvasY = canvasY + deltaY
EndIf
If newCanvasY < -(GadgetHeight(picker(index)\canvas) - picker(index)\parentHeight)
canvasY = -(GadgetHeight(picker(index)\canvas) - picker(index)\parentHeight)
ElseIf newCanvasY > 0
canvasY = 0
Else
canvasY = newCanvasY
EndIf
ResizeGadget(picker(index)\canvas, #PB_Ignore, canvasY, #PB_Ignore, #PB_Ignore)
ProcedureReturn canvasY
EndProcedure
Procedure checkClickPoint(*click.coords)
Protected i, selectedImageIndex
For i = 1 To ArraySize(picker(index)\images())
If *click\x > picker(index)\images(i)\imgX1 And *click\x < picker(index)\images(i)\imgX2
If *click\y > picker(index)\images(i)\imgY1 And *click\y < picker(index)\images(i)\imgY2
selectedImageIndex = i
EndIf
EndIf
Next i
ProcedureReturn selectedImageIndex
EndProcedure
Procedure windowHandler()
CloseWindow(EventWindow())
EndProcedure
Procedure gadgetHandler()
Shared selectedImage, selectedImageName
Protected i, delta, selectedImageIndex, click.coords
Static scrollerOn, touchScrollerOn, touchScrollStart
index = GetGadgetData(EventGadget())
Select EventGadget()
Case picker(index)\scroller
Select EventType()
Case #PB_EventType_LeftButtonDown
picker(index)\scroll\y = GetGadgetAttribute(picker(index)\scroller, #PB_Canvas_MouseY)
scrollerOn = #True
drawScroller(@picker(index)\scroll)
scrollCanvas(-(picker(index)\scroll\y * picker(index)\scrollFactor), #True)
Case #PB_EventType_LeftButtonUp
scrollerOn = #False
Case #PB_EventType_MouseMove
If scrollerOn And picker(index)\scrollerVisible
picker(index)\scroll\y = GetGadgetAttribute(picker(index)\scroller, #PB_Canvas_MouseY)
If picker(index)\scroll\y > 0 And picker(index)\scroll\y < picker(index)\parentHeight
drawScroller(@picker(index)\scroll)
scrollCanvas(-(picker(index)\scroll\y * picker(index)\scrollFactor), #True)
EndIf
EndIf
EndSelect
Case picker(index)\canvas
Select EventType()
Case #PB_EventType_LeftClick
click\x = GetGadgetAttribute(picker(index)\canvas, #PB_Canvas_MouseX)
click\y = GetGadgetAttribute(picker(index)\canvas, #PB_Canvas_MouseY)
selectedImageIndex = checkClickPoint(@click)
If selectedImageIndex
If picker(index)\previewSelectedImage
displayImage(selectedImageIndex)
Else
selectedImage = picker(index)\images(selectedImageIndex)\imgNum
selectedImageName = picker(index)\images(selectedImageIndex)\imgName
PostEvent(#ImageSelected)
EndIf
EndIf
Case #PB_EventType_MouseWheel
If picker(index)\scrollerVisible
picker(index)\scroll\y = Abs(scrollCanvas(GetGadgetAttribute(picker(index)\canvas, #PB_Canvas_WheelDelta) * picker(index)\scrollSpeed, #False)) / picker(index)\scrollFactor
drawScroller(@picker(index)\scroll)
EndIf
Case #PB_EventType_LeftButtonDown
touchScrollStart = GetGadgetAttribute(picker(index)\canvas, #PB_Canvas_MouseY)
touchScrollerOn = #True
Case #PB_EventType_LeftButtonUp
touchScrollerOn = #False
Case #PB_EventType_MouseMove
If touchScrollerOn And picker(index)\scrollerVisible
delta = touchScrollStart - GetGadgetAttribute(picker(index)\canvas, #PB_Canvas_MouseY)
picker(index)\scroll\y = Abs(scrollCanvas(-delta, #False)) / picker(index)\scrollFactor
drawScroller(@picker(index)\scroll)
EndIf
EndSelect
EndSelect
EndProcedure
EndModule
Implementation Examples
NOTE: The image picker gadget module should be saved as ImagePickerGadget.pbi and included in the implementations.
Here's a basic-mode implementation in its simplest form:
Code: Select all
XIncludeFile "ImagePickerGadget.pbi"
mainWindow = OpenWindow(#PB_Any, 0, 0, 270, 270, "Image Picker Gadget (basic mode)",
#PB_Window_SystemMenu | #PB_Window_ScreenCentered)
container = ContainerGadget(#PB_Any, 10, 10, 250, 250)
ImagePicker::ImagePicker(container, imagesFolder$, #True)
While WaitWindowEvent() ! #PB_Event_CloseWindow : Wend
Here's an advanced-mode implementation with all the options:
Code: Select all
XIncludeFile "ImagePickerGadget.pbi"
mainWindow = OpenWindow(#PB_Any, 0, 0, 490, 380, "Image Picker Gadget (advanced mode)",
#PB_Window_SystemMenu | #PB_Window_ScreenCentered)
container = ContainerGadget(#PB_Any, 10, 10, 470, 350)
UseModule ImagePicker
parent = container
scrollerWidth = 10
thumbnailSize = 200
thumbnailSpacing = 20
thumbnailTitle = #True
thumbnailBorder = #Black
thumbnailBackground = #Black
gadgetBackground = RGB(214, 197, 236)
previewImageSize = 600
previewSelectedImage = #True
imagesFolder = imagesFolder$
initImagePicker()
UnuseModule ImagePicker
While WaitWindowEvent() ! #PB_Event_CloseWindow : Wend
Here's another advaced-mode implementation on multiple panels in a panel gadget:
Code: Select all
XIncludeFile "ImagePickerGadget.pbi"
mainWindow = OpenWindow(#PB_Any, 0, 0, 480, 380, "Image Picker Gadget (advanced mode)",
#PB_Window_SystemMenu | #PB_Window_ScreenCentered)
SetWindowColor(mainWindow, RGB(214, 197, 236))
; works with panel gadgets on multiple panels
panel = PanelGadget(#PB_Any, 10, 10, 450, 350)
AddGadgetItem(panel, 0, "Image Set 1")
UseModule ImagePicker
parent = panel
panelIndex = 0 ; drawn on the 1st panel
scrollerWidth = 10 ; scroller width = 10px
thumbnailSize = 120 ; thumbnail size = 180px
thumbnailSpacing = 15 ; thumbnail spacing = 20px
thumbnailTitle = #True ; thumbnail title = display
thumbnailBorder = #Black ; thumbnail border colour = black
thumbnailBackground = #Gray ; thumbnail background colour = gray
previewImageSize = 300 ; image preview window = 300px
previewSelectedImage = #True ; selected image will be displayed
imagesFolder = imagesFolder$ ; the images source folder (full or relative path)
initImagePicker()
UnuseModule ImagePicker
; for additional panels in the panel gadget
OpenGadgetList(panel)
AddGadgetItem(panel, 1, "Image Set 2")
UseModule ImagePicker
parent = panel
panelIndex = 1 ; drawn on the 2nd panel with all default options
previewSelectedImage = #True
imagesFolder = imagesFolder$
initImagePicker()
UnuseModule ImagePicker
While WaitWindowEvent() ! #PB_Event_CloseWindow : Wend
It's a simple plug & play tool with much room to grow. So, please feel free to expand upon it as required.
I hope that it proves to be a useful addition to the PureBasic toolbox.

Updates
27th November 2024:
- originally supported only JPG and PNG image types to minimise image library size
- added support for BMP, GIF, TIFF image types by loading the respective libraries dynamically only as required
- added a fix for newer versions of macOS where contents overflow panels in panel gadgets (thanks to @deseven)
- added a fix for panel gadgets where the contents get clipped (affected all platforms)
28th November 2024:
- program crashes when attempting to load images beyond the canvas size limit (32767 pixels)
- added a fix to stop loading images once this limit is reached with notification (thanks to @HeXOR)
29th November 2024:
- the gadget now supports images of different sizes and orientations - the preview window will auto-rotate accordingly
- the thumbnails will be displayed in a square frame with options to display a border, background, and image-name titling
- the gadget also features a background-colour option and a busy notification window when loading large number of images
- program crashes while loading images if the user-provided image folder value is missing the trailing path separator
- added a fix to check and append the path separator to the image folder if missing (thanks to @AZJIO)