Initial commit
This commit is contained in:
commit
40dbc7134b
11 changed files with 542 additions and 0 deletions
47
README.md
Normal file
47
README.md
Normal file
|
@ -0,0 +1,47 @@
|
|||
# QUICKIE
|
||||
|
||||
Quickie is an [immediate mode gui][IMGUI] library for LÖVE.
|
||||
|
||||
## Example
|
||||
|
||||
local gui = require 'quickie'
|
||||
|
||||
-- widgets are "created" by calling their corresponding functions in love.update.
|
||||
-- if you want to remove a widget, simply don't call the function (just like with
|
||||
-- any other love drawable). widgets dont hold their own state - this is your job:
|
||||
--
|
||||
-- sliders have a value and optional a minimum (default = 0) and maximum (default = 1)
|
||||
local slider = {value = 10, min = 0, max = 100}
|
||||
-- input boxes have a text and a cursor position (defaults to end of string)
|
||||
local input = {text = "Hello, World!", cursor = 0}
|
||||
-- checkboxes have only a `checked' status
|
||||
local checkbox = {checked = false}
|
||||
|
||||
function love.update(dt)
|
||||
-- widgets are defined by simply calling them. usually a widget returns true if
|
||||
-- if its value changed or if it was activated (click on button, ...)
|
||||
if gui.Input(input, 10, 10, 300, 20) then
|
||||
print('Text changed:', input.text)
|
||||
end
|
||||
|
||||
if gui.Button('Clear', 320,10,100,20) then
|
||||
input.text = ""
|
||||
end
|
||||
|
||||
-- add more widgets here
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
-- draw the widgets which were "created" in love.update
|
||||
gui.core.draw()
|
||||
end
|
||||
|
||||
function love.keypressed(key,code)
|
||||
-- forward keyboard events to the gui. If you don't want widget tabbing and
|
||||
-- input widgets, skip this line
|
||||
gui.core.keyboard.pressed(key, code)
|
||||
end
|
||||
|
||||
## Documentation
|
||||
|
||||
TODO
|
27
button.lua
Normal file
27
button.lua
Normal file
|
@ -0,0 +1,27 @@
|
|||
local core = require((...):match("^(.+)%.[^%.]+") .. '.core')
|
||||
|
||||
-- the widget
|
||||
return function(title, x,y, w,h, draw)
|
||||
-- Generate unique identifier for gui state update and querying.
|
||||
local id = core.generateID()
|
||||
|
||||
-- The widget mouse-state can be:
|
||||
-- hot (mouse over widget),
|
||||
-- active (mouse pressed on widget) or
|
||||
-- normal (mouse not on widget and not pressed on widget).
|
||||
--
|
||||
-- core.mouse.updateState(id, x,y,w,h) updates the state for this widget.
|
||||
core.mouse.updateState(id, x,y,w,h)
|
||||
|
||||
-- core.makeTabable makes the item focus on tab. Tab order is determied
|
||||
-- by the order you call the widget functions.
|
||||
core.makeTabable(id)
|
||||
|
||||
-- core.registerDraw(id, drawfunction, drawfunction-arguments...)
|
||||
-- shows widget when core.draw() is called.
|
||||
core.registerDraw(id, draw or core.style.Button, title,x,y,w,h)
|
||||
|
||||
return core.mouse.releasedOn(id) or
|
||||
(core.keyboard.key == 'return' and core.hasKeyFocus(id))
|
||||
end
|
||||
|
19
checkbox.lua
Normal file
19
checkbox.lua
Normal file
|
@ -0,0 +1,19 @@
|
|||
local core = require((...):match("^(.+)%.[^%.]+") .. '.core')
|
||||
|
||||
return function(info, x,y, w,h, draw)
|
||||
local id = core.generateID()
|
||||
|
||||
core.mouse.updateState(id, x,y,w,h)
|
||||
core.makeTabable(id)
|
||||
core.registerDraw(id, draw or core.style.Checkbox, info.checked,x,y,w,h)
|
||||
|
||||
local checked = info.checked
|
||||
local key = core.keyboard.key
|
||||
if core.mouse.releasedOn(id) or
|
||||
(core.hasKeyFocus(id) and key == 'return' or key == ' ') then
|
||||
info.checked = not info.checked
|
||||
end
|
||||
|
||||
return info.checked ~= checked
|
||||
end
|
||||
|
153
core.lua
Normal file
153
core.lua
Normal file
|
@ -0,0 +1,153 @@
|
|||
-- state
|
||||
local context = {maxid = 0}
|
||||
local NO_WIDGET = function()end
|
||||
|
||||
local function generateID()
|
||||
context.maxid = context.maxid + 1
|
||||
return context.maxid
|
||||
end
|
||||
|
||||
local function setHot(id) context.hot = id end
|
||||
local function isHot(id) return context.hot == id end
|
||||
|
||||
local function setActive(id) context.active = id end
|
||||
local function isActive(id) return context.active == id end
|
||||
|
||||
local function setKeyFocus(id) context.keyfocus = id end
|
||||
local function hasKeyFocus(id) return context.keyfocus == id end
|
||||
|
||||
-- input
|
||||
local mouse = {x = 0, y = 0, down = false}
|
||||
local keyboard = {key = nil, code = -1}
|
||||
|
||||
function mouse.inRect(x,y,w,h)
|
||||
return mouse.x >= x and mouse.x <= x+w and mouse.y >= y and mouse.y <= y+h
|
||||
end
|
||||
|
||||
function mouse.updateState(id, x,y,w,h)
|
||||
if mouse.inRect(x,y,w,h) then
|
||||
setHot(id)
|
||||
if not context.active and mouse.down then
|
||||
setActive(id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mouse.releasedOn(id)
|
||||
return not mouse.down and isHot(id) and isActive(id)
|
||||
end
|
||||
|
||||
function keyboard.pressed(key, code)
|
||||
keyboard.key = key
|
||||
keyboard.code = code
|
||||
end
|
||||
|
||||
function keyboard.tryGrab(id)
|
||||
if not context.keyfocus then
|
||||
context.keyfocus = id
|
||||
end
|
||||
end
|
||||
|
||||
local function makeTabable(id)
|
||||
keyboard.tryGrab(id)
|
||||
if hasKeyFocus(id) and keyboard.key == 'tab' then
|
||||
if love.keyboard.isDown('rshift', 'lshift') then
|
||||
setKeyFocus(context.lastwidget)
|
||||
else
|
||||
setKeyFocus(nil)
|
||||
end
|
||||
keyboard.key = nil
|
||||
end
|
||||
context.lastwidget = id
|
||||
end
|
||||
|
||||
-- helper functions
|
||||
local function strictAnd(...)
|
||||
local n = select("#", ...)
|
||||
local ret = true
|
||||
for i = 1,n do ret = select(i, ...) and ret end
|
||||
return ret
|
||||
end
|
||||
|
||||
local function strictOr(...)
|
||||
local n = select("#", ...)
|
||||
local ret = false
|
||||
for i = 1,n do ret = select(i, ...) or ret end
|
||||
return ret
|
||||
end
|
||||
|
||||
-- allow packed nil
|
||||
local function save_pack(...)
|
||||
return {n = select('#', ...), ...}
|
||||
end
|
||||
|
||||
local function save_unpack(t, i)
|
||||
i = i or 1
|
||||
if i >= t.n then return t[i] end
|
||||
return t[i], save_unpack(t, i+1)
|
||||
end
|
||||
|
||||
local draw_items = {n = 0}
|
||||
local function registerDraw(id, f, ...)
|
||||
assert(type(f) == 'function' or (getmetatable(f) or {}).__call,
|
||||
'Drawing function is not a callable type!')
|
||||
|
||||
local state = 'normal'
|
||||
if isHot(id) or hasKeyFocus(id) then
|
||||
state = isActive(id) and 'active' or 'hot'
|
||||
end
|
||||
local rest = save_pack(...)
|
||||
draw_items.n = draw_items.n + 1
|
||||
draw_items[draw_items.n] = function() f(state, save_unpack(rest)) end
|
||||
end
|
||||
|
||||
-- actually update-and-draw
|
||||
local function draw()
|
||||
-- close frame state
|
||||
if not mouse.down then -- released
|
||||
setActive(nil)
|
||||
elseif not context.active then -- clicked outside
|
||||
setActive(NO_WIDGET)
|
||||
end
|
||||
|
||||
for i = 1,draw_items.n do draw_items[i]() end
|
||||
|
||||
-- prepare for next frame
|
||||
draw_items.n = 0
|
||||
context.maxid = 0
|
||||
|
||||
-- update mouse status
|
||||
setHot(nil)
|
||||
mouse.x, mouse.y = love.mouse.getPosition()
|
||||
mouse.down = love.mouse.isDown('l')
|
||||
|
||||
-- clear keyboard focus if nobody wants it
|
||||
if keyboard.key == 'tab' then
|
||||
context.keyboardfocus = nil
|
||||
end
|
||||
keyboard.key, keyboard.code = nil, -1
|
||||
end
|
||||
|
||||
return {
|
||||
mouse = mouse,
|
||||
keyboard = keyboard,
|
||||
|
||||
generateID = generateID,
|
||||
setHot = setHot,
|
||||
setActive = setActive,
|
||||
setKeyFocus = setKeyFocus,
|
||||
isHot = isHot,
|
||||
isActive = isActive,
|
||||
hasKeyFocus = hasKeyFocus,
|
||||
makeTabable = makeTabable,
|
||||
|
||||
style = require((...):match("^(.+)%.[^%.]+") .. '.style-default'),
|
||||
color = color,
|
||||
registerDraw = registerDraw,
|
||||
draw = draw,
|
||||
|
||||
strictAnd = strictAnd,
|
||||
strictOr = strictOr,
|
||||
save_pack = save_pack,
|
||||
save_unpack = save_unpack,
|
||||
}
|
BIN
imgui.love
Normal file
BIN
imgui.love
Normal file
Binary file not shown.
11
init.lua
Normal file
11
init.lua
Normal file
|
@ -0,0 +1,11 @@
|
|||
local BASE = (...) .. '.'
|
||||
|
||||
return {
|
||||
core = require(BASE .. 'core'),
|
||||
Button = require(BASE .. 'button'),
|
||||
Slider = require(BASE .. 'slider'),
|
||||
Slider2D = require(BASE .. 'slider2d'),
|
||||
Label = require(BASE .. 'label'),
|
||||
Input = require(BASE .. 'input'),
|
||||
Checkbox = require(BASE .. 'checkbox')
|
||||
}
|
43
input.lua
Normal file
43
input.lua
Normal file
|
@ -0,0 +1,43 @@
|
|||
local core = require((...):match("^(.+)%.[^%.]+") .. '.core')
|
||||
|
||||
return function(info, x,y,w,h, draw)
|
||||
info.text = info.text or ""
|
||||
info.cursor = math.min(info.cursor or info.text:len(), info.text:len())
|
||||
|
||||
local id = core.generateID()
|
||||
core.mouse.updateState(id, x,y,w,h)
|
||||
core.makeTabable(id)
|
||||
if core.isActive(id) then core.setKeyFocus(id) end
|
||||
|
||||
core.registerDraw(id, draw or core.style.Input, info.text, info.cursor, x,y,w,h)
|
||||
|
||||
local changed = false
|
||||
-- editing
|
||||
if core.keyboard.key == 'backspace' then
|
||||
info.text = info.text:sub(1,info.cursor-1) .. info.text:sub(info.cursor+1)
|
||||
info.cursor = math.max(0, info.cursor-1)
|
||||
changed = true
|
||||
elseif core.keyboard.key == 'delete' then
|
||||
info.text = info.text:sub(1,info.cursor) .. info.text:sub(info.cursor+2)
|
||||
info.cursor = math.min(info.text:len(), info.cursor)
|
||||
changed = true
|
||||
-- movement
|
||||
elseif core.keyboard.key == 'left' then
|
||||
info.cursor = math.max(0, info.cursor-1)
|
||||
elseif core.keyboard.key == 'right' then
|
||||
info.cursor = math.min(info.text:len(), info.cursor+1)
|
||||
elseif core.keyboard.key == 'home' then
|
||||
info.cursor = 0
|
||||
elseif core.keyboard.key == 'end' then
|
||||
info.cursor = info.text:len()
|
||||
-- input
|
||||
elseif core.keyboard.code >= 32 and core.keyboard.code < 127 then
|
||||
local left = info.text:sub(1,info.cursor)
|
||||
local right = info.text:sub(info.cursor+1)
|
||||
info.text = table.concat{left, string.char(core.keyboard.code), right}
|
||||
info.cursor = info.cursor + 1
|
||||
changed = true
|
||||
end
|
||||
|
||||
return changed
|
||||
end
|
9
label.lua
Normal file
9
label.lua
Normal file
|
@ -0,0 +1,9 @@
|
|||
local core = require((...):match("^(.+)%.[^%.]+") .. '.core')
|
||||
|
||||
return function(text, x,y,w,h,align, draw)
|
||||
local id = core.generateID()
|
||||
w, h, align = w or 0, h or 0, align or 'left'
|
||||
core.registerDraw(id, draw or core.style.Label, text,x,y,w,h,align)
|
||||
return false
|
||||
end
|
||||
|
44
slider.lua
Normal file
44
slider.lua
Normal file
|
@ -0,0 +1,44 @@
|
|||
local core = require((...):match("^(.+)%.[^%.]+") .. '.core')
|
||||
|
||||
return function(info, x,y,w,h, draw)
|
||||
assert(type(info) == 'table' and info.value, "Incomplete slider value info")
|
||||
info.min = info.min or 0
|
||||
info.max = info.max or math.max(info.value, 1)
|
||||
info.step = info.step or (info.max - info.min) / 50
|
||||
local fraction = (info.value - info.min) / (info.max - info.min)
|
||||
|
||||
local id = core.generateID()
|
||||
core.mouse.updateState(id, x,y,w,h)
|
||||
core.makeTabable(id)
|
||||
core.registerDraw(id,draw or core.style.Slider, fraction, x,y,w,h, info.vertical)
|
||||
|
||||
-- mouse update
|
||||
if core.isActive(id) then
|
||||
core.setKeyFocus(id)
|
||||
if info.vertical then
|
||||
fraction = math.min(1, math.max(0, (y - core.mouse.y + h) / h))
|
||||
else
|
||||
fraction = math.min(1, math.max(0, (core.mouse.x - x) / w))
|
||||
end
|
||||
local v = fraction * (info.max - info.min) + info.min
|
||||
if v ~= info.value then
|
||||
info.value = v
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- keyboard update
|
||||
local changed = false
|
||||
if core.hasKeyFocus(id) then
|
||||
local keys = info.vertical and {'up', 'down'} or {'right', 'left'}
|
||||
if core.keyboard.key == keys[1] then
|
||||
info.value = math.min(info.max, info.value + info.step)
|
||||
changed = true
|
||||
elseif core.keyboard.key == keys[2] then
|
||||
info.value = math.max(info.min, info.value - info.step)
|
||||
changed = true
|
||||
end
|
||||
end
|
||||
|
||||
return changed
|
||||
end
|
55
slider2d.lua
Normal file
55
slider2d.lua
Normal file
|
@ -0,0 +1,55 @@
|
|||
local core = require((...):match("^(.+)%.[^%.]+") .. '.core')
|
||||
|
||||
return function(info, x,y,w,h, draw)
|
||||
assert(type(info) == 'table' and type(info.value) == "table", "Incomplete slider value info")
|
||||
info.min = info.min or {x = 0, y = 0}
|
||||
info.max = info.max or {x = math.max(info.value.x or 0, 1), y = math.max(info.value.y or 0, 1)}
|
||||
info.step = info.step or {x = (info.max.x - info.min.x)/50, y = (info.max.y - info.min.y)/50}
|
||||
local fraction = {
|
||||
x = (info.value.x - info.min.x) / (info.max.x - info.min.x),
|
||||
y = (info.value.y - info.min.y) / (info.max.y - info.min.y),
|
||||
}
|
||||
|
||||
local id = core.generateID()
|
||||
core.mouse.updateState(id, x,y,w,h)
|
||||
core.makeTabable(id)
|
||||
core.registerDraw(id,draw or core.style.Slider2D, fraction, x,y,w,h)
|
||||
|
||||
-- update value
|
||||
if core.isActive(id) then
|
||||
core.setKeyFocus(id)
|
||||
fraction = {
|
||||
x = (core.mouse.x - x) / w,
|
||||
y = (core.mouse.y - y) / h,
|
||||
}
|
||||
fraction.x = math.min(1, math.max(0, fraction.x))
|
||||
fraction.y = math.min(1, math.max(0, fraction.y))
|
||||
local v = {
|
||||
x = fraction.x * (info.max.x - info.min.x) + info.min.x,
|
||||
y = fraction.y * (info.max.y - info.min.y) + info.min.y,
|
||||
}
|
||||
if v.x ~= info.value.x or v.y ~= info.value.y then
|
||||
info.value = v
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local changed = false
|
||||
if core.hasKeyFocus(id) then
|
||||
if core.keyboard.key == 'down' then
|
||||
info.value.y = math.min(info.max.y, info.value.y + info.step.y)
|
||||
changed = true
|
||||
elseif core.keyboard.key == 'up' then
|
||||
info.value.y = math.max(info.min.y, info.value.y - info.step.y)
|
||||
changed = true
|
||||
end
|
||||
if core.keyboard.key == 'right' then
|
||||
info.value.x = math.min(info.max.x, info.value.x + info.step.x)
|
||||
changed = true
|
||||
elseif core.keyboard.key == 'left' then
|
||||
info.value.x = math.max(info.min.x, info.value.x - info.step.x)
|
||||
changed = true
|
||||
end
|
||||
end
|
||||
return changed
|
||||
end
|
134
style-default.lua
Normal file
134
style-default.lua
Normal file
|
@ -0,0 +1,134 @@
|
|||
-- default style
|
||||
local color = {
|
||||
normal = {bg = {128,128,128,200}, fg = {59,59,59,200}},
|
||||
hot = {bg = {145,153,153,200}, fg = {60,61,54,200}},
|
||||
active = {bg = {145,153,153,255}, fg = {60,61,54,255}}
|
||||
}
|
||||
|
||||
-- load default font
|
||||
if not love.graphics.getFont() then
|
||||
love.graphics.setFont(love.graphics.newFont(12))
|
||||
end
|
||||
|
||||
local function Button(state, title, x,y,w,h)
|
||||
local c = color[state]
|
||||
if state ~= 'normal' then
|
||||
love.graphics.setColor(c.fg)
|
||||
love.graphics.rectangle('fill', x+3,y+3,w,h)
|
||||
end
|
||||
love.graphics.setColor(c.bg)
|
||||
love.graphics.rectangle('fill', x,y,w,h)
|
||||
love.graphics.setColor(c.fg)
|
||||
local f = love.graphics.getFont()
|
||||
love.graphics.print(title, x + (w-f:getWidth(title))/2, y + (h-f:getHeight(title))/2)
|
||||
end
|
||||
|
||||
local function Label(state, text, x,y,w,h,align)
|
||||
local c = color[state]
|
||||
love.graphics.setColor(c.fg)
|
||||
local f = assert(love.graphics.getFont())
|
||||
if align == 'center' then
|
||||
x = x + (w - f:getWidth(text))/2
|
||||
y = y + (h - f:getHeight(text))/2
|
||||
elseif align == 'right' then
|
||||
x = x + w - f:getWidth(text)
|
||||
y = y + h - f:getHeight(text)
|
||||
end
|
||||
love.graphics.print(text, x,y)
|
||||
end
|
||||
|
||||
local function Slider(state, fraction, x,y,w,h, vertical)
|
||||
local c = color[state]
|
||||
if state ~= 'normal' then
|
||||
love.graphics.setColor(c.fg)
|
||||
love.graphics.rectangle('fill', x+3,y+3,w,h)
|
||||
end
|
||||
love.graphics.setColor(c.bg)
|
||||
love.graphics.rectangle('fill', x,y,w,h)
|
||||
|
||||
love.graphics.setColor(c.fg)
|
||||
local hw,hh = w,h
|
||||
if vertical then
|
||||
hh = h * fraction
|
||||
y = y + (h - hh)
|
||||
else
|
||||
hw = w * fraction
|
||||
end
|
||||
love.graphics.rectangle('fill', x,y,hw,hh)
|
||||
end
|
||||
|
||||
local function Slider2D(state, fraction, x,y,w,h, vertical)
|
||||
local c = color[state]
|
||||
if state ~= 'normal' then
|
||||
love.graphics.setColor(c.fg)
|
||||
love.graphics.rectangle('fill', x+3,y+3,w,h)
|
||||
end
|
||||
love.graphics.setColor(c.bg)
|
||||
love.graphics.rectangle('fill', x,y,w,h)
|
||||
|
||||
-- draw quadrants
|
||||
local lw = love.graphics.getLineWidth()
|
||||
love.graphics.setLineWidth(1)
|
||||
love.graphics.setColor(c.fg[1], c.fg[2], c.fg[3], math.min(c.fg[4], 127))
|
||||
love.graphics.line(x+w/2,y, x+w/2,y+h)
|
||||
love.graphics.line(x,y+h/2, x+w,y+h/2)
|
||||
love.graphics.setLineWidth(lw)
|
||||
|
||||
-- draw cursor
|
||||
local xx = x + fraction.x * w
|
||||
local yy = y + fraction.y * h
|
||||
love.graphics.setColor(c.fg)
|
||||
love.graphics.circle('fill', xx,yy,4,4)
|
||||
end
|
||||
|
||||
local function Input(state, text, cursor, x,y,w,h)
|
||||
local c = color[state]
|
||||
if state ~= 'normal' then
|
||||
love.graphics.setColor(c.fg)
|
||||
love.graphics.rectangle('fill', x+3,y+3,w,h)
|
||||
end
|
||||
love.graphics.setColor(c.bg)
|
||||
love.graphics.rectangle('fill', x,y,w,h)
|
||||
love.graphics.setColor(c.fg)
|
||||
|
||||
local lw = love.graphics.getLineWidth()
|
||||
love.graphics.setLineWidth(1)
|
||||
love.graphics.rectangle('line', x,y,w,h)
|
||||
|
||||
local f = love.graphics.getFont()
|
||||
local th = f:getHeight(text)
|
||||
local cursorPos = x + 2 + f:getWidth(text:sub(1,cursor))
|
||||
|
||||
love.graphics.print(text, x+2,y+(h-th)/2)
|
||||
love.graphics.line(cursorPos, y+4, cursorPos, y+h-4)
|
||||
|
||||
love.graphics.setLineWidth(lw)
|
||||
end
|
||||
|
||||
local function Checkbox(state, checked, x,y,w,h)
|
||||
local c = color[state]
|
||||
if state ~= 'normal' then
|
||||
love.graphics.setColor(c.fg)
|
||||
love.graphics.rectangle('fill', x+3,y+3,w,h)
|
||||
end
|
||||
love.graphics.setColor(c.bg)
|
||||
love.graphics.rectangle('fill', x,y,w,h)
|
||||
love.graphics.setColor(c.fg)
|
||||
love.graphics.rectangle('line', x,y,w,h)
|
||||
if checked then
|
||||
local r = math.max(math.min(w/2,h/2) - 3, 2)
|
||||
love.graphics.circle('fill', x+w/2,y+h/2,r)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- the style
|
||||
return {
|
||||
color = color,
|
||||
Button = Button,
|
||||
Label = Label,
|
||||
Slider = Slider,
|
||||
Slider2D = Slider2D,
|
||||
Input = Input,
|
||||
Checkbox = Checkbox,
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue