Let us study how to move a displayable object on a 'DisplaySurface'. For example, let us see how to move an image (taken from the screen) around the screen following the mouse. The easiest way is to repeatedly display the image and restore the background as the mouse moves around the screen. Examine the following pseudocode:
----------------------------------------------------- oldPoint := newPoint := get current coordinates of the mouse. display the image. [repeat condition] whileTrue: [newPoint := get current coordinates of the mouse. newPoint = oldPoint ifFalse: [repair background. display the image. oldPoint := newPoint]]. repair background. -------------------------------------------------------
Program 13-1 is the actual program. Please pay careful attention to the use of block closures. Notice that the image is only displayed inside the window in which this code was evaluated.
Program-13-1: (Image, GraphicsContext; display:at:, cursorPoint, intersect:, clippingBox, value:) ---------------------------------------------------------- | anImage activeWindow aSensor displayContext displayBlock restoreBlock oldPoint newPoint | anImage := Image fromUser. activeWindow := ScheduledControllers activeController view. aSensor := activeWindow sensor. displayContext := activeWindow graphicsContext. displayBlock := [:aPoint | displayContext display: anImage at: aPoint]. restoreBlock := [:aPoint | | tempContext clippingBox | tempContext := displayContext copy. clippingBox := anImage bounds translatedBy: aPoint. clippingBox := clippingBox intersect: displayContext medium bounds. tempContext clippingRectangle: clippingBox. tempContext medium displayOn: tempContext]. oldPoint := newPoint := aSensor cursorPoint. displayBlock value: newPoint. [aSensor noButtonPressed] whileTrue: [newPoint := aSensor cursorPoint. newPoint = oldPoint ifFalse: [restoreBlock value: oldPoint. displayBlock value: newPoint. oldPoint := newPoint]]. restoreBlock value: oldPoint. ----------------------------------------------------------
Program 13-1 redraws the background of an image whenever the image is moved. The program does not have to save the background image to redraw. Therefore even if several of these display programs are running in parallel, the view will be ok. However, you probably noticed a problem of flicker caused by repeated painting the screen.
To fix the flicker problem, change the program to save the background of an image to redraw it. The pseudocode is as follows. The only change is to save the background before displaying the image.
----------------------------------------------------- oldPoint := newPoint := get current coordinates of the mouse. save the background. dispay the image. [repeat condition] whileTrue: [newPoint := get current coordinates of the mouse. newPoint = oldPoint ifFalse: [repair background. save the new background. display the image. oldPoint := newPoint]]. repair background. -------------------------------------------------------
Program 13-2 is the revised version. This program saves the background of the image as an instance of 'Pixmap' in order to redraw the background quickly. However, if several of these programs are running in parallel, one of them can change the background between the time another program saves the background and restores it. So, Program 13-2 is a simple and economical program suitable only for a single process.
Program-13-2: (Image, Pixmap, GraphicsContext; display:at:, cursorPoint, copyArea:from:sourceOffset:destinationOffset:, value:) ------------------------------------------------------------- | anImage activeWindow aSensor displayContext storePixmap storeContext displayBlock restoreBlock storeBlock oldPoint newPoint | anImage := Image fromUser. activeWindow := ScheduledControllers activeController view. aSensor := activeWindow sensor. displayContext := activeWindow graphicsContext. storePixmap := Pixmap extent: anImage bounds extent. storeContext := storePixmap graphicsContext. displayBlock := [:aPoint | displayContext display: anImage at: aPoint]. restoreBlock := [:aPoint | displayContext copyArea: storePixmap bounds from: storeContext sourceOffset: Point zero destinationOffset: aPoint]. storeBlock := [:aPoint | storeContext copyArea: storePixmap bounds from: displayContext sourceOffset: aPoint destinationOffset: Point zero]. oldPoint := newPoint := aSensor cursorPoint. storeBlock value: newPoint. displayBlock value: newPoint. [aSensor noButtonPressed] whileTrue: [newPoint := aSensor cursorPoint. newPoint = oldPoint ifFalse: [restoreBlock value: oldPoint. storeBlock value: newPoint. displayBlock value: newPoint. oldPoint := newPoint]]. restoreBlock value: oldPoint. storePixmap close. -------------------------------------------------------------
The flicker problem is now much better than before. Let's try to make further improvements. The new pseudocode is as follows.
----------------------------------------------------- oldPoint := newPoint := get current coordinates of the mouse. save background dispay the image. [repeat condition] whileTrue: [newPoint := get current coordinates of the mouse. newPoint = oldPoint ifFalse: [ image display area overlaps previous ? ifTrue: [repair, save, display to prevent flickering] ifFalse: [repair background. save background. display the image. oldPoint := newPoint]]. repair background. -------------------------------------------------------
[repair, save, display to prevent flickering] is a special way to redraw the image as follows.
You can prevent the flickering by displaying the result of the workspace only if the display area of the image overlaps the previous display area. Program 13-3 shows the revision.
Program-13-3: (Image, Pixmap, GraphicsContext; display:at:, cursorPoint, copyArea:from:sourceOffset:destinationOffset:, merge:, value:) ----------------------------------------------------------------- | anImage activeWindow aSensor displayContext storePixmap storeContext workPixmap workContext displayBlock restoreBlock storeBlock workBlock oldPoint newPoint newBounds oldBounds | anImage := Image fromUser. activeWindow := ScheduledControllers activeController view. aSensor := activeWindow sensor. displayContext := activeWindow graphicsContext. storePixmap := Pixmap extent: anImage bounds extent. storeContext := storePixmap graphicsContext. workPixmap := Pixmap extent: anImage extent * 2. workContext := workPixmap graphicsContext. displayBlock := [:aPoint | displayContext display: anImage at: aPoint]. restoreBlock := [:aPoint | displayContext copyArea: storePixmap bounds from: storeContext sourceOffset: Point zero destinationOffset: aPoint]. storeBlock := [:aPoint | storeContext copyArea: storePixmap bounds from: displayContext sourceOffset: aPoint destinationOffset: Point zero]. workBlock := [:oldBox :newBox | | mergeBox | mergeBox := oldBox merge: newBox. workContext copyArea: (Point zero extent: mergeBox extent) from: displayContext sourceOffset: mergeBox origin destinationOffset: Point zero. workContext copyArea: storePixmap bounds from: storeContext sourceOffset: Point zero destinationOffset: oldBox origin - mergeBox origin. storeContext copyArea: storePixmap bounds from: workContext sourceOffset: newBox origin - mergeBox origin destinationOffset: Point zero. workContext display: anImage at: newBox origin - mergeBox origin. displayContext copyArea: (Point zero extent: mergeBox extent) from: workContext sourceOffset: Point zero destinationOffset: mergeBox origin]. oldPoint := newPoint := aSensor cursorPoint. storeBlock value: newPoint. displayBlock value: newPoint. [aSensor noButtonPressed] whileTrue: [newPoint := aSensor cursorPoint. newPoint = oldPoint ifFalse: [newBounds := newPoint extent: anImage extent. oldBounds := oldPoint extent: anImage extent. (newBounds intersects: oldBounds) ifTrue: [workBlock value: oldBounds value: newBounds] ifFalse: [restoreBlock value: oldPoint. storeBlock value: newPoint. displayBlock value: newPoint]. oldPoint := newPoint]]. restoreBlock value: oldPoint. workPixmap close. storePixmap close. -----------------------------------------------------------------
I believe you'll notice better movement of the image you select from the screen. If the image does not move smoothly, it means your computer is too slow to run Smalltalk programs and I recommend you get a faster computer!
Before ending this section I present you with a useful class called 'EngiVisualTransporter'. This implements a transportor running on a display surface to carry any object which accepts 'bounds' and 'displayOn:at:' messages. The program is in Appendix 06.
Example 1 of Appendix 06 moves an image with the mouse cursor, like the above examples. Example 2 moves an image from 0@0 to 200@200 at a specified speed. Example 3 moves an image from 0@0 to 200@00 by a specified number of steps. Try to guess how many steps are used to move the image. Example 4 moves an image along the path of a line you draw on the screen. The image moves rather quickly, so draw a long line, perhaps in the shape of a spiral.