aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Richter <vrld@vrld.org>2012-02-07 23:10:29 +0100
committerMatthias Richter <vrld@vrld.org>2012-02-07 23:10:29 +0100
commit40dbc7134bae5cff48c2262d92721a3dd5394f84 (patch)
treede6097abf421b873aa6052928c56fbd578bafa01
downloadQuickie-40dbc7134bae5cff48c2262d92721a3dd5394f84.tar.gz
Quickie-40dbc7134bae5cff48c2262d92721a3dd5394f84.tar.bz2
Quickie-40dbc7134bae5cff48c2262d92721a3dd5394f84.tar.xz
Quickie-40dbc7134bae5cff48c2262d92721a3dd5394f84.zip
Initial commit
-rw-r--r--README.md47
-rw-r--r--button.lua27
-rw-r--r--checkbox.lua19
-rw-r--r--core.lua153
-rw-r--r--imgui.lovebin0 -> 5053 bytes
-rw-r--r--init.lua11
-rw-r--r--input.lua43
-rw-r--r--label.lua9
-rw-r--r--slider.lua44
-rw-r--r--slider2d.lua55
-rw-r--r--style-default.lua134
11 files changed, 542 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5aed0ff
--- /dev/null
+++ b/README.md
@@ -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
diff --git a/button.lua b/button.lua
new file mode 100644
index 0000000..2374645
--- /dev/null
+++ b/button.lua
@@ -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
+
diff --git a/checkbox.lua b/checkbox.lua
new file mode 100644
index 0000000..c5f5fce
--- /dev/null
+++ b/checkbox.lua
@@ -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
+
diff --git a/core.lua b/core.lua
new file mode 100644
index 0000000..f57caa9
--- /dev/null
+++ b/core.lua
@@ -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,
+}
diff --git a/imgui.love b/imgui.love
new file mode 100644
index 0000000..720c9b1
--- /dev/null
+++ b/imgui.love
Binary files differ
diff --git a/init.lua b/init.lua
new file mode 100644
index 0000000..3b4e70c
--- /dev/null
+++ b/init.lua
@@ -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')
+}
diff --git a/input.lua b/input.lua
new file mode 100644
index 0000000..2ba29b5
--- /dev/null
+++ b/input.lua
@@ -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
diff --git a/label.lua b/label.lua
new file mode 100644
index 0000000..76f3c92
--- /dev/null
+++ b/label.lua
@@ -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
+
diff --git a/slider.lua b/slider.lua
new file mode 100644
index 0000000..ae75c01
--- /dev/null
+++ b/slider.lua
@@ -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
diff --git a/slider2d.lua b/slider2d.lua
new file mode 100644
index 0000000..023d16f
--- /dev/null
+++ b/slider2d.lua
@@ -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
diff --git a/style-default.lua b/style-default.lua
new file mode 100644
index 0000000..7c85e46
--- /dev/null
+++ b/style-default.lua
@@ -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,
+}