edit: also requires Diggsey's advanced2d plugin, but uses of it can be substituted.
edit2: forgot - you'll also need four images of about 512x512 for it to load. The images I've used are attached to this post.
This is in small part my own code but moreso a convenient amalgamation of a number of techniques from such members as
IanM,
CodemanV, and probably one or two other people. To clarify, most of this code is not mine, but I imagine it will be useful as a basis for app development, particularly editors.
Features
- This demo's window can be resized and the client will adjust it's resolution to fit (ie. it isn't left stretched), like a normal program.
- Will recover lost assets without crashing in the event of resizing, computer lock, ctrl+alt+del, computer
sleep - anything that causes d3d surface loss.
- Can draw stuff while being dragged around.
- Assets are loaded using a coroutine (ie. "in the background", though not technically). Demo's a simple working progress bar.
- Rejects mouse clicks in window border (noticed this happening when I clicked the title to drag the window and the program thought I wanted to load assets again).
- Responds to windows messages. Also catches close button press, popping open a dialog.
Try
- Left-click to repeat asset loading (note: this won't delete images, just "overwrites" them).
- Up/down arrow keys change pbTickRate. Specifies number of milliseconds to spend at a time loading assets before switching back to drawing. Higher values -> more stuttery screen.
- Maximising window; restoring window and resizing; moving window; minimising; locking, ctrl+alt+del'ing; closing.
- When assets have loaded, sweep the mouse back and forth to "flip" through loaded images (just to show/prove that prove they're there).
- Adjusting numAssetToLoad.
Issues/Concerns
- On resize I limit the window dimensions to desktop
dimension() - 1. For some reason, missing off the -1 and then maximising causes a "Display Using 32 Bit is not supported by available hardware at line 0" error. When maximised (here, to 1440x821), there is a doubled-up/skipped vertical line of pixels in the middle indicative of a slightly-off resolution (it seems to be off by 1, but as per above, trying to make it up crashes it). This doesn't occur when not maximised.
- You can sometimes see flickers of backdrop when you start manipulating the window (blue previously; since using color backdrop it's become unnoticable - but might be an issue in cases where there are various colours on screen).
- Drawing during window manipulation only happens once every second, or if currently dragging.
Code
// setup window.
set display mode 640, 480, 32, 1
set window position (desktop width()/2) - 320, (desktop height()/2) -240
set window on
maximize window
color backdrop rgb(12,36,48)
// function handles windows messages as they arise.
set message callback "WndProc"
sync on
sync rate 60
#constant USER32DLL 255
#constant WM_ENTERSIZEMOVE 0x0231
#constant WM_EXITSIZEMOVE 0x232
#constant WM_RESIZE 0x0005
#constant WM_SIZING 0x0214
#constant WM_CLOSE 0x0010
`#constant WM_MOUSELEAVE 0x0215`0x02A3
#constant SIMULATE_RANDOM_LARGE_ASSET 0`1
// types for receiving C++ point strutures.
type tStructPointInfo
x, y
endtype
// stores mouse desktop coords.
dim Pnt() as tStructPointInfo
global msPtr as dword
// stores dbp app client coords.
dim clientPos() as tStructPointInfo
global clientPosPtr as dword
// text fonts.
global splashFont
global splashCommFont
global tw
// Asset list.
global numAssetToLoad = 256`384
global currAssetToLoad = 0
dim AssetToLoad(numAssetToLoad) as float
for n = 1 to numAssetToLoad
// will load one of 4 images at random just for demonstration. Fourth image is very big (less chance of use).
if rnd(12)=0 then AssetToLoad(n) = 4 else AssetToLoad(n) = 1 + rnd(2)
next n
// Asset loading/Progress Bar globals.
global pbProg = 0
global pbTitle$ = ""
global pbComm$ = ""
global pbTickRate = 16
// Input.
global kUp, old_kUp
global kDown, old_kDown
global mmX, mmY, mc, mouseInClient
mmX = mousemovex()
mmY = mousemovey()
// Window size.
global winW, old_winW
global winH, old_winH
// Ticker used just for changing num. animating circles.
make ticker 2, 500
global numAnimCircles = 5
// Asset loading coroutine.
create coroutine 1, get ptr to function("LoadAssets")
make ticker 1, pbTickRate
// Drawing coroutine.
create coroutine 2, get ptr to function("Draw")
// Wait for things to settle before starting.
waitForValidDisplay()
// window handle.
global hwnd_dbp
hwnd_dbp = get dbpro window()
global wndSizeMove = 0
load dll "user32.dll", USER32DLL
add to queue pnt()
add to queue clientPos()
msPtr = pnt()
clientPosPtr = clientPos()
// //////////////////////////////////
// MAIN LOOP ////////////////////////
// //////////////////////////////////
do
// update input.
old_kUp = kUp : kUp = upkey()
old_kDown = kDown : kDown = downkey()
// handle mouse.
mmX = mousemovex()
mmY = mousemovey()
mc = mouseclick() * mouseInClient
// get window position of active dbp app, and mouse cursor position relative to desktop.
temp = call dll(USER32DLL, "ClientToScreen", hwnd_dbp, clientPosPtr)
temp = call dll(USER32DLL, "GetCursorPos", msPtr)
// check mouse in app client (and reset values; seems to keep adding to it).
mouseInClient = ((pnt().x >= clientPos().x) and (pnt().x <= clientPos().x + get window client width())) and ((pnt().y >= clientPos().y) and (Pnt().y <= clientPos().y + get window client height()))
clientPos().x = 0
clientPos().y = 0
// While the app is minimised, wait for new windows messages until no longer minimised.
while is minimised()
os_waitMessage()
endwhile
// update window dimensions.
old_winW = winW
old_winH = winH
winW = get window client width()
winH = get window client height()
// Handle change in window size or loss of d3d screen surface.
if winW <> old_winW or winH <> old_winH or sprite exist(1) = 0 `or screen invalid()
// wait for screen surface recovery.
waitForValidDisplay()
// adjust res to fit resized window.
winW = clamp(winW, 320, desktop width() - 1)
winH = clamp(winH, 240, desktop height() - 1)
set message callback ""
set display mode winW, winH, 32, 1
set message callback "WndProc"
set window client size winW, winH
winW = get window client width()
winH = get window client height()
// rebuild assets.
createFonts()
createSprites()
// Prepare to reload assets.
currAssetToLoad = 0
set ticker rate 1, pbTickRate
ticker reset 1
if coroutine active(1) = 0 then restart coroutine 1
endif
// ASSET LOADING
if currAssetToLoad < numAssetToLoad
switch to coroutine 1
else
// up/down arrowkeys adjust pbTickRate.
temp = 8 + (8*(pbTickRate>=64)) + (16*(pbTickRate>=256))
if kUp and old_kUp = 0 then pbTickRate = min(pbTickRate + temp, 1024)
if kDown and old_kDown = 0 then pbTickRate = max(pbTickRate - temp, 8)
// left-clicking forces asset reload.
if mc = 1
currAssetToLoad = 0
set ticker rate 1, pbTickRate
ticker reset 1
restart coroutine 1
endif
endif
// GAME LOGIC GOES ABOUT HERE //
// DRAW LOADING SCREEN //
switch to coroutine 2
if coroutine active(2) = 0 then restart coroutine 2
loop
// //////////////////////////////////
// FUNCTIONS ////////////////////////
// //////////////////////////////////
function LoadAssets()
pbTitle$ = "Loading Assets"
pbComm$ = "Information about current loading progress goes here."
// load assets.
while 1
inc currAssetToLoad
i = AssetToLoad(currAssetToLoad)
load image "myImg_" + str$(i) + ".png", currAssetToLoad, 1
if SIMULATE_RANDOM_LARGE_ASSET and rnd(5)=0 then nice wait 10+rnd(120)
pbProg = currAssetToLoad * (100.0/numAssetToLoad)
pbComm$ = "Loaded asset image " + str$(currAssetToLoad) + " of " + str$(numAssetToLoad) + "..."
if currAssetToLoad = numAssetToLoad then exit
if ticker(1) = 1 then switch to coroutine 0
endwhile
// finish loading.
pbComm$ = "Assets loaded. Left-click (or cause d3d surface loss) to start demonstration over." + crlf$() + "Use up/down arrow keys to try different tick rates."
pbTitle$ = "Loading Complete"
endfunction
// MOST OF THIS IS JUST BEAUTIFICATION NONSENSE.
function Draw()
// SPINNER - INNER CIRCLES
x = (screen width()*0.5) - (tw/2) - 52 `0.333
y = screen height()*0.5
if ticker(2) = 1 then numAnimCircles = 2+rnd(6)
if currAssetToLoad = numAssetToLoad then numAnimCircles = 8
spinnerCircles(x, y, numAnimCircles, 1)
// SPINNER - RING
for n = 1 to 100
m# = n * (360.0/100.0)
a# = (timer()*0.8*(currAssetToLoad<>numAssetToLoad)) + m#
d# = 38
e# = d# + ((sin( clamp(wrapvalue(timer()*0.6), 0, 180) ) * 6) * (0.65 + (0.35*(currAssetToLoad<>numAssetToLoad))))
f# = d# + ((sin( clamp(wrapvalue(180+(timer()*0.6)), 0, 180) ) * 6) * (0.65 + (0.35*(currAssetToLoad<>numAssetToLoad))))
x1 = x + (cos(a#) * e#) : y1 = y + (sin(a#) * e#)
x2 = x + (cos(a# + (360/15)) * f#) : y2 = y + (sin(a# + (360/15)) * f#)
`circle x + (cos(a#) * d#), y + (sin(a#) * d#), 4
c = hsv to rgb(m#, 255, 255)
a2line x1, y1, x2, y2, c
next n
// LOADING TEXT
a2text splashFont, (screen width()*0.5) - (tw/2), y - 24, pbTitle$, rgb(64, 128, 255)
a2text splashCommFont, (screen width()*0.5) - (tw/2), y - 16 - (a2getlineheight(splashCommFont)*(1+(find char(pbComm$, crlf$())>0))), pbComm$, rgb(222, 64, 128, 255)
// SOME FAINT GRAIN/METAL LINES
for u=1 to screen width() step 2
a2line u, 0, u, screen height(), rgb(8, 255, 255, 255)
next x
for v=1 to screen height() step 2
a2line 0, v, screen width(), v, rgb(4, 255, 255, 255)
next y
// TOP/BOTTOM BORDERS
a2fillbox 0, 0, screen width(), y - 52, rgb(64, 0,0,0), rgb(32, 0,0,0), rgb(64, 255,255,255), rgb(32, 255,255,255)
a2fillbox 0, y + 52, screen width(), screen height(), rgb(64, 255,255,255), rgb(32, 255,255,255), rgb(64, 0,0,0), rgb(32, 0,0,0)
print "TEST: ", (currAssetToLoad < numAssetToLoad)
if currAssetToLoad < numAssetToLoad
// PROGRESS BAR
pbW = tw
y = y + 58
x1 = (screen width()*0.5) - (pbW/2)
x2 = (screen width()*0.5) + (pbW/2)
x2 = x1 + (pbW * (pbProg/100.0))
c = hsv to rgb(48, pbProg*0.01, 0.5 + (pbProg*0.005))
a2fillbox x1, y, x2, y + 2, c
else
// VIEW LOADED IMAGES.
x1 = screen width()*0.3
x2 = screen width()*0.7
x = clamp(mousex(), x1, x2)
img = clamp(ceil((x-x1) * (numAssetToLoad*1.0/(x2-x1))), 1, numAssetToLoad)
a = clamp(-2, 0, 1 + ((-1)*img))
b = clamp(2, 0, numAssetToLoad-img)
for n = a to b
i = img + n
iX = x + (image width(img) * 0.2 * ((n>0) - (n<0))) + (n*132)
set sprite image 1, i
offset sprite 1, image width(i) / 2, 0
scale sprite 1, (25 * (512.0/image height(i))) + (25 * (n=0))
paste sprite 1, iX, y + 58
center text iX, y + 60 + (image height(i)*((0.25 * (512.0/image height(i))) + (0.25 * (n=0)))), "#" + str$(i)
next n
endif
// DEBUG STUFF.
`print x
`print a
`print b
print screen fps()
print mouseInClient
print winW, ", ", winH, "(", old_winW, ", ", old_winH, ")"
print currAssetToLoad, "/", numAssetToLoad
text 0, screen height()-14, "default pbTickRate is 16 for 60fps (approx. 1000/16). Higher values will reduce fps of load screen."
text 0, screen height()-28, "pbTickRate = " + str$(pbTickRate)
// FINISH.
sync
cls rgb(12,36,48)
endfunction
// This bit in particular seperated from main Draw function so can appear when resizing/moving window.
function spinnerCircles(x, y, o, varyWithLoadProgress)
varyWithLoadProgress = varyWithLoadProgress * (currAssetToLoad = numAssetToLoad)
for n = 1 to o
m# = n * (360/o)
b# = wrapvalue(timer()*0.2) * (sin(timer()*0.2)) * (varyWithLoadProgress=0)
a# = (int(b#*0.05)*20) + m#
c# = ((wrapvalue(timer()*0.07) * (varyWithLoadProgress=0)) + (10*varyWithLoadProgress)) * (sin(timer()*0.15))
d# = sin(c#) * 20.0
if varyWithLoadProgress then d# = 18 + (sin(c#) * 2.0)
c = hsv to rgb(a#, (m#/360.0), 255)
a2fillcircle x + (cos(a#) * d#), y + (sin(a#) * d#), 11, c
next n
endfunction
// //////////////////////////////////
// CREATION/ RECOVERY STUFF /////////
// //////////////////////////////////
function createFonts()
splashFont = a2createfont("cambria", 64, a2size_cell(), 0, 0)
splashCommFont = a2createfont("verdana", 16, a2size_cell(), 0, 0)
tw = a2gettextwidth(splashFont, "Loading Assets")
endfunction
function createSprites()
get image 1, 0, 0, 2, 2, 1
sprite 1, 0, 0, 1
hide all sprites
if camera backdrop on() then backdrop off
endfunction
function WaitForValidDisplay()
while screen invalid()
endwhile
endfunction
function OS_WaitMessage()
local Result as dword
if dll exist(USER32DLL) = 0 then load dll "user32.dll", USER32DLL
Result = call dll(USER32DLL, "WaitMessage")
endfunction Result
// //////////////////////////////////
// WINDOWS MESSAGE CALLBACK FUNC ////
// //////////////////////////////////
function WndProc(hwnd as dword, msg as dword, wparam as dword, lparam as dword)
`text 0, 0, str$(hwnd) + " - " + str$(msg) + " - " + str$(wparam) + " - " + str$(lparam)
if wndSizeMove
cls rgb(12,36,48)
spinnerCircles(screen width()/2, screen height()/2, 3, 0)
endif
select msg
case WM_ENTERSIZEMOVE`, WM_SIZING, WM_RESIZE
wndSizeMove = 1
endcase
case WM_EXITSIZEMOVE
wndSizeMove = 0
endcase
`case WM_MOUSELEAVE
` mouseInClient = 0
`endcase
case WM_CLOSE
stop current message
if dll exist(USER32DLL) = 0 then load dll "user32.dll", USER32DLL
temp = call dll(USER32DLL, "MessageBoxA", hwnd_dbp, "Save changes to document?" + crlf$() + "(demonstration dummy dialog, won't actually be saving anything)", "DBP Program", 0x00000003||0x00000030)`1||48
stop current message
select temp
case 2 // Cancel.
`do nothing.
endcase
case 6 // Yes. (save work) and end program.
endProgram()
endcase
case 7 // No. (discard work) and end program.
endProgram()
endcase
endselect
endcase
endselect
endfunction 0
// Add unloading safeguards stuff here.
function endProgram()
if dll exist(USER32DLL) then delete dll USER32DLL
end
endfunction
edit: circles animate properly now.
Ta