'From Squeak3.2gamma of 12 January 2002 [latest update: #4857] on 3 May 2002 at 10:21:03 pm'! "Change Set: DrawingTweaks-ar Date: 3 May 2002 Author: Andreas Raab Improve drawing for many small invalidated portions. Noticable for instance when activating system windows. Profiled against the following benchmark: | n mm | n := 4. mm := Array new: n. 1 to: n do:[:i| Browser fullOnClass: SystemDictionary selector: #macroBenchmarks. mm at: i put: World firstSubmorph. ]. MessageTally spyOn:[ 1 to: 10 do:[:i| 1 to: n do:[:j| (mm at: j) activate. World displayWorld]]. ]. mm do:[:m| m delete]. "! !Canvas methodsFor: 'testing' stamp: 'ar 4/30/2002 22:58'! seesRoundedCornerOf: aRectangle "Return true if this canvas sees any of the rounded corners of aRectangle" | roundRect clipRect | roundRect := aRectangle insetBy: CornerRounder cornerSize. clipRect := self clipRect. "Check horizontal overlaps" (clipRect origin x >= roundRect origin x and:[clipRect corner x <= roundRect corner x]) ifTrue:[^false]. "Check vertical overlaps" (clipRect origin y >= roundRect origin y and:[clipRect corner y <= roundRect corner y]) ifTrue:[^false]. ^true ! ! !Canvas methodsFor: 'drawing-general' stamp: 'ar 5/3/2002 20:04'! drawMorph: aMorph damageList: damageList | box | box := aMorph fullBounds. damageList do:[:rect| (box intersects: rect) ifTrue:[self clipBy: rect during:[:cc| cc drawMorph: aMorph]]. ].! ! !Canvas methodsFor: 'drawing-general' stamp: 'ar 5/3/2002 20:04'! fullDrawMorph: aMorph damageList: damageList | box | box := aMorph fullBounds. damageList do:[:rect| (box intersects: rect) ifTrue:[self clipBy: rect during:[:cc| cc fullDrawMorph: aMorph]]. ].! ! !CornerRounder class methodsFor: 'all' stamp: 'ar 4/30/2002 22:58'! cornerSize "Return the size of a rounded corner" ^6@6! ! !CornerRounder class methodsFor: 'all' stamp: 'ar 5/1/2002 01:04'! rectWithinCornersOf: aRectangle "Return a single sub-rectangle that lies entirely inside corners that are made by me. Used to identify large regions of window that do not need to be redrawn." aRectangle height <= aRectangle width ifTrue:[^ aRectangle insetBy: 0@6] ifFalse:[^ aRectangle insetBy: 6@0]! ! !FormCanvas methodsFor: 'testing' stamp: 'ar 5/3/2002 20:49'! isVisible: aRectangle "Optimization" (aRectangle right + origin x) < clipRect left ifTrue: [^ false]. (aRectangle left + origin x) >= clipRect right ifTrue: [^ false]. (aRectangle bottom + origin y) < clipRect top ifTrue: [^ false]. (aRectangle top + origin y) >= clipRect bottom ifTrue: [^ false]. ^ true ! ! !FormCanvas methodsFor: 'drawing-general' stamp: 'ar 4/30/2002 22:59'! roundCornersOf: aMorph in: bounds during: aBlock aMorph wantsRoundedCorners ifFalse:[^aBlock value]. (self seesRoundedCornerOf: bounds) ifFalse: ["Don't bother with corner logic if the region is inside them" ^ aBlock value]. CornerRounder roundCornersOf: aMorph on: self in: bounds displayBlock: aBlock borderWidth: aMorph borderWidthForRounding corners: aMorph roundedCorners! ! !Morph methodsFor: 'drawing' stamp: 'ar 5/3/2002 20:05'! topDownFullDrawOn: aCanvas damageList: damageList "Draw the receiver and all its submorphs on the given canvas. This method uses a top-down strategy for redrawing which will eliminate much of the actual work if many morphs overlap and are (at least partly) transparent. This strategy is useful, for instance, in worlds (using overlapping windows) but other morphs may use it as well. Note that for optimal performance, aCanvas' clip rect should be set to encapsulate the minimum bounding area of all the rectangles in damage list." ^self topDownFullDrawOn: aCanvas damageList: damageList dirtyRect: aCanvas clipRect startingAt: 1 ! ! !Morph methodsFor: 'drawing' stamp: 'ar 5/3/2002 20:17'! topDownFullDrawOn: aCanvas damageList: damageList dirtyRect: damageBounds startingAt: idx "Draw the receiver and all its submorphs on the given canvas. This method uses a top-down strategy for redrawing which will eliminate much of the actual work if many morphs overlap and are (at least partly) transparent. This strategy is useful, for instance, in worlds (using overlapping windows) but other morphs may use it as well. Note that for optimal performance, aCanvas' clip rect should be set to encapsulate the minimum bounding area of all the rectangles in damage list." | child stillDirty index | damageList size = 0 ifTrue:[^self]. "no damage no work" "Find the next visible child intersecting the damage area" index := idx. child := nil. [index <= submorphs size and:[child == nil]] whileTrue:[ child := submorphs at: index. (child visible and:[child fullBounds intersects: damageBounds]) ifFalse:[child := nil]. index := index+1]. "If we have no more children draw the receiver" child ifNil:[^aCanvas drawMorph: self damageList: damageList]. "Collect left-over damage for this child" stillDirty := OrderedCollection new: damageList size. damageList do:[:rect| (child areasRemainingToFill: rect) do:[:dirty| stillDirty add: dirty]. ]. "Now recursively update everything behind the child" self topDownFullDrawOn: aCanvas damageList: stillDirty dirtyRect: damageBounds startingAt: index. "Everything below the child has been restored, now restore the child itself" ^aCanvas fullDrawMorph: child damageList: damageList. ! ! !BorderedMorph methodsFor: 'drawing' stamp: 'ar 5/3/2002 22:05'! areasRemainingToFill: aRectangle (color isColor and: [color isTranslucent]) ifTrue: [^ Array with: aRectangle]. self wantsRoundedCorners ifFalse: [ (borderWidth > 0 and: [borderColor isColor and: [borderColor isTranslucent]]) ifTrue: [^ aRectangle areasOutside: self innerBounds] ifFalse: [^ aRectangle areasOutside: self bounds]]. (borderWidth > 0 and: [borderColor isColor and: [borderColor isTranslucent]]) ifTrue: [^ aRectangle areasOutside: (self innerBounds intersect: self boundsWithinCorners)]. "Eliminate most of the damage for the common case when just the receiver is being redrawn. Mostly noticable when activating system windows." aRectangle = bounds ifTrue:[^aRectangle cornerRectsOfSize: CornerRounder cornerSize] ifFalse:[^aRectangle areasOutside: self boundsWithinCorners]! ! !Number methodsFor: 'coercing' stamp: 'ar 4/30/2002 22:41'! expandRectangle: aRectangle ^Rectangle origin: (aRectangle origin - self asPoint) corner: (aRectangle corner + self asPoint)! ! !Number methodsFor: 'coercing' stamp: 'ar 4/30/2002 22:45'! extendRectangle: aRectangle ^Rectangle origin: (aRectangle origin) corner: (aRectangle corner + self asPoint)! ! !Number methodsFor: 'coercing' stamp: 'ar 4/30/2002 22:41'! insetRectangle: aRectangle ^Rectangle origin: (aRectangle origin + self asPoint) corner: (aRectangle corner - self asPoint)! ! !PluggableCanvas methodsFor: 'canvas methods' stamp: 'ar 4/30/2002 23:00'! roundCornersOf: aMorph in: bounds during: aBlock aMorph wantsRoundedCorners ifFalse:[^aBlock value]. (self seesRoundedCornerOf: bounds) ifFalse: ["Don't bother with corner logic if the region is inside them" ^ aBlock value]. CornerRounder roundCornersOf: aMorph on: self in: bounds displayBlock: aBlock borderWidth: aMorph borderWidthForRounding corners: aMorph roundedCorners! ! !Point methodsFor: 'coercing' stamp: 'ar 4/30/2002 22:42'! expandRectangle: aRectangle ^Rectangle origin: (aRectangle origin - self) corner: (aRectangle corner + self)! ! !Point methodsFor: 'coercing' stamp: 'ar 4/30/2002 22:45'! extendRectangle: aRectangle ^Rectangle origin: (aRectangle origin) corner: (aRectangle corner + self)! ! !Point methodsFor: 'coercing' stamp: 'ar 4/30/2002 22:42'! insetRectangle: aRectangle ^Rectangle origin: (aRectangle origin + self) corner: (aRectangle corner - self)! ! !Rectangle methodsFor: 'rectangle functions' stamp: 'ar 5/1/2002 01:33'! areasOutside: aRectangle "Answer an Array of Rectangles comprising the parts of the receiver not intersecting aRectangle." | areas yOrigin yCorner | "Make sure the intersection is non-empty" (self intersects: aRectangle) ifFalse: [^Array with: self]. areas _ OrderedCollection new. aRectangle origin y > origin y ifTrue: [areas addLast: (origin corner: corner x @ (yOrigin _ aRectangle origin y))] ifFalse: [yOrigin _ origin y]. aRectangle corner y < corner y ifTrue: [areas addLast: (origin x @ (yCorner _ aRectangle corner y) corner: corner)] ifFalse: [yCorner _ corner y]. aRectangle origin x > origin x ifTrue: [areas addLast: (origin x @ yOrigin corner: aRectangle origin x @ yCorner)]. aRectangle corner x < corner x ifTrue: [areas addLast: (aRectangle corner x @ yOrigin corner: corner x @ yCorner)]. ^areas! ! !Rectangle methodsFor: 'rectangle functions' stamp: 'ar 5/3/2002 22:04'! cornerRectsOfSize: aPoint "Answer an array of rectangles making up the receiver's corner of a given size" (aPoint x isZero or:[aPoint y isZero]) ifTrue:[^#()]. ^Array with: (self topLeft extent: aPoint) with: (self topRight - (aPoint x @ 0) extent: aPoint) with: (self bottomLeft - (0 @ aPoint y) extent: aPoint) with: (self topRight - aPoint extent: aPoint).! ! !Rectangle methodsFor: 'rectangle functions' stamp: 'ar 4/30/2002 22:44'! expandBy: delta "Answer a Rectangle that is outset from the receiver by delta. delta is a Rectangle, Point, or scalar." ^delta expandRectangle: self! ! !Rectangle methodsFor: 'rectangle functions' stamp: 'ar 4/30/2002 22:43'! expandRectangle: aRectangle ^Rectangle origin: (aRectangle origin - origin) corner: (aRectangle corner + corner)! ! !Rectangle methodsFor: 'rectangle functions' stamp: 'ar 4/30/2002 22:45'! extendBy: delta "Answer a Rectangle with the same origin as the receiver, but whose corner is offset by delta. delta is a Rectangle, Point, or scalar." ^delta extendRectangle: self! ! !Rectangle methodsFor: 'rectangle functions' stamp: 'ar 4/30/2002 22:45'! extendRectangle: aRectangle ^Rectangle origin: (aRectangle origin) corner: (aRectangle corner + corner)! ! !Rectangle methodsFor: 'rectangle functions' stamp: 'ar 4/30/2002 22:44'! insetBy: delta "Answer a Rectangle that is inset from the receiver by delta. delta is a Rectangle, Point, or scalar." ^delta insetRectangle: self! ! !Rectangle methodsFor: 'rectangle functions' stamp: 'ar 4/30/2002 22:43'! insetRectangle: aRectangle ^Rectangle origin: (aRectangle origin + origin) corner: (aRectangle corner - corner)! ! !Rectangle methodsFor: 'rectangle functions' stamp: 'ar 4/30/2002 22:45'! outsetBy: delta "Answer a Rectangle that is outset from the receiver by delta. delta is a Rectangle, Point, or scalar." ^delta expandRectangle: self! ! !Rectangle methodsFor: 'private' stamp: 'ar 5/1/2002 00:41'! display Display getCanvas frameRectangle: self width: 1 color: Color red.! ! !RemoteCanvas methodsFor: 'accessing' stamp: 'ar 4/30/2002 23:00'! roundCornersOf: aMorph in: bounds during: aBlock self flag: #roundedRudeness. aMorph wantsRoundedCorners ifFalse:[^aBlock value]. (self seesRoundedCornerOf: bounds) ifFalse: ["Don't bother with corner logic if the region is inside them" ^ aBlock value]. CornerRounder roundCornersOf: aMorph on: self in: bounds displayBlock: aBlock borderWidth: aMorph borderWidthForRounding corners: aMorph roundedCorners! ! !TextMorph methodsFor: 'geometry' stamp: 'ar 5/3/2002 21:27'! areasRemainingToFill: aRectangle "Overridden from BorderedMorph to test backgroundColor instead of (text) color." (backgroundColor isNil or: [backgroundColor isTranslucent]) ifTrue: [^ Array with: aRectangle]. ^super areasRemainingToFill: aRectangle! ! !WorldState methodsFor: 'update cycle' stamp: 'ar 5/3/2002 22:17'! drawWorld: aWorld submorphs: submorphs invalidAreasOn: aCanvas "Redraw the damaged areas of the given canvas and clear the damage list. Return a collection of the areas that were redrawn." | rectList rectToFill validList | rectList _ damageRecorder invalidRectsFullBounds: aWorld viewBox. "sort by areas to draw largest portions first" rectList _ rectList asArray sort:[:r1 :r2| r1 area > r2 area]. damageRecorder reset. validList _ OrderedCollection new: rectList size. rectToFill := nil. rectList do: [:dirtyRect | dirtyRect allAreasOutsideList: validList do:[:r| rectToFill ifNil:[rectToFill := r copy] ifNotNil:[rectToFill := rectToFill quickMerge: r]. validList add: r]]. rectToFill ifNotNil:[ aCanvas clipBy: rectToFill during:[:cc| aWorld topDownFullDrawOn: cc damageList: validList]. ]. ^validList! !