Improved ScrollBarGadget

Got an idea for enhancing PureBasic? New command(s) you'd like to see?
User avatar
Shardik
Addict
Addict
Posts: 1989
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: 1989
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: 1989
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: 3219
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: 902
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.04 LTS (x64)
- 64 Gb RAM
User avatar
Shardik
Addict
Addict
Posts: 1989
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: 902
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.04 LTS (x64)
- 64 Gb RAM
User avatar
Shardik
Addict
Addict
Posts: 1989
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:
User avatar
charvista
Addict
Addict
Posts: 902
Joined: Tue Sep 23, 2008 11:38 pm
Location: Belgium

Re: Improved ScrollBarGadget

Post by charvista »

Indeed, I must admit with your proof that it makes quite a large difference when narrowing to the events to save CPU usage. :wink:
So I agree that narrowing is necessary.
- Narrowing to the Scrollbar Gadget is of course mandatory. :mrgreen:
- Narrowing to #WM_HSCROLL and #WM_VSCROLL is clever. :D
- Alas, narrowing to ScrollCode is nonsense. :cry: Because the number of events between #WM_HSCROLL (or #WM_VSCROLL) is the same or the difference is extremely low.

However, we still need ScrollCode! :twisted: Now you are really puzzled :mrgreen:
Yes, if you examine the code, you will see that I embedded #WM_HSCROLL with the ScrollCode to get a halt in the events if the GetGadgetState() has changed compared with the kept state in the last WindowCallback! (=variables Hstate/Vstate)
It is rather complex, but it is the compromise between speed and having the image completely and nicely at the left or right side (or completely at the top/bottom).
There is no flickering nor erratic movements with large images.

I would like to thank you for your cooperation involving this problem, Shardik. :P :wink:

If you use the free software Paint.NET, you can compare the image scrolling there: open a large jpg file, do a "Save As" in jpg format, give a new name, and then you will get a window to adjust the jpeg quality, together with a scrollable image. PureBasic can do that now too! :wink:

Note: please adjust the LoadImage() to open a large image.

Code: Select all

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

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,ScrollCode

Procedure WindowCallback(WindowHandle.i,Msg.i,wParam.i,lParam.i)
    Static.i MsgCount1,MsgCount2,MsgCount3,Hstate,Vstate
    Shared GWidth,GHeight,ScrollCode
    If lParam=GadgetID(#ScbGadH) Or lParam=GadgetID(#ScbGadV)
        MsgCount1+1
        ScrollCode.i=WParam & $FFFF
        If (Msg=#WM_HSCROLL And ScrollCode<>#SB_THUMBPOSITION And ScrollCode<>#SB_ENDSCROLL) Or GetGadgetState(#ScbGadH)<>Hstate Or (Msg=#WM_VSCROLL And ScrollCode<>#SB_THUMBPOSITION And ScrollCode<>#SB_ENDSCROLL) Or GetGadgetState(#ScbGadV)<>Vstate
            ; with #WM_HSCROLL/#WM_VSCROLL, 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
            MsgCount2+1
            ;ScrollCode.i=WParam & $FFFF
            ;If ScrollCode<>#SB_THUMBPOSITION And ScrollCode<>#SB_ENDSCROLL
                ;MsgCount3+1
                Hstate=GetGadgetState(#ScbGadH)
                Vstate=GetGadgetState(#ScbGadV)
                GrabImage(#Image,#ImageBis,Hstate,Vstate,GWidth,GHeight)
                SetGadgetState(#Gadget,ImageID(#ImageBis))
                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

UsePNGImageDecoder()
UseJPEGImageDecoder()
LoadImage(#Image,"c:/users/myname/pictures/aVeryLargeImage.png")  ;#PB_Compiler_Home+"/examples/3D/Data/Water/Foam.png")
WinWidth=390;200
WinHeight=320;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,GHeight,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.04 LTS (x64)
- 64 Gb RAM
Post Reply