-- Optional game viewport inside the OS window. A layout mod may reserve any -- window-space rectangle through render.viewport; the game then renders as if -- that rectangle were its whole display. With no subscriber this module is a -- pass-through and allocates no canvas. local Runtime = require("src.mods.Runtime") local Viewport = { rect = nil, full = nil, canvas = nil, generation = nil, frameActive = false, } local function finite(value) return type(value) == "number" and value == value and value > -math.huge and value < math.huge end local function realMetrics() local G = love.graphics local w, h = G.getDimensions() local pw, ph = w, h if G.getPixelDimensions then pw, ph = G.getPixelDimensions() end local dpiX = w > 0 and pw / w or 1 local dpiY = h > 0 and ph / h or 1 if dpiX < 1e-6 then dpiX = 1 end if dpiY < 1e-6 then dpiY = 1 end return math.max(1, w), math.max(1, h), math.max(1, pw), math.max(1, ph), dpiX, dpiY end local function clampRect(value, w, h) if type(value) ~= "table" then return { x = 0, y = 0, width = w, height = h } end local x = finite(value.x) and math.floor(value.x) or 0 local y = finite(value.y) and math.floor(value.y) or 0 local rw = finite(value.width) and math.floor(value.width) or w local rh = finite(value.height) and math.floor(value.height) or h x = math.max(0, math.min(x, w - 1)) y = math.max(0, math.min(y, h - 1)) rw = math.max(1, math.min(rw, w - x)) rh = math.max(1, math.min(rh, h - y)) return { x = x, y = y, width = rw, height = rh } end local function sameSize(canvas, w, h) return canvas and canvas:getWidth() == w and canvas:getHeight() == h end function Viewport.begin(generation) local w, h, pw, ph, dpiX, dpiY = realMetrics() local context = { width = w, height = h, pixelWidth = pw, pixelHeight = ph, dpiX = dpiX, dpiY = dpiY, generation = generation, } local requested if Runtime.wantsHook("render.viewport") then requested = Runtime.call("render.viewport", function(ctx) return { x = 0, y = 0, width = ctx.width, height = ctx.height } end, context) end local rect = clampRect(requested, w, h) Viewport.full = context Viewport.rect = rect Viewport.generation = generation local active = type(requested) == "table" and requested.capture == true or rect.x ~= 0 or rect.y ~= 0 or rect.width ~= w or rect.height ~= h Viewport.frameActive = active if active then if not sameSize(Viewport.canvas, rect.width, rect.height) then if Viewport.canvas and Viewport.canvas.release then Viewport.canvas:release() end Viewport.canvas = love.graphics.newCanvas(rect.width, rect.height) Viewport.canvas:setFilter("nearest", "nearest") end else if Viewport.canvas and Viewport.canvas.release then Viewport.canvas:release() end Viewport.canvas = nil end return rect end function Viewport.active() return Viewport.frameActive == true and Viewport.canvas ~= nil end function Viewport.dimensions() if Viewport.active() then return Viewport.rect.width, Viewport.rect.height end return love.graphics.getDimensions() end function Viewport.pixelDimensions() if Viewport.active() then if Viewport.canvas.getPixelDimensions then local w, h = Viewport.canvas:getPixelDimensions() return math.max(1, w), math.max(1, h) end return math.max(1, math.floor(Viewport.rect.width * Viewport.full.dpiX)), math.max(1, math.floor(Viewport.rect.height * Viewport.full.dpiY)) end if love.graphics.getPixelDimensions then return love.graphics.getPixelDimensions() end return love.graphics.getDimensions() end function Viewport.fullDimensions() if Viewport.full then return Viewport.full.width, Viewport.full.height end return love.graphics.getDimensions() end function Viewport.target() return Viewport.canvas end function Viewport.setTarget() love.graphics.setCanvas(Viewport.canvas) end function Viewport.toLocal(x, y) local rect = Viewport.rect if not rect then return x, y, true end local lx, ly = x - rect.x, y - rect.y return lx, ly, lx >= 0 and ly >= 0 and lx < rect.width and ly < rect.height end function Viewport.localSafeRect(x, y, w, h) local rect = Viewport.rect if not Viewport.active() or not rect then return x, y, w, h end local x1, y1 = math.max(x, rect.x), math.max(y, rect.y) local x2 = math.min(x + w, rect.x + rect.width) local y2 = math.min(y + h, rect.y + rect.height) if x2 <= x1 or y2 <= y1 then return 0, 0, rect.width, rect.height end return x1 - rect.x, y1 - rect.y, x2 - x1, y2 - y1 end function Viewport.finish(game) if not Viewport.active() then return end local G = love.graphics local rect, full = Viewport.rect, Viewport.full G.setCanvas() G.push("all") G.origin() G.setScissor() G.setShader() G.setBlendMode("alpha") G.clear(0, 0, 0, 1) local context = { canvas = Viewport.canvas, x = rect.x, y = rect.y, width = rect.width, height = rect.height, windowWidth = full.width, windowHeight = full.height, dpiX = full.dpiX, dpiY = full.dpiY, generation = Viewport.generation, } Runtime.call("render.window", function(_, ctx) G.setColor(1, 1, 1, 1) G.draw(ctx.canvas, ctx.x, ctx.y) end, game, context) G.pop() end function Viewport.reset() Viewport.frameActive = false Viewport.rect = nil Viewport.full = nil Viewport.generation = nil if Viewport.canvas and Viewport.canvas.release then Viewport.canvas:release() end Viewport.canvas = nil end return Viewport