I have actually implemented the drawing of a view hierarchy offscreen myself.
It is non-trivial, even for a very simple view hierarchy where all views are
subclasses NSControl and have -(BOOL) isFlipped { return YES; }
Here's one small portion of that code, which will give you an idea of the pain of doing this:
- (void)drawRect:(NSRect)iDirtyBigRect
{
#ifdef DISABLE_PSEUDOVIEW
debugRectsBeingDrawn();
return;
#endif
if ( !drawableShape )
{
if ( [[[self window] contentView] wantsLayer] )
{
CGRect cgRect = NSRectToCGRect([self bounds]);
drawableShape = HIShapeCreateMutableWithRect( &cgRect );
}
else
{
DUMP( @"our superview should have set up our drawableShape!" );
[self setNeedsDisplay:YES];
return;
}
}
else if ( iDirtyBigRect.size.width <= 0 )
{
// Our drawable shape is empty.
// This can happen for several valid reasons:
// 1) dirtyShape is fully masked by opaqueShape
// 2) for some reason, we were called to obey needsDisplay when not needed
DLog( @"nothing to draw %@", self );
return; // must return, otherwise we risk drawing outside our bounds
}
//NSArray *hardSubviews = [self subviews];
for ( NSView *pseudoSubview in pseudoSubviews )
{
if ( ![pseudoSubview isHidden] &&
[pseudoSubview window] == offscreenWindow ) // only draw pseudo view if it's in offscreen window
{
NSRect rectToDraw = [pseudoSubview frame];
// if ( rectToDraw.size.width == 0 )
// {
// DLog( @"clipped to superview %@", pseudoSubview );
// }
CGRect cgRect = NSRectToCGRect(rectToDraw);
if ( HIShapeIntersectsRect(drawableShape, &cgRect) )
{
// the magic: transform to subview coordinates
NSAffineTransform* xform = [NSAffineTransform transform];
[xform translateXBy:rectToDraw.origin.x yBy:rectToDraw.origin.y];
if ( [pseudoSubview isFlipped] != [self isFlipped] )
{
if ( ![pseudoSubview isKindOfClass: [NSColorWell class]] )
{
DUMP( @"hmmm flippedness different %d %d", [pseudoSubview isFlipped], [self isFlipped] );
}
[xform translateXBy:0.0 yBy:rectToDraw.size.height];
[xform scaleXBy:1.0 yBy:-1.0];
}
[xform concat];
HIMutableShapeRef newShape = HIShapeCreateMutableWithRect( &cgRect );
HIShapeIntersect( newShape, drawableShape, newShape ); // clip to subview frame
HIShapeOffset( newShape, -cgRect.origin.x, -cgRect.origin.y ); // translate to subview coords
CGRect dirtyRect;
HIShapeGetBounds(newShape, &dirtyRect);
// if ( dirtyRect.size.width <= 0 )
// {
// DUMP( @"why?" );
// }
if ( [pseudoSubview isKindOfClass:[PseudoView class]] )
{
//DLog( @"drawing Pseudo %@ dirtyRect %@ ", pseudoSubview, NSStringFromRect(NSRectFromCGRect(dirtyRect)));
PseudoView *pView = (PseudoView *)pseudoSubview;
if ( pView->drawableShape )
{
CFRelease( pView->drawableShape );
pView->drawableShape = NULL;
}
// create subview personal drawableShape from my drawable shape
pView->drawableShape = newShape;
if ( [pseudoSubview isOpaque] )
gDrawDirtyPixels += dirtyRect.size.width * dirtyRect.size.height;
if ( dirtyRect.size.width > 0 )
[pseudoSubview drawRect: NSRectFromCGRect(dirtyRect)];
}
else
{
//DLog( @"drawing non-Pseudo %@ rectToDraw %@ ", pseudoSubview, NSStringFromRect( rectToDraw ));
UInt64 t1 = CpuCycles();
CFRelease( newShape );
// sacrifice efficiency to avoid bugs...
// always draw the entire view.
[pseudoSubview drawRect:[pseudoSubview bounds]]; // NSRectFromCGRect(dirtyRect)]; // [pseudoSubview bounds]];
UnitTestQuartz( [pseudoSubview bounds] );
// NSLog( @"drawRect %@ %@ sUnitTestQuartzToggle %f\n -- superv %@\n -- self %@", pseudoSubview, NSStringFromRect(rectToDraw), sUnitTestQuartzToggle*100, [pseudoSubview superview], self );
UInt64 t2 = CpuCycles();
int diff = (int)(t2-t1);
gDrawControl += diff;
}
[xform invert]; // restore
[xform concat];
}
}
}
//if ( [self isKindOfClass:[PseudoRootView class]] )
// [offscreenWindow setViewsNeedDisplay:NO];
}