added some dependencies

This commit is contained in:
2021-05-08 10:08:47 +02:00
parent 5e40214c67
commit e05d4812dc
32 changed files with 3565 additions and 0 deletions

105
deps/cargo.lua vendored Normal file
View File

@@ -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

325
deps/peachy.lua vendored Normal file
View File

@@ -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

77
deps/suit/README.md vendored Normal file
View File

@@ -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
```

23
deps/suit/button.lua vendored Normal file
View File

@@ -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

27
deps/suit/checkbox.lua vendored Normal file
View File

@@ -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

217
deps/suit/core.lua vendored Normal file
View File

@@ -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

192
deps/suit/docs/Makefile vendored Normal file
View File

@@ -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 <target>' where <target> 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."

BIN
deps/suit/docs/_static/demo.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
deps/suit/docs/_static/different-ids.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

BIN
deps/suit/docs/_static/hello-world.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
deps/suit/docs/_static/keyboard.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
deps/suit/docs/_static/layout.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
deps/suit/docs/_static/mutable-state.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
deps/suit/docs/_static/options.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
deps/suit/docs/_static/same-ids.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

293
deps/suit/docs/conf.py vendored Normal file
View File

@@ -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
# "<project> v<release> 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 <link> 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()]

241
deps/suit/docs/core.rst vendored Normal file
View File

@@ -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 <instance-theme>` subsection in the
:doc:`gettingstarted` guide.

398
deps/suit/docs/gettingstarted.rst vendored Normal file
View File

@@ -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 <Button>` (including :func:`Image Buttons <ImageButton>`)
- :func:`Text Labels <Label>`
- :func:`Checkboxes <Checkbox>`
- :func:`Text Input <Input>`
- :func:`Value Sliders <Slider>`
SUIT is comfortable: It has a straightforward, yet effective row/column-based
layout engine.
SUIT is adaptable: It is possible to change the color scheme, single drawing
functions for all widget or the whole theme.
SUIT is hackable: Custom widgets can leverage the extensive :doc:`core library
<core>`.
**SUIT is good at games!**
What SUIT is not
^^^^^^^^^^^^^^^^
SUIT is not a complete GUI library: It does not provide dropdowns, table views,
menu bars, modal dialogs, etc.
SUIT is not a complete GUI library: It does not have a markup language or tools
to design a user interface.
SUIT is not a complete GUI library: It does not take control of the runtime.
You have to do everything yourself [1]_.
**SUIT is not good at processing words!**
Hello, World!
-------------
SUITing up is is straightforward: Define your GUI in ``love.update()``, and
draw it in ``love.draw()``::
suit = require 'suit'
local show_message = false
function love.update(dt)
-- Put a button on the screen. If hit, show a message.
if suit.Button("Hello, World!", 100,100, 300,30).hit then
show_message = true
end
-- if the button was pressed at least one time, but a label below
if show_message then
suit.Label("How are you today?", 100,150, 300,30)
end
end
function love.draw()
suit.draw()
end
This will produce this UI:
.. image:: _static/hello-world.gif
The two widgets (the button and the label) are each created by a function call
(:func:`suit.Button <Button>` and :func:`suit.Label <Label>`). The first
argument to a widget function always defines the *payload* of the widget.
Different widgets expect different payloads.
Here, both :func:`suit.Button <Button>` and :func:`suit.Label <Label>` expect a
string.
The last four arguments of a widget function define the position and dimension
of the widget.
The function returns a table that indicates the UI state of the widget.
Here, the state ``hit`` is used to figure out if the mouse was clicked and
released on the button. See :doc:`Widgets <widgets>` for more info on widget
states.
Mutable state
-------------
Widgets that mutate some state - input boxes, checkboxes and sliders - expect
a table as their payload, e.g.::
local slider = {value = 1, min = 0, max = 2}
function love.update(dt)
suit.Slider(slider, 100,100, 200,20)
suit.Label(tostring(slider.value), 300,100, 200,20)
end
.. image:: _static/mutable-state.gif
The widget function updates the payload when some user interaction occurs. In
the above example, ``slider.value`` may be changed by the :func:`Slider`
widget. The value is then shown by a :func:`Label` next to the slider.
Options
-------
You can define optional, well, options after the payload. Most options affect
how the widget is drawn. For example, to align the label text to the left::
local slider = {value = 1, max = 2}
function love.update(dt)
suit.Slider(slider, 100,100, 200,30)
suit.Label(tostring(slider.value), {align = "left"}, 300,100, 200,30)
end
.. image:: _static/options.gif
What options are available and what they are doing depends on the widget and
the theme. See :doc:`Widgets <widgets>` for more info on widget options.
Keyboard input
--------------
The :func:`Input` widget requires that you forward the ``keypressed`` and
``textinput`` events to SUIT::
local input = {text = ""}
function love.update(dt)
suit.Input(input, 100,100,200,30)
suit.Label("Hello, "..input.text, {align="left"}, 100,150,200,30)
end
-- forward keyboard events
function love.textinput(t)
suit.textinput(t)
end
function love.keypressed(key)
suit.keypressed(key)
end
.. image:: _static/keyboard.gif
The :func:`Slider` widget can also react to keyboard input. The mouse state is
automatically updated, but you can provide your own version of reality if you
need to. See the :doc:`Core functions <core>` for more details.
Layout
------
It is tedious to calculate the position and size of each widget you want to put
on the screen. Especially when all you want is to put three buttons beneath
each other. SUIT implements a simple, yet effective layout engine. All the
engine does is put cells next to each other (below or right). It does not care
what you put into those cells, but assumes that you probably need them for
widgets. Cells are reported by four numbers (left, top, width and height) that
you can directly pass as the final four arguments to the widget functions.
If you have ever dabbled with `Qt's <http://qt.io>`_ ``QBoxLayout``, you
already know 89% [2]_ of what you need to know.
Hello, World! can be rewritten as follows::
suit = require 'suit'
local show_message = false
function love.update(dt)
-- put the layout origin at position (100,100)
-- cells will grow down and to the right of the origin
-- note the colon syntax
suit.layout:reset(100,100)
-- put 10 extra pixels between cells in each direction
suit.layout:padding(10,10)
-- construct a cell of size 300x30 px and put the button into it
if suit.Button("Hello, World!", suit.layout:row(300,30)).hit then
show_message = true
end
-- add another cell below the first cell
-- the size of the cell is the same as the first cell
if show_message then
suit.Label("How are you today?", suit.layout:row())
end
end
function love.draw()
suit.draw()
end
.. image:: _static/layout.gif
At the beginning of each frame, the layout origin (and some internal layout
state) has to be reset. You can also define optional padding between cells.
Cells are added using ``layout:row(w,h)`` (which puts the new cell below the
old cell) and ``layout:col(w,h)`` (which puts the new cell to the right of the
old cell). If omitted, the width and height of the new cell are copied from
the old cell. There are also special identifiers that calculate the size from
the sizes of all cells that were created since the last ``reset()``: ``max``,
``min`` and ``median``. They do what you expect them to do.
It is also possible to nest cells and to let cells dynamically fill the
available space (but you have to tell how much space there is beforehand).
Refer to the :doc:`Layout <layout>` documentation for more information.
Widget ids
----------
Each widget is identified by an ``id`` [4]_. Internally, this ``id`` is used to
figure out which widget should handle user input like mouse clicks and keyboard
presses.
Unless specified otherwise, the ``id`` is the same as the payload, i.e.,
the ``id`` of ``Button("Hello, World!", ...)`` will be the string
``"Hello, World!"``.
In almost all of the cases, this will work fine and you don't have to worry about
this ``id`` business.
Well, almost. Problems arise when two widgets share the same id, like here::
local suit = require 'suit'
function love.update()
suit.layout:reset(100, 100)
suit.layout:padding(10)
if suit.Button("Button", suit.layout:row(200, 30)).hit then
love.graphics.setBackgroundColor(255,255,255)
end
if suit.Button("Button", suit.layout:row()).hit then
love.graphics.setBackgroundColor(0,0,0)
end
end
function love.draw()
suit:draw()
end
.. image:: _static/same-ids.gif
If the first button is hovered, both buttons will be highlighted, and if it pressed,
both actions will be carried out.
Hovering the second button will not affect the first, and clicking it will highlight
both buttons, but only execute the action of the second button [5]_.
Luckily, there is a fix: you can specify the ``id`` of any widget using the ``id``
option, like so::
local suit = require 'suit'
function love.update()
suit.layout:reset(100, 100)
suit.layout:padding(10)
if suit.Button("Button", {id=1}, suit.layout:row(200, 30)).hit then
love.graphics.setBackgroundColor(255,255,255)
end
if suit.Button("Button", {id=2}, suit.layout:row()).hit then
love.graphics.setBackgroundColor(0,0,0)
end
end
function love.draw()
suit:draw()
end
.. image:: _static/different-ids.gif
Now, events from one button will not propagate to the other. Here, the both ``id`` s
are numbers, but you can use any Lua value except ``nil`` and ``false``.
Themeing
--------
SUIT lets you customize how any widget (except :func:`ImageButton`) is drawn.
Each widget (except, :func:`you know <ImageButton>`) is drawn by a function in
the table ``suit.theme``. Conveniently, the name of the function
responsible for drawing a widget is named after it, so, a button is drawn by
the function ``suit.theme.Button``. If you want to change how a button is
drawn, simply overwrite the function. If you want to redecorate completely, it
might be easiest to start from scratch and swap the whole table.
However, if you just don't like the colors, the default theme is open to change.
It requires you to change the background (``bg``) and foreground (``fg``) color
of three possible widget states: ``normal``, when nothing out of
the ordinary happened, ``hovered``, when the mouse hovers above a widget, and
``active``, when the mouse hovers above, and the mouse button is pressed (but
not yet released) on the widget. The colors are saved in the table
``suit.theme.color``. The default color scheme is this::
suit.theme.color = {
normal = {bg = { 66, 66, 66}, fg = {188,188,188}},
hovered = {bg = { 50,153,187}, fg = {255,255,255}},
active = {bg = {255,153, 0}, fg = {225,225,225}}
}
You can also do minimally invasive surgery::
function love.load()
suit.theme.color.normal.fg = {255,255,255}
suit.theme.color.hovered = {bg = {200,230,255}, fg = {0,0,0}}
end
GUI Instances
-------------
Sometimes you might feel the need to separate parts of the GUI. Maybe certain
UI elements should always be drawn before or after other UI elements, or maybe
you don't want the UI state to "leak" (e.g., from a stacked pause gamestate to
the main gamestate).
For this reason, SUIT allows you to create GUI instances::
local dress = suit.new()
The IO and layout state of ``dress`` is totally contained in the instance and
does not affect any other instances (including the "global" instance ``suit``).
In particular, ``suit.draw()`` will not draw anything from ``dress``. Luckily,
you can do that yourself::
dress:draw()
Notice that instances require that you use the colon syntax. This is true for
every `core <core>` function as well as the widgets. To create a button, for
example, you have to write::
dress:Button("Click?", dress.layout:row())
.. _instance-theme:
Instance Theme
^^^^^^^^^^^^^^
Unlike UI and layout state, themes **are** shared among instances. The reason
is that the ``suit.theme`` and ``dress.theme`` are **references**, and point to
the same table (unless you make either of them point somewhere else). Usually
this is a feature, but please still consider this
.. warning::
Changes in a shared theme will be shared across GUI instances.
If this is an issue---for example because you only want to change the color
scheme of an instance---you can either `deep-copy
<http://hump.readthedocs.org/en/latest/class.html#class:clone>`_ the theme
table or use some metatable magic::
dress.theme = setmetatable({}, {__index = suit.theme})
-- NOTE: you have to replace the whole color table. E.g., replacing only
-- dress.theme.color.normal will also change suit.theme.color.normal!
dress.theme.color = {
normal = {bg = {188,188,188}, fg = { 66, 66, 66}},
hovered = {bg = {255,255,255}, fg = { 50,153,187}},
active = {bg = {255,255,255}, fg = {225,153, 0}}
}
function dress.theme.Label(text, opt, x,y,w,h)
-- draw the label in a fancier way
end
.. [1] But it thinks you can handle that.
.. [2] Proportion determined by rigorous scientific experiments [3]_.
.. [3] And theoretic reasoning. Mostly that, actually.
.. [4] Welcome to the tautology club!
.. [5] Immediate mode is to blame: When the second button is processed, the first
one is already fully evaluated. Time can not be reversed, not even by love.

243
deps/suit/docs/index.rst vendored Normal file
View File

@@ -0,0 +1,243 @@
SUIT
====
Simple User Interface Toolkit for `LÖVE <http://love2d.org>`_.
SUIT up
-------
You can download SUIT and view the code on github: `vrld/SUIT
<http://github.com/vrld/SUIT>`_.
You may also download the sourcecode as a `zip
<http://github.com/vrld/SUIT/zipball/master>`_ or `tar
<http://github.com/vrld/SUIT/tarball/master>`_ file.
Otherwise, use `Git <http://git-scm.com>`_::
git clone git://github.com/vrld/SUIT
Update::
git pull
Hello, Suit::
suit = require 'suit'
local show_message = false
function love.update(dt)
-- Put a button on the screen. If hit, show a message.
if suit.Button("Hello, World!", 100,100, 300,30).hit then
show_message = true
end
-- if the button was pressed at least one time, but a label below
if show_message then
suit.Label("How are you today?", 100,150, 300,30)
end
end
function love.draw()
suit.draw()
end
Read on
-------
.. toctree::
:maxdepth: 2
Getting Started <gettingstarted>
Widgets <widgets>
Layout <layout>
Core Functions <core>
Themeing <themes>
License <license>
Example code
------------
The following code will create this UI:
.. image:: _static/demo.gif
::
local suit = require 'suit'
-- generate some assets (below)
function love.load()
snd = generateClickySound()
normal, hovered, active, mask = generateImageButton()
smallerFont = love.graphics.newFont(10)
end
-- data for a slider, an input box and a checkbox
local slider= {value = 0.5, min = -2, max = 2}
local input = {text = "Hello"}
local chk = {text = "Check?"}
-- 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)
-- cells will grown down and to the right from this point
-- also set cell padding to 20 pixels to the right and to the bottom
suit.layout:reset(100,100, 20,20)
-- put a button at the layout origin
-- the cell of the button has a size of 200 by 30 pixels
state = suit.Button("Click?", suit.layout:row(200,30))
-- if the button was entered, play a sound
if state.entered then love.audio.play(snd) end
-- if the button was pressed, take damage
if state.hit then print("Ouch!") end
-- put an input box below the button
-- the cell of the input box has the same size as the cell above
-- if the input cell is submitted, print the text
if suit.Input(input, suit.layout:row()).submitted then
print(input.text)
end
-- put a button below the input box
-- the width of the cell will be the same as above, the height will be 40 px
if suit.Button("Hover?", suit.layout:row(nil,40)).hovered then
-- if the button is hovered, show two other buttons
-- this will shift all other ui elements down
-- put a button below the previous button
-- the cell height will be 30 px
-- the label of the button will be aligned top left
suit.Button("You can see", {align='left', valign='top'}, suit.layout:row(nil,30))
-- put a button below the previous button
-- the cell size will be the same as the one above
-- the label will be aligned bottom right
suit.Button("...but you can't touch!", {align='right', valign='bottom'},
suit.layout:row())
end
-- put a checkbox below the button
-- the size will be the same as above
-- (NOTE: height depends on whether "Hover?" is hovered)
-- the label "Check?" will be aligned right
suit.Checkbox(chk, {align='right'}, suit.layout:row())
-- put a nested layout
-- the size of the cell will be as big as the cell above or as big as the
-- nested content, whichever is bigger
suit.layout:push(suit.layout:row())
-- change cell padding to 3 pixels in either direction
suit.layout:padding(3)
-- put a slider in the cell
-- the inner cell will be 160 px wide and 20 px high
suit.Slider(slider, suit.layout:col(160, 20))
-- put a label that shows the slider value to the right of the slider
-- the width of the label will be 40 px
suit.Label(("%.02f"):format(slider.value), suit.layout:col(40))
-- close the nested layout
suit.layout:pop()
-- put an image button below the nested cell
-- the size of the cell will be 200 by 100 px,
-- but the image may be bigger or smaller
-- the button shows the image `normal' when the button is inactive
-- the button shows the image `hovered` if the mouse is over an opaque pixel
-- of the ImageData `mask`
-- the button shows the image `active` if the mouse is above an opaque pixel
-- of the ImageData `mask` and the mouse button is pressed
-- if `mask` is omitted, the alpha-test will be swapped for a test whether
-- the mouse is in the area occupied by the widget
suit.ImageButton(normal, {mask = mask, hovered = hovered, active = active}, suit.layout:row(200,50))
-- if the checkbox is checked, display a precomputed layout
if chk.checked then
-- the precomputed layout will be 3 rows below each other
-- the origin of the layout will be at (400,100)
-- the minimal height of the layout will be 300 px
rows = suit.layout:rows{pos = {400,100}, min_height = 300,
{200, 30}, -- the first cell will measure 200 by 30 px
{30, 'fill'}, -- the second cell will be 30 px wide and fill the
-- remaining vertical space between the other cells
{200, 30}, -- the third cell will be 200 by 30 px
}
-- the first cell will contain a witty label
-- the label will be aligned left
-- the font of the label will be smaller than the usual font
suit.Label("You uncovered the secret!", {align="left", font = smallerFont},
rows.cell(1))
-- the third cell will contain a label that shows the value of the slider
suit.Label(slider.value, {align='left'}, rows.cell(3))
-- the second cell will show a slider
-- the slider will operate on the same data as the first slider
-- the slider will be vertical instead of horizontal
-- the id of the slider will be 'slider two'. this is necessary, because
-- the two sliders should not both react to UI events
suit.Slider(slider, {vertical = true, id = 'slider two'}, rows.cell(2))
end
end
function love.draw()
-- draw the gui
suit.draw()
end
function love.textinput(t)
-- forward text input to SUIT
suit.textinput(t)
end
function love.keypressed(key)
-- forward keypressed to SUIT
suit.keypressed(key)
end
-- generate assets (see love.load)
function generateClickySound()
local snd = love.sound.newSoundData(512, 44100, 16, 1)
for i = 0,snd:getSampleCount()-1 do
local t = i / 44100
local s = i / snd:getSampleCount()
snd:setSample(i, (.7*(2*love.math.random()-1) + .3*math.sin(t*9000*math.pi)) * (1-s)^1.2 * .3)
end
return love.audio.newSource(snd)
end
function generateImageButton()
local metaballs = function(t, r,g,b)
return function(x,y)
local px, py = 2*(x/200-.5), 2*(y/100-.5)
local d1 = math.exp(-((px-.6)^2 + (py-.1)^2))
local d2 = math.exp(-((px+.7)^2 + (py+.1)^2) * 2)
local d = (d1 + d2)/2
if d > t then
return r,g,b, ((d-t) / (1-t))^.2
end
return 0,0,0,0
end
end
local normal, hovered, active = love.image.newImageData(200,100), love.image.newImageData(200,100), love.image.newImageData(200,100)
normal:mapPixel(metaballs(.48, .74,.74,.74))
hovered:mapPixel(metaballs(.46, .2,.6,.6))
active:mapPixel(metaballs(.43, 1,.6,0))
return love.graphics.newImage(normal), love.graphics.newImage(hovered), love.graphics.newImage(active), normal
end
Indices and tables
------------------
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

292
deps/suit/docs/layout.rst vendored Normal file
View File

@@ -0,0 +1,292 @@
Layout
======
.. note::
Still under construction...
Immediate Mode Layouts
----------------------
.. function:: reset([x,y, [pad_x, [pad_y]]])
:param numbers x,y: Origin of the layout (optional).
:param pad_x,pad_y: Cell padding (optional).
Reset the layout, puts the origin at ``(x,y)`` and sets the cell padding to
``pad_x`` and ``pad_y``.
If ``x`` and ``y`` are omitted, they default to ``(0,0)``. If ``pad_x`` is
omitted, it defaults to 0. If ``pad_y`` is omitted, it defaults to ``pad_x``.
.. function:: padding([pad_x, [pad_y]])
:param pad_x: Cell padding in x direction (optional).
:param pad_y: Cell padding in y direction (optional, defaults to ``pad_x``).
:returns: The current (or new) cell padding.
Get and set the current cell padding.
If given, sets the cell padding to ``pad_x`` and ``pad_y``.
If only ``pad_x`` is given, set both padding in ``x`` and ``y`` direction to ``pad_x``.
.. function:: size()
:returns: ``width,height`` - The size of the last cell.
Get the size of the last cell.
.. function:: nextRow()
:returns: ``x,y`` - Upper left corner of the next row cell.
Get the position of the upper left corner of the next cell in a row layout.
Use for mixing precomputed and immediate mode layouts.
.. function:: nextCol()
:returns: ``x,y`` - Upper left corner of the next column cell.
Get the position of the upper left corner of the next cell in a column layout.
Use for mixing precomputed and immediate mode layouts.
.. function:: push([x,y])
:param numbers x,y: Origin of the layout (optional).
Saves the layout state (position, padding, sizes, etc.) on a stack, resets the
layout with position ``(x,y)``.
If ``x`` and ``y`` are omitted, they default to ``(0,0)``.
Used for nested row/column layouts.
.. function:: pop()
Restores the layout parameters from the stack and advances the layout position
according to the size of the popped layout.
Used for nested row/column layouts.
.. _layout-row:
.. function:: row(w,h)
:param mixed w,h: Cell width and height (optional).
:returns: Position and size of the cell: ``x,y,w,h``.
Creates a new cell below the current cell with width ``w`` and height ``h``. If
either ``w`` or ``h`` is omitted, the value is set the last used value. Both
``w`` and ``h`` can be a string, which takes the following meaning:
``max``
Maximum of all values since the last reset.
``min``
Mimimum of all values since the last reset.
``median``
Median of all values since the last reset.
Used to provide the last four arguments to a widget, e.g.::
suit.Button("Start Game", suit.layout:row(100,30))
suit.Button("Options", suit.layout:row())
suit.Button("Quit", suit.layout:row(nil, "median"))
.. function:: down(w,h)
An alias for :ref:`layout:row() <layout-row>`.
.. _layout-col:
.. function:: col(w,h)
:param mixed w,h: Cell width and height (optional).
:returns: Position and size of the cell: ``x,y,w,h``.
Creates a new cell to the right of the current cell with width ``w`` and height
``h``. If either ``w`` or ``h`` is omitted, the value is set the last used
value. Both ``w`` and ``h`` can be a string, which takes the following meaning:
``max``
Maximum of all values since the last reset.
``min``
Mimimum of all values since the last reset.
``median``
Median of all values since the last reset.
Used to provide the last four arguments to a widget, e.g.::
suit.Button("OK", suit.layout:col(100,30))
suit.Button("Cancel", suit.layout:col("max"))
.. function:: right(w,h)
An alias for :ref:`layout:col() <layout-col>`.
.. function:: up(w,h)
:param mixed w,h: Cell width and height (optional).
:returns: Position and size of the cell: ``x,y,w,h``.
Creates a new cell above the current cell with width ``w`` and height ``h``. If
either ``w`` or ``h`` is omitted, the value is set the last used value. Both
``w`` and ``h`` can be a string, which takes the following meaning:
``max``
Maximum of all values since the last reset.
``min``
Mimimum of all values since the last reset.
``median``
Median of all values since the last reset.
Be careful when mixing ``up()`` and :ref:`layout:row() <layout-row>`, as suit
does no checking to make sure cells don't overlap. e.g.::
suit.Button("A", suit.layout:row(100,30))
suit.Button("B", suit.layout:row())
suit.Button("Also A", suit.layout:up())
.. function:: left(w,h)
:param mixed w,h: Cell width and height (optional).
:returns: Position and size of the cell: ``x,y,w,h``.
Creates a new cell to the left of the current cell with width ``w`` and height
``h``. If either ``w`` or ``h`` is omitted, the value is set the last used
value. Both ``w`` and ``h`` can be a string, which takes the following meaning:
``max``
Maximum of all values since the last reset.
``min``
Mimimum of all values since the last reset.
``median``
Median of all values since the last reset.
Be careful when mixing ``left()`` and :ref:`layout:col() <layout-col>`, as suit
does no checking to make sure cells don't overlap. e.g.::
suit.Button("A", suit.layout:col(100,30))
suit.Button("B", suit.layout:col())
suit.Button("Also A", suit.layout:left())
Precomputed Layouts
-------------------
Apart from immediate mode layouts, you can specify layouts in advance.
The specification is a table of tables, where each inner table follows the
convention of :func:`row` and :func:`col`.
The result is a layout definition object that can be used to access the cells.
There are almost only two reasons to do so: (1) You know the area of your
layout in advance (say, the screen size), and want certain cells to dynamically
fill the available space; (2) You want to animate the cells.
.. note::
Unlike immediate mode layouts, precomputed layouts **can not be nested**.
You can mix immediate mode and precomputed layouts to achieve nested
layouts with precomputed cells, however.
Layout Specifications
^^^^^^^^^^^^^^^^^^^^^
Layout specifications are tables of tables, where the each inner table
corresponds to a cell. The inner tables define the width and height of the cell
according to the rules of :func:`row` and :func:`col`, with one additonal
keyword:
``fill``
Fills the available space, determined by ``min_height`` or ``min_width`` and
the number of cells with property ``fill``.
For example, this row specification makes the height of the second cell to
``(300 - 50 - 50) / 1 = 200``::
{min_height = 300,
{100, 50},
{nil, 'fill'},
{nil, 50},
}
This column specification divides the space evenly among two cells::
{min_width = 300,
{'fill', 100}
{'fill'}
}
Apart from ``min_height`` and ``min_width``, layout specifications can also
define the position (upper left corner) of the layout using the ``pos`` keyword::
{min_width = 300, pos = {100,100},
{'fill', 100}
{'fill'}
}
You can also define a padding::
{min_width = 300, pos = {100,100}, padding = {5,5},
{'fill', 100}
{'fill'}
}
Layout Definition Objects
^^^^^^^^^^^^^^^^^^^^^^^^^
Once constructed, the cells can be accessed in two ways:
- Using iterators::
for i, x,y,w,h in definition() do
suit.Button("Button "..i, x,y,w,h)
end
- Using the ``cell(i)`` accessor::
suit.Button("Button 1", definition.cell(1))
suit.Button("Button 3", definition.cell(3))
suit.Button("Button 2", definition.cell(2))
There is actually a third way: Because layout definitions are just tables, you
can access the cells directly::
local cell = definition[1]
suit.Button("Button 1", cell[1], cell[2], cell[3], cell[4])
-- or suit.Button("Button 1", unpack(cell))
This is especially useful if you want to animate the cells, for example with a
`tween <http://hump.readthedocs.org/en/latest/timer.html#Timer.tween>`_::
for i,cell in ipairs(definition)
local destination = {[2] = cell[2]} -- save cell y position
cell[2] = -cell[4] -- move cell just outside of the screen
-- let the cells fall into the screen one after another
timer.after(i / 10, function()
timer.tween(0.7, cell, destination, 'bounce')
end)
end
Constructors
^^^^^^^^^^^^
.. function:: rows(spec)
:param table spec: Layout specification.
:returns: Layout definition object.
Defines a row layout.
.. function:: cols(spec)
:param table spec: Layout specification.
:returns: Layout definition object.
Defines a column layout.

26
deps/suit/docs/license.rst vendored Normal file
View File

@@ -0,0 +1,26 @@
License
=======
Copyright (c) 2016 Matthias Richter
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.
Except as contained in this notice, the name(s) of the above copyright holders
shall not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization.
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.

5
deps/suit/docs/themes.rst vendored Normal file
View File

@@ -0,0 +1,5 @@
Themeing
========
.. note::
Under construction.

213
deps/suit/docs/widgets.rst vendored Normal file
View File

@@ -0,0 +1,213 @@
Widgets
=======
.. note::
Still under construction...
Immutable Widgets
-----------------
.. function:: Button(text, [options], x,y,w,h)
:param string text: Button label.
:param table options: Optional settings (see below).
:param numbers x,y: Upper left corner of the widget.
:param numbers w,h: Width and height of the widget.o
:returns: Return state (see below).
Creates a button widget at position ``(x,y)`` with width ``w`` and height
``h``.
.. function:: Label(text, [options], x,y,w,h)
:param string text: Label text.
:param table options: Optional settings (see below).
:param numbers x,y: Upper left corner of the widget.
:param numbers w,h: Width and height of the widget.o
:returns: Return state (see below).
Creates a label at position ``(x,y)`` with width ``w`` and height ``h``.
.. function:: ImageButton(normal, options, x,y)
:param mixed normal: Image of the button in normal state.
:param table options: Widget options.
:param numbers x,y: Upper left corner of the widget.
:returns: Return state (see below).
Creates an image button widget at position ``(x,y)``.
Unlike all other widgets, an ``ImageButton`` is not affected by the current
theme.
The argument ``normal`` defines the image of the normal state as well as the
area of the widget.
The button activates when the mouse enters the area occupied by the widget.
If the option ``mask`` defined, the button activates only if the mouse is over
a pixel with non-zero alpha.
You can provide additional ``hovered`` and ``active`` images, but the widget area
is always computed from the ``normal`` image.
You can provide additional ``hovered`` and ``active`` images, but the widget area
is always computed from the ``normal`` image.
**Additional Options:**
``mask``
Alpha-mask of the button, i.e. an ``ImageData`` of the same size as the
``normal`` image that has non-zero alpha where the button should activate.
``normal``
Image for the normal state of the widget. Defaults to widget payload.
``hovered``
Image for the hovered state of the widget. Defaults to ``normal`` if omitted.
``active``
Image for the active state of the widget. Defaults to ``hovered`` if omitted.
.. note::
``ImageButton`` does not recieve width and height parameters. As such, it
does not necessarily honor the cell size of a :doc:`layout`.
.. note::
Unlike other widgets, ``ImageButton`` is tinted by the currently active
color. If you want the button to appear untinted, make sure the active color
is set to white before adding the button, e.g.::
love.graphics.setColor(255,255,255)
suit.ImageButton(push_me, {hovered=and_then_just, active=touch_me},
suit.layout:row())
Mutable Widgets
---------------
.. function:: Checkbox(checkbox, [options], x,y,w,h)
:param table checkbox: Checkbox state.
:param table options: Optional settings (see below).
:param numbers x,y: Upper left corner of the widget.
:param numbers w,h: Width and height of the widget.o
:returns: Return state (see below).
Creates a checkbox at position ``(x,y)`` with width ``w`` and height ``h``.
**State:**
``checkbox`` is a table with the following components:
``checked``
``true`` if the checkbox is checked, ``false`` otherwise.
``text``
Optional label to show besides the checkbox.
.. function:: Slider(slider, [options], x,y,w,h)
:param table slider: Slider state.
:param table options: Optional settings (see below).
:param numbers x,y: Upper left corner of the widget.
:param numbers w,h: Width and height of the widget.o
:returns: Return state (see below).
Creates a slider at position ``(x,y)`` with width ``w`` and height ``h``.
Sliders can be horizontal (default) or vertical.
**State:**
``value``
Current value of the slider. Mandatory argument.
``min``
Minimum value of the slider. Defaults to ``min(value, 0)`` if omitted.
``max``
Maximum value of the slider. Defaults to ``min(value, 1)`` if omitted.
``step``
Value stepping for keyboard input. Defaults to ``(max - min)/10`` if omitted.
**Additional Options:**
``vertical``
Whether the slider is vertical or horizontal.
**Additional Return State:**
``changed``
``true`` when the slider value was changed, ``false`` otherwise.
.. function:: Input(input, [options], x,y,w,h)
:param table input: Checkbox state
:param table options: Optional settings (see below).
:param numbers x,y: Upper left corner of the widget.
:param numbers w,h: Width and height of the widget.o
:returns: Return state (see below).
Creates an input box at position ``(x,y)`` with width ``w`` and height ``h``.
Implements typical movement (arrow keys, home and end key) and editing
(deletion with backspace and delete) facilities.
**State:**
``text``
Current text inside the input box. Defaults to the empty string if omitted.
``cursor``
Cursor position. Defined as the position before the character (including
EOS), so ``1`` is the position before the first character, etc. Defaults to
the end of ``text`` if omitted.
**Additional Return State:**
``submitted``
``true`` when enter was pressed while the widget has keyboard focus.
Common Options
--------------
``id``
Identifier of the widget regarding user interaction. Defaults to the first
argument (e.g., ``text`` for buttons) if omitted.
``font``
Font of the label. Defaults to the current font (``love.graphics.getFont()``).
``align``
Horizontal alignment of the label. One of ``"left"``, ``"center"``, or
``"right"``. Defaults to ``"center"``.
``valign``
Vertical alignment of the label. On of ``"top"``, ``"middle"``, or
``"bottom"``. Defaults to ``"middle"``.
``color``
A table to overwrite the color. Undefined colors default to the theme colors.
``cornerRadius``
The corner radius for boxes. Overwrites the theme corner radius.
``draw``
A function to replace the drawing function. Refer to :doc:`themes` for more information about the function signatures.
Common Return States
--------------------
``id``
Identifier of the widget.
``hit``
``true`` if the mouse was pressed and released on the button, ``false``
otherwise.
``hovered``
``true`` if the mouse is above the widget, ``false`` otherwise.
``entered``
``true`` if the mouse entered the widget area, ``false`` otherwise.
``left``
``true`` if the mouse left the widget area, ``false`` otherwise.

56
deps/suit/imagebutton.lua vendored Normal file
View File

@@ -0,0 +1,56 @@
-- This file is part of SUIT, copyright (c) 2016 Matthias Richter
local BASE = (...):match('(.-)[^%.]+$')
local function isType(val, typ)
return type(val) == "userdata" and val.typeOf and val:typeOf(typ)
end
return function(core, normal, ...)
local opt, x,y = core.getOptionsAndSize(...)
opt.normal = normal or opt.normal or opt[1]
opt.hovered = opt.hovered or opt[2] or opt.normal
opt.active = opt.active or opt[3] or opt.hovered
opt.id = opt.id or opt.normal
local image = assert(opt.normal, "No image for state `normal'")
core:registerMouseHit(opt.id, x, y, function(u,v)
-- mouse in image?
u, v = math.floor(u+.5), math.floor(v+.5)
if u < 0 or u >= image:getWidth() or v < 0 or v >= image:getHeight() then
return false
end
if opt.mask then
-- alpha test
assert(isType(opt.mask, "ImageData"), "Option `mask` is not a love.image.ImageData")
assert(u < mask:getWidth() and v < mask:getHeight(), "Mask may not be smaller than image.")
local _,_,_,a = mask:getPixel(u,v)
return a > 0
end
return true
end)
if core:isActive(opt.id) then
image = opt.active
elseif core:isHovered(opt.id) then
image = opt.hovered
end
assert(isType(image, "Image"), "state image is not a love.graphics.image")
core:registerDraw(opt.draw or function(image,x,y, r,g,b,a)
love.graphics.setColor(r,g,b,a)
love.graphics.draw(image,x,y)
end, image, x,y, love.graphics.getColor())
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

67
deps/suit/init.lua vendored Normal file
View File

@@ -0,0 +1,67 @@
-- This file is part of SUIT, copyright (c) 2016 Matthias Richter
local BASE = (...) .. "."
local suit = require(BASE .. "core")
local instance = suit.new()
return setmetatable({
_instance = instance,
new = suit.new,
getOptionsAndSize = suit.getOptionsAndSize,
-- core functions
setHovered = function(...) return instance:setHovered(...) end,
anyHovered = function(...) return instance:anyHovered(...) end,
isHovered = function(...) return instance:isHovered(...) end,
wasHovered = function(...) return instance:wasHovered(...) end,
anyActive = function(...) return instance:anyActive(...) end,
setActive = function(...) return instance:setActive(...) end,
isActive = function(...) return instance:isActive(...) end,
setHit = function(...) return instance:setHit(...) end,
anyHit = function(...) return instance:anyHit(...) end,
isHit = function(...) return instance:isHit(...) end,
mouseInRect = function(...) return instance:mouseInRect(...) end,
registerHitbox = function(...) return instance:registerHitbox(...) end,
registerMouseHit = function(...) return instance:registerMouseHit(...) end,
mouseReleasedOn = function(...) return instance:mouseReleasedOn(...) end,
updateMouse = function(...) return instance:updateMouse(...) end,
getMousePosition = function(...) return instance:getMousePosition(...) end,
getPressedKey = function(...) return instance:getPressedKey(...) end,
keypressed = function(...) return instance:keypressed(...) end,
textinput = function(...) return instance:textinput(...) end,
textedited = function(...) return instance:textedited(...) end,
grabKeyboardFocus = function(...) return instance:grabKeyboardFocus(...) end,
hasKeyboardFocus = function(...) return instance:hasKeyboardFocus(...) end,
keyPressedOn = function(...) return instance:keyPressedOn(...) end,
enterFrame = function(...) return instance:enterFrame(...) end,
exitFrame = function(...) return instance:exitFrame(...) end,
registerDraw = function(...) return instance:registerDraw(...) end,
draw = function(...) return instance:draw(...) end,
-- widgets
Button = function(...) return instance:Button(...) end,
ImageButton = function(...) return instance:ImageButton(...) end,
Label = function(...) return instance:Label(...) end,
Checkbox = function(...) return instance:Checkbox(...) end,
Input = function(...) return instance:Input(...) end,
Slider = function(...) return instance:Slider(...) end,
-- layout
layout = instance.layout
}, {
-- theme
__newindex = function(t, k, v)
if k == "theme" then
instance.theme = v
else
rawset(instance, k, v)
end
end,
__index = function(t, k)
return k == "theme" and instance.theme or rawget(t, k)
end,
})

116
deps/suit/input.lua vendored Normal file
View File

@@ -0,0 +1,116 @@
-- This file is part of SUIT, copyright (c) 2016 Matthias Richter
local BASE = (...):match('(.-)[^%.]+$')
local utf8 = require 'utf8'
local function split(str, pos)
local offset = utf8.offset(str, pos) or 0
return str:sub(1, offset-1), str:sub(offset)
end
return function(core, input, ...)
local opt, x,y,w,h = core.getOptionsAndSize(...)
opt.id = opt.id or input
opt.font = opt.font or love.graphics.getFont()
local text_width = opt.font:getWidth(input.text)
w = w or text_width + 6
h = h or opt.font:getHeight() + 4
input.text = input.text or ""
input.cursor = math.max(1, math.min(utf8.len(input.text)+1, input.cursor or utf8.len(input.text)+1))
-- cursor is position *before* the character (including EOS) i.e. in "hello":
-- position 1: |hello
-- position 2: h|ello
-- ...
-- position 6: hello|
-- get size of text and cursor position
opt.cursor_pos = 0
if input.cursor > 1 then
local s = input.text:sub(1, utf8.offset(input.text, input.cursor)-1)
opt.cursor_pos = opt.font:getWidth(s)
end
-- compute drawing offset
local wm = w - 6 -- consider margin
input.text_draw_offset = input.text_draw_offset or 0
if opt.cursor_pos - input.text_draw_offset < 0 then
-- cursor left of input box
input.text_draw_offset = opt.cursor_pos
end
if opt.cursor_pos - input.text_draw_offset > wm then
-- cursor right of input box
input.text_draw_offset = opt.cursor_pos - wm
end
if text_width - input.text_draw_offset < wm and text_width > wm then
-- text bigger than input box, but does not fill it
input.text_draw_offset = text_width - wm
end
-- user interaction
if input.forcefocus ~= nil and input.forcefocus then
core.active = opt.id
input.forcefocus = false
end
opt.state = core:registerHitbox(opt.id, x,y,w,h)
opt.hasKeyboardFocus = core:grabKeyboardFocus(opt.id)
if (core.candidate_text.text == "") and opt.hasKeyboardFocus then
local keycode,char = core:getPressedKey()
-- text input
if char and char ~= "" then
local a,b = split(input.text, input.cursor)
input.text = table.concat{a, char, b}
input.cursor = input.cursor + utf8.len(char)
end
-- text editing
if keycode == 'backspace' then
local a,b = split(input.text, input.cursor)
input.text = table.concat{split(a,utf8.len(a)), b}
input.cursor = math.max(1, input.cursor-1)
elseif keycode == 'delete' then
local a,b = split(input.text, input.cursor)
local _,b = split(b, 2)
input.text = table.concat{a, b}
end
-- cursor movement
if keycode =='left' then
input.cursor = math.max(0, input.cursor-1)
elseif keycode =='right' then -- cursor movement
input.cursor = math.min(utf8.len(input.text)+1, input.cursor+1)
elseif keycode =='home' then -- cursor movement
input.cursor = 1
elseif keycode =='end' then -- cursor movement
input.cursor = utf8.len(input.text)+1
end
-- move cursor position with mouse when clicked on
if core:mouseReleasedOn(opt.id) then
local mx = core:getMousePosition() - x + input.text_draw_offset
input.cursor = utf8.len(input.text) + 1
for c = 1,input.cursor do
local s = input.text:sub(0, utf8.offset(input.text, c)-1)
if opt.font:getWidth(s) >= mx then
input.cursor = c-1
break
end
end
end
end
input.candidate_text = {text=core.candidate_text.text, start=core.candidate_text.start, length=core.candidate_text.length}
core:registerDraw(opt.draw or core.theme.Input, input, opt, x,y,w,h)
return {
id = opt.id,
hit = core:mouseReleasedOn(opt.id),
submitted = core:keyPressedOn(opt.id, "return"),
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

23
deps/suit/label.lua vendored Normal file
View File

@@ -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.Label, 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

369
deps/suit/layout.lua vendored Normal file
View File

@@ -0,0 +1,369 @@
-- This file is part of SUIT, copyright (c) 2016 Matthias Richter
local Layout = {}
function Layout.new(x,y,padx,pady)
return setmetatable({_stack = {}}, {__index = Layout}):reset(x,y,padx,pady)
end
function Layout:reset(x,y, padx,pady)
self._x = x or 0
self._y = y or 0
self._padx = padx or 0
self._pady = pady or self._padx
self._w = nil
self._h = nil
self._widths = {}
self._heights = {}
self._isFirstCell = true
return self
end
function Layout:padding(padx,pady)
if padx then
self._padx = padx
self._pady = pady or padx
end
return self._padx, self._pady
end
function Layout:size()
return self._w, self._h
end
function Layout:nextRow()
return self._x, self._y + self._h + self._pady
end
Layout.nextDown = Layout.nextRow
function Layout:nextCol()
return self._x + self._w + self._padx, self._y
end
Layout.nextRight = Layout.nextCol
function Layout:push(x,y)
self._stack[#self._stack+1] = {
self._x, self._y,
self._padx, self._pady,
self._w, self._h,
self._widths,
self._heights,
}
return self:reset(x,y, padx or self._padx, pady or self._pady)
end
function Layout:pop()
assert(#self._stack > 0, "Nothing to pop")
local w,h = self._w, self._h
self._x, self._y,
self._padx,self._pady,
self._w, self._h,
self._widths, self._heights = unpack(self._stack[#self._stack])
self._isFirstCell = false
self._stack[#self._stack] = nil
self._w, self._h = math.max(w, self._w or 0), math.max(h, self._h or 0)
return w, h
end
--- recursive binary search for position of v
local function insert_sorted_helper(t, i0, i1, v)
if i1 <= i0 then
table.insert(t, i0, v)
return
end
local i = i0 + math.floor((i1-i0)/2)
if t[i] < v then
return insert_sorted_helper(t, i+1, i1, v)
elseif t[i] > v then
return insert_sorted_helper(t, i0, i-1, v)
else
table.insert(t, i, v)
end
end
local function insert_sorted(t, v)
if v <= 0 then return end
insert_sorted_helper(t, 1, #t, v)
end
local function calc_width_height(self, w, h)
if w == "" or w == nil then
w = self._w
elseif w == "max" then
w = self._widths[#self._widths]
elseif w == "min" then
w = self._widths[1]
elseif w == "median" then
w = self._widths[math.ceil(#self._widths/2)] or 0
elseif type(w) ~= "number" then
error("width: invalid value (" .. tostring(w) .. ")", 3)
end
if h == "" or h == nil then
h = self._h
elseif h == "max" then
h = self._heights[#self._heights]
elseif h == "min" then
h = self._heights[1]
elseif h == "median" then
h = self._heights[math.ceil(#self._heights/2)] or 0
elseif type(h) ~= "number" then
error("width: invalid value (" .. tostring(w) .. ")", 3)
end
if not w or not h then
error("Invalid cell size", 3)
end
insert_sorted(self._widths, w)
insert_sorted(self._heights, h)
return w,h
end
function Layout:row(w, h)
w,h = calc_width_height(self, w, h)
local x,y = self._x, self._y + (self._h or 0)
if not self._isFirstCell then
y = y + self._pady
end
self._isFirstCell = false
self._y, self._w, self._h = y, w, h
return x,y,w,h
end
Layout.down = Layout.row
function Layout:up(w, h)
w,h = calc_width_height(self, w, h)
local x,y = self._x, self._y - (self._h and h or 0)
if not self._isFirstCell then
y = y - self._pady
end
self._isFirstCell = false
self._y, self._w, self._h = y, w, h
return x,y,w,h
end
function Layout:col(w, h)
w,h = calc_width_height(self, w, h)
local x,y = self._x + (self._w or 0), self._y
if not self._isFirstCell then
x = x + self._padx
end
self._isFirstCell = false
self._x, self._w, self._h = x, w, h
return x,y,w,h
end
Layout.right = Layout.col
function Layout:left(w, h)
w,h = calc_width_height(self, w, h)
local x,y = self._x - (self._w and w or 0), self._y
if not self._isFirstCell then
x = x - self._padx
end
self._isFirstCell = false
self._x, self._w, self._h = x, w, h
return x,y,w,h
end
local function layout_iterator(t, idx)
idx = (idx or 1) + 1
if t[idx] == nil then return nil end
return idx, unpack(t[idx])
end
local function layout_retained_mode(self, t, constructor, string_argument_to_table, fill_width, fill_height)
-- sanity check
local p = t.pos or {0,0}
if type(p) ~= "table" then
error("Invalid argument `pos' (table expected, got "..type(p)..")", 2)
end
local pad = t.padding or {}
if type(p) ~= "table" then
error("Invalid argument `padding' (table expected, got "..type(p)..")", 2)
end
self:push(p[1] or 0, p[2] or 0)
self:padding(pad[1] or self._padx, pad[2] or self._pady)
-- first pass: get dimensions, add layout info
local layout = {n_fill_w = 0, n_fill_h = 0}
for i,v in ipairs(t) do
if type(v) == "string" then
v = string_argument_to_table(v)
end
local x,y,w,h = 0,0, v[1], v[2]
if v[1] == "fill" then w = 0 end
if v[2] == "fill" then h = 0 end
x,y, w,h = constructor(self, w,h)
if v[1] == "fill" then
w = "fill"
layout.n_fill_w = layout.n_fill_w + 1
end
if v[2] == "fill" then
h = "fill"
layout.n_fill_h = layout.n_fill_h + 1
end
layout[i] = {x,y,w,h, unpack(v,3)}
end
-- second pass: extend "fill" cells and shift others accordingly
local fill_w = fill_width(layout, t.min_width or 0, self._x + self._w - p[1])
local fill_h = fill_height(layout, t.min_height or 0, self._y + self._h - p[2])
local dx,dy = 0,0
for _,v in ipairs(layout) do
v[1], v[2] = v[1] + dx, v[2] + dy
if v[3] == "fill" then
v[3] = fill_w
dx = dx + v[3]
end
if v[4] == "fill" then
v[4] = fill_h
dy = dy + v[4]
end
end
-- finally: return layout with iterator
local w, h = self:pop()
layout.cell = function(self, i)
if self ~= layout then -- allow either colon or dot syntax
i = self
end
return unpack(layout[i])
end
layout.size = function()
return w, h
end
return setmetatable(layout, {__call = function()
return layout_iterator, layout, 0
end})
end
function Layout:rows(t)
return layout_retained_mode(self, t, self.row,
function(v) return {nil, v} end,
function() return self._widths[#self._widths] end, -- fill width
function(l,mh,h) return (mh - h) / l.n_fill_h end) -- fill height
end
function Layout:cols(t)
return layout_retained_mode(self, t, self.col,
function(v) return {v} end,
function(l,mw,w) return (mw - w) / l.n_fill_w end, -- fill width
function() return self._heights[#self._heights] end) -- fill height
end
--[[ "Tests"
do
L = Layout.new()
print("immediate mode")
print("--------------")
x,y,w,h = L:row(100,20) -- x,y,w,h = 0,0, 100,20
print(1,x,y,w,h)
x,y,w,h = L:row() -- x,y,w,h = 0, 20, 100,20 (default: reuse last dimensions)
print(2,x,y,w,h)
x,y,w,h = L:col(20) -- x,y,w,h = 100, 20, 20, 20
print(3,x,y,w,h)
x,y,w,h = L:row(nil,30) -- x,y,w,h = 100, 20, 20, 30
print(4,x,y,w,h)
print('','','', L:size()) -- w,h = 20, 30
print()
L:reset()
local layout = L:rows{
pos = {10,10}, -- optional, default {0,0}
{100, 10},
{nil, 10}, -- {100, 10}
{100, 20}, -- {100, 20}
{}, -- {100, 20} -- default = last value
{nil, "median"}, -- {100, 20}
"median", -- {100, 20}
"max", -- {100, 20}
"min", -- {100, 10}
"" -- {100, 10} -- default = last value
}
print("rows")
print("----")
for i,x,y,w,h in layout() do
print(i,x,y,w,h)
end
print()
-- +-------+-------+----------------+-------+
-- | | | | |
-- 70 {100, | "max" | "fill" | "min" |
-- | 70} | | | |
-- +--100--+--100--+------220-------+--100--+
--
-- `-------------------,--------------------'
-- 520
local layout = L:cols{
pos = {10,10},
min_width = 520,
{100, 70},
"max", -- {100, 70}
"fill", -- {min_width - width_of_items, 70} = {220, 70}
"min", -- {100,70}
}
print("cols")
print("----")
for i,x,y,w,h in layout() do
print(i,x,y,w,h)
end
print()
L:push()
L:row()
end
--]]
-- TODO: nesting a la rows{..., cols{...} } ?
local instance = Layout.new()
return setmetatable({
new = Layout.new,
reset = function(...) return instance:reset(...) end,
padding = function(...) return instance:padding(...) end,
push = function(...) return instance:push(...) end,
pop = function(...) return instance:pop(...) end,
row = function(...) return instance:row(...) end,
col = function(...) return instance:col(...) end,
down = function(...) return instance:down(...) end,
up = function(...) return instance:up(...) end,
left = function(...) return instance:left(...) end,
right = function(...) return instance:right(...) end,
rows = function(...) return instance:rows(...) end,
cols = function(...) return instance:cols(...) end,
}, {__call = function(_,...) return Layout.new(...) end})

23
deps/suit/license.txt vendored Normal file
View File

@@ -0,0 +1,23 @@
Copyright (c) 2016 Matthias Richter
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.
Except as contained in this notice, the name(s) of the above copyright holders
shall not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization.
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.

54
deps/suit/slider.lua vendored Normal file
View File

@@ -0,0 +1,54 @@
-- This file is part of SUIT, copyright (c) 2016 Matthias Richter
local BASE = (...):match('(.-)[^%.]+$')
return function(core, info, ...)
local opt, x,y,w,h = core.getOptionsAndSize(...)
opt.id = opt.id or info
info.min = info.min or math.min(info.value, 0)
info.max = info.max or math.max(info.value, 1)
info.step = info.step or (info.max - info.min) / 10
local fraction = (info.value - info.min) / (info.max - info.min)
local value_changed = false
opt.state = core:registerHitbox(opt.id, x,y,w,h)
if core:isActive(opt.id) then
-- mouse update
local mx,my = core:getMousePosition()
if opt.vertical then
fraction = math.min(1, math.max(0, (y+h - my) / h))
else
fraction = math.min(1, math.max(0, (mx - x) / w))
end
local v = fraction * (info.max - info.min) + info.min
if v ~= info.value then
info.value = v
value_changed = true
end
-- keyboard update
local key_up = opt.vertical and 'up' or 'right'
local key_down = opt.vertical and 'down' or 'left'
if core:getPressedKey() == key_up then
info.value = math.min(info.max, info.value + info.step)
value_changed = true
elseif core:getPressedKey() == key_down then
info.value = math.max(info.min, info.value - info.step)
value_changed = true
end
end
core:registerDraw(opt.draw or core.theme.Slider, fraction, opt, x,y,w,h)
return {
id = opt.id,
hit = core:mouseReleasedOn(opt.id),
changed = value_changed,
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

28
deps/suit/suit-0.1-1.rockspec vendored Normal file
View File

@@ -0,0 +1,28 @@
package = "suit"
version = "0.1-1"
source = {
url = "git://github.com/vrld/suit.git"
}
description = {
summary="Immediate mode GUI library in pure Lua.",
homepage = "https://suit.readthedocs.io",
license = "MIT",
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
["suit"] = "init.lua",
["suit.button"] = "button.lua",
["suit.checkbox"] = "checkbox.lua",
["suit.core"] = "core.lua",
["suit.imagebutton"] = "imagebutton.lua",
["suit.input"] = "input.lua",
["suit.label"] = "label.lua",
["suit.layout"] = "layout.lua",
["suit.slider"] = "slider.lua",
["suit.theme"] = "theme.lua",
}
}

152
deps/suit/theme.lua vendored Normal file
View File

@@ -0,0 +1,152 @@
-- This file is part of SUIT, copyright (c) 2016 Matthias Richter
local BASE = (...):match('(.-)[^%.]+$')
local theme = {}
theme.cornerRadius = 4
theme.color = {
normal = {bg = { 0.25, 0.25, 0.25}, fg = {0.73,0.73,0.73}},
hovered = {bg = { 0.19,0.6,0.73}, fg = {1,1,1}},
active = {bg = {1,0.6, 0}, fg = {1,1,1}}
}
-- HELPER
function theme.getColorForState(opt)
local s = opt.state or "normal"
return (opt.color and opt.color[opt.state]) or theme.color[s]
end
function theme.drawBox(x,y,w,h, colors, cornerRadius)
colors = colors or theme.getColorForState(opt)
cornerRadius = cornerRadius or theme.cornerRadius
w = math.max(cornerRadius/2, w)
if h < cornerRadius/2 then
y,h = y - (cornerRadius - h), cornerRadius/2
end
love.graphics.setColor(colors.bg)
love.graphics.rectangle('fill', x,y, w,h, cornerRadius)
end
function theme.getVerticalOffsetForAlign(valign, font, h)
if valign == "top" then
return 0
elseif valign == "bottom" then
return h - font:getHeight()
end
-- else: "middle"
return (h - font:getHeight()) / 2
end
-- WIDGET VIEWS
function theme.Label(text, opt, x,y,w,h)
y = y + theme.getVerticalOffsetForAlign(opt.valign, opt.font, h)
love.graphics.setColor((opt.color and opt.color.normal or {}).fg or theme.color.normal.fg)
love.graphics.setFont(opt.font)
love.graphics.printf(text, x+2, y, w-4, opt.align or "center")
end
function theme.Button(text, opt, x,y,w,h)
local c = theme.getColorForState(opt)
theme.drawBox(x,y,w,h, c, opt.cornerRadius)
love.graphics.setColor(c.fg)
love.graphics.setFont(opt.font)
y = y + theme.getVerticalOffsetForAlign(opt.valign, opt.font, h)
love.graphics.printf(text, x+2, y, w-4, opt.align or "center")
end
function theme.Checkbox(chk, opt, x,y,w,h)
local c = theme.getColorForState(opt)
local th = opt.font:getHeight()
theme.drawBox(x+h/10,y+h/10,h*.8,h*.8, c, opt.cornerRadius)
love.graphics.setColor(c.fg)
if chk.checked then
love.graphics.setLineStyle('smooth')
love.graphics.setLineWidth(5)
love.graphics.setLineJoin("bevel")
love.graphics.line(x+h*.2,y+h*.55, x+h*.45,y+h*.75, x+h*.8,y+h*.2)
end
if chk.text then
love.graphics.setFont(opt.font)
y = y + theme.getVerticalOffsetForAlign(opt.valign, opt.font, h)
love.graphics.printf(chk.text, x + h, y, w - h, opt.align or "left")
end
end
function theme.Slider(fraction, opt, x,y,w,h)
local xb, yb, wb, hb -- size of the progress bar
local r = math.min(w,h) / 2.1
if opt.vertical then
x, w = x + w*.25, w*.5
xb, yb, wb, hb = x, y+h*(1-fraction), w, h*fraction
else
y, h = y + h*.25, h*.5
xb, yb, wb, hb = x,y, w*fraction, h
end
local c = theme.getColorForState(opt)
theme.drawBox(x,y,w,h, c, opt.cornerRadius)
theme.drawBox(xb,yb,wb,hb, {bg=c.fg}, opt.cornerRadius)
if opt.state ~= nil and opt.state ~= "normal" then
love.graphics.setColor((opt.color and opt.color.active or {}).fg or theme.color.active.fg)
if opt.vertical then
love.graphics.circle('fill', x+wb/2, yb, r)
else
love.graphics.circle('fill', x+wb, yb+hb/2, r)
end
end
end
function theme.Input(input, opt, x,y,w,h)
local utf8 = require 'utf8'
theme.drawBox(x,y,w,h, (opt.color and opt.color.normal) or theme.color.normal, opt.cornerRadius)
x = x + 3
w = w - 6
local th = opt.font:getHeight()
-- set scissors
local sx, sy, sw, sh = love.graphics.getScissor()
love.graphics.setScissor(x-1,y,w+2,h)
x = x - input.text_draw_offset
-- text
love.graphics.setColor((opt.color and opt.color.normal and opt.color.normal.fg) or theme.color.normal.fg)
love.graphics.setFont(opt.font)
love.graphics.print(input.text, x, y+(h-th)/2)
-- candidate text
local tw = opt.font:getWidth(input.text)
local ctw = opt.font:getWidth(input.candidate_text.text)
love.graphics.setColor((opt.color and opt.color.normal and opt.color.normal.fg) or theme.color.normal.fg)
love.graphics.print(input.candidate_text.text, x + tw, y+(h-th)/2)
-- candidate text rectangle box
love.graphics.rectangle("line", x + tw, y+(h-th)/2, ctw, th)
-- cursor
if opt.hasKeyboardFocus and (love.timer.getTime() % 1) > .5 then
local ct = input.candidate_text;
local ss = ct.text:sub(1, utf8.offset(ct.text, ct.start))
local ws = opt.font:getWidth(ss)
if ct.start == 0 then ws = 0 end
love.graphics.setLineWidth(1)
love.graphics.setLineStyle('rough')
love.graphics.line(x + opt.cursor_pos + ws, y + (h-th)/2,
x + opt.cursor_pos + ws, y + (h+th)/2)
end
-- reset scissor
love.graphics.setScissor(sx,sy,sw,sh)
end
return theme