Monday, June 8, 2009

Introduction to Viewports and Scrolling in Gumbo

Computer displays are never big enough. At the moment I'm using a 14x10" laptop display that's about 120 dpi. I've got a 24inch display at my desk back at work and if had my druthers the display where I spend my quality time would be as big as the office walls. And if the rule about personal income holds true - you always feel as though you'd be satisfied if your income was about 3X what you're making now - then I probably wouldn't be truly happy until I needed my own personal movie theater just to read email. That scenario doesn't seem likely until the day comes when the computer's display is wired directly into my visual cortex. That is a development I'm willing to wait for future generations to beta test.

One of the reasons big displays are so attractive is that we often interact with documents which are much bigger than our puny screens. This has been a computer problem since Teletypes slowly slapped upper case text on a fat scroll of butcher paper. It continued to be a problem when 80 line CRT terminals were considered a luxury, and it's still a problem now, even with displays that are so big you have to move your head to take them all in. The modern solution for displaying a large document on a small display hasn't really changed over the years, in a way it's no different than the approach an Egyptian scribe would have employed thousands of years ago to read a papyrus scroll.

We give the user a way to "scroll" a small rectangular window that exposes a large document a little bit at a time. When we say scrolling, we're referring to moving the origin of the little clipping window relative to the large document. One conventional GUI for scrolling is a pair of vertical and horizontal scrollbars, whose thumb positions define the X and Y origin of the clipping rectangle.

Here's a diagram that shows the basic geometry of the situation in terms of a "viewport", a GUI container that exposes a rectangular clip of its contents.

The Flex Gumbo SDK provides MXML and ActionScript support for this model of scrolling. A new component called Scroller connects a pair of scrollbars to a viewport and manages the layout of the three parts in a conventional way.

Scroller binds the horizontal and vertical scrollbars' values (which are based on their thumbs' positions) to the viewport's horizontalScrollPosition and verticalScrollPosition properties. The size of the scrollbar thumbs and the maximum limit for their values is based on the relative size of the viewport's content, which is defined by properties called contentWidth and contentHeight. The origin of the viewport's clipping rectangle is defined by the scrollPosition and its size (width, height) is bound to the viewport's size. The viewport displays the content in the clipping rectangle.

The code below is a simple example of the Scroller class. The Scroller's viewport is a Group that contains an Image:

   <?xml version="1.0" encoding="utf-8"?>
   <s:Application
       xmlns:fx="http://ns.adobe.com/mxml/2009"
       xmlns:s="library://ns.adobe.com/flex/spark"
       xmlns:mx="library://ns.adobe.com/flex/halo">

       <s:Scroller left="1" right="1" top="1" bottom="1">
           <s:Group>
               <mx:Image source="guyrobot.jpg"/>
           </s:Group>
       </s:Scroller>
   </s:Application>

Here's a working version of the example. It's been complicated a little to display the current values of the various viewport properties. The source code for this version of the example can be found here.
The Scroller Example in Action

The basic Flex Gumbo container classes are called Group and DataGroup and they share a common base class called GroupBase. GroupBase is-a IViewport, so anything you can create within a Group or a DataGroup can be displayed in a Scroller, since Scroller just requires its viewport to implement IViewport.
Most of the IViewport interface should look familiar based on the discussion so far:

   public interface IViewport extends IVisualElement
   {
       function get width():Number;
       function get height():Number;
       function get contentWidth():Number;
       function get contentHeight():Number;
       function get horizontalScrollPosition():Number;
       function set horizontalScrollPosition(value:Number):void;
       function get verticalScrollPosition():Number;
       function set verticalScrollPosition(value:Number):void;
       function getHorizontalScrollPositionDelta(scrollUnit:uint):Number;
       function getVerticalScrollPositionDelta(scrollUnit:uint):Number;
       function get clipAndEnableScrolling():Boolean;
       function set clipAndEnableScrolling(value:Boolean):void;
   }

A viewport's width and height are its actual width and height and its contentWidth,Height is the maximum X and Y extent of its contents. For example if a viewport contained a rectangle with origin at 10,20 and width,height of 100, then it's contentWidth would be 110, contentHeight 120. In the example above the Image's origin is 0,0 (the default) and so the Group's contentWidth,Height is the same as the image's width and height.

There are two additional features in the IViewport interface: the scrollPositionDelta methods that compute the distance to needed to scroll by a line or a page and the clipAndEnableScrolling flag that triggers use of the Flash DisplayObject scrollRect property to clip and render efficiently. Once the clipAndEnableScrolling flag is set, changing the scrollPosition properties will scroll the viewport. When you click on an Scroller's scrollbar track or up/down buttons, roll the mouse wheel, or press they keyboard page up/down or arrow keys, the viewport's scrollPositionDelta methods are used to compute the distance to be scrolled.

The Gumbo viewport and scrolling API is similar to the one provided by Halo. As before, the fundamental container classes, Container in Halo, Group and DataGroup in Gumbo, are capable of scrolling. In Gumbo that capability is disabled by default, to enable it one must set the Group or DataGroup's clipAndEnableScrolling flag to true. In Halo all containers would sprout scrollbars whenever they detected the need, but in Gumbo if you want conventional scrolling via scrollbars you need to put your viewport in a Scroller component. These changes were motivated by a desire to reduce the cost of typical applications. Most containers in most application don't need scrollbars or scrolling and there's a cost in complexity and footprint for giving all containers that ability in Halo. Gumbo's policy is sometimes referred to as "pay as you go", which is to say that your application's footprint and performance should just reflect the SDK features that you need.

I always fight the temptation to conclude a blog by announcing that I'm planning to write more on the same topic. That's because more often than not I fail to actually deliver the goods. This time will have to be an exception because the viewport/scrolling topic still warrants a great deal more explanation and a collection of helpful examples. Eventually all of this material will also be covered in formal specs and ASDoc but I think it's worth publishing the details here and now, while it's still new enough to change.

Note also: there are more robots like the one in the example at http://guyrobot.com/.

10 comments:

  1. Thanks Hans for posting this. Very cool stuff. I'm interested to hear more about this and how it relates to virtualization, smooth scrolling, etc.

    -James

    ReplyDelete
  2. Thanks for the post, it surely clear things out!I've seen your presentation at ADC and jumped to Flash Builder and make my own scroll type container that let us scroll it with click and then mouse move and i never thought it would be SO easy as it was!

    ReplyDelete
  3. Is there any way to set a maximum on the contentWidth/height, for purposes of scrolling? Eg if I have a 200x200 image inside a 50x50 scrollpane, but I only want to allow scrolling to show a maximum of 100x100 of the image.

    ReplyDelete
  4. How do you skin this. I can't seem to find a default example of the scroll skin class

    ReplyDelete
  5. Nice Article Hans thanks for the post,
    but regarding a scroll bar for the halo components in flex like list had a livescrolling option which the spark components no longer has.
    do you anything about it

    ReplyDelete
  6. Thanks, Hans, for the clear explanation.
    Is there a way to dynamically load a content - say, a 'Group' component - into the scroller's viewport at a runtime?
    Thanks,
    Igor Borodin

    ReplyDelete
  7. Hello Hans, great example. Just want to point out a pitfall for your readers: if we try to refactor the scroller code into a skin for the application. The viewport's properties will be considered as un-bindable and the solution won't work any more ... so its better to stick to the way that Hans does it here.

    ReplyDelete
  8. when i scroll the list in flex grey color screen appears how to eliminate this

    ReplyDelete