Let's say you got a table filled with windows, called "windows". Index 1 is the one at the bottom, 2 is above that, 3 is above that, yadda yadda until index #windows, which is the one on top - no windows may render on top of index #windows.
Each window has an x/y position on the screen, a height, and a width. Let's assume we can get at these via windows[x].x, windows[x].y, windows[x].width and windows[x].height. We'll also put in a "visible" attribute, which we can toggle if we want to eg minimise a window or otherwise stop rendering it, and we'll give each window a "redraw" function we can call if we want to re-render what's in its buffer. Each window will also need a way to know which index it's currently at within the "windows" table; so update window[x].index whenever you move them up/down.
Let's go with a basic cache to keep track of which screen positions have windows covering them. We'll use a table called "layers", such that layers[y][x] will contain the index of the window that's visible at any given x/y co-ord. If no window covers that screen location, then we'll just set the index to 0.
You'd build a function that can be passed a top-left co-ord and a lower right co-ord, able to update "layers" with the window data of the rectangle formed between the two points. A basic version might look like this:
local updateRegion(x1, y1, x2, y2)
for curLayer = #windows, 1, -1 do -- Loop from the top window down.
local thisLayer = windows[curLayer]
local winX1, winY1, winX2, winY2 = thisLayer.x, thisLayer.y, thisLayer.x + thisLayer.width - 1, thisLayer.y + thisLayer.height - 1
if not (winX1 > x2 or winY1 > y2 or x1 > winX2 or y1 > winY2) then -- If the window overlaps the area we're checking, then...
-- Loop through the overlapping area:
for y = math.max(winY1, y1), math.min(winY2, y2) do
for x = math.max(winX1, x1), math.min(winX2, x2) do
if layers[y][x] < curLayer and thisLayer.visible then
-- This is the topmost layer visible at this point, stick the index into the map.
layers[y][x] = curLayer
elseif layers[y][x] == curLayer and not thisLayer.visible then
-- This is the topmost layer at this point, but it's hidden, so set to background for now.
layers[y][x] = 0
else
end
end
if thisLayer.visible then thisLayer.redraw() end -- Layer map isn't fully updated yet, but it's accurate for THIS layer, so draw the buffered content.
end
end
for y = y1, y2 do for x = x1, x2 do
if layers[y][x] == 0 then
-- Draw background colour at that point.
end
end end
end
Easy! Granted, it could be made more efficient, but for now I'm just trying to get the point across. Some simple loops performing some simple checks. It doesn't really matter because this code is only getting called when windows move around in some way (as opposed to when window
content gets updated in some way).
A window's redraw function might go along these lines:
local function newWindow(...)
local theWindow = {}
...
theWindow.redraw() = function()
if not theWindow.visible then return end
for y = 1, theWindow.height do for x = 1, theWindow.width do
if layers[y + theWindow.y - 1][x + theWindow.x - 1] == theWindow.index then
-- draw theWindow.buffer[y][x] to the screen location.
end
end end
end
...
return theWindow
end
So let's say you wanted to move a window from one side of the screen to the other. You'd hide it, update the layer map, change its co-ords, unhide it, and update the map again:
windows[i].visible = false
updateRegion(windows[i].x, windows[i].y, windows[i].x + windows[i].width - 1, windows[i].y + windows[i].height - 1)
windows[i].x, windows[i].y = newX, newY
windows[i].visible = true
updateRegion(windows[i].x, windows[i].y, windows[i].x + windows[i].width - 1, windows[i].y + windows[i].height - 1)
Yes, this sort of code may take enough time to execute that you notice a very slight flicker when running it. But when you're not hiding/moving/adding windows, you're not running any of the above! You're simply storing the new content into eg windows[someWindow].buffer[y][x], and immediately updating the screen with it (assuming layers[y][x] gives you permission to do so for each point).
There's tons more code you'd need to wrap around the psuedo-code above - for example, you'd need functions so that window content can render through a window's buffer, and you need ways to update the layer map if windows are moved up/down the pile (tip: it can be done in a very, very similar to the "moving" example above - make windows with new indexes invisible, update map, update indexes, make affected windows visible again, update map), windows are added, removed, yadda yadda yadda.
Basically, think about what code runs often, and what code doesn't. Then rig things so that "work intensive" functions get called rarely, and start piling as much "work" from your commonly-called functions into those rare ones as you can. You could, for example, implement a system that spots when a window has been entirely covered, and if so, sets a separate secondary "invisibility" flag for it which it can use to spot that there's no point in checking every index in layers[y][x] because they're all covered and hence every check will get the same answer. There are tons of options. The more thinking
you do, the less thinking you can make your
script do, on average.