Improved ScrollBarGadget

Got an idea for enhancing PureBasic? New command(s) you'd like to see?
Polo
Addict
Addict
Posts: 2422
Joined: Tue May 06, 2003 5:07 pm
Location: UK

Improved ScrollBarGadget

Post by Polo »

Hello,

I'd like the "live" ScrollBar position to be returned through WaitWindowEvent. At the moment, the only way to get the live position of the Scrollbar is to use a thread (see link below), although this is not very convenient.

viewtopic.php?f=13&t=46396

On MacOSX, the Purebasic ScrollBarGadget doesn't behave like most scrollbars from other apps, the scrollbar tracker position doesnt change position while dragged (and nor is the scrollbar live position returned) (see link below)

viewtopic.php?f=3&t=45713
freak wrote:This is a windows specific weirdness. It basically stops sending any messages to the event queue
while the draging of the scrollbar is processed.

The only way to work around this is to write all the mouse handling code for
the scrollbar manually, which would be a nightmare to get right for every windows version.

You'll just have to live with the callback solution on Windows.
For Linux and OSX all this is no problem. The events come in as expected.
viewtopic.php?f=5&t=31141

I don't know about Linux, but on OSX (last version) the event does not come as expected: the scrollbar fire an event on mouseup only.
User avatar
Shardik
Addict
Addict
Posts: 2058
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Improved ScrollBarGadget

Post by Shardik »

Polo wrote:On MacOSX, the Purebasic ScrollBarGadget doesn't behave like most scrollbars from other apps, the scrollbar tracker position doesnt change position while dragged (and nor is the scrollbar live position returned) (see link below)

viewtopic.php?f=3&t=45713
This is a MacOS X workaround with a ScrollBar created with an API call which provides
true live tracking. Unfortunately I didn't find any way to enable live tracking in a PB
ScrollBarGadget because this feature can only to be enabled when creating this control.
So I endorse Niffo's proposal to enable live tracking when creating the ScrollBarGadget
in PB internally.

Code: Select all

ImportC ""
  CreateScrollBarControl(WindowRef.L, *BoundsRect, CurrentValue.L, MinimumValue.L, MaximumValue.L, ViewSize.L, LiveTracking.L, LiveTrackingProc.L, *ControlRef)
  GetControl32BitMaximum(ControlRef.L)
  GetControl32BitMinimum(ControlRef.L)
  GetControl32BitValue(ControlRef.L)
  SetControl32BitValue(ControlRef.L, NewValue.L)
  ShowControl(ControlRef.L)
EndImport

#kControlDownButtonPart = 21
#kControlUpButtonPart = 20

Structure Rect
  Top.W
  Left.W
  Bottom.W
  Right.W
EndStructure

ProcedureCDLL LiveTrackingProc(ControlRef.L, PartCode.L)
  Shared ScrollBarRef.L

  Protected CurrentPosition.L

  If ControlRef = ScrollBarRef
    Select PartCode
      Case #kControlUpButtonPart
        CurrentPosition = GetControl32BitValue(ControlRef)

        If CurrentPosition > GetControl32BitMinimum(ControlRef)
          SetControl32BitValue(ControlRef.L, CurrentPosition - 1)
        EndIf
      Case #kControlDownButtonPart
        CurrentPosition = GetControl32BitValue(ControlRef)

        If CurrentPosition < GetControl32BitMaximum(ControlRef)
          SetControl32BitValue(ControlRef.L, CurrentPosition + 1)
        EndIf
    EndSelect

    SetGadgetText(3, Str(GetControl32BitValue(ControlRef)))
  EndIf
EndProcedure

Define Bounds.Rect

Bounds\Left   = 10
Bounds\Top    = 51
Bounds\Right  = 190
Bounds\Bottom = 70

OpenWindow(0, 200, 100, 510, 90, "ScrollBar with Live Tracking")
ScrollBarGadget(0, 10, 20, 180, 20, 0, 120, 20)
SetGadgetState(0, 50)
TextGadget(1, 200, 19, 40, 17, "50", #PB_Text_Border | #PB_Text_Center)
TextGadget(2, 250, 20, 300, 20, "PB ScrollBarGadget with 'Ghost Thumb'")
TextGadget(3, 200, 50, 40, 17, "50", #PB_Text_Border | #PB_Text_Center)
TextGadget(4, 250, 50, 300, 20, "API ScrollBar with 'Live Tracking'")

If CreateScrollBarControl(WindowID(0), @Bounds, 50, 0, 100, 20, #True, @LiveTrackingProc(), @ScrollBarRef) = 0
  ShowControl(ScrollBarRef)

  Repeat
    Select WaitWindowEvent()
      Case #PB_Event_CloseWindow
        Break
      Case #PB_Event_Gadget
        If EventGadget() = 0
          SetGadgetText(1, Str(GetGadgetState(0)))
        EndIf
    EndSelect
  ForEver
EndIf
User avatar
Shardik
Addict
Addict
Posts: 2058
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Improved ScrollBarGadget

Post by Shardik »

Polo wrote:At the moment, the only way to get the live position of the Scrollbar is to use a thread (see link below), although this is not very convenient.

viewtopic.php?f=13&t=46396
You don't need to use a thread to get the live position of the ScrollBar. In Windows
you can utilize a window callback or subclass the ScrollBarGadget. And in Linux
and MacOS X it isn't too difficult neither. Take a look into this cross-platform
example of using a ScrollBar with live scrolling to scroll an image. I have tested it
with PB 4.51 in Windows 98 SE, Windows XP SP2 + SP3, Windows 7 x86 and x64,
andLinux/Kubuntu 9.04 and MacOS X 10.6.7 (Snow Leopard):

Code: Select all

EnableExplicit

Define ScrollStep.F

CompilerSelect #PB_Compiler_OS
  CompilerCase #PB_OS_Linux
    ProcedureC ScrollBarCallback(*Range.GtkRange, ScrollType.L, NewValue.D, UserData.L)
      Shared ScrollStep.F

      GrabImage(0, 1, Int(GetGadgetState(1) * ScrollStep), 0, GadgetWidth(0) - 4, ImageHeight(0))
      SetGadgetState(0, ImageID(1))
    EndProcedure
  CompilerCase #PB_OS_MacOS
    ImportC ""
      CreateScrollBarControl(WindowRef.L, *BoundsRect, CurrentValue.L, MinimumValue.L, MaximumValue.L, ViewSize.L, LiveTracking.L, LiveTrackingProc.L, *ControlRef)
      GetControl32BitMaximum(ControlRef.L)
      GetControl32BitMinimum(ControlRef.L)
      GetControl32BitValue(ControlRef.L)
      SetControl32BitValue(ControlRef.L, NewValue.L)
      ShowControl(ControlRef.L)
    EndImport

    #kControlDownButtonPart = 21
    #kControlUpButtonPart = 20

    Structure Rect
      Top.W
      Left.W
      Bottom.W
      Right.W
    EndStructure

    ProcedureC ScrollBarCallback(ControlRef.L, PartCode.L)
      Shared ScrollStep.F
      Protected CurrentPosition.L
      
      Select PartCode
        Case #kControlUpButtonPart
          CurrentPosition = GetControl32BitValue(ControlRef)
          
          If CurrentPosition > GetControl32BitMinimum(ControlRef)
            SetControl32BitValue(ControlRef.L, CurrentPosition - Int(ScrollStep))
          EndIf
        Case #kControlDownButtonPart
          CurrentPosition = GetControl32BitValue(ControlRef)
          
          If CurrentPosition < GetControl32BitMaximum(ControlRef)
            SetControl32BitValue(ControlRef.L, CurrentPosition + Int(ScrollStep))
          EndIf
      EndSelect
        
      GrabImage(0, 1, Int(GetControl32BitValue(ControlRef) * ScrollStep), 0, GadgetWidth(0) - 4, ImageHeight(0))
      SetGadgetState(0, ImageID(1))
    EndProcedure
  CompilerCase #PB_OS_Windows
    Procedure WindowCallback(WindowHandle.I, Msg.I, WParam.I, LParam.I)
      Shared ScrollStep.F

      If LParam = GadgetID(1)
        If Msg = #WM_HSCROLL
          GrabImage(0, 1, Int(GetGadgetState(1) * ScrollStep), 0, GadgetWidth(0) - 4, ImageHeight(0))
          SetGadgetState(0, ImageID(1))
        EndIf
      EndIf 

      ProcedureReturn #PB_ProcessPureBasicEvents 
    EndProcedure 
CompilerEndSelect

If LoadImage(0, #PB_Compiler_Home + "/examples/sources/Data/PureBasicLogo.bmp")
  OpenWindow(0, 200, 100, 172, 100, "Live tracking", #PB_Window_SystemMenu)
  ImageGadget(0, 5, 5, WindowWidth(0) - 9, ImageHeight(0), 0, #PB_Image_Border)

  CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
    Define Bounds.Rect
    Define ScrollBarRef.L
    
    Bounds\Left   = 5
    Bounds\Top    = WindowHeight(0) - 20
    Bounds\Right  = WindowWidth(0) - 5
    Bounds\Bottom = WindowHeight(0) - 5

    If CreateScrollBarControl(WindowID(0), @Bounds, 0, 0, GadgetWidth(0), GadgetWidth(0), #True, @ScrollBarCallback(), @ScrollBarRef) = 0
      ShowControl(ScrollBarRef)
      GrabImage(0, 1, GetControl32BitValue(ScrollBarRef), 0, GadgetWidth(0) - 4, ImageHeight(0))
      ScrollStep = (ImageWidth(0) - GadgetWidth(0)) / GadgetWidth(0)
    EndIf
  CompilerElse
    Define ViewSize.I

    ScrollStep = ImageWidth(0) / GadgetWidth(0)
    ViewSize = Int(0.0 + GadgetWidth(0) / ImageWidth(0) * GadgetWidth(0))
    ScrollBarGadget(1, 5, WindowHeight(0) - 20, WindowWidth(0) - 10, 15, 0, GadgetWidth(0), ViewSize)
    GrabImage(0, 1, GetGadgetState(1), 0, GadgetWidth(0) - 4, ImageHeight(0))
  CompilerEndIf

  SetGadgetState(0, ImageID(1))

  CompilerSelect #PB_Compiler_OS
    CompilerCase #PB_OS_Linux
      g_signal_connect_data_(GadgetID(1), "change-value", @ScrollBarCallback(), 0, 0, 0) 
    CompilerCase #PB_OS_Windows
      SetWindowCallback(@WindowCallback(), 0)
  CompilerEndSelect
  
  Repeat
  Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
Polo
Addict
Addict
Posts: 2422
Joined: Tue May 06, 2003 5:07 pm
Location: UK

Re: Improved ScrollBarGadget

Post by Polo »

looks very nice indeed mate, cool to see some cross platform code!

Wish this will be implemented natively in PB though, as this code might not work in the future (i.e. if Purebasic switch to Cocoa :))

Thanks, I'm going to use that! :)
Polo
Addict
Addict
Posts: 2422
Joined: Tue May 06, 2003 5:07 pm
Location: UK

Re: Improved ScrollBarGadget

Post by Polo »

Hello!

I'm wondering where i can find an API reference for Carbon, i'm struggling to find anything on google...

I'd like to detect in the callback if the user pressed on the up, right, top or bottom arrow of the scrollbar (depending if it's a horizontal or vertical scrollbar), but i'm not sure on how to do this?

Any ideas?

Thanks
Gaetan
User avatar
Shardik
Addict
Addict
Posts: 2058
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Improved ScrollBarGadget

Post by Shardik »

Polo wrote:I'm wondering where i can find an API reference for Carbon, i'm struggling to find anything on google...
Control Manager Reference (PDF)
Polo wrote:I'd like to detect in the callback if the user pressed on the up, right, top or bottom arrow of the scrollbar (depending if it's a horizontal or vertical scrollbar), but i'm not sure on how to do this?
Didn't you try the Mac example of my first posting in this thread? The
Callback LiveTrackingProc() checks the PartCode (the part of the control
which was clicked on) for #kControlUpButtonPart (up arrow in ScrollBar)
and #kControlDownButtonPart (Down arrow in ScrollBar). In a vertical
ScrollBar #kControlUpButtonPart is set on a click onto the left button and
#kControlDownButtonPart for a click onto the right button. You are able
to discriminate between different ScrollBars in the callback by comparing
the parameter ControlRef with the Reference of your different ScrollBar
controls... :wink:

There are no part codes for "Top" and "Bottom" because there are simply
no control elements in the ScrollBar to position the slider to the top or
bottom position at once. I presume that you think of the possibility to
click into an area below or above a slider to move a page up or down.
This is still not implemented in my above example but the PartCodes
constants are:

Code: Select all

#kControlPageUpPart = 22
#kControlPageDownPart = 23
Last edited by Shardik on Thu Jun 09, 2011 8:35 pm, edited 1 time in total.
Polo
Addict
Addict
Posts: 2422
Joined: Tue May 06, 2003 5:07 pm
Location: UK

Re: Improved ScrollBarGadget

Post by Polo »

Can't believe I didn't realise that..!!! Sorry sorry!! :oops:
Thanks again !! :)
Polo
Addict
Addict
Posts: 2422
Joined: Tue May 06, 2003 5:07 pm
Location: UK

Re: Improved ScrollBarGadget

Post by Polo »

Hi,

I've worked on your code Shardik and it's great. I added the page up and down quite easily.

However, I need to get a notification when the main scrollbar button dragging is finished.
With #kControlIndicatorPart = 129 I receive an event in the callback each time the position is moved, while i'd rather have the event when it has finished moving, thus on the mouse up.

Any idea on how to do this? I've seen some code to report mouse events on gadgets, though I'm not sure this is the way to go as it'd be handy to do everything in the same callback :)
Thanks again!
Gaetan
User avatar
Shardik
Addict
Addict
Posts: 2058
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Improved ScrollBarGadget

Post by Shardik »

Polo wrote:However, I need to get a notification when the main scrollbar button dragging is finished.
With #kControlIndicatorPart = 129 I receive an event in the callback each time the position is moved, while i'd rather have the event when it has finished moving, thus on the mouse up.

Any idea on how to do this? I've seen some code to report mouse events on gadgets, though I'm not sure this is the way to go as it'd be handy to do everything in the same callback :)

Code: Select all

EnableExplicit

ImportC ""
  CreateScrollBarControl(WindowRef.L, *BoundsRect, CurrentValue.L, MinimumValue.L, MaximumValue.L, ScrollBarViewSize.L, LiveTracking.L, LiveTrackingProc.L, *ControlRef)
  GetControl32BitValue(ControlRef.L)
  SetControl32BitValue(ControlRef.L, NewValue.L)
  ShowControl(ControlRef.L)
  TrackMouseLocation(GrafPortPtr.L, *MouseLocation, *TrackingEvent)
EndImport

#ScrollBarMaximum = 100
#ScrollBarMinimum = 0
#ScrollBarViewSize = 20
#ScrollBarWidth = 170
#ScrollBarX = 20

#kControlDownButtonPart = 21
#kControlIndicatorPart = 129
#kControlPageUpPart = 22
#kControlPageDownPart = 23
#kControlUpButtonPart = 20
#kMouseTrackingMouseUp = 2

Structure Rect
  Top.W
  Left.W
  Bottom.W
  Right.W
EndStructure

ProcedureC LiveTrackingCallback(ScrollBarRef.L, PartCode.L)
  Protected MousePosition.L
  Protected SliderPosition.L
  Protected TrackingEvent.L
  Protected MouseX.L

  Select PartCode
    Case #kControlIndicatorPart
      While TrackingEvent <> #kMouseTrackingMouseUp
        TrackMouseLocation(0, @MousePosition, @TrackingEvent)
        MouseX = MousePosition >> 16
        SliderPosition = Int((MouseX + 0.0) / (#ScrollBarWidth - 64) * (#ScrollBarMaximum - #ScrollBarMinimum)) - 36

        If SliderPosition < #ScrollBarMinimum
          SliderPosition = #ScrollBarMinimum
        EndIf
        
        If SliderPosition > #ScrollBarMaximum
          SliderPosition = #ScrollBarMaximum
        EndIf
        
        SetControl32BitValue(ScrollBarRef, SliderPosition)
        SetGadgetText(1, Str(SliderPosition))
      Wend
      
      Debug "Scroll button was released"
    Case #kControlUpButtonPart
      SliderPosition = GetControl32BitValue(ScrollBarRef)
      
      If SliderPosition > #ScrollBarMinimum
        SetControl32BitValue(ScrollBarRef, SliderPosition - 1)
      EndIf
    Case #kControlDownButtonPart
      SliderPosition = GetControl32BitValue(ScrollBarRef)
      
      If SliderPosition < #ScrollBarMaximum
        SetControl32BitValue(ScrollBarRef, SliderPosition + 1)
      EndIf
    Case #kControlPageUpPart
      SliderPosition = GetControl32BitValue(ScrollBarRef)
      
      If SliderPosition - #ScrollBarViewSize >= #ScrollBarMinimum
        SetControl32BitValue(ScrollBarRef.L, SliderPosition - #ScrollBarViewSize)
      Else
        SetControl32BitValue(ScrollBarRef.L, #ScrollBarMinimum)
      EndIf        
    Case #kControlPageDownPart 
      SliderPosition = GetControl32BitValue(ScrollBarRef)
      
      If SliderPosition + #ScrollBarViewSize <= #ScrollBarMaximum
        SetControl32BitValue(ScrollBarRef, SliderPosition + #ScrollBarViewSize)
      Else
        SetControl32BitValue(ScrollBarRef, #ScrollBarMaximum)
      EndIf
  EndSelect
  
  SetGadgetText(1, Str(GetControl32BitValue(ScrollBarRef)))
EndProcedure

Define Bounds.Rect
Define ScrollBarRef.L

Bounds\Left   = #ScrollBarX
Bounds\Top    = 21
Bounds\Right  = #ScrollBarX + #ScrollBarWidth
Bounds\Bottom = 70

OpenWindow(0, 200, 100, 510, 60, "ScrollBar with Live Tracking")
TextGadget(1, 200, 20, 40, 17, "50", #PB_Text_Border | #PB_Text_Center)
TextGadget(2, 250, 20, 300, 20, "API ScrollBar with 'Live Tracking'")

If CreateScrollBarControl(WindowID(0), @Bounds, (#ScrollBarMaximum - #ScrollBarMinimum) / 2, #ScrollBarMinimum, #ScrollBarMaximum, 20, #True, @LiveTrackingCallback(), @ScrollBarRef) = 0
  ShowControl(ScrollBarRef)

  Repeat
  Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
Polo
Addict
Addict
Posts: 2422
Joined: Tue May 06, 2003 5:07 pm
Location: UK

Re: Improved ScrollBarGadget

Post by Polo »

Amazing, cheers mate you're the best! :)
User avatar
DoubleDutch
Addict
Addict
Posts: 3220
Joined: Thu Aug 07, 2003 7:01 pm
Location: United Kingdom
Contact:

Re: Improved ScrollBarGadget

Post by DoubleDutch »

It would be great if live tracking of the scrollbars was native to PureBasic in the final 4.60...

Live scrollbars make canvas gadgets much more flexible - you can make custom tree systems, grids, text editors, etc..
https://deluxepixel.com <- My Business website
https://reportcomplete.com <- School end of term reports system
User avatar
charvista
Addict
Addict
Posts: 949
Joined: Tue Sep 23, 2008 11:38 pm
Location: Belgium

Re: Improved ScrollBarGadget

Post by charvista »

@Shardik
I have reviewed your code completely, analyzed it from top to bottom because it got my greatest attention. :) But only the Windows part, because I cannot test the other platforms.
ScrollStep is not necessary! Because it is always 1. This is because the logic was not very correct.
I also added the vertical scrollbar for completeness.

Code: Select all

;(c) Shardik 2011-05-25
;(c) Charvista 2011-12-26

EnableExplicit

Enumeration
    #Image     ; loaded image
    #ImageBis  ; duplicate image
    #Gadget    ; image gadget
    #Window    ; window
    #ScbGadH   ; scrollbar gadget horizontal
    #ScbGadV   ; scrollbar gadget vertical
EndEnumeration

Define.i WinWidth,WinHeight,HMarge,VMarge,GWidth,GHeight

Procedure WindowCallback(WindowHandle.i,Msg.i,wParam.i,lParam.i)
    Shared GWidth,GHeight
    If lParam=GadgetID(#ScbGadH) Or lParam=GadgetID(#ScbGadV)
        ;If Msg=#WM_HSCROLL; with this, it will only update if the cursor is ON the scrollbar
                           ; if used here and scrollbar is moved to start or end too quickly, will show some "latency" when the mouse button is released
            GrabImage(#Image,#ImageBis,GetGadgetState(#ScbGadH),GetGadgetState(#ScbGadV),GWidth,GHeight)
            SetGadgetState(#Gadget,ImageID(#ImageBis))
        ;EndIf
    EndIf
    ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure

UsePNGImageDecoder()
LoadImage(#Image,#PB_Compiler_Home+"/examples/3D/Data/Water/Foam.png")
WinWidth=200
WinHeight=200
OpenWindow(#Window,0,0,WinWidth,WinHeight,"Image scrolling",#PB_Window_SystemMenu|#PB_Window_ScreenCentered|#PB_Window_SizeGadget)
HMarge=5
VMarge=5
GWidth=WinWidth-(2*HMarge)-20; marge left & right
GHeight=WinHeight-(2*VMarge)-20; marge top & bottom
ImageGadget(#Gadget,HMarge,VMarge,GWidth,ImageHeight(#Image),0,#PB_Image_Border)
; never use ImageWidth() as width for ImageGadget() because this will automatically replace the width of the ImageGadget() with the width of the image!
; so if you want a fixed ImageGadget width, use a fixed value => here GWidth used.
; same rule applies for ImageHeight().

ScrollBarGadget(#ScbGadH,HMarge,WindowHeight(#Window)-20,GWidth,15,0,ImageWidth(#Image),GWidth)
ScrollBarGadget(#ScbGadV,WindowWidth(#Window)-20,VMarge,15,GHeight,0,ImageHeight(#Image),GHeight,#PB_ScrollBar_Vertical)
GrabImage(#Image,#ImageBis,0,0,GWidth,GHeight)
SetGadgetState(#Gadget,ImageID(#ImageBis))
SetWindowCallback(@WindowCallback(),#Window)

Repeat
Until WaitWindowEvent()=#PB_Event_CloseWindow
- Windows 11 Home 64-bit
- PureBasic 6.10 LTS (x64)
- 64 Gb RAM
- 13th Gen Intel(R) Core(TM) i9-13900K 3.00 GHz
- 5K monitor with DPI @ 200%
User avatar
Shardik
Addict
Addict
Posts: 2058
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Improved ScrollBarGadget

Post by Shardik »

charvista,

thank you for reviewing and expanding my example code.
charvista wrote:ScrollStep is not necessary! Because it is always 1. This is because the logic was not very correct.
You are right! Although ScrollStep is only 1 if the width/height ratio is 1. In my
example ScrollStep is 2.33742332458496 :wink:
My example isn't wrong but it makes things more difficult as they are. The problem arose
because I used GadgetWidth(0) instead of ImageWidth(0) in the definition of my
ScrollBarGadget... :twisted:

One hint to improve your example: you commented out the test on #WM_HSCROLL in your
WindowCallback. This generates a lot of unneccessary CPU load because on every message
from your ScrollBarGadgets the image will be redrawn. But you are right that my solution is
not very clean because when positioning the scrollbar knob at the beginning or end the image
will be moved a bit when releasing the knob. For a solution you only need to take a look into
the code example from Windows API guru srod: he only updates the image position if the
scrollcode doesn't indicate #SB_THUMBPOSITION or #SB_ENDSCROLL. Therefore I have updated
my Windows example:

Code: Select all

EnableExplicit

Procedure WindowCallback(WindowHandle.I, Msg.I, WParam.I, LParam.I)
  Protected ScrollCode.L
  
  If LParam = GadgetID(1)
    If Msg = #WM_HSCROLL
      ScrollCode = WParam & $FFFF

      If ScrollCode <> #SB_THUMBPOSITION And ScrollCode <> #SB_ENDSCROLL
        GrabImage(0, 1, GetGadgetState(1), 0, GadgetWidth(0) - 4, ImageHeight(0))
        SetGadgetState(0, ImageID(1))
      EndIf
    EndIf
  EndIf
  
  ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure

If LoadImage(0, #PB_Compiler_Home + "/examples/sources/Data/PureBasicLogo.bmp")
  OpenWindow(0, 200, 100, 172, 100, "Live tracking", #PB_Window_SystemMenu)
  ImageGadget(0, 5, 5, WindowWidth(0) - 9, ImageHeight(0), 0, #PB_Image_Border)
  ScrollBarGadget(1, 5, WindowHeight(0) - 20, WindowWidth(0) - 10, 15, 0, ImageWidth(0), WindowWidth(0) - 10)
  GrabImage(0, 1, GetGadgetState(1), 0, GadgetWidth(0) - 4, ImageHeight(0))
  SetGadgetState(0, ImageID(1))
  SetWindowCallback(@WindowCallback(), 0)

  Repeat
  Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
And this is the updated cross-platform example:

Code: Select all

EnableExplicit

CompilerSelect #PB_Compiler_OS
  CompilerCase #PB_OS_Linux
    ProcedureC ScrollBarCallback(*Range.GtkRange, ScrollType.L, NewValue.D, UserData.L)
      GrabImage(0, 1, GetGadgetState(1), 0, GadgetWidth(0) - 4, ImageHeight(0))
      SetGadgetState(0, ImageID(1))
    EndProcedure
  CompilerCase #PB_OS_MacOS
    ImportC ""
      CreateScrollBarControl(WindowRef.L, *BoundsRect, CurrentValue.L, MinimumValue.L, MaximumValue.L, ViewSize.L, LiveTracking.L, LiveTrackingProc.L, *ControlRef)
      GetControl32BitMaximum(ControlRef.L)
      GetControl32BitMinimum(ControlRef.L)
      GetControl32BitValue(ControlRef.L)
      SetControl32BitValue(ControlRef.L, NewValue.L)
      ShowControl(ControlRef.L)
    EndImport

    #kControlDownButtonPart = 21
    #kControlUpButtonPart = 20

    Structure Rect
      Top.W
      Left.W
      Bottom.W
      Right.W
    EndStructure

    ProcedureC ScrollBarCallback(ControlRef.L, PartCode.L)
      Protected CurrentPosition.L
     
      Select PartCode
        Case #kControlUpButtonPart
          CurrentPosition = GetControl32BitValue(ControlRef)
         
          If CurrentPosition > GetControl32BitMinimum(ControlRef)
            SetControl32BitValue(ControlRef.L, CurrentPosition)
          EndIf
        Case #kControlDownButtonPart
          CurrentPosition = GetControl32BitValue(ControlRef)
         
          If CurrentPosition < GetControl32BitMaximum(ControlRef)
            SetControl32BitValue(ControlRef.L, CurrentPosition)
          EndIf
      EndSelect
       
      GrabImage(0, 1, GetControl32BitValue(ControlRef), 0, GadgetWidth(0) - 4, ImageHeight(0))
      SetGadgetState(0, ImageID(1))
    EndProcedure
  CompilerCase #PB_OS_Windows
    Procedure WindowCallback(WindowHandle.I, Msg.I, WParam.I, LParam.I)
      Protected ScrollCode.L

      If LParam = GadgetID(1)
        If Msg = #WM_HSCROLL
          ScrollCode = WParam & $FFFF

          If ScrollCode <> #SB_THUMBPOSITION And ScrollCode <> #SB_ENDSCROLL
            GrabImage(0, 1, GetGadgetState(1), 0, GadgetWidth(0) - 4, ImageHeight(0))
            SetGadgetState(0, ImageID(1))
          EndIf
        EndIf
      EndIf

      ProcedureReturn #PB_ProcessPureBasicEvents
    EndProcedure
CompilerEndSelect

If LoadImage(0, #PB_Compiler_Home + "/examples/sources/Data/PureBasicLogo.bmp")
  OpenWindow(0, 200, 100, 172, 100, "Live tracking", #PB_Window_SystemMenu)
  ImageGadget(0, 5, 5, WindowWidth(0) - 9, ImageHeight(0), 0, #PB_Image_Border)

  CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
    Define Bounds.Rect
    Define ScrollBarRef.L
   
    Bounds\Left   = 5
    Bounds\Top    = WindowHeight(0) - 20
    Bounds\Right  = WindowWidth(0) - 5
    Bounds\Bottom = WindowHeight(0) - 5

    If CreateScrollBarControl(WindowID(0), @Bounds, 0, 0, GadgetWidth(0), GadgetWidth(0), #True, @ScrollBarCallback(), @ScrollBarRef) = 0
      ShowControl(ScrollBarRef)
      GrabImage(0, 1, GetControl32BitValue(ScrollBarRef), 0, GadgetWidth(0) - 4, ImageHeight(0))
    EndIf
  CompilerElse
    ScrollBarGadget(1, 5, WindowHeight(0) - 20, WindowWidth(0) - 10, 15, 0, ImageWidth(0), WindowWidth(0) - 10)
    GrabImage(0, 1, GetGadgetState(1), 0, GadgetWidth(0) - 4, ImageHeight(0))
  CompilerEndIf

  SetGadgetState(0, ImageID(1))

  CompilerSelect #PB_Compiler_OS
    CompilerCase #PB_OS_Linux
      g_signal_connect_data_(GadgetID(1), "change-value", @ScrollBarCallback(), 0, 0, 0)
    CompilerCase #PB_OS_Windows
      SetWindowCallback(@WindowCallback(), 0)
  CompilerEndSelect
 
  Repeat
  Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
User avatar
charvista
Addict
Addict
Posts: 949
Joined: Tue Sep 23, 2008 11:38 pm
Location: Belgium

Re: Improved ScrollBarGadget

Post by charvista »

Shardik,
It was a real pleasure to look at your code :)
Your code is much better now :D And true, adding Srod's code for your ScrollCode is good to put a halt to CPU consuming, but there is still a practical problem. For the eyes, your updated code is much nicer as it no longer moves a bit when releasing the knob, but... the image is not at position 0 as the user should expect when doing that rapid movement: you still need to click several times on the small arrow of the scrollbar to go to the real start or end of the image. I still need to do more tests to see how to solve your problem while maintaining #WM_HSCROLL (or #WM_VSCROLL)...
As a side question: does it really consume so much CPU? How can I verify that? The Windows Task Manager does not show exceptional CPU changes... besides, it occurs only when the user has the mouse clicked on the scrollbar, so it is temporary... (and we don't expect the user keeping his finger down on that scrollbar for hours, do we? Yes, this can occur when thinking about the image while scrolling slowly, but then scrolling occurs effectively). Otherwise, adding Delay(10) in the WindowCallback() can already slow down CPU usage, without feeling erratic image movements.
- Windows 11 Home 64-bit
- PureBasic 6.10 LTS (x64)
- 64 Gb RAM
- 13th Gen Intel(R) Core(TM) i9-13900K 3.00 GHz
- 5K monitor with DPI @ 200%
User avatar
Shardik
Addict
Addict
Posts: 2058
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Improved ScrollBarGadget

Post by Shardik »

charvista wrote:As a side question: does it really consume so much CPU? How can I verify that?
You can indirectly prove that by establishing 3 counters in the WindowCallback
and counting how many redraws would have been done with and without
filtering events. And you should also take into account that scrolling a small
image might be negligible but scrolling a large image should produce an
annoying flicker if you redraw the image on every occasion... :wink:

Code: Select all

EnableExplicit

Procedure WindowCallback(WindowHandle.I, Msg.I, WParam.I, LParam.I)
  Static MsgCount1.I
  Static MsgCount2.I
  Static MsgCount3.I
  Protected ScrollCode.L
 
  If LParam = GadgetID(1)
    MsgCount1 + 1

    If Msg = #WM_HSCROLL
      MsgCount2 + 1
      ScrollCode = WParam & $FFFF

      If ScrollCode <> #SB_THUMBPOSITION And ScrollCode <> #SB_ENDSCROLL
        MsgCount3 + 1
        GrabImage(0, 1, GetGadgetState(1), 0, GadgetWidth(0) - 4, ImageHeight(0))
        SetGadgetState(0, ImageID(1))

        Debug "Redraws for messages generated by ScrollBarGadget: " + Str(MsgCount1)
        Debug "Redraws with filtering of #WM_HSCROLL: " + Str(MsgCount2)
        Debug "Redraws with filtering of #WM_HSCROLL and ScrollCode: " + Str(MsgCount3)
      EndIf
    EndIf
  EndIf
 
  ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure

If LoadImage(0, #PB_Compiler_Home + "/examples/sources/Data/PureBasicLogo.bmp")
  OpenWindow(0, 200, 100, 172, 100, "Live tracking", #PB_Window_SystemMenu)
  ImageGadget(0, 5, 5, WindowWidth(0) - 9, ImageHeight(0), 0, #PB_Image_Border)
  ScrollBarGadget(1, 5, WindowHeight(0) - 20, WindowWidth(0) - 10, 15, 0, ImageWidth(0), WindowWidth(0) - 10)
  GrabImage(0, 1, GetGadgetState(1), 0, GadgetWidth(0) - 4, ImageHeight(0))
  SetGadgetState(0, ImageID(1))
  SetWindowCallback(@WindowCallback(), 0)

  Repeat
  Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
Try to move the scroll knob several times and you will see a lot of unneccessary redraws
without event filtering... :wink:
Post Reply