The Artima Developer Community
Sponsored Link

Agile Buzz Forum
Vertical Scrollbar Starting API

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
James Robertson

Posts: 29924
Nickname: jarober61
Registered: Jun, 2003

David Buck, Smalltalker at large
Vertical Scrollbar Starting API Posted: Aug 31, 2007 1:32 PM
Reply to this message Reply

This post originated from an RSS feed registered with Agile Buzz by James Robertson.
Original Post: Vertical Scrollbar Starting API
Feed Title: Pollock
Feed URL: http://www.cincomsmalltalk.com/rssBlog/pollock-rss.xml
Feed Description: Pollock - the next VW GUI
Latest Agile Buzz Posts
Latest Agile Buzz Posts by James Robertson
Latest Posts From Pollock

Advertisement

We've created and set up our ScrollingCanvas and an artist for it, ScrollingCanvasArtist. Now we need to add the API that the Scrollbars need in order to interact with our pane.

Today we'll address only the vertical scrollbar.

Testing 1-2 3

First we need to tell the world if we have an interior decoration. This is a simple method on our Artist
	ScrollingCanvasArtist>>hasInteriorDecoration
		^interiorDecoration notNil

This method is used by the inner edge clipping mechanism we have previously discussed to know if it should take into account a decorations additional clipping area. The default implementation in Artist answers false. If we don't answer the right value here, then when it comes time to display the pane after a scrollbar is turned on, the area where the scrollbar should display will be over drawn by the pane itself.

Everything I Know I Learned While Sleeping

There are two basic pieces of information that any scrollbar needs to know in order to do its job.

First it needs to know how high or wide the full scrolling area is. For instance, for a ListBox, this information vertically would be the number of items in the list box multiplied by the height of each item. Horizontally, it would be the maximum width of the longest item in the list.

Second it needs to know how what the current offset from the top or left of the whole area is currently at the top of the pane. For instance, for a ListBox that has 100 items, and can display only 10, the top might be 5.

Each of these has an accessor on our Artist.

For the full scrolling area it requires the method #totalScrollHeight.

For the top offset, it requires the method #topItemIndex.

There is another value needed. Scrollbars have a notion of a list. This is because they typically deal with text or enumeration panes. So to scroll one step, when pressing the up or down buttons, it asks how big the list is, and how big an item is.

For now, we'll say that an item is 1 (one pixel), and that the top is fixed at 1, and the list size is the height of our drawing.

In order to get up and running, we'll just put in fake values for now, and hook them up to real values later.

	ScrollingCanvasArtist>>topItemIndex
		^1
	
	ScrollingCanvasArtist>>totalScrollHeight
		^1

	ScrollingCanvasArtist>>listSize
		^self frame bounds height

In a future posting we'll hook up #listSize to a larger value so that we can do actual scrolling, but for now, this value will do.

Now we go back to our CustomWidgetWork and it to turn on the vertical scrollbar:

	CustomWidgetWork>>hookupInterface

		customWidget drawActionBlock:
			[:aGraphicsContext :pane |
			| oldLineWidth oldPaint |
			oldLineWidth := aGraphicsContext lineWidth.
			oldPaint := aGraphicsContext paint.
			aGraphicsContext lineWidth: 5.
			aGraphicsContext paint: ColorValue red.
			aGraphicsContext displayLineFrom: 0 @ 0 to: pane frame bounds corner.
			aGraphicsContext lineWidth: 10.
			aGraphicsContext paint: ColorValue blue.
			aGraphicsContext displayLineFrom: pane frame bounds bottomLeft to: pane frame bounds topRight.
			aGraphicsContext paint: oldPaint.
			aGraphicsContext lineWidth: oldLineWidth].
		"Optional Border code:"
		"customWidget borderType: #ridged"
		"customWidget borderType: #raised"
		"customWidget borderType: #etched"
		"customWidget borderType: #lowered"
		"customWidget borderType: #line"
		"customWidget border: TwistBorder new"
		customWidget verticalScrollbar: true

And if we execute CustomWidgetWork open we see this:

We don't see the Scrollbar yet, but we do see that the right edge of the drawing is clipped to make room for it.

So the question is, why didn't the Scrollbar display?

The answer is simple... We never told it to display!

Do What I Say

In the inherited #displayOn: method, there is no code to tell the interior decoration to display itself:

	CanvasArtist>>displayOn: aGraphicsContext 
		| oldPaint oldClippingRectangle |
		oldClippingRectangle := aGraphicsContext clippingBounds.
		oldPaint := aGraphicsContext paint.
		aGraphicsContext clippingRectangle: (self artistAdjustedFrameBounds intersect: oldClippingRectangle).
		self hasBorder ifTrue: [self border displayOn: aGraphicsContext in: self artistAdjustedFrameBounds].
		(drawAction notNil and: [oldClippingRectangle intersects: self artistAdjustedInnerBounds]) ifTrue: 
			[aGraphicsContext clippingRectangle: (self artistAdjustedInnerBounds intersect: oldClippingRectangle).
			self executeDrawActionWith: aGraphicsContext].
		aGraphicsContext clippingRectangle: oldClippingRectangle.
		aGraphicsContext paint: oldPaint

In Artists that have interior decorations, there would be a line right after the self hasBorder that reads:

	self hasInteriorDecoration ifTrue: [self interiorDecoration displayOn: aGraphicsContext in: self interiorDecorationRectangle].

So we have to write our own displayOn: for our Artist:

	ScrollingCanvasArtist>>displayOn: aGraphicsContext 
		| oldPaint oldClippingRectangle |
		oldClippingRectangle := aGraphicsContext clippingBounds.
		oldPaint := aGraphicsContext paint.
		aGraphicsContext clippingRectangle: (self artistAdjustedFrameBounds intersect: oldClippingRectangle).
		self hasBorder ifTrue: [self border displayOn: aGraphicsContext in: self artistAdjustedFrameBounds].
		self hasInteriorDecoration ifTrue: [self interiorDecoration displayOn: aGraphicsContext in: self interiorDecorationRectangle].
		(drawAction notNil and: [oldClippingRectangle intersects: self artistAdjustedInnerBounds]) ifTrue: 
			[aGraphicsContext clippingRectangle: (self artistAdjustedInnerBounds intersect: oldClippingRectangle).
			self executeDrawActionWith: aGraphicsContext].
		aGraphicsContext clippingRectangle: oldClippingRectangle.
		aGraphicsContext paint: oldPaint

And we have to give it a #interiorDecorationRectangle method too:

	ScrollingCanvasArtist>>interiorDecorationRectangle
		^self hasBorder 
			ifTrue: [self frame bounds insetBy: self border innerEdgeRectangle] 
			ifFalse: [self frame bounds]

Coffee Klatch

While our new #displayOn: is basically just a copy of what is in CanvasArtist, we should discuss what is going on here, so if you ever want to do a more custom #displayOn: method, you can do it for yourself.

In any #displayOn: method, there is always a prolog and epilog which gathers the existing clipping bounds and paint, and later resets them. This is a requirement you should always obey. You Have Been Warned!

The next thing is also boilerplate where we set the #clippingRectangle: for the GraphicsContext. We do this by taking the clipping rectangle that came out of the GraphicsContext when it came in and have it intersect with what is called the #artistAdjustedFrameBounds. This method, #artistAdjustedFrameBounds has a default implementation on Artist, and for most situations, we don't have to be concerned about it.

However, you should know what it is for in case you need it. For some widgets in some looks, the bounds the user gives a pane isn't totally available to be displayed in. For instance, in the MacOSX look, most panes have a blue haze around them when they are active. Thus, you'll see in the artists for many of the MacOSX look panes, this area is clipped out of the bounds.

The next thing you should know is why we do this clipping at all, even if there is no special #artistAdjustedFrameBounds. During the regular course of events, when a window is asked to display itself or parts of itself, it gathers up a rectangle that represents the minimum amount of area that is required to redraw the window. That area may be bigger or smaller than the area of our pane, or may otherwise intersect the bounds of our pane. By making sure that we give the GraphicsContext a new clipping area that is only within our bounds, we can do two things. First, if for some reason the bounds it is giving us is outside of our bounds, we can as you see below our new line of code, skip doing our drawing at all. A small optimization, but one that is important. Second, if you have a smart draw routine you can only draw in the area that needs drawing, instead of drawing the whole pane.

Since it is possible that only the interior decoration has asked to display itself, we do our test for the first scenario based on the method #artistAdjustedInnerBounds. This is similar to the #artistAdjustedFrameBounds, except it is based on the inner bounds of the pane. It too has a default implementation on Artist, but you can override that implementation in your artist if you need to.

Our ScrollingCanvas has no smart drawing code to utilize for the second scenario, but it is there if you need it. Enumeration panes and TextEdits in Widgetry do have smart drawing.

Then we have the simple draw border code, and simple interior decoration code. Note as we discussed a couple of postings ago, borders and interior decorations are displayed using the #displayOn:in: protocol. We pass in the bounds of the area in which we want the object to display.

For the border, we give it the same #artistAdjustedFrameBounds we used for the clipping. For the interior decoration, we have a new method named #interiorDecorationRectangle. And as you see in the code above, it gives us a rectangle that is the whole bounds if there isn't a border, and the bounds inset by the border's inner edge if there is a border. This makes sure that the interior decoration never displays over the area where a border might be.

If you look at other implementers of #interiorDecorationRectangle, you'll see that some do extra clipping. Just like for the #artistAdjustedFrameBounds, this gives us the ability to do look or pane relative tweaking if needed. We don't need anything special so our #interiorDecorationRectangle is simple.

Pretty Picture

Now if we open our CustomWidgetWork, we see this:

We now see our vertical scrollbar, but it is disabled. That's right though.

Because we have told it that the top item is 1, and the scroll height is 1, it thinks there is nothing to scroll, and in fact that is correct. The scrollbar thus displays itself disabled.

What's Next

We'll leave our vertical scrollbar and next time work on the horizontal scrollbar, adding the API it needs to also show itself disabled.

And So It Goes
Sames

Read: Vertical Scrollbar Starting API

Topic: An interesting, but weak problem, finally Previous Topic   Next Topic Topic: Plaxo and identity aggregation

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use