
//
// Bit Blit demo (pooled layers). October 2025, Raoul Watson
//

//Include "CocoaUI/CoreAnimation/CALayer.incl"

include "NSLog.incl"
include resources "tree1.png"
include resources "tree2.png"
include resources "tree3.png"

_mainWindow = 1
_img1 = 2
_img2 = 3
_img3 = 4
_copy  = 5

_overlayView = 7777    // tag for overlay host view

CGFloat meWidth, meHeight
//toolbox CALayerSetGeometryFlipped( CALayerRef layer, BOOL flipped )

// Globals

// One overlay layer (sits above everything in the host view)
CALayerRef gBitBlitOverlayLayer = 0

// Pool of reusable blit layers (each shows one copied rect)
// Adjust capacity if you want more than 100 at once

_kMaxBlitLayers = 100
CALayerRef gBlitLayers(_kMaxBlitLayers)   // 0.._kMaxBlitLayers-1
SInt32     gBlitPoolSize = 0              // number allocated so far

// Rolling index (wraps) for the next slot we’ll use
SInt32     gActiveBlitCount = 0           // 0.._kMaxBlitLayers-1 (wrap-around)

// Per-layer "generation" so we can hide all layers not used this scene
SInt32     gLayerGen(_kMaxBlitLayers)     // 0 if never used
SInt32     gSceneGeneration = 0           // incremented at BeginBlitScene

// Build window-

void local fn BuildWindow
dim as SInt32 wndStyleMask
wndStyleMask  = NSWindowStyleMaskTitled
wndStyleMask += NSWindowStyleMaskClosable

CGRect r = fn CGRectMake( 0, 0, 800, 500 )
window _mainWindow, @" Bit-Blit ", r, wndStyleMask
WindowSetBackgroundColor( _mainWindow, fn ColorWindowBackground )
ViewSetFlipped( _WindowContentViewTag, YES )
WindowSubclassContentView( _mainWindow )

// Overlay host view
CGRect b = fn ViewBounds( _WindowContentViewTag )
view _overlayView, b, _mainWindow
ViewSetWantsLayer( _overlayView, _true )
ViewSetFlipped( _overlayView, YES )
ViewSetAutoresizingMask( _overlayView, NSViewWidthSizable + NSViewHeightSizable )
ViewAddSubviewPositioned( _WindowContentViewTag, _overlayView, NSWindowAbove, 0 )
CALayerSetZPosition( fn ViewLayer( _overlayView ), kCGMaximumWindowLevelKey )

// Make the *visible* window smaller to hide the right-side images
r = fn CGRectMake( 0, 0, 510, 500 )
window _mainWindow,, r

windowcenter( _mainWindow )
WindowMakeFirstResponder( _mainWindow, _WindowContentViewTag )
ViewSetAcceptsFirstResponder( _mainWindow, _true )
ResponderSetMenuRef( fn WindowPrintView( _mainWindow ), NULL )
meWidth = window( _windowWidth )
meHeight = window( _windowHeight )
end fn

local fn DrawControls
CGRect r
r = fn CGRectMake( 560, 0, 160, 160 )
imageview _img1, YES, @"tree1.png", r, NSImageScaleProportionallyUpOrDown,,, _mainWindow
r = fn CGRectMake( 560, 160, 160, 160 )
imageview _img2, YES, @"tree2.png", r, NSImageScaleProportionallyUpOrDown,,, _mainWindow
r = fn CGRectMake( 560, 320, 160, 160 )
imageview _img3, YES, @"tree3.png", r, NSImageScaleProportionallyUpOrDown,,, _mainWindow
button _copy, YES, , @"Create Scene", (60,460,120,32),,, _mainWindow
end fn



// Ensure the persistent overlay layer exists and covers the host

local fn EnsureBlitOverlay( hostViewTag as NSInteger ) as CALayerRef
CALayerRef host
fn ViewSetWantsLayer( hostViewTag, _true )
host = fn ViewLayer( hostViewTag )
if ( host == 0 ) then exit fn = 0

if ( gBitBlitOverlayLayer == 0 )
gBitBlitOverlayLayer = fn CALayerInit
if ( gBitBlitOverlayLayer == 0 ) then exit fn = 0
CALayerSetFrame( gBitBlitOverlayLayer, fn ViewBounds( hostViewTag ) )
CALayerSetMasksToBounds( gBitBlitOverlayLayer, _false )
CALayerSetContentsScale( gBitBlitOverlayLayer, 1.0 )
CALayerAddSublayer( host, gBitBlitOverlayLayer )
// If you prefer flipped layer coords, uncomment:
// CALayerSetGeometryFlipped( gBitBlitOverlayLayer, YES ) // needs FB 7.0.36 >
end if

// Keep sized to current bounds (in case window resized)
CALayerSetFrame( gBitBlitOverlayLayer, fn ViewBounds( hostViewTag ) )
end fn = gBitBlitOverlayLayer

// Start a new scene: bump generation; we’ll mark any used layer with this gen
local fn BeginBlitScene( hostViewTag as NSInteger )
// Ensure overlay exists/sized
fn EnsureBlitOverlay( hostViewTag )
// Advance generation (avoid 0 so “never used” stays 0)
gSceneGeneration++
if ( gSceneGeneration == 0 ) then gSceneGeneration = 1
end fn

// Hide any pooled layers that were NOT touched this scene
local fn EndBlitScene
CFIndex i
for i = 0 to gBlitPoolSize - 1
if ( gBlitLayers(i) ) and ( gLayerGen(i) <> gSceneGeneration )
CALayerSetHidden( gBlitLayers(i), _true )
end if
next
end fn

// Hard reset
// Remove every blit layer and the overlay (optional hard reset)
local fn ClearBlitPool
CFIndex i
for i = 0 to gBlitPoolSize - 1
if ( gBlitLayers(i) )
CALayerRemoveFromSuperlayer( gBlitLayers(i) )
gBlitLayers(i) = 0
gLayerGen(i) = 0
end if
next
gBlitPoolSize     = 0
gActiveBlitCount  = 0
gSceneGeneration  = 0

if ( gBitBlitOverlayLayer )
CALayerRemoveFromSuperlayer( gBitBlitOverlayLayer )
gBitBlitOverlayLayer = 0
end if
end fn

//
// Copies src rectangle to dst rectangle and shows it in a pooled sublayer.
// Uses wrap-around when the number of blits exceeds pool capacity.
//
local fn BitBlit( srcViewTag as NSInteger, hostViewTag as NSInteger, ¬
srcRect as CGRect, dstRect as CGRect, gravity as CFStringRef ) as BOOL

BitmapImageRepRef  rep
CGImageRef         cg
CALayerRef         overlay, L
CGRect             vb, dl
SInt32             idx

if fn ViewExists( srcViewTag )  == _false then RETURN _false
if fn ViewExists( hostViewTag ) == _false then RETURN _false

// include subviews when caching what’s *visible*
ViewSetCanDrawSubviewsIntoLayer( srcViewTag, _true )

// snapshot visible pixels (view coords)
rep = fn ViewBitmapImageRepForCachingDisplayInRect( srcViewTag, srcRect )
if rep == 0 then exit fn = _false
ViewCacheDisplayInRectToBitmapImageRep( srcViewTag, srcRect, rep )
cg = fn BitmapImageRepCGImage( rep )
if cg == 0 then RETURN _false

overlay = fn EnsureBlitOverlay( hostViewTag )
if overlay == 0 then exit fn = _false

// Next slot in the pool (WRAP-AROUND 0.._kMaxBlitLayers-1)
idx = gActiveBlitCount
if ( idx >= _kMaxBlitLayers ) then idx = 0
gActiveBlitCount = idx + 1
if ( gActiveBlitCount >= _kMaxBlitLayers ) then gActiveBlitCount = 0

L = gBlitLayers(idx)
if ( L == 0 )
L = fn CALayerInit
if ( L == 0 ) then exit fn = _false
gBlitLayers(idx) = L
CALayerSetMasksToBounds( L, _true )
CALayerSetContentsScale( L, 1.0 )
CALayerAddSublayer( overlay, L )
if ( idx + 1 > gBlitPoolSize ) then gBlitPoolSize = idx + 1
end if

// Place the layer (flip Y because CALayer is bottom-left, view is flipped)
vb = fn ViewBounds( hostViewTag )
dl = dstRect
dl.origin.y = vb.size.height - dl.origin.y - dl.size.height

CALayerSetFrame( L, dl )
CALayerSetContents( L, cg )
CALayerSetContentsGravity( L, gravity )
CALayerSetHidden( L, _false )

// Mark this layer as used in the current scene
gLayerGen(idx) = gSceneGeneration

end fn = _true

// Create a scene (demo)

void local fn DoScene
// Begin a new frame/scene of blits
fn BeginBlitScene( _overlayView )

CGRect src, dst

src = fn CGRectMake( 560, 160, 160, 160 )  // tree 2
dst = fn CGRectMake( 150, 160, 160, 160 )  // dest
fn BitBlit( _WindowContentViewTag, _overlayView, src, dst, kCAGravityResizeAspectFill )

src = fn CGRectMake( 560, 320, 160, 160 )  // tree 3
dst = fn CGRectMake( 40,  200, 160, 160 )
fn BitBlit( _WindowContentViewTag, _overlayView, src, dst, kCAGravityResizeAspectFill )

src = fn CGRectMake( 560,   0, 160, 160 )  // tree 1
dst = fn CGRectMake( 100, 240, 160, 160 )
fn BitBlit( _WindowContentViewTag, _overlayView, src, dst, kCAGravityResizeAspectFill )

// smaller version of tree3
src = fn CGRectMake( 560, 320, 160, 160 )
dst = fn CGRectMake( 220, 260, 80, 80 )    // half size
fn BitBlit( _WindowContentViewTag, _overlayView, src, dst, kCAGravityResizeAspectFill )

// Hide any pooled layers not touched this scene
fn EndBlitScene
end fn

// Dialog handler

void local fn DoDialog( ev as long, tag as long, wnd as long )
select ( ev )
case _btnClick
select case tag
case _copy
fn DoScene
end select
case _WindowShouldClose
end
end select
end fn

// Main

fn BuildWindow

print @
print @" There are three tree images located to the right of this"
print @" Window at x coordinate 560. They are not visible to you"
print @" because the visible window width is only 510."
print @
print @" When you click the button [Create Scene], these trees from"
print @" the off-screen environment are Bit-Blit to the screen."
print @
print @" BTW, they can be made any size you want in this Window,"
print @" and all this are done without using any ImageView at the"
print @" destination Window."

fn DrawControls

on dialog fn DoDialog

HandleEvents
