The new controller is implemented as a subclass of ScrollController. Class ScrollController implements vertical scrolling and the new class has to add only horizontal scrolling. To this purpose, we add three instance variables to keep the horizontal scrollbar, the horizontal marker and the screen area that is obscured by the horizontal scrollbar when it is displayed.
The change set for this example is FullScrollExample01.1.cs
ScrollController subclass: #BidirectionalScrollController instanceVariableNames: 'scrollBarX markerX savedAreaX' classVariableNames: 'CursorLeftArrow' poolDictionaries: '' category: 'MVCTutorial-Scrolling'
scrollBarX - Quadrangle
This quadrangle keeps the position and the display properties of the horizontal scrollbar.
markerX - Quadrangle
This quadrangle keeps the position and the display properties of the horizontal marker, the
rectangular area that is displayed in the horizontal scrollbar to give you a hint which part
of the scrollable view content is currently visible.
savedAreaX - Form
As long as the horizontal scrollbar is visible, the portion of the display that is covered
by the scrollbar is stored in this form. When the scrollbar is removed,
its display area is restored from the savedAreaX and the savedAreaX is deleted.
CursorLeftArrow - CursorWithMask
This Variable keeps the cursor that is displayed when the mouse pointer
points into the horizontal scrollbar area below the marker.
The cursor form shows an arrow that points to the left.
To demonstrate the use of that controller, we introduce a new view:
View subclass: #FormDisplayView instanceVariableNames: 'form scrollOffset' classVariableNames: '' poolDictionaries: '' category: 'MVCTutorial-Scrolling'
The example is implemented in class :
Model subclass: #BidirectionalScrollExample1 instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'MVCTutorial-Scrolling'
This model will be only used to provide an initial window extent:
intitialExtent ^300@320
The methods of BidirectionalScrollController are schematically derived from the methods of the superclass. We have to:
Thus, the method computeMarkerRegion of ScrollController, which reads:
computeMarkerXRegion " Answer the rectangular area in which the gray area of the scroll bar should be displayed. " ^0@0 extent: Preferences scrollBarWidth @ ((view window height asFloat / view boundingBox height * scrollBar inside height) rounded min: scrollBar inside height)
becomes:
computeMarkerXRegion " Answer the rectangular area in which the gray area of the scroll bar should be displayed. " ^0@0 extent: ((view window width asFloat / view boundingBox width * scrollBarX inside width) rounded min: scrollBarX inside width ) @ Preferences scrollBarWidth
Note that the coordinates of the extent point were transposed.
Most of the important code is in the FormViewDisplay:
This view uses a BidirectionalScrollController as its preferred controller:
defaultControllerClass ^BidirectionalScrollController
The size of the inset display box and the current scroll position are answered by the method window:
window ^scrollOffset negated extent: self insetDisplayBox extent
The size of the image to be displayed is:
boundingBox ^form isNil ifTrue: [self getWindow] ifFalse: [0@0 extent: form extent].
Displaying:
displayView form isNil ifTrue: [self clearInside] ifFalse: [Display copy: (scrollOffset negated extent: self insetDisplayBox extent) from: form to: self insetDisplayBox origin rule: Form over ]
Scrolling
scrollBy: aPoint " the coordinates of aPoint can be floats " | oldOffset cornerOfScrolledForm corner | oldOffset := scrollOffset. scrollOffset := scrollOffset + aPoint rounded. cornerOfScrolledForm := scrollOffset + form extent. corner := self insetDisplayBox extent. cornerOfScrolledForm x < corner x ifTrue: [scrollOffset := (corner x - cornerOfScrolledForm x) @ 0 + scrollOffset]. cornerOfScrolledForm y < corner y ifTrue: [scrollOffset := 0 @ (corner y - cornerOfScrolledForm y) + scrollOffset]. ^oldOffset ~= scrollOffset " a value of true tells the controller that the view should be redisplayed "
It is the responsibility of the controller to redraw the view during scrolling. This is done in ScrollController>>scrollView: and in BidirectionalScrollController>>scrollViewX:, where we find:
(view scrollBy:(<aPoint>)) ifTrue: [view clearInside; display. ^true]
This is a very general solution. Due to its generality, it can not use information about the view content that would perhaps allow the use of a more efficient redraw algorithm. Some specialized controllers (like ListController and ParagraphEditor) delegate the view repainting to their views.