Initial commit

This commit is contained in:
Matthias Richter 2012-02-07 23:10:29 +01:00
commit 40dbc7134b
11 changed files with 542 additions and 0 deletions

47
README.md Normal file
View 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
View 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
View 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
View 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

Binary file not shown.

11
init.lua Normal file
View 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
View 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
View 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
View 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
View 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
View 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,
}