diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d398c33 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Visual Studio Code +.vscode/* diff --git a/README.md b/README.md index 8d0007c..5fb8497 100644 --- a/README.md +++ b/README.md @@ -1,180 +1,116 @@ -**⚠️ Looking for maintainer:** as I am not working with LÖVE anymore, this repository is not actively maintained, except for critical fixes. Please do reach out if you're interested in maintaining this repository. +# push +**push** is a simple resolution-handling library for LÖVE that allows you to focus on making your game with a fixed resolution. -push -============== +![Screenshot](images/screenshot.png) -push is a simple resolution-handling library that allows you to focus on making your game with a fixed resolution. - -![image](https://media.giphy.com/media/xTb1RycLHeAOPDownu/giphy.gif) - -Setup ----------------- -Fullscreen +## Demo +This demo creates a 1280x720 resizable window and sets push to an upscaled 800x600 resolution. Under the "Draw stuff here!" comment, add some drawing functions to see push in action! ```lua -local push = require "push" +push = require "push" -local gameWidth, gameHeight = 1080, 720 --fixed game resolution -local windowWidth, windowHeight = love.window.getDesktopDimensions() +love.window.setMode(1280, 720, {resizable = true}) -- Resizable 1280x720 window +push.setupScreen(800, 600, {upscale = "normal"}) -- 800x600 game resolution, upscaled -push:setupScreen(gameWidth, gameHeight, windowWidth, windowHeight, {fullscreen = true}) - -function love.draw() - push:start() - - --draw here - - push:finish() +-- Make sure push follows LÖVE's resizes +function love.resize(width, height) + push.resize(width, height) end -``` - -Windowed -```lua -local push = require "push" - -local gameWidth, gameHeight = 1080, 720 --fixed game resolution -local windowWidth, windowHeight = love.window.getDesktopDimensions() -windowWidth, windowHeight = windowWidth*.7, windowHeight*.7 --make the window a bit smaller than the screen itself - -push:setupScreen(gameWidth, gameHeight, windowWidth, windowHeight, {fullscreen = false}) function love.draw() - push:start() - - --draw here - - push:finish() + push.start() + -- Draw stuff here! + push.finish() end ``` -Usage ----------------- - -Init push -```lua -push:setupScreen(gameWidth, gameHeight, windowWidth, windowHeight, {fullscreen, resizable, canvas, pixelperfect}) -``` -**gameWidth**, **gameHeight** represent the game's fixed resolution. **windowWidth** and **windowHeight** are the dimensions of the window you need to adapt the game to. - -The last argument is a table containing: -- **fullscreen** (bool): turns fullscreen mode on or off -- **resizable** (bool): allows resizing the window -- **canvas** (bool): uses canvas -- **pixelperfect** (bool): enables pixel-perfect mode (integer scaling 1x, 2x, 3x, ...) -- **highdpi** (bool): enables high-dpi mode on supported screens (e.g. Retina) -- **stretched** (bool): stretches the game to window dimensions - -Apply **push** transforms +## Usage +After applying changes to LÖVE's window using `love.window.setMode()`, init **push**: ```lua -push:start() ---draw here -push:finish() - ---alias -push:apply(operation) +push.setupScreen(pushWidth, pushHeight, {upscale = ..., canvas = ...}) ``` -**operation** should be equal to "start" or "end", meaning "before" or "after" your main drawing logic - -Mobile support ----------------- +`pushWidth` and `pushHeight` represent **push's** fixed resolution. -**push** does *not* have built-in support for mobile platforms, but it is trivial to handle mobile screens correctly. +The last argument is a table containing settings for **push**: +* `upscale` (string): upscale **push's** resolution to the current window size + * `"normal"`: fit to the current window size, preserving aspect ratio + * `"pixel-perfect"`: pixel-perfect scaling using integer scaling (for values ≥1, otherwise uses normal scaling) + * `"stretched"`: stretch to the current window size +* `canvas` (bool): use and upscale canvas set to **push's** resolution -A possible solution is to initialize **push** in fullscreen mode: +Hook **push** into the `love.resize()` function so that it follows LÖVE's resizes: ```lua -local screenWidth, screenHeight = love.window.getDesktopDimensions() -push:setupScreen(gameWidth, gameHeight, screenWidth, screenHeight, { fullscreen = true, resizable = false, ... }) +function love.resize(width, height) + push.resize(width, height) +end ``` -And listen to screen orientation changes: +Finally, apply **push** transforms: ```lua -function love.resize(w, h) - return push:resize(w, h) +function love.draw() + push.start() + -- Draw stuff here! + push.finish() end ``` -Multiple shaders ----------------- - +## Multiple shaders Any method that takes a shader as an argument can also take a *table* of shaders instead. The shaders will be applied in the order they're provided. Set multiple global shaders ```lua -push:setShader({ shader1, shader2 }) +push.setShader({ shader1, shader2 }) ``` Set multiple canvas-specific shaders ```lua -push:setupCanvas({ { name = "multiple_shaders", shader = { shader1, shader2 } } }) +push.setupCanvas({{name = "multiple_shaders", shader = {shader1, shader2}}}) ``` -Advanced canvases/shaders ----------------- - -**push** provides basic canvas and shader functionality through the *canvas* flag in push:setupScreen() and push:setShader(), but you can also create additional canvases, name them for later use and apply multiple shaders to them. +## Advanced canvases/shaders +**push** provides basic canvas and shader functionality through the `canvas` flag in push:setupScreen() and push:setShader(), but you can also create additional canvases, name them for later use and apply multiple shaders to them. -Set up custom canvases +Set up custom canvases: ```lua -push:setupCanvas(canvasList) +push.setupCanvas(canvasList) ---e.g. push:setupCanvas({ { name = "foreground", shader = foregroundShader }, { name = "background" } }) +-- e.g. push.setupCanvas({{name = "foreground", shader = foregroundShader}, {name = "background"}}) ``` Shaders can be passed to canvases directly through push:setupCanvas(), or you can choose to set them later. ```lua -push:setShader(canvasName, shader) +push.setShader(canvasName, shader) ``` Then, you just need to draw your game on different canvases like you'd do with love.graphics.setCanvas(): ```lua -push:setCanvas(canvasName) +push.setCanvas(canvasName) ``` -Resizing the window ----------------- - -In order for push to take in account window resizing (if you have set {resizable = true} in push:setupScreen()), you need to call push:resize() like so: - +## Misc +Update settings: ```lua -function love.resize(w, h) - push:resize(w, h) -end +push.updateSettings({settings}) ``` -Misc ----------------- - -Switch fullscreen +Set a post-processing shader (will apply to the whole screen): ```lua -push:switchFullscreen(w, h) +push.setShader([canvasName], shader) ``` -**w** and **h** are optional parameters that are used in case the game switches to windowed mode +You don't need to call this every frame. Simply call it once, and it will be stored into **push** until you change it back to something else. If no `canvasName` is passed, shader will apply to the final render. Use it at your advantage to combine shader effects. -Set a post-processing shader (will apply to the whole screen) +Convert coordinates: ```lua -push:setShader([canvasName], shader) -``` -You don't need to call this every frame. Simply call it once, and it will be stored into **push** until you change it back to something else. -If no canvasName is passed, shader will apply to the final render. Use it at your advantage to combine shader effects. - -Convert coordinates -```lua -push:toGame(x, y) --convert coordinates from screen to game (useful for mouse position) ---push:toGame will return nil for the values that are outside the game - be sure to check that before using them +push.toGame(x, y) -- Convert coordinates from screen to game (useful for mouse position) +-- push.toGame will return false for values that are outside the game, be sure to check that before using them! -push:toReal(x, y) --convert coordinates from game to screen +push.toReal(x, y) -- Convert coordinates from game to screen ``` -Get game dimensions +Get game dimensions: ```lua -push:getDimensions() --returns push:getWidth(), push:getHeight() +push.getWidth() -- Returns game width -push:getWidth() --returns game width +push.getHeight() -- Returns game height -push:getHeight() --returns game height +push.getDimensions() -- Returns push.getWidth(), push.getHeight() ``` - -Set border color -```lua -push:setBorderColor(r, g, b, a) --also accepts a table -``` - diff --git a/examples/low-res/background.png b/examples/low-res/background.png deleted file mode 100644 index 90773c7..0000000 Binary files a/examples/low-res/background.png and /dev/null differ diff --git a/examples/low-res/image.png b/examples/low-res/image.png new file mode 100644 index 0000000..5cf8443 Binary files /dev/null and b/examples/low-res/image.png differ diff --git a/examples/low-res/init.lua b/examples/low-res/init.lua index 477f7ec..84a16ae 100644 --- a/examples/low-res/init.lua +++ b/examples/low-res/init.lua @@ -1,77 +1,56 @@ --[[ Low resolution ]]-- return function() - - love.graphics.setDefaultFilter("nearest", "nearest") --disable blurry scaling - - local gameWidth, gameHeight = 64, 64 - local windowWidth, windowHeight = love.window.getDesktopDimensions() - windowWidth, windowHeight = windowWidth*.5, windowHeight*.5 + love.graphics.setDefaultFilter("nearest", "nearest") -- Disable blurry scaling - push:setupScreen(gameWidth, gameHeight, windowWidth, windowHeight, { - fullscreen = false, - resizable = true, - pixelperfect = true - }) - push:setBorderColor{0, 0, 0} --default value - - -- - - time = 0 + love.window.setMode(640, 480, {resizable = true}) -- LÖVE resolution 640x480, resizable + push.setupScreen(64, 64, {upscale = "pixel-perfect", canvas = true}) -- push resolution 64x64, pixel perfect scaling, drawn to a canvas - function love.load() - mario = love.graphics.newImage("examples/low-res/mario.png") - background = love.graphics.newImage("examples/low-res/background.png") - - love.graphics.setNewFont(16) - end - - function love.update(dt) - - time = (time + dt) % 1 - - end + -- - function love.draw() - push:apply("start") - - local mouseX, mouseY = love.mouse.getPosition() - mouseX, mouseY = push:toGame(mouseX, mouseY) - --if nil is returned, that means the mouse is outside the game screen - - local abs = math.abs(time-.5) - local pi = math.cos(math.pi*2*time) - local pi2 = math.cos(math.pi*8*time) - local w = push:getWidth() - --for animating basic stuff - - love.graphics.draw(background, 0, 0) - - love.graphics.setScissor(0, 0, push:getWidth(), push:getHeight()-16) - love.graphics.setColor(0, 0, 0, 100) - love.graphics.draw(mario, 27, 33) - love.graphics.setScissor() - love.graphics.setColor(255, 255, 255) - love.graphics.draw(mario, 26, 32) - - love.graphics.setColor(0, 0, 0, 100) - love.graphics.printf("Hi!", 34+1, 22-pi*2+1, w, "center", -.15+.5*abs, abs*.25+1, abs*.25+1, w*.5, 12) - love.graphics.setColor(255, 255, 255) - love.graphics.printf("Hi!", 34, 22-pi*2, w, "center", -.15+.5*abs, abs*.25+1, abs*.25+1, w*.5, 12) + time = 0 - love.graphics.setColor(255, 255, 255) - if mouseX and mouseY then --cursor - love.graphics.points( - mouseX, mouseY-1, - mouseX-1, mouseY, - mouseX, mouseY, - mouseX+1, mouseY, - mouseX, mouseY+1 - ) - end - - push:apply("end") - end - -end \ No newline at end of file + function love.load() + image = love.graphics.newImage("examples/low-res/image.png") + + love.graphics.setNewFont(16) + end + + function love.update(dt) + + time = (time + dt) % 1 + + end + + function love.draw() + push.start() + local mouseX, mouseY = love.mouse.getPosition() + mouseX, mouseY = push.toGame(mouseX, mouseY) + -- If false is returned, that means the mouse is outside the game screen + + local abs = math.abs(time-.5) + local pi = math.cos(math.pi*2*time) + local w = push:getWidth() + --for animating basic stuff + + love.graphics.draw(image, 0, 0) + + love.graphics.setColor(0, 0, 0, 0.5) + love.graphics.printf("Hi!", 31, 23-pi*2, w, "center", -.15+.5*abs, abs*.25+1, abs*.25+1, w*.5, 12) + love.graphics.setColor(1, 1, 1) + love.graphics.printf("Hi!", 30, 22-pi*2, w, "center", -.15+.5*abs, abs*.25+1, abs*.25+1, w*.5, 12) + + love.graphics.setColor(1, 1, 1) + if mouseX and mouseY then --cursor + love.graphics.points( + mouseX, mouseY-1, + mouseX-1, mouseY, + mouseX, mouseY, + mouseX+1, mouseY, + mouseX, mouseY+1 + ) + end + push.finish() + end +end diff --git a/examples/low-res/mario.png b/examples/low-res/mario.png deleted file mode 100644 index 6962b59..0000000 Binary files a/examples/low-res/mario.png and /dev/null differ diff --git a/examples/mouse-input/init.lua b/examples/mouse-input/init.lua index aa73280..a9be55e 100644 --- a/examples/mouse-input/init.lua +++ b/examples/mouse-input/init.lua @@ -1,43 +1,34 @@ --[[ Mouse input ]]-- return function () - - love.graphics.setDefaultFilter("linear", "linear") --default filter - - local gameWidth, gameHeight = 1080, 720 - - local windowWidth, windowHeight = love.window.getDesktopDimensions() - windowWidth, windowHeight = windowWidth*.5, windowHeight*.5 - - push:setupScreen(gameWidth, gameHeight, windowWidth, windowHeight, { - fullscreen = false, - resizable = true, - highdpi = true, - canvas = false - }) - push:setBorderColor(0, 0, 0) --default value - - function love.load() - love.graphics.setNewFont(32) - end - - function love.draw() - push:apply("start") - - love.graphics.setColor(50, 0, 0) - love.graphics.rectangle("fill", 0, 0, gameWidth, gameHeight) - - local mouseX, mouseY = love.mouse.getPosition() - mouseX, mouseY = push:toGame(mouseX, mouseY) - --nil is returned if mouse is outside the game screen - - love.graphics.setColor(255, 255, 255) - if mouseX and mouseY then love.graphics.circle("line", mouseX, mouseY, 10) end - - love.graphics.printf("mouse x : " .. (mouseX or "outside"), 25, 25, gameWidth, "left") - love.graphics.printf("mouse y : " .. (mouseY or "outside"), 25, 50, gameWidth, "left") - - push:apply("end") - end - -end \ No newline at end of file + + love.graphics.setDefaultFilter("linear", "linear") -- Bilinear scaling + + love.window.setMode(1280, 720, {resizable = true}) -- LÖVE resolution 1280x720, resizable + push.setupScreen(800, 600, {canvas = true}) -- push resolution 64x64, pixel perfect scaling, drawn to a canvas + + function love.load() + love.graphics.setNewFont(32) + end + + function love.draw() + local pushWidth, pushHeight = push.getDimensions() + + push.start() + love.graphics.setColor(50, 0, 0) + love.graphics.rectangle("fill", 0, 0, pushWidth, pushHeight) + + local mouseX, mouseY = love.mouse.getPosition() + mouseX, mouseY = push.toGame(mouseX, mouseY) -- false is returned if mouse is outside the game screen + -- Good practice to floor returned values when simulating screen pixels + if mouseX then mouseX = math.floor(mouseX) end + if mouseY then mouseY = math.floor(mouseY) end + + love.graphics.setColor(255, 255, 255) + if mouseX and mouseY then love.graphics.circle("line", mouseX, mouseY, 10) end + + love.graphics.printf("mouse x : " .. (mouseX or "outside"), 25, 25, pushWidth, "left") + love.graphics.printf("mouse y : " .. (mouseY or "outside"), 25, 50, pushWidth, "left") + push.finish() + end +end diff --git a/examples/stencil/background.png b/examples/stencil/background.png deleted file mode 100644 index 90773c7..0000000 Binary files a/examples/stencil/background.png and /dev/null differ diff --git a/images/screenshot.png b/images/screenshot.png new file mode 100644 index 0000000..18c31fc Binary files /dev/null and b/images/screenshot.png differ diff --git a/main.lua b/main.lua index 42a0a88..a959830 100644 --- a/main.lua +++ b/main.lua @@ -1,40 +1,40 @@ -io.stdout:setvbuf('no') - push = require "push" --require the library -love.window.setTitle("Press space to switch examples") +love.window.setTitle("Press space to switch examples!") local examples = { - "low-res", - "single-shader", - "multiple-shaders", - "mouse-input", - "canvases-shaders", - "stencil" + "low-res", + --[[ + "single-shader", + "multiple-shaders", + --]] + "mouse-input" + --[[ + "canvases-shaders", + "stencil" + --]] } local example = 1 for i = 1, #examples do - examples[i] = require("examples." .. examples[i]) + examples[i] = require("examples." .. examples[i]) end function love.resize(w, h) - push:resize(w, h) + push.resize(w, h) end -function love.keypressed(key, scancode, isrepeat) - - if key == "space" then - example = (example < #examples) and example + 1 or 1 - - --be sure to reset push settings - push:resetSettings() - - examples[example]() - love.load() - elseif key == "f" then --activate fullscreen mode - push:switchFullscreen() --optional width and height parameters for window mode - end - +function love.keypressed(key) + if key == "space" then + example = (example < #examples) and example + 1 or 1 + + examples[example]() + love.load() + elseif key == "f" then -- Activate fullscreen mode + love.window.setMode(0, 0, {fullscreen = true, fullscreentype = "desktop"}) + push.resize(love.graphics.getDimensions()) + elseif key == "escape" then + love.event.quit() + end end -examples[example]() \ No newline at end of file +examples[example]() diff --git a/push.lua b/push.lua index b22f698..1c101a8 100644 --- a/push.lua +++ b/push.lua @@ -1,281 +1,272 @@ --- push.lua v0.4 - --- Copyright (c) 2020 Ulysse Ramage --- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: --- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. --- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -local love11 = love.getVersion() == 11 -local getDPI = love11 and love.window.getDPIScale or love.window.getPixelScale -local windowUpdateMode = love11 and love.window.updateMode or function(width, height, settings) - local _, _, flags = love.window.getMode() - for k, v in pairs(settings) do flags[k] = v end - love.window.setMode(width, height, flags) -end +--[[ +push.lua v1.0 -local push = { - - defaults = { - fullscreen = false, - resizable = false, - pixelperfect = false, - highdpi = true, - canvas = true, - stencil = true - } - -} -setmetatable(push, push) +The MIT License (MIT) -function push:applySettings(settings) - for k, v in pairs(settings) do - self["_" .. k] = v - end -end +Copyright (c) 2018 Ulysse Ramage -function push:resetSettings() return self:applySettings(self.defaults) end +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -function push:setupScreen(WWIDTH, WHEIGHT, RWIDTH, RHEIGHT, settings) +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. - settings = settings or {} +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +--]] - self._WWIDTH, self._WHEIGHT = WWIDTH, WHEIGHT - self._RWIDTH, self._RHEIGHT = RWIDTH, RHEIGHT +local settings - self:applySettings(self.defaults) --set defaults first - self:applySettings(settings) --then fill with custom settings - - windowUpdateMode(self._RWIDTH, self._RHEIGHT, { - fullscreen = self._fullscreen, - resizable = self._resizable, - highdpi = self._highdpi - }) +local pushWidth, pushHeight +local windowWidth, windowHeight - self:initValues() +local scale = {x = 0, y = 0} +local offset = {x = 0, y = 0} - if self._canvas then - self:setupCanvas({ "default" }) --setup canvas - end +local drawWidth, drawHeight - self._borderColor = {0, 0, 0} +local canvases - self._drawFunctions = { - ["start"] = self.start, - ["end"] = self.finish - } +local canvasOptions - return self -end +local function initValues() + if settings.upscale then + scale.x = windowWidth / pushWidth + scale.y = windowHeight / pushHeight -function push:setupCanvas(canvases) - table.insert(canvases, { name = "_render", private = true }) --final render + if settings.upscale == "normal" or settings.upscale == "pixel-perfect" then + local scaleVal - self._canvas = true - self.canvases = {} + scaleVal = math.min(scale.x, scale.y) + if scaleVal >= 1 and settings.upscale == "pixel-perfect" then scaleVal = math.floor(scaleVal) end - for i = 1, #canvases do - push:addCanvas(canvases[i]) - end + offset.x = math.floor((scale.x - scaleVal) * (pushWidth / 2)) + offset.y = math.floor((scale.y - scaleVal) * (pushHeight / 2)) - return self -end -function push:addCanvas(params) - table.insert(self.canvases, { - name = params.name, - private = params.private, - shader = params.shader, - canvas = love.graphics.newCanvas(self._WWIDTH, self._WHEIGHT), - stencil = params.stencil or self._stencil - }) -end + scale.x, scale.y = scaleVal, scaleVal -- Apply same scale to width and height + elseif settings.upscale == "stretched" then -- If stretched, no need to apply offset + offset.x, offset.y = 0, 0 + else + error("Invalid upscale setting") + end + else + scale.x, scale.y = 1, 1 -function push:setCanvas(name) - if not self._canvas then return true end - local canvasTable = self:getCanvasTable(name) - return love.graphics.setCanvas({ canvasTable.canvas, stencil = canvasTable.stencil }) -end -function push:getCanvasTable(name) - for i = 1, #self.canvases do - if self.canvases[i].name == name then - return self.canvases[i] - end - end -end -function push:setShader(name, shader) - if not shader then - self:getCanvasTable("_render").shader = name - else - self:getCanvasTable(name).shader = shader - end -end + offset.x = math.floor((windowWidth / pushWidth - 1) * (pushWidth / 2)) + offset.y = math.floor((windowHeight / pushHeight - 1) * (pushHeight / 2)) + end -function push:initValues() - self._PSCALE = (not love11 and self._highdpi) and getDPI() or 1 - - self._SCALE = { - x = self._RWIDTH/self._WWIDTH * self._PSCALE, - y = self._RHEIGHT/self._WHEIGHT * self._PSCALE - } - - if self._stretched then --if stretched, no need to apply offset - self._OFFSET = {x = 0, y = 0} - else - local scale = math.min(self._SCALE.x, self._SCALE.y) - if self._pixelperfect then scale = math.floor(scale) end - - self._OFFSET = {x = (self._SCALE.x - scale) * (self._WWIDTH/2), y = (self._SCALE.y - scale) * (self._WHEIGHT/2)} - self._SCALE.x, self._SCALE.y = scale, scale --apply same scale to X and Y - end - - self._GWIDTH = self._RWIDTH * self._PSCALE - self._OFFSET.x * 2 - self._GHEIGHT = self._RHEIGHT * self._PSCALE - self._OFFSET.y * 2 + drawWidth = windowWidth - offset.x * 2 + drawHeight = windowHeight - offset.y * 2 end -function push:apply(operation, shader) - self._drawFunctions[operation](self, shader) -end +local function setupCanvas(canvasTable) + table.insert(canvasTable, {name = "_render", private = true}) -- Final render -function push:start() - if self._canvas then - love.graphics.push() - love.graphics.setCanvas({ self.canvases[1].canvas, stencil = self.canvases[1].stencil }) - - else - love.graphics.translate(self._OFFSET.x, self._OFFSET.y) - love.graphics.setScissor(self._OFFSET.x, self._OFFSET.y, self._WWIDTH*self._SCALE.x, self._WHEIGHT*self._SCALE.y) - love.graphics.push() - love.graphics.scale(self._SCALE.x, self._SCALE.y) - end -end + canvases = {} -function push:applyShaders(canvas, shaders) - local _shader = love.graphics.getShader() - if #shaders <= 1 then - love.graphics.setShader(shaders[1]) - love.graphics.draw(canvas) - else - local _canvas = love.graphics.getCanvas() - - local _tmp = self:getCanvasTable("_tmp") - if not _tmp then --create temp canvas only if needed - self:addCanvas({ name = "_tmp", private = true, shader = nil }) - _tmp = self:getCanvasTable("_tmp") - end - - love.graphics.push() - love.graphics.origin() - local outputCanvas - for i = 1, #shaders do - local inputCanvas = i % 2 == 1 and canvas or _tmp.canvas - outputCanvas = i % 2 == 0 and canvas or _tmp.canvas - love.graphics.setCanvas(outputCanvas) - love.graphics.clear() - love.graphics.setShader(shaders[i]) - love.graphics.draw(inputCanvas) - love.graphics.setCanvas(inputCanvas) - end - love.graphics.pop() - - love.graphics.setCanvas(_canvas) - love.graphics.draw(outputCanvas) - end - love.graphics.setShader(_shader) -end + for i = 1, #canvasTable do + local params = canvasTable[i] -function push:finish(shader) - love.graphics.setBackgroundColor(unpack(self._borderColor)) - if self._canvas then - local _render = self:getCanvasTable("_render") - - love.graphics.pop() - - local white = love11 and 1 or 255 - love.graphics.setColor(white, white, white) - - --draw canvas - love.graphics.setCanvas(_render.canvas) - for i = 1, #self.canvases do --do not draw _render yet - local _table = self.canvases[i] - if not _table.private then - local _canvas = _table.canvas - local _shader = _table.shader - self:applyShaders(_canvas, type(_shader) == "table" and _shader or { _shader }) - end - end - love.graphics.setCanvas() - - --draw render - love.graphics.translate(self._OFFSET.x, self._OFFSET.y) - local shader = shader or _render.shader - love.graphics.push() - love.graphics.scale(self._SCALE.x, self._SCALE.y) - self:applyShaders(_render.canvas, type(shader) == "table" and shader or { shader }) - love.graphics.pop() - - --clear canvas - for i = 1, #self.canvases do - love.graphics.setCanvas(self.canvases[i].canvas) - love.graphics.clear() - end - - love.graphics.setCanvas() - love.graphics.setShader() - else - love.graphics.pop() - love.graphics.setScissor() - end -end + table.insert( + canvases, + { + name = params.name, + private = params.private, + shader = params.shader, + canvas = love.graphics.newCanvas(pushWidth, pushHeight), + stencil = params.stencil + } + ) + end -function push:setBorderColor(color, g, b) - self._borderColor = g and {color, g, b} or color + canvasOptions = {canvases[1].canvas, stencil = canvases[1].stencil} end -function push:toGame(x, y) - x, y = x - self._OFFSET.x, y - self._OFFSET.y - local normalX, normalY = x / self._GWIDTH, y / self._GHEIGHT - - x = (x >= 0 and x <= self._WWIDTH * self._SCALE.x) and normalX * self._WWIDTH or nil - y = (y >= 0 and y <= self._WHEIGHT * self._SCALE.y) and normalY * self._WHEIGHT or nil - - return x, y +local function getCanvasTable(name) + for i = 1, #canvases do + if canvases[i].name == name then + return canvases[i] + end + end end -function push:toReal(x, y) - local realX = self._OFFSET.x + (self._GWIDTH * x)/self._WWIDTH - local realY = self._OFFSET.y + (self._GHEIGHT * y)/self._WHEIGHT - return realX, realY +local function start() + if settings.canvas then + love.graphics.push() + love.graphics.setCanvas(canvasOptions) + else + love.graphics.translate(offset.x, offset.y) + love.graphics.setScissor(offset.x, offset.y, pushWidth * scale.x, pushHeight * scale.y) + love.graphics.push() + love.graphics.scale(scale.x, scale.y) + end end -function push:switchFullscreen(winw, winh) - self._fullscreen = not self._fullscreen - local windowWidth, windowHeight = love.window.getDesktopDimensions() - - if self._fullscreen then --save windowed dimensions for later - self._WINWIDTH, self._WINHEIGHT = self._RWIDTH, self._RHEIGHT - elseif not self._WINWIDTH or not self._WINHEIGHT then - self._WINWIDTH, self._WINHEIGHT = windowWidth * .5, windowHeight * .5 - end - - self._RWIDTH = self._fullscreen and windowWidth or winw or self._WINWIDTH - self._RHEIGHT = self._fullscreen and windowHeight or winh or self._WINHEIGHT - - self:initValues() - - love.window.setFullscreen(self._fullscreen, "desktop") - if not self._fullscreen and (winw or winh) then - windowUpdateMode(self._RWIDTH, self._RHEIGHT) --set window dimensions - end +local function applyShaders(canvas, shaders) + local shader = love.graphics.getShader() + + if #shaders <= 1 then + love.graphics.setShader(shaders[1]) + love.graphics.draw(canvas) + else + local canvas = love.graphics.getCanvas() + local tmp = getCanvasTable("_tmp") + local outputCanvas + local inputCanvas + + -- Only create "_tmp" canvas if needed + if not tmp then + table.insert( + canvases, + { + name = "_tmp", + private = true, + canvas = love.graphics.newCanvas(pushWidth, pushHeight) + } + ) + + tmp = getCanvasTable("_tmp") + end + + love.graphics.push() + love.graphics.origin() + for i = 1, #shaders do + inputCanvas = i % 2 == 1 and canvas or tmp.canvas + outputCanvas = i % 2 == 0 and canvas or tmp.canvas + love.graphics.setCanvas(outputCanvas) + love.graphics.clear() + love.graphics.setShader(shaders[i]) + love.graphics.draw(inputCanvas) + love.graphics.setCanvas(inputCanvas) + end + love.graphics.pop() + + love.graphics.setCanvas(canvas) + love.graphics.draw(outputCanvas) + end + + love.graphics.setShader(shader) end -function push:resize(w, h) - if self._highdpi then w, h = w / self._PSCALE, h / self._PSCALE end - self._RWIDTH = w - self._RHEIGHT = h - self:initValues() +local function finish(shader) + if settings.canvas then + local render = getCanvasTable("_render") + + love.graphics.pop() + + -- Draw canvas + love.graphics.setCanvas(render.canvas) + -- Do not draw render yet + for i = 1, #canvases do + local canvasTable = canvases[i] + + if not canvasTable.private then + local shader = canvasTable.shader + + applyShaders(canvasTable.canvas, type(shader) == "table" and shader or {shader}) + end + end + love.graphics.setCanvas() + + -- Now draw render + love.graphics.translate(offset.x, offset.y) + love.graphics.push() + love.graphics.scale(scale.x, scale.y) + do + local shader = shader or render.shader + + applyShaders(render.canvas, type(shader) == "table" and shader or {shader}) + end + love.graphics.pop() + + -- Clear canvas + for i = 1, #canvases do + love.graphics.setCanvas(canvases[i].canvas) + love.graphics.clear() + end + + love.graphics.setCanvas() + love.graphics.setShader() + else + love.graphics.pop() + love.graphics.setScissor() + end end -function push:getWidth() return self._WWIDTH end -function push:getHeight() return self._WHEIGHT end -function push:getDimensions() return self._WWIDTH, self._WHEIGHT end +return { + setupScreen = function(width, height, settingsTable) + pushWidth, pushHeight = width, height + windowWidth, windowHeight = love.graphics.getDimensions() + + settings = settingsTable + + initValues() -return push + if settings.canvas then + setupCanvas({"default"}) + end + end, + + setupCanvas = setupCanvas, + setCanvas = function(name) + local canvasTable + + if not settings.canvas then return true end + + canvasTable = getCanvasTable(name) + return love.graphics.setCanvas({canvasTable.canvas, stencil = canvasTable.stencil}) + end, + setShader = function(name, shader) + if not shader then + getCanvasTable("_render").shader = name + else + getCanvasTable(name).shader = shader + end + end, + + updateSettings = function(settingsTable) + settings.upscale = settingsTable.upscale or settings.upscale + settings.canvas = settingsTable.canvas or settings.canvas + end, + + toGame = function(x, y) + local normalX, normalY + + x, y = x - offset.x, y - offset.y + normalX, normalY = x / drawWidth, y / drawHeight + + x = (x >= 0 and x <= pushWidth * scale.x) and math.floor(normalX * pushWidth) or false + y = (y >= 0 and y <= pushHeight * scale.y) and math.floor(normalY * pushHeight) or false + + return x, y + end, + toReal = function(x, y) + local realX = offset.x + (drawWidth * x) / pushWidth + local realY = offset.y + (drawHeight * y)/ pushHeight + + return realX, realY + end, + + start = start, + finish = finish, + + resize = function(width, height) + windowWidth, windowHeight = width, height + + initValues() + end, + + getWidth = function() return pushWidth end, + getHeight = function() return pushHeight end, + getDimensions = function() return pushWidth, pushHeight end +}