Using Custom Cursors

Share your advanced PureBasic knowledge/code with the community.
User avatar
Jacobus
Enthusiast
Enthusiast
Posts: 140
Joined: Wed Nov 16, 2005 7:51 pm
Location: France
Contact:

Using Custom Cursors

Post by Jacobus »

Hello everyone
The old LoadCursorFromFile_(name) API used to load a cursor is still functional but deprecated; it has been replaced by LoadImage_(hInst,name,#type,cx,cy,#flags).
If you want to use cursors other than those provided by the system in your drawing applications (or any other application), there's a simple way to do so using PureBasic's functions.
For the usage example, I've adapted the one provided in the documentation.
For those who don't have their own cursors, I'm offering some of mine, which you can download here: MyCursors.zip
Have fun :wink:

Tested with Windows11, PB 6.21B8, x64 and x86
Example of use:

Code: Select all

;================================
; Loads an icon, cursor, animated cursor, or bitmap.
; https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadimagea#return-value

; HANDLE LoadImageA(
;   [in, optional] HINSTANCE hInst,  (A handle to the module in a DLL or executable (.exe) containing the image to load. To load a predefined image or a standalone resource (icon, cursor, or bitmap file), set this parameter to NULL.)
;   [in]           LPCSTR    name,   (full file name)
;   [in]           UINT      type,   (file type defined by: #IMAGE_BITMAP or #IMAGE_CURSOR or #IMAGE_ICON)
;   [in]           int       cx,     (cursor width = 32 by default)
;   [in]           int       cy,     (cursor height = 32 by default)
;   [in]           UINT      fuLoad  (#flags)
; );

; Return value : HANDLE 
; - If the function succeeds, the return value is the handle of the newly loaded image.
; - If the function fails, the return value is NULL. 

; --> HANDLE = LoadImage_(hInst,name,#type,cx,cy,#flags)

;-FLAGS :
; #LR_CREATEDIBSECTION - When the uType parameter specifies IMAGE_BITMAP, causes the function to return a DIB section bitmap rather than a compatible bitmap. This flag is useful for loading a bitmap without mapping it to the colors of the display device. 
; #LR_DEFAULTCOLOR     - The default flag; it does nothing. All it means is "not LR_MONOCHROME". 
; #LR_DEFAULTSIZE      - Uses the width or height specified by the system metric values for cursors or icons, if the cxDesired or cyDesired values are set to zero. If this flag is not specified and cxDesired and cyDesired are set to zero, the function uses the actual resource size. If the resource contains multiple images, the function uses the size of the first image. 
; #LR_LOADFROMFILE     - Loads the standalone image from the file specified by name (icon, cursor, or bitmap file). 
; #LR_LOADMAP3DCOLORS  - Searches the color table for the image and replaces the following shades of gray with the corresponding 3-D color. Do not use this option if you are loading a bitmap with a color depth greater than 8bpp.
; #LR_LOADTRANSPARENT  - Retrieves the color value of the first pixel in the image and replaces the corresponding entry in the color table with the default window color (COLOR_WINDOW). All pixels in the image that use that entry become the default window color. This value applies only to images that have corresponding color tables. Do not use this option if you are loading a bitmap with a color depth greater than 8bpp.
; #LR_MONOCHROME       - Loads the image in black and white. 
; #LR_SHARED           - Shares the image handle if the image is loaded multiple times. If LR_SHARED is not set, a second call to LoadImage for the same resource will load the image again and return a different handle. Do not use LR_SHARED for images that have non-standard sizes, that may change after loading, or that are loaded from a file.
; #LR_VGACOLOR         - Uses true VGA colors. 

; To release the used resource:
; Bitmap :	DeleteObject(HGDIOBJ)
; Cursor :	DestroyCursor(HCURSOR)
; Icon 	 :  DestroyIcon(HICON)
;================================
Enumeration
  #IMAGE_Content  ; stores the previous CanvasGadget content while the mouse is down
  #IMAGE_Color
  #IMAGE_LoadSave
EndEnumeration

Enumeration
  #GADGET_Canvas
  #GADGET_Color
  #GADGET_Brush
  #GADGET_Line
  #GADGET_Box
  #GADGET_Circle
  #GADGET_Fill
  #GADGET_Clear
  #GADGET_Load
  #GADGET_Save
EndEnumeration

;With new API
Global HCURSOR_F = LoadImage_(#Null,"MyCursors\Paint_fill.cur",#IMAGE_CURSOR,32,32,#LR_LOADFROMFILE|#LR_VGACOLOR)
; If HCURSOR_F = #Null
;   Debug "Erreur lors du chargement du curseur!"
; EndIf
;With old API
Global HCURSOR_B = LoadCursorFromFile_("MyCursors\Rectangle_full.cur")
Global HCURSOR_C = LoadCursorFromFile_("MyCursors\Pencil_1.cur")
;Global HCURSOR_F = LoadCursorFromFile_("MyCursors\Paint_fill.cur")
Global HCURSOR_E = LoadCursorFromFile_("MyCursors\Ellipse_empty.cur")
Global HCURSOR_L = LoadCursorFromFile_("MyCursors\Line.cur")
Global HCURSOR_P = LoadCursorFromFile_("MyCursors\Clic_on_plus.ani") ; curseur animé

Procedure Destroy_Curseurs()
  DestroyCursor_(HCURSOR_B) 
  DestroyCursor_(HCURSOR_C)
  DestroyCursor_(HCURSOR_F)
  DestroyCursor_(HCURSOR_E)
  DestroyCursor_(HCURSOR_L)
  DestroyCursor_(HCURSOR_P)
EndProcedure


Global CurrentColor, CurrentMode, StartX, StartY
; Draw the mouse action result depending on the currently selected mode and event type
;
Procedure DrawAction(x, y, EventType)

  If StartDrawing(CanvasOutput(#GADGET_Canvas))
    Select CurrentMode
    
      Case #GADGET_Brush
        If EventType = #PB_EventType_LeftButtonDown Or EventType = #PB_EventType_MouseMove
          Circle(x, y, 5, CurrentColor)
        EndIf
        
      Case #GADGET_Line
        DrawImage(ImageID(#IMAGE_Content), 0, 0)
          LineXY(StartX, StartY, x, y, CurrentColor)
       
      Case #GADGET_Box
        DrawImage(ImageID(#IMAGE_Content), 0, 0)
        Box(StartX, StartY, x-StartX, y-StartY, CurrentColor)
        
      Case #GADGET_Circle
        DrawImage(ImageID(#IMAGE_Content), 0, 0)
        
        If x > StartX
          rx = x - StartX
        Else
          rx = StartX - x
        EndIf
        
        If y > StartY
          ry = y - StartY
        Else
          ry = StartY - y
        EndIf
        
        If GetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_Modifiers) & #PB_Canvas_Control
          ry = rx
        EndIf
        
        Ellipse(StartX, StartY, rx, ry, CurrentColor)
      
      Case #GADGET_Fill
        If EventType = #PB_EventType_LeftButtonDown
          FillArea(x, y, -1, CurrentColor)
        EndIf
  
    EndSelect
    
    StopDrawing()
  EndIf

EndProcedure

UseJPEGImageDecoder()
UseJPEGImageEncoder()

CurrentColor = $000000
CurrentMode  = #GADGET_Brush
CreateImage(#IMAGE_Color, DesktopScaledX(35), DesktopScaledY(35), 24)
CreateImage(#IMAGE_Content, DesktopScaledX(380), DesktopScaledY(380), 24)

If OpenWindow(0, 0, 0, 460, 400, "CanvasGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  CanvasGadget(#GADGET_Canvas, 10, 10, 380, 380, #PB_Canvas_ClipMouse)

  ButtonImageGadget(#GADGET_Color, 400, 10, 50, 50, ImageID(#IMAGE_Color))
  
  ButtonGadget(#GADGET_Brush,  400, 100, 50, 25, "Brush",  #PB_Button_Toggle)
  ButtonGadget(#GADGET_Line,   400, 130, 50, 25, "Line",   #PB_Button_Toggle)
  ButtonGadget(#GADGET_Box,    400, 160, 50, 25, "Box",    #PB_Button_Toggle)
  ButtonGadget(#GADGET_Circle, 400, 190, 50, 25, "Circle", #PB_Button_Toggle)
  ButtonGadget(#GADGET_Fill,   400, 220, 50, 25, "Fill",   #PB_Button_Toggle)
    
  ButtonGadget(#GADGET_Clear,  400, 280, 50, 25, "Clear")
  
  ButtonGadget(#GADGET_Load,   400, 335, 50, 25, "Load")
  ButtonGadget(#GADGET_Save,   400, 365, 50, 25, "Save")
  
  SetGadgetState(#GADGET_Brush, 1)
  ;SetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_Cursor, #PB_Cursor_Cross)
  SetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_CustomCursor, HCURSOR_P)
  

  Repeat
    Event = WaitWindowEvent()
    
    If Event = #PB_Event_Gadget
    
      Select EventGadget()
          
        Case #GADGET_Brush
          SetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_CustomCursor, HCURSOR_P)
          CurrentMode = EventGadget()
          SetGadgetState(#GADGET_Brush, 1) : SetGadgetState(#GADGET_Line, 0) : SetGadgetState(#GADGET_Box, 0) : SetGadgetState(#GADGET_Circle, 0) : SetGadgetState(#GADGET_Fill, 0) 
          
        Case #GADGET_Line
          SetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_CustomCursor, HCURSOR_L)
          CurrentMode = EventGadget()
          SetGadgetState(#GADGET_Brush, 0) : SetGadgetState(#GADGET_Line, 1) : SetGadgetState(#GADGET_Box, 0) : SetGadgetState(#GADGET_Circle, 0) : SetGadgetState(#GADGET_Fill, 0) 
          
        Case #GADGET_Box
          SetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_CustomCursor, HCURSOR_B)
          CurrentMode = EventGadget()
          SetGadgetState(#GADGET_Brush, 0) : SetGadgetState(#GADGET_Line, 0) : SetGadgetState(#GADGET_Box, 1) : SetGadgetState(#GADGET_Circle, 0) : SetGadgetState(#GADGET_Fill, 0) 
          
        Case #GADGET_Circle
          SetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_CustomCursor, HCURSOR_E)
          CurrentMode = EventGadget()
          SetGadgetState(#GADGET_Brush, 0) : SetGadgetState(#GADGET_Line, 0) : SetGadgetState(#GADGET_Box, 0) : SetGadgetState(#GADGET_Circle, 1) : SetGadgetState(#GADGET_Fill, 0) 
          
        Case #GADGET_Fill
          SetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_CustomCursor, HCURSOR_F)
          CurrentMode = EventGadget()
          SetGadgetState(#GADGET_Brush, 0) : SetGadgetState(#GADGET_Line, 0) : SetGadgetState(#GADGET_Box, 0) : SetGadgetState(#GADGET_Circle, 0) : SetGadgetState(#GADGET_Fill, 1) 
                   
        Case #GADGET_Canvas ;{
          X = GetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_MouseX)
          Y = GetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_MouseY)
          Type = EventType()
        
          Select EventType()
          
            Case #PB_EventType_LeftButtonDown
              ;
              ; This stores the current content of the CanvasGadget in #IMAGE_Content,
              ; so it can be re-drawn while the mouse moves
              ;
              If StartDrawing(ImageOutput(#IMAGE_Content))
                DrawImage(GetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_Image), 0, 0)
                StopDrawing()
              EndIf
              
              StartX = X
              StartY = Y
              DrawAction(X, Y, EventType())

            
            Case #PB_EventType_LeftButtonUp
              DrawAction(X, Y, EventType())
            
            Case #PB_EventType_MouseMove
              If GetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_Buttons) & #PB_Canvas_LeftButton
                DrawAction(X, Y, EventType())
              EndIf
                      
          EndSelect
          ;}
          
        Case #GADGET_Color ;{
          CurrentColor = ColorRequester(CurrentColor)
          If StartDrawing(ImageOutput(#IMAGE_Color))
            Box(0, 0, 35, 35, CurrentColor)
            StopDrawing()
            SetGadgetAttribute(#GADGET_Color, #PB_Button_Image, ImageID(#IMAGE_Color))
          EndIf
          ;}        
        Case #GADGET_Clear ;{
          If StartDrawing(CanvasOutput(#GADGET_Canvas))
            Box(0, 0, 380, 380, $FFFFFF)
            StopDrawing()
          EndIf
          ;}         
        Case #GADGET_Load ;{
          File$ = OpenFileRequester("Load Image...", "", "JPEG Images|*.jpeg|All Files|*.*", 0)
          If File$
            If LoadImage(#IMAGE_LoadSave, File$)
              If StartDrawing(CanvasOutput(#GADGET_Canvas))
                Box(0, 0, 380, 380, $FFFFFF)
                DrawImage(ImageID(#IMAGE_LoadSave), 0, 0)
                StopDrawing()
              EndIf
              FreeImage(#IMAGE_LoadSave)
            Else
              MessageRequester("CanvasGadget", "Cannot load image: " + File$)
            EndIf
          EndIf
          ;}         
        Case #GADGET_Save ;{
          File$ = SaveFileRequester("Save Image...", File$, "JPEG Images|*.jpeg|All Files|*.*", 0)
          If File$ And (FileSize(File$) = -1 Or MessageRequester("CanvasGadget", "Overwrite this file? " + File$, #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes)
            If CreateImage(#IMAGE_LoadSave, 380, 380, 24) And StartDrawing(ImageOutput(#IMAGE_LoadSave))
              DrawImage(GetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_Image), 0, 0)
              StopDrawing()
              
              If SaveImage(#IMAGE_LoadSave, File$, #PB_ImagePlugin_JPEG) = 0
                MessageRequester("CanvasGadget", "Cannot save image: " + File$)
              EndIf
              
              FreeImage(#IMAGE_LoadSave)
            EndIf
          EndIf
          ;}
          
      EndSelect
    
    EndIf
    
  Until Event = #PB_Event_CloseWindow
 Destroy_Curseurs()
EndIf
PureBasicien tu es, PureBasicien tu resteras.
User avatar
minimy
Enthusiast
Enthusiast
Posts: 613
Joined: Mon Jul 08, 2013 8:43 pm
Location: off world

Re: Using Custom Cursors

Post by minimy »

Hello, work perfect over PB6.21beta6. Thanks for share!!
And thanks for cursors lib, are very nice. Good job!
If translation=Error: reply="Sorry, Im Spanish": Endif
User avatar
Jacobus
Enthusiast
Enthusiast
Posts: 140
Joined: Wed Nov 16, 2005 7:51 pm
Location: France
Contact:

Re: Using Custom Cursors

Post by Jacobus »

Thanks for the feedback, Minimy. I'm happy to hear it helps.
PureBasicien tu es, PureBasicien tu resteras.
Post Reply