Strict
Import MaxGUI.MaxGUI
' TScrollPanel Proxy Gadget
' Author: Seb Hollington
Const SCROLLPANEL_SUNKEN:Int = 1
Const SCROLLPANEL_HALWAYS:Int = 2
Const SCROLLPANEL_VALWAYS:Int = 4
Const SCROLLPANEL_HSCALING:Int = 8
Const SCROLLPANEL_VSCALING:Int = 16
Const SCROLLPANEL_HNEVER:Int = 32
Const SCROLLPANEL_VNEVER:Int = 64
Rem
bbdoc: Creates a scrollable panel.
about: A scroll panel can be used to present a large number of gadgets in a small area. Scrollbars are displayed to allow the
user to move around a client-area that is viewed through a, typically smaller, viewport. The #ScrollPanelX and #ScrollPanelY functions
can be used to retrieve the current scroll position, and the #ScrollScrollPanel command, to set the scroll position. A @TScrollPanel gadget
emits the following event when %{the user} scrolls around the scroll area:
[ @{Event} | @{EventX} | @{EventY}
* EVENT_GADGETACTION | New value of #ScrollPanelX. | New value of #ScrollPanelY.
]
Any combination of the following style flags are supported:
[ @Constant | @Meaning
* SCROLLPANEL_SUNKEN | The scroll-panel will be drawn with a sunken border.
* SCROLLPANEL_HALWAYS | The horizontal scroll-bar will be shown at all times (even if not necessary).
* SCROLLPANEL_VALWAYS | The vertical scroll-bar will be shown at all times (even if not necessary).
* SCROLLPANEL_HNEVER | The horizontal scroll-bar will never be shown (even if client-area width is greater than viewport's).
* SCROLLPANEL_VNEVER | The vertical scroll-bar will never be shown (even if client-area height is greater than viewport's).
]
The above can also be combined with any of the following behavioural flags which determine how the scrollable client-area resizes with the viewport:
[ @Constant | @Meaning
* SCROLLPANEL_HSCALING | The client area's width grows uniformly as the viewport is sized.
* SCROLLPANEL_VSCALING | The client area's height grows uniformly as the viewport is sized.
]
[
* The @TScrollPanel instance itself represents the viewport of the scroll-panel, which can be manipulated (e.g. resized/shown/hidden) using the
standard MaxGUI commands.
* The client area is the panel that will actually be scrolled and is retrieved using the #ScrollPanelClient command. This is the panel
whose dimensions determine the total scrollable area, and is also the panel that all your child gadgets should be added to.
]
The dimensions given above can each be retrieved programatically:
{{
GadgetWidth( myScrollPanel ) 'Gadget Width
GadgetHeight( myScrollPanel ) 'Gadget Height
ClientWidth( myScrollPanel ) 'Viewport Width
ClientHeight( myScrollPanel ) 'Viewport Height
ClientWidth( ScrollPanelClient( myScrollPanel ) ) 'Client Area Width
ClientHeight( ScrollPanelClient( myScrollPanel ) ) 'Client Area Height
}}
And the gadget and client dimensions can be set programatically using (viewport sizing is handled automatically):
{{
'Set Gadget dimensions (and position).
SetGadgetShape( myScrollPanel, x, y, w, h )
'Set Client Area dimensions (position parameters are ignored).
SetGadgetShape( ScrollPanelClient( myScrollPanel ), 0, 0, w, h )
}}
See Also: #ScrollPanelClient, #FitScrollPanelClient, #ScrollScrollPanel, #ScrollPanelX, #ScrollPanelY and #FitScrollPanelClient.
End Rem
Function CreateScrollPanel:TScrollPanel( x,y,w,h,group:TGadget,flags=0 )
Return New TScrollPanel.Create(x,y,w,h,group,flags)
EndFunction
Rem
bbdoc: Retrieves the panel that is scrolled.
about: This panel represents the total scrollable region of the gadget. As such, use #SetGadgetShape on this panel to alter the
scrollable region (the xpos and ypos parameters will be ignored) or use the helper function #FitScrollPanelClient to resize the client area to
common dimensions. In either case, it is important to note that, contrary to typical MaxGUI behaviour, resizing the client panel
%{will not alter the position or dimensions of the children}, irrespective of any sizing behaviour previously defined using #SetGadgetLayout.
See #CreateScrollPanel for more information.
End Rem
Function ScrollPanelClient:TGadget( scrollpanel:TScrollPanel )
Return scrollpanel.pnlClientArea
EndFunction
Const SCROLLPANEL_SIZETOKIDS:Int = 0
Const SCROLLPANEL_SIZETOVIEWPORT:Int = 1
Rem
bbdoc: Helper function that resizes the client area to common dimensions.
about: This function resizes the scrollable area of a @TScrollPanel widget. Any child gadgets will retain their current
position and dimensions, irrespective of any sizing behaviour previously defined using #SetGadgetLayout. This function will
also reset the current visible area, to the furthest top-left.
[
* @scrollpanel: The scrollpanel whose client you want to resize.
* @fitType: Should be one of the following constants:
]
[ @Constant | @Meaning
* SCROLLPANEL_SIZETOKIDS | The client area will be resized so that its width and height are just enough to enclose all child gadgets.
* SCROLLPANEL_SIZETOVIEWPORT | The client area will be resized so that it is the same size that the viewport is currently (effectively removing the scrollbars).
]
See #CreateScrollPanel and #ScrollPanelClient for more information.
End Rem
Function FitScrollPanelClient( scrollpanel:TScrollPanel, fitType% = SCROLLPANEL_SIZETOKIDS )
Select fitType
Case SCROLLPANEL_SIZETOKIDS
scrollpanel.FitToChildren()
Case SCROLLPANEL_SIZETOVIEWPORT
scrollpanel.FitToViewport()
EndSelect
EndFunction
Const SCROLLPANEL_HOLD:Int = -1
Const SCROLLPANEL_TOP:Int = 0
Const SCROLLPANEL_LEFT:Int = 0
Const SCROLLPANEL_BOTTOM:Int = 2147483647
Const SCROLLPANEL_RIGHT:Int = 2147483647
Rem
bbdoc: Scrolls the current viewport to a new position.
about: This function moves the client area of the scroll panel so that the the top-left corner of the viewport is as close
as possible to the specified @{pX}, @{pY} position in the client-area.
There are 4 position constants provided:
[ @Constant | @{Position}
* SCROLLPANEL_TOP | Top-most edge.
* SCROLLPANEL_LEFT | Left-most edge.
* SCROLLPANEL_BOTTOM | Bottom-most edge.
* SCROLLPANEL_RIGHT | Right-most edge.
* SCROLLPANEL_HOLD | Current position.
]
For example, both of these commands...
{{
ScrollScrollPanel( myScrollPanel, SCROLLPANEL_LEFT, SCROLLPANEL_TOP )
ScrollScrollPanel( myScrollPanel, 0, 0 )
}}
...would scroll to the top-leftmost section of the client area. Conversely, we can scroll to the bottom-right most
region of the client area by calling:
{{
ScrollScrollPanel( myScrollPanel, SCROLLPANEL_RIGHT, SCROLLPANEL_BOTTOM )
}}
If we only want to change just the horizontal or just the vertical scroll position, we can use the SCROLLPANEL_HOLD constant. E.g.
to scroll to the left most side without changing the current vertical scroll position, we could use:
{{
ScrollScrollPanel( myScrollPanel, SCROLLPANEL_LEFT, SCROLLPANEL_HOLD )
}}
See #CreateScrollPanel, #ScrollPanelX, #ScrollPanelY and #ScrollPanelClient for more information.
EndRem
Function ScrollScrollPanel( scrollpanel:TScrollPanel, pX = SCROLLPANEL_TOP, pY = SCROLLPANEL_LEFT )
scrollpanel.ScrollTo( pX, pY )
scrollpanel.Update()
EndFunction
Rem
bbdoc: Returns the x position of the client-area that is currently at the top-left of the viewport.
about: Complementary function to #ScrollPanelY and #ScrollScrollPanel. See #ScrollScrollPanel for a visual representation
of this value.
See #CreateScrollPanel for more information.
EndRem
Function ScrollPanelX:Int( scrollpanel:TScrollpanel )
Return scrollpanel.GetXScroll()
EndFunction
Rem
bbdoc: Returns the y position of the client-area that is currently at the top-left of the viewport.
about: Complementary function to #ScrollPanelX and #ScrollScrollPanel. See #ScrollScrollPanel for a visual representation
of this value.
See #CreateScrollPanel for more information.
EndRem
Function ScrollPanelY:Int( scrollpanel:TScrollpanel )
Return scrollpanel.GetYScroll()
EndFunction
Type TScrollPanel Extends TProxyGadget
Field flags:Int
Field pnlEntire:TGadget
Field pnlViewport:TGadget
Field pnlClientArea:TScrollClient
Field scrHorizontal:TGadget
Field scrVertical:TGadget
Field currentH%, currentV%, clientW%, clientH%
Const SCROLL_WIDTH% = 18
Method New()
AddHook EmitEventHook,eventHandler,Self, -1
RemoveVerticalScroll();RemoveHorizontalScroll()
EndMethod
Method Create:TScrollPanel(pX%, pY%, pWidth%, pHeight%, pParent:TGadget, pFlags% = 0)
Local tmpPanelFlags:Int
flags = pFlags
If flags & (SCROLLPANEL_SUNKEN) Then tmpPanelFlags:|PANEL_SUNKEN
pnlEntire = CreatePanel(pX,pY,pWidth,pHeight,pParent,tmpPanelFlags);HideGadget(pnlEntire);SetProxy(pnlEntire)
pnlViewport = CreatePanel(0,0,pnlEntire.ClientWidth(),pnlEntire.ClientHeight(), pnlEntire)
pnlClientArea = New TScrollClient
pnlClientArea.SetProxy(CreatePanel(0,0,pnlViewport.ClientWidth(),pnlViewport.ClientHeight(), pnlViewport))
scrHorizontal = CreateSlider(0,pnlEntire.ClientHeight()-SCROLL_WIDTH,pnlEntire.ClientWidth()-SCROLL_WIDTH,SCROLL_WIDTH, pnlEntire, SLIDER_HORIZONTAL|SLIDER_SCROLLBAR )
scrVertical = CreateSlider(pnlEntire.ClientWidth()-SCROLL_WIDTH,0,SCROLL_WIDTH,pnlEntire.ClientHeight()-SCROLL_WIDTH, pnlEntire, SLIDER_VERTICAL|SLIDER_SCROLLBAR )
SetGadgetLayout(pnlViewport, EDGE_ALIGNED, EDGE_ALIGNED, EDGE_ALIGNED, EDGE_ALIGNED )
SetGadgetLayout(scrHorizontal, EDGE_ALIGNED, EDGE_ALIGNED, EDGE_CENTERED, EDGE_ALIGNED )
SetGadgetLayout(scrVertical, EDGE_CENTERED, EDGE_ALIGNED, EDGE_ALIGNED, EDGE_ALIGNED )
Select (flags & (SCROLLPANEL_HSCALING|SCROLLPANEL_VSCALING))
Case 0
SetGadgetLayout(pnlClientArea.GetProxy(), EDGE_ALIGNED, EDGE_CENTERED, EDGE_ALIGNED, EDGE_CENTERED )
Case SCROLLPANEL_HSCALING
SetGadgetLayout(pnlClientArea.GetProxy(), EDGE_ALIGNED, EDGE_ALIGNED, EDGE_ALIGNED, EDGE_CENTERED )
Case SCROLLPANEL_VSCALING
SetGadgetLayout(pnlClientArea.GetProxy(), EDGE_ALIGNED, EDGE_CENTERED, EDGE_ALIGNED, EDGE_ALIGNED )
Case SCROLLPANEL_HSCALING|SCROLLPANEL_VSCALING
SetGadgetLayout(pnlClientArea.GetProxy(), EDGE_ALIGNED, EDGE_ALIGNED, EDGE_ALIGNED, EDGE_ALIGNED )
EndSelect
HideGadget(scrHorizontal);HideGadget(scrVertical)
ShowGadget(pnlEntire)
Return Self
EndMethod
Method SetShape(x,y,w,h)
Super.SetShape(x,y,w,h)
Update()
EndMethod
Method ClientWidth:Int()
Return pnlViewport.ClientWidth()
EndMethod
Method ClientHeight:Int()
Return pnlViewport.ClientHeight()
EndMethod
Method GetXScroll:Int()
Return currentH
EndMethod
Method GetYScroll:Int()
Return currentV
EndMethod
Method ScrollTo(pHSlider%, pVSlider%)
Local tmpRight:Int = Max( pnlClientArea.ClientWidth()-pnlViewport.ClientWidth(), 0 )
Local tmpBottom:Int = Max( pnlClientArea.ClientHeight()-pnlViewport.ClientHeight(), 0 )
If (pHSlider > tmpRight) Then pHSlider = tmpRight
If (pVSlider > tmpBottom) Then pVSlider = tmpBottom
If (pHSlider >= 0) Then currentH = pHSlider
If (pVSlider >= 0) Then currentV = pVSlider
SetGadgetShape(pnlClientArea.GetProxy(),-currentH,-currentV,pnlClientArea.GetWidth(),pnlClientArea.GetHeight())
EndMethod
Method FitToChildren( pRightMargin:Int = 0, pBottomMargin:Int = 0 )
Local tmpRight:Int, tmpBottom:Int
For Local tmpChild:TGadget = EachIn pnlClientArea.proxy.kids
tmpRight = Max(tmpRight,GadgetX(tmpChild)+GadgetWidth(tmpChild))
tmpBottom = Max(tmpBottom,GadgetY(tmpChild)+GadgetHeight(tmpChild))
Next
HideGadget( pnlViewport )
pnlClientArea.SetShape(0,0,tmpRight + pRightMargin,tmpBottom + pBottomMargin)
ScrollTo(0,0)
ShowGadget( pnlViewport )
EndMethod
Method FitToViewport()
HideGadget( pnlViewport )
pnlClientArea.SetShape(0,0,pnlViewport.ClientWidth(),pnlViewport.ClientHeight())
ScrollTo(0,0)
ShowGadget( pnlViewport )
EndMethod
Method Update()
Local tmpDiff:Int, tmpPos:Int
If Not pnlClientArea Then Return
If pnlViewport.ClientWidth() < pnlClientArea.GetWidth() Then
AddHorizontalScroll(pnlViewport.ClientWidth(),pnlClientArea.GetXPos(),pnlClientArea.GetWidth())
Else
RemoveHorizontalScroll()
EndIf
If pnlViewport.ClientHeight() < pnlClientArea.GetHeight() Then
AddVerticalScroll(pnlViewport.ClientHeight(),pnlClientArea.GetYPos(),pnlClientArea.GetHeight())
Else
RemoveVerticalScroll()
EndIf
If pnlViewport.ClientWidth() < pnlClientArea.GetWidth() Then
tmpDiff = Max(pnlViewport.ClientWidth()-(pnlClientArea.GetXpos() + pnlClientArea.GetWidth()),0)
tmpPos = Min(pnlClientArea.GetXPos()+tmpDiff,0)
AddHorizontalScroll(pnlViewport.ClientWidth(),tmpPos,pnlClientArea.GetWidth())
ScrollTo( -tmpPos, currentV )
Else
RemoveHorizontalScroll()
EndIf
If pnlViewport.ClientHeight() < pnlClientArea.GetHeight() Then
tmpDiff = Max(pnlViewport.ClientHeight()-(pnlClientArea.GetYPos() + pnlClientArea.GetHeight()),0)
tmpPos = Min(pnlClientArea.GetYPos()+tmpDiff,0)
AddVerticalScroll(pnlViewport.ClientHeight(),tmpPos,pnlClientArea.GetHeight())
ScrollTo( currentH, -tmpPos )
Else
RemoveVerticalScroll()
EndIf
EndMethod
Method AddVerticalScroll(pVisible%, pY%, pHeight%)
If scrVertical And Not (flags&SCROLLPANEL_VNEVER) Then
SetGadgetShape(pnlViewport, 0, 0, pnlEntire.ClientWidth() - SCROLL_WIDTH, GadgetHeight(pnlViewport))
SetGadgetShape(scrVertical, pnlEntire.ClientWidth()-SCROLL_WIDTH, 0, SCROLL_WIDTH, GadgetHeight(pnlViewport))
SetSliderRange(scrVertical, pVisible, pHeight);SetSliderValue(scrVertical,-pY)
EnableGadget(scrVertical);ShowGadget(scrVertical)
EndIf
EndMethod
Method RemoveVerticalScroll()
If scrVertical And Not GadgetDisabled(scrVertical) Then
If Not (flags&SCROLLPANEL_VALWAYS) Then
HideGadget(scrVertical)
SetGadgetShape(pnlViewport, 0, 0, pnlEntire.ClientWidth(), GadgetHeight(pnlViewport))
EndIf
DisableGadget(scrVertical)
ScrollTo(currentH,0)
EndIf
EndMethod
Method AddHorizontalScroll(pVisible%, pX%, pWidth%)
If scrHorizontal And Not (flags&SCROLLPANEL_HNEVER) Then
SetGadgetShape(pnlViewport, 0, 0, GadgetWidth(pnlViewport), pnlEntire.ClientHeight()-SCROLL_WIDTH )
SetGadgetShape(scrHorizontal, 0, pnlEntire.ClientHeight()-SCROLL_WIDTH, GadgetWidth(pnlViewport), SCROLL_WIDTH)
SetSliderRange(scrHorizontal, pVisible, pWidth);SetSliderValue(scrHorizontal,-pX)
EnableGadget(scrHorizontal);ShowGadget(scrHorizontal)
EndIf
EndMethod
Method RemoveHorizontalScroll()
If scrHorizontal And Not GadgetDisabled(scrHorizontal) Then
If Not (flags&SCROLLPANEL_HALWAYS) Then
SetGadgetShape(pnlViewport, 0, 0, GadgetWidth(pnlViewport), pnlEntire.ClientHeight())
HideGadget(scrHorizontal)
EndIf
DisableGadget(scrHorizontal)
ScrollTo(0,currentV)
EndIf
EndMethod
Method eventHook:Object(pID%, pData:Object, pContext:Object)
Local tmpEvent:TEvent = TEvent(pData)
If tmpEvent = Null Then Return pData
Select tmpEvent.id
Case EVENT_WINDOWSIZE
If CheckParent(pnlEntire, TGadget(tmpEvent.source)) Then Update()
Case EVENT_GADGETACTION
Local tmpH:Int = currentH, tmpV:Int = currentV
Select tmpEvent.source
Case scrHorizontal
tmpH = SliderValue(scrHorizontal)
Case scrVertical
tmpV = SliderValue(scrVertical)
Default
Return pData
EndSelect
ScrollTo(tmpH, tmpV)
EmitEvent CreateEvent( EVENT_GADGETACTION, Self, 0, 0, currentH, currentV, Null )
pData = Null
EndSelect
Return pData
EndMethod
Method CleanUp()
RemoveHook EmitEventHook, eventHandler, Self
SetProxy(Null)
If pnlClientArea Then pnlClientArea.SetProxy(Null);pnlClientArea = Null
If pnlEntire Then HideGadget(pnlEntire);FreeGadget(pnlEntire)
EndMethod
Function eventHandler:Object(pID%, pData:Object, pContext:Object)
Local tmpSuperPanel:TScrollPanel = TScrollPanel(pContext)
If tmpSuperPanel Then pData = tmpSuperPanel.eventHook(pID%, pData:Object, pContext:Object)
Return pData
EndFunction
Function CheckParent%( pGadget:TGadget, pParentToCheck:TGadget )
If pGadget = pParentToCheck Then Return True
If pGadget.parent Then Return CheckParent(pGadget.parent, pParentToCheck)
EndFunction
EndType
Private
Type TScrollClient Extends TProxyGadget
Method SetShape(x,y,w,h)
Local i:Int, arrDimensions:Int[][], tmpDimensions:Int[]
For Local tmpChild:TGadget = EachIn proxy.kids
tmpDimensions = [GadgetX(tmpChild),GadgetY(tmpChild),GadgetWidth(tmpChild),GadgetHeight(tmpChild)]
arrDimensions:+[tmpDimensions]
Next
Super.SetShape(GetXPos(),GetYPos(),w,h)
TScrollPanel(proxy.parent.parent.source).Update()
For Local tmpChild:TGadget = EachIn proxy.kids
tmpDimensions = arrDimensions[i];i:+1
SetGadgetShape( tmpChild, tmpDimensions[0], tmpDimensions[1], tmpDimensions[2], tmpDimensions[3] )
Next
EndMethod
Method SetLayout(lft,rht,top,bot)
'Do nothing
EndMethod
EndType