diff --git a/deps/cargo.lua b/deps/cargo.lua new file mode 100644 index 0000000..e380146 --- /dev/null +++ b/deps/cargo.lua @@ -0,0 +1,105 @@ +-- cargo v0.1.1 +-- https://github.com/bjornbytes/cargo +-- MIT License + +local cargo = {} + +local function merge(target, source, ...) + if not target or not source then return target end + for k, v in pairs(source) do target[k] = v end + return merge(target, ...) +end + +local la, lf, lg = love.audio, love.filesystem, love.graphics + +local function makeSound(path) + local info = lf.getInfo(path, 'file') + return la.newSource(path, (info and info.size and info.size < 5e5) and 'static' or 'stream') +end + +local function makeFont(path) + return function(size) + return lg.newFont(path, size) + end +end + +local function loadFile(path) + return lf.load(path)() +end + +cargo.loaders = { + lua = lf and loadFile, + png = lg and lg.newImage, + jpg = lg and lg.newImage, + dds = lg and lg.newImage, + ogv = lg and lg.newVideo, + glsl = lg and lg.newShader, + mp3 = la and makeSound, + ogg = la and makeSound, + wav = la and makeSound, + flac = la and makeSound, + txt = lf and lf.read, + ttf = lg and makeFont, + otf = lg and makeFont, + fnt = lg and lg.newFont +} + +cargo.processors = {} + +function cargo.init(config) + if type(config) == 'string' then + config = { dir = config } + end + + local loaders = merge({}, cargo.loaders, config.loaders) + local processors = merge({}, cargo.processors, config.processors) + + local init + + local function halp(t, k) + local path = (t._path .. '/' .. k):gsub('^/+', '') + local fileInfo = lf.getInfo(path, 'directory') + if fileInfo then + rawset(t, k, init(path)) + return t[k] + else + for extension, loader in pairs(loaders) do + local file = path .. '.' .. extension + local fileInfo = lf.getInfo(file) + if loader and fileInfo then + local asset = loader(file) + rawset(t, k, asset) + for pattern, processor in pairs(processors) do + if file:match(pattern) then + processor(asset, file, t) + end + end + return asset + end + end + end + + return rawget(t, k) + end + + local function __call(t, recurse) + for i, f in ipairs(love.filesystem.getDirectoryItems(t._path)) do + local key = f:gsub('%..-$', '') + halp(t, key) + + if recurse and love.filesystem.getInfo(t._path .. '/' .. f, 'directory') then + t[key](recurse) + end + end + + return t + end + + init = function(path) + return setmetatable({ _path = path }, { __index = halp, __call = __call }) + end + + return init(config.dir) +end + +return cargo diff --git a/deps/peachy.lua b/deps/peachy.lua new file mode 100644 index 0000000..8b2c536 --- /dev/null +++ b/deps/peachy.lua @@ -0,0 +1,325 @@ +--- A parser/renderer for Aseprite animations in LÖVE. +-- @classmod peachy + +local peachy = { + _VERSION = "1.0.0-alpha", + _DESCRIPTION = "A parser/renderer for Aseprite animations in LÖVE.", + _URL = "https://github.com/josh-perry/peachy", + _LICENSE = [[ + MIT License + + Copyright (c) 2018 Josh Perry + + 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 PATH = select('1', ...):match(".+%.") or "" +local json = require(PATH.."/lib/json") +local cron = require(PATH.."/lib/cron") + +peachy.__index = peachy + +--- Creates a new Peachy animation object. + -- + -- If imageData isn't specified then Peachy will attempt to load it using the + -- filename from the JSON data. + -- + -- If no initial tag is set then the object will be paused (i.e. not displayed) with no tag. + -- The animation will start playing immediately once created. + -- + -- @usage + -- -- Load the image ourselves and set animation tag to "Spin". + -- -- Will start playing immediately. + -- spinner = peachy.new("spinner.json", love.graphics.newImage("spinner.png"), "Spin") + -- + -- @tparam string dataFile a path to an Aseprite JSON file. It is also possible to pass a predecoded table, + -- which is useful for performance when creating large amounts of the same animation. + -- @tparam Image imageData a LÖVE image to animate. + -- @tparam string initialTag the name of the animation tag to use initially. + -- @return the new Peachy object. + function peachy.new(dataFile, imageData, initialTag) + assert(dataFile ~= nil, "No JSON data!") + + local self = setmetatable({}, peachy) + + -- check if datafile is a lua table (i.e. pre decoded) + if type(dataFile) == 'table' then + self._jsonData = dataFile + else + -- Read the data + self._jsonData = json.decode(love.filesystem.read(dataFile)) + end + + -- Load the image + self.image = imageData or love.graphics.newImage(self._jsonData.meta.image) + + self:_checkImageSize() + + self:_initializeFrames() + self:_initializeTags() + + self.paused = true + + self.tag = nil + self.tagName = nil + self.direction = nil + + if initialTag then + self:setTag(initialTag) + self.paused = false + end + + return self +end + +--- Switch to a different animation tag. +-- In the case that we're attempting to switch to the animation currently playing, +-- nothing will happen. +-- +-- @tparam string tag the animation tag name to switch to. +function peachy:setTag(tag) + assert(tag, "No animation tag specified!") + assert(self.frameTags[tag], "Tag "..tag.." not found in frametags!") + + if self.tag == self.frameTags[tag] then + return + end + + self.tagName = tag + self.tag = self.frameTags[self.tagName] + self.frameIndex = nil + self.direction = self.tag.direction + + if self.direction == "pingpong" then + self.direction = "forward" + end + + self:nextFrame() +end + +--- Jump to a particular frame index (1-based indexes) in the current animation. +-- +-- Errors if the frame is outside the tag's frame range. +-- +-- @usage +-- -- Go to the 4th frame +-- sound:setFrame(4) +-- +-- @tparam number frame the frame index to jump to. +function peachy:setFrame(frame) + if frame < 1 or frame > #self.tag.frames then + error("Frame "..frame.." is out of range of tag '"..self.tagName.."' (1.."..#self.tag.frames..")") + end + + self.frameIndex = frame + + self.frame = self.tag.frames[self.frameIndex] + self.frameTimer = cron.after(self.frame.duration, self.nextFrame, self) +end + +--- Draw the animation's current frame in a specified location. +-- @tparam number x the x position. +-- @tparam number y the y position. +-- @tparam number rot the rotation to draw at. +-- @tparam number sx the x scaling. +-- @tparam number sy the y scaling. +-- @tparam number ox the origin offset x. +-- @tparam number oy the origin offset y. +function peachy:draw(x, y, rot, sx, sy, ox, oy) + if not self.frame then + return + end + + love.graphics.draw(self.image, self.frame.quad, x, y, rot or 0, sx or 1, sy or 1, ox or 0, oy or 0) +end + +--- Update the animation. +-- @tparam number dt frame delta. Should be called from love.update and given the dt. +function peachy:update(dt) + assert(dt, "No dt passed into update!") + + if self.paused then + return + end + + -- If we're trying to play an animation and it's nil or hasn't been set up + -- properly then error + assert(self.tag, "No animation tag has been set!") + assert(self.frameTimer, "Frame timer hasn't been initialized!") + + -- Update timer in milliseconds since that's how Aseprite stores durations + self.frameTimer:update(dt * 1000) +end + +--- Move to the next frame. +-- Internal: unless you want to skip frames, this generally will not ever +-- need to be called manually. +function peachy:nextFrame() + local forward = self.direction == "forward" + + if forward then + self.frameIndex = (self.frameIndex or 0) + 1 + else + self.frameIndex = (self.frameIndex or #self.tag.frames + 1) - 1 + end + + -- Looping + if forward and self.frameIndex > #self.tag.frames then + if self.tag.direction == "pingpong" then + self:_pingpongBounce() + else + self.frameIndex = 1 + end + self:call_onLoop() + elseif not forward and self.frameIndex < 1 then + if self.tag.direction == "pingpong" then + self:_pingpongBounce() + else + self.frameIndex = #self.tag.frames + self:call_onLoop() + end + end + + -- Get next frame + self.frame = self.tag.frames[self.frameIndex] + + self.frameTimer = cron.after(self.frame.duration, self.nextFrame, self) +end + +--- Check for callbacks +function peachy:call_onLoop() + if self.callback_onLoop then self.callback_onLoop(unpack(self.args_onLoop)) end +end + +--- Pauses the animation. +function peachy:pause() + self.paused = true +end + +--- Unpauses the animation. +function peachy:play() + self.paused = false +end + +--- Stops the animation (pause it then return to first frame or last if specified) +function peachy:stop(onLast) + local index = 1 + self.paused = true + if onLast then index = #self.tag.frames end + self:setFrame(index) +end + +--- Adds a callback function that will be called when the animation loops +function peachy:onLoop(fn, ...) + self.callback_onLoop = fn + self.args_onLoop = {...} +end + +--- Toggle between playing/paused. +function peachy:togglePlay() + if self.paused then + self:play() + else + self:pause() + end +end + +--- Provides width stored in the metadata of a current frame +function peachy:getWidth() + return self._jsonData.frames[self.frameIndex].frame.w +end + +--- Provides height stored in the metadata of a current frame +function peachy:getHeight() + return self._jsonData.frames[self.frameIndex].frame.h +end + +--- Internal: handles the ping-pong animation type. +-- +-- Should only be called when we actually want to bounce. +-- Swaps the direction. +function peachy:_pingpongBounce() + -- We need to increment/decrement frame index by 2 because + -- at this point we've already gone to the next frame + if self.direction == "forward" then + self.direction = "reverse" + self.frameIndex = self.frameIndex - 2 + else + self.direction = "forward" + self.frameIndex = self.frameIndex + 2 + end +end + +--- Internal: loads all of the frames +-- +-- Loads quads and frame duration data from the JSON. +-- +-- Called from peachy.new +function peachy:_initializeFrames() + assert(self._jsonData ~= nil, "No JSON data!") + assert(self._jsonData.meta ~= nil, "No metadata in JSON!") + assert(self._jsonData.frames ~= nil, "No frame data in JSON!") + + -- Initialize all the quads + self.frames = {} + for _, frameData in ipairs(self._jsonData.frames) do + local frame = {} + + local fd = frameData.frame + frame.quad = love.graphics.newQuad(fd.x, fd.y, fd.w, fd.h, self._jsonData.meta.size.w, self._jsonData.meta.size.h) + frame.duration = frameData.duration + + table.insert(self.frames, frame) + end +end + +--- Internal: loads all of the animation tags +-- +-- Called from peachy.new +function peachy:_initializeTags() + assert(self._jsonData ~= nil, "No JSON data!") + assert(self._jsonData.meta ~= nil, "No metadata in JSON!") + assert(self._jsonData.meta.frameTags ~= nil, "No frame tags in JSON! Make sure you exported them in Aseprite!") + + self.frameTags = {} + + for _, frameTag in ipairs(self._jsonData.meta.frameTags) do + local ft = {} + ft.direction = frameTag.direction + ft.frames = {} + + for frame = frameTag.from + 1, frameTag.to + 1 do + table.insert(ft.frames, self.frames[frame]) + end + + self.frameTags[frameTag.name] = ft + end +end + +--- Internal: checks that the loaded image size matches the metadata +-- +-- Called from peachy.new +function peachy:_checkImageSize() + local imageWidth, imageHeight = self._jsonData.meta.size.w, self._jsonData.meta.size.h + assert(imageWidth == self.image:getWidth(), "Image width metadata doesn't match actual width of file") + assert(imageHeight == self.image:getHeight(), "Image height metadata doesn't match actual height of file") +end + +return peachy diff --git a/deps/suit/README.md b/deps/suit/README.md new file mode 100644 index 0000000..80b3b48 --- /dev/null +++ b/deps/suit/README.md @@ -0,0 +1,77 @@ +# SUIT + +Simple User Interface Toolkit for LÖVE. + +SUIT is an immediate mode GUI library. + +## Documentation? + +Over at [readthedocs](http://suit.readthedocs.org/en/latest/). + +## Looks? + +Here is how SUIT looks like with the default theme: + +![Demo of all widgets](docs/_static/demo.gif) + +More info and code is over at [readthedocs](http://suit.readthedocs.org/en/latest/). + +## Hello, World! + +```lua +-- suit up +local suit = require 'suit' + +-- storage for text input +local input = {text = ""} + +-- make love use font which support CJK text +function love.load() + local font = love.graphics.newFont("NotoSansHans-Regular.otf", 20) + love.graphics.setFont(font) +end + +-- all the UI is defined in love.update or functions that are called from here +function love.update(dt) + -- put the layout origin at position (100,100) + -- the layout will grow down and to the right from this point + suit.layout:reset(100,100) + + -- put an input widget at the layout origin, with a cell size of 200 by 30 pixels + suit.Input(input, suit.layout:row(200,30)) + + -- put a label that displays the text below the first cell + -- the cell size is the same as the last one (200x30 px) + -- the label text will be aligned to the left + suit.Label("Hello, "..input.text, {align = "left"}, suit.layout:row()) + + -- put an empty cell that has the same size as the last cell (200x30 px) + suit.layout:row() + + -- put a button of size 200x30 px in the cell below + -- if the button is pressed, quit the game + if suit.Button("Close", suit.layout:row()).hit then + love.event.quit() + end +end + +function love.draw() + -- draw the gui + suit.draw() +end + +function love.textedited(text, start, length) + -- for IME input + suit.textedited(text, start, length) +end + +function love.textinput(t) + -- forward text input to SUIT + suit.textinput(t) +end + +function love.keypressed(key) + -- forward keypresses to SUIT + suit.keypressed(key) +end +``` diff --git a/deps/suit/button.lua b/deps/suit/button.lua new file mode 100644 index 0000000..c063ffa --- /dev/null +++ b/deps/suit/button.lua @@ -0,0 +1,23 @@ +-- This file is part of SUIT, copyright (c) 2016 Matthias Richter + +local BASE = (...):match('(.-)[^%.]+$') + +return function(core, text, ...) + local opt, x,y,w,h = core.getOptionsAndSize(...) + opt.id = opt.id or text + opt.font = opt.font or love.graphics.getFont() + + w = w or opt.font:getWidth(text) + 4 + h = h or opt.font:getHeight() + 4 + + opt.state = core:registerHitbox(opt.id, x,y,w,h) + core:registerDraw(opt.draw or core.theme.Button, text, opt, x,y,w,h) + + return { + id = opt.id, + hit = core:mouseReleasedOn(opt.id), + hovered = core:isHovered(opt.id), + entered = core:isHovered(opt.id) and not core:wasHovered(opt.id), + left = not core:isHovered(opt.id) and core:wasHovered(opt.id) + } +end diff --git a/deps/suit/checkbox.lua b/deps/suit/checkbox.lua new file mode 100644 index 0000000..5244c4f --- /dev/null +++ b/deps/suit/checkbox.lua @@ -0,0 +1,27 @@ +-- This file is part of SUIT, copyright (c) 2016 Matthias Richter + +local BASE = (...):match('(.-)[^%.]+$') + +return function(core, checkbox, ...) + local opt, x,y,w,h = core.getOptionsAndSize(...) + opt.id = opt.id or checkbox + opt.font = opt.font or love.graphics.getFont() + + w = w or (opt.font:getWidth(checkbox.text) + opt.font:getHeight() + 4) + h = h or opt.font:getHeight() + 4 + + opt.state = core:registerHitbox(opt.id, x,y,w,h) + local hit = core:mouseReleasedOn(opt.id) + if hit then + checkbox.checked = not checkbox.checked + end + core:registerDraw(opt.draw or core.theme.Checkbox, checkbox, opt, x,y,w,h) + + return { + id = opt.id, + hit = hit, + hovered = core:isHovered(opt.id), + entered = core:isHovered(opt.id) and not core:wasHovered(opt.id), + left = not core:isHovered(opt.id) and core:wasHovered(opt.id) + } +end diff --git a/deps/suit/core.lua b/deps/suit/core.lua new file mode 100644 index 0000000..716aa6c --- /dev/null +++ b/deps/suit/core.lua @@ -0,0 +1,217 @@ +-- This file is part of SUIT, copyright (c) 2016 Matthias Richter + +local NONE = {} +local BASE = (...):match('(.-)[^%.]+$') +local default_theme = require(BASE..'theme') + +local suit = {} +suit.__index = suit + +function suit.new(theme) + return setmetatable({ + -- TODO: deep copy/copy on write? better to let user handle => documentation? + theme = theme or default_theme, + mouse_x = 0, mouse_y = 0, + mouse_button_down = false, + candidate_text = {text="", start=0, length=0}, + + draw_queue = {n = 0}, + + Button = require(BASE.."button"), + ImageButton = require(BASE.."imagebutton"), + Label = require(BASE.."label"), + Checkbox = require(BASE.."checkbox"), + Input = require(BASE.."input"), + Slider = require(BASE.."slider"), + + layout = require(BASE.."layout").new(), + }, suit) +end + +-- helper +function suit.getOptionsAndSize(opt, ...) + if type(opt) == "table" then + return opt, ... + end + return {}, opt, ... +end + +-- gui state +function suit:setHovered(id) + return self.hovered ~= id +end + +function suit:anyHovered() + return self.hovered ~= nil +end + +function suit:isHovered(id) + return id == self.hovered +end + +function suit:wasHovered(id) + return id == self.hovered_last +end + +function suit:setActive(id) + return self.active ~= nil +end + +function suit:anyActive() + return self.active ~= nil +end + +function suit:isActive(id) + return id == self.active +end + + +function suit:setHit(id) + self.hit = id + -- simulate mouse release on button -- see suit:mouseReleasedOn() + self.mouse_button_down = false + self.active = id + self.hovered = id +end + +function suit:anyHit() + return self.hit ~= nil +end + +function suit:isHit(id) + return id == self.hit +end + +function suit:getStateName(id) + if self:isActive(id) then + return "active" + elseif self:isHovered(id) then + return "hovered" + elseif self:isHit(id) then + return "hit" + end + return "normal" +end + +-- mouse handling +function suit:mouseInRect(x,y,w,h) + return self.mouse_x >= x and self.mouse_y >= y and + self.mouse_x <= x+w and self.mouse_y <= y+h +end + +function suit:registerMouseHit(id, ul_x, ul_y, hit) + if not self.hovered and hit(self.mouse_x - ul_x, self.mouse_y - ul_y) then + self.hovered = id + if self.active == nil and self.mouse_button_down then + self.active = id + end + end + return self:getStateName(id) +end + +function suit:registerHitbox(id, x,y,w,h) + return self:registerMouseHit(id, x,y, function(x,y) + return x >= 0 and x <= w and y >= 0 and y <= h + end) +end + +function suit:mouseReleasedOn(id) + if not self.mouse_button_down and self:isActive(id) and self:isHovered(id) then + self.hit = id + return true + end + return false +end + +function suit:updateMouse(x, y, button_down) + self.mouse_x, self.mouse_y = x,y + if button_down ~= nil then + self.mouse_button_down = button_down + end +end + +function suit:getMousePosition() + return self.mouse_x, self.mouse_y +end + +-- keyboard handling +function suit:getPressedKey() + return self.key_down, self.textchar +end + +function suit:keypressed(key) + self.key_down = key +end + +function suit:textinput(char) + self.textchar = char +end + +function suit:textedited(text, start, length) + self.candidate_text.text = text + self.candidate_text.start = start + self.candidate_text.length = length +end + +function suit:grabKeyboardFocus(id) + if self:isActive(id) then + if love.system.getOS() == "Android" or love.system.getOS() == "iOS" then + if id == NONE then + love.keyboard.setTextInput( false ) + else + love.keyboard.setTextInput( true ) + end + end + self.keyboardFocus = id + end + return self:hasKeyboardFocus(id) +end + +function suit:hasKeyboardFocus(id) + return self.keyboardFocus == id +end + +function suit:keyPressedOn(id, key) + return self:hasKeyboardFocus(id) and self.key_down == key +end + +-- state update +function suit:enterFrame() + if not self.mouse_button_down then + self.active = nil + elseif self.active == nil then + self.active = NONE + end + + self.hovered_last, self.hovered = self.hovered, nil + self:updateMouse(love.mouse.getX(), love.mouse.getY(), love.mouse.isDown(1)) + self.key_down, self.textchar = nil, "" + self:grabKeyboardFocus(NONE) + self.hit = nil +end + +function suit:exitFrame() +end + +-- draw +function suit:registerDraw(f, ...) + local args = {...} + local nargs = select('#', ...) + self.draw_queue.n = self.draw_queue.n + 1 + self.draw_queue[self.draw_queue.n] = function() + f(unpack(args, 1, nargs)) + end +end + +function suit:draw() + self:exitFrame() + love.graphics.push('all') + for i = self.draw_queue.n,1,-1 do + self.draw_queue[i]() + end + love.graphics.pop() + self.draw_queue.n = 0 + self:enterFrame() +end + +return suit diff --git a/deps/suit/docs/Makefile b/deps/suit/docs/Makefile new file mode 100644 index 0000000..2ce17ef --- /dev/null +++ b/deps/suit/docs/Makefile @@ -0,0 +1,192 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/hump.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/hump.qhc" + +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/hump" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/hump" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/deps/suit/docs/_static/demo.gif b/deps/suit/docs/_static/demo.gif new file mode 100644 index 0000000..d3f69e9 Binary files /dev/null and b/deps/suit/docs/_static/demo.gif differ diff --git a/deps/suit/docs/_static/different-ids.gif b/deps/suit/docs/_static/different-ids.gif new file mode 100644 index 0000000..bb53636 Binary files /dev/null and b/deps/suit/docs/_static/different-ids.gif differ diff --git a/deps/suit/docs/_static/hello-world.gif b/deps/suit/docs/_static/hello-world.gif new file mode 100644 index 0000000..dcee926 Binary files /dev/null and b/deps/suit/docs/_static/hello-world.gif differ diff --git a/deps/suit/docs/_static/keyboard.gif b/deps/suit/docs/_static/keyboard.gif new file mode 100644 index 0000000..b80c986 Binary files /dev/null and b/deps/suit/docs/_static/keyboard.gif differ diff --git a/deps/suit/docs/_static/layout.gif b/deps/suit/docs/_static/layout.gif new file mode 100644 index 0000000..e62f953 Binary files /dev/null and b/deps/suit/docs/_static/layout.gif differ diff --git a/deps/suit/docs/_static/mutable-state.gif b/deps/suit/docs/_static/mutable-state.gif new file mode 100644 index 0000000..5583e80 Binary files /dev/null and b/deps/suit/docs/_static/mutable-state.gif differ diff --git a/deps/suit/docs/_static/options.gif b/deps/suit/docs/_static/options.gif new file mode 100644 index 0000000..e39a018 Binary files /dev/null and b/deps/suit/docs/_static/options.gif differ diff --git a/deps/suit/docs/_static/same-ids.gif b/deps/suit/docs/_static/same-ids.gif new file mode 100644 index 0000000..9dab1fe Binary files /dev/null and b/deps/suit/docs/_static/same-ids.gif differ diff --git a/deps/suit/docs/conf.py b/deps/suit/docs/conf.py new file mode 100644 index 0000000..4692884 --- /dev/null +++ b/deps/suit/docs/conf.py @@ -0,0 +1,293 @@ +# -*- coding: utf-8 -*- +# +# SUIT documentation build configuration file, created by +# sphinx-quickstart on Sat Oct 10 13:10:12 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import shlex + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.mathjax', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'SUIT' +copyright = u'2016, Matthias Richter' +author = u'Matthias Richter' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.0' +# The full version, including alpha/beta/rc tags. +release = '1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'suitdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'suit.tex', u'SUIT Documentation', + u'Matthias Richter', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'SUIT', u'SUIT Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'SUIT', u'SUIT Documentation', + author, 'SUIT', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + +primary_domain = "js" +highlight_language = "lua" + +import sphinx_rtd_theme +html_theme = 'sphinx_rtd_theme' +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] diff --git a/deps/suit/docs/core.rst b/deps/suit/docs/core.rst new file mode 100644 index 0000000..731c95e --- /dev/null +++ b/deps/suit/docs/core.rst @@ -0,0 +1,241 @@ +Core Functions +============== + +The core functions can be divided into two parts: Functions of interest to the +user and functions of interest to the (widget) developer. + +External Interface +------------------ + +Drawing +^^^^^^^ + +.. function:: draw() + +Draw the GUI - call in ``love.draw``. + +.. data:: theme + +The current theme. See :doc:`themes`. + + +Mouse Input +^^^^^^^^^^^ + +.. function:: updateMouse(x,y, buttonDown) + + :param number x,y: Position of the mouse. + :param boolean buttonDown: Whether the mouse button is down. + +Update mouse position and button status. You do not need to call this function, +unless you use some screen transformation (e.g., scaling, camera systems, ...). + +Keyboard Input +^^^^^^^^^^^^^^ + +.. function:: keypressed(key) + + :param KeyConstant key: The pressed key. + +Forwards a ``love.keypressed(key)`` event to SUIT. + +.. function:: textinput(char) + + :param string char: The pressed character + +Forwards a ``love.textinput(key)`` event to SUIT. + + +GUI State +^^^^^^^^^ + +.. function:: anyHovered() + + :returns: ``true`` if any widget is hovered by the mouse. + +Checks if any widget is hovered by the mouse. + +.. function:: isHovered(id) + + :param mixed id: Identifier of the widget. + :returns: ``true`` if the widget is hovered by the mouse. + +Checks if the widget identified by ``id`` is hovered by the mouse. + +.. function:: wasHovered(id) + + :param mixed id: Identifier of the widget. + :returns: ``true`` if the widget was in the hovered by the mouse in the last frame. + +Checks if the widget identified by ``id`` was hovered by the mouse in the last frame. + +.. function:: anyActive() + + :returns: ``true`` if any widget is in the ``active`` state. + +Checks whether the mouse button is pressed and held on any widget. + +.. function:: isActive(id) + + :param mixed id: Identifier of the widget. + :returns: ``true`` if the widget is in the ``active`` state. + +Checks whether the mouse button is pressed and held on the widget identified by ``id``. + +.. function:: anyHit() + + :returns: ``true`` if the mouse was pressed and released on any widget. + +Check whether the mouse was pressed and released on any widget. + +.. function:: isHit(id) + + :param mixed id: Identifier of the widget. + :returns: ``true`` if the mouse was pressed and released on the widget. + +Check whether the mouse was pressed and released on the widget identified by ``id``. + + +Internal Helpers +---------------- + +.. function:: getOptionsAndSize(...) + + :param mixed ...: Varargs. + :returns: ``options, x,y,w,h``. + +Converts varargs to option table and size definition. Used in the widget +functions. + +.. function:: registerDraw(f, ...) + + :param function f: Function to call in ``draw()``. + :param mixed ...: Arguments to f. + +Registers a function to be executed during :func:`draw()`. Used by widgets to +make themselves visible. + +.. function:: enterFrame() + +Prepares GUI state when entering a frame. + +.. function:: exitFrame() + +Clears GUI state when exiting a frame. + + +Mouse Input +^^^^^^^^^^^ + +.. function:: mouseInRect(x,y,w,h) + + :param numbers x,y,w,h: Rectangle definition. + :returns: ``true`` if the mouse cursor is in the rectangle. + +Checks whether the mouse cursor is in the rectangle defined by ``x,y,w,h``. + +.. function:: registerMouseHit(id, ul_x, ul_y, hit) + + :param mixed id: Identifier of the widget. + :param numbers ul_x, ul_y: Upper left corner of the widget. + :param function hit: Function to perform the hit test. + +Registers a hit-test defined by the function ``hit`` for the widget identified +by ``id``. Sets the widget to ``hovered`` if th hit-test returns ``true``. Sets the +widget to ``active`` if the hit-test returns ``true`` and the mouse button is +pressed. + +The hit test receives coordinates in the coordinate system of the widget, i.e. +``(0,0)`` is the upper left corner of the widget. + +.. function:: registerHitbox(id, x,y,w,h) + + :param mixed id: Identifier of the widget. + :param numbers x,y,w,h: Rectangle definition. + +Registers a hitbox for the widget identified by ``id``. Literally this function:: + + function registerHitbox(id, x,y,w,h) + return registerMouseHit(id, x,y, function(u,v) + return u >= 0 and u <= w and v >= 0 and v <= h + end) + end + +.. function:: mouseReleasedOn(id) + + :param mixed id: Identifier of the widget. + :returns: ``true`` if the mouse was released on the widget. + +Checks whether the mouse button was released on the widget identified by ``id``. + +.. function:: getMousePosition() + + :returns: Mouse positon ``mx, my``. + +Get the mouse position. + +Keyboard Input +^^^^^^^^^^^^^^ + +.. function:: getPressedKey() + + :returns: KeyConstant + +Get the currently pressed key (if any). + +.. function:: grabKeyboardFocus(id) + + :param mixed id: Identifier of the widget. + +Try to grab keyboard focus. Successful only if the widget is in the ``active`` +state. + +.. function:: hasKeyboardFocus(id) + + :param mixed id: Identifier of the widget. + :returns: ``true`` if the widget has keyboard focus. + +Checks whether the widget identified by ``id`` currently has keyboard focus. + +.. function:: keyPressedOn(id, key) + + :param mixed id: Identifier of the widget. + :param KeyConstant key: Key to query. + :returns: ``true`` if ``key`` was pressed on the widget. + +Checks whether the key ``key`` was pressed while the widget identified by +``id`` has keyboard focus. + + +Instancing +---------- + +.. function:: new() + + :returns: Separate UI state. + +Create a separate UI and layout state. Everything that happens in the new +state will not affect any other state. You can use the new state like the +"global" state ``suit``, but call functions with the colon syntax instead of +the dot syntax, e.g.:: + + function love.load() + dress = suit.new() + end + + function love.update() + dress.layout:reset() + dress:Label("Hello, World!", dress.layout:row(200,30)) + dress:Input(input, dress.layout:row()) + end + + function love.draw() + dress:draw() + end + +.. warning:: + + Unlike UI and layout state, the theme might be shared with other states. + Changes in a shared theme will be shared across all themes. + See the :ref:`Instance Theme ` subsection in the + :doc:`gettingstarted` guide. diff --git a/deps/suit/docs/gettingstarted.rst b/deps/suit/docs/gettingstarted.rst new file mode 100644 index 0000000..3f0e560 --- /dev/null +++ b/deps/suit/docs/gettingstarted.rst @@ -0,0 +1,398 @@ +Getting Started +=============== + +Before actually getting started, it is important to understand the motivation +and mechanics behind SUIT: + +- **Immediate mode is better than retained mode** +- **Layout should not care about content** +- **Less is more** + +Immediate mode? +--------------- + +With classical (retained) mode libraries you typically have a stage where you +create the whole UI when the program initializes. This includes what happens +when events like button presses or slider changes occur. After that point, the +GUI is expected to not change very much. This is great for word processors +where the interaction is consistent and straightforward, but bad for games, +where everything changes all the time. + +With immediate mode libraries, on the other hand, the GUI is created every +frame from scratch. Because that would be wasteful, there are no widget +objects. Instead, widgets are created by functions that react to UI state and +present some data. Where this data comes from and how it is maintained does +not concern the widget at all. This is, after all, your job. This gives great +control over what is shown where and when. The widget code can be right next +to the code that does what should happen if the widget state changes. The +layout is also very flexible: adding a widget is one more function call, and if +you want to hide a widget, you simply don't call the corresponding function. + +This separation of data and behaviour is great when a lot of stuff is going on, +but takes a bit of time getting used to. + + +What SUIT is +^^^^^^^^^^^^ + +SUIT is simple: It provides only a few basic widgets that are important for +games: + +- :func:`Buttons