aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Richter <vrld@vrld.org>2012-05-09 21:27:45 +0200
committerMatthias Richter <vrld@vrld.org>2012-05-09 21:27:45 +0200
commitadc78875875a64d40b4cf65a8618fb49a77e8080 (patch)
tree132bc89a60cf3bc60cf9acacdf0c31aad6df969e
parent7b1b6e4176829d4c9d897821c8a2d51da78e0243 (diff)
downloadQuickie-adc78875875a64d40b4cf65a8618fb49a77e8080.tar.gz
Quickie-adc78875875a64d40b4cf65a8618fb49a77e8080.tar.bz2
Quickie-adc78875875a64d40b4cf65a8618fb49a77e8080.tar.xz
Quickie-adc78875875a64d40b4cf65a8618fb49a77e8080.zip
Mega update: Auto layout, code cleanup, api change.
Basically half a rewrite.
-rw-r--r--button.lua44
-rw-r--r--checkbox.lua46
-rw-r--r--core.lua174
-rw-r--r--group.lua143
-rw-r--r--init.lua3
-rw-r--r--input.lua69
-rw-r--r--keyboard.lua89
-rw-r--r--label.lua36
-rw-r--r--mouse.lua86
-rw-r--r--slider.lua61
-rw-r--r--slider2d.lua82
11 files changed, 605 insertions, 228 deletions
diff --git a/button.lua b/button.lua
index b55f35c..fd0cff4 100644
--- a/button.lua
+++ b/button.lua
@@ -23,31 +23,55 @@ 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 core = require((...):match("(.-)[^%.]+$") .. 'core')
+local BASE = (...):match("(.-)[^%.]+$")
+local core = require(BASE .. 'core')
+local group = require(BASE .. 'group')
+local mouse = require(BASE .. 'mouse')
+local keyboard = require(BASE .. 'keyboard')
-- the widget
-return function(title, x,y, w,h, widgetHit, draw)
+-- {text = text, pos = {x, y}, size={w, h}, widgetHit=widgetHit, draw=draw}
+return function(w)
+ assert(type(w) == "table" and w.text, "Invalid argument")
+
+ -- if tight fit requested, compute the size according to text size
+ -- have a 2px margin around the text
+ local tight = w.size and (w.size[1] == 'tight' or w.size[2] == 'tight')
+ if tight then
+ local f = assert(love.graphics.getFont())
+ if w.size[1] == 'tight' then
+ w.size[1] = f:getWidth(w.text) + 4
+ end
+ if w.size[2] == 'tight' then
+ w.size[2] = f:getHeight(w.text) + 4
+ end
+ end
+
-- Generate unique identifier for gui state update and querying.
local id = core.generateID()
+ -- group.getRect determines the position and size of the widget according
+ -- to the currently active group. Both arguments may be omitted.
+ local pos, size = group.getRect(w.pos, w.size)
+
+ -- mouse.updateWidget(id, {x,y}, {w,h}, widgetHit) updates the state for this widget.
+ -- widgetHit may be nil, in which case mouse.widgetHit will be used.
-- 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, widgetHit, x,y,w,h) updates the state for this widget.
- core.mouse.updateState(id, widgetHit or core.style.widgetHit, x,y,w,h)
+ mouse.updateWidget(id, pos, size, w.widgetHit)
- -- core.makeCyclable makes the item focus on tab or whatever binding is
+ -- keyboard.makeCyclable makes the item focus on tab or whatever binding is
-- in place (see core.keyboard.cycle). Cycle order is determied by the
-- order you call the widget functions.
- core.makeCyclable(id)
+ keyboard.makeCyclable(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)
+ core.registerDraw(id, w.draw or core.style.Button,
+ w.text, pos[1],pos[2], size[1],size[2])
- return core.mouse.releasedOn(id) or
- (core.keyboard.key == 'return' and core.hasKeyFocus(id))
+ return mouse.releasedOn(id) or (keyboard.key == 'return' and keyboard.hasFocus(id))
end
diff --git a/checkbox.lua b/checkbox.lua
index 22c325a..e04f6a6 100644
--- a/checkbox.lua
+++ b/checkbox.lua
@@ -23,22 +23,46 @@ 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 core = require((...):match("(.-)[^%.]+$") .. 'core')
+local BASE = (...):match("(.-)[^%.]+$")
+local core = require(BASE .. 'core')
+local group = require(BASE .. 'group')
+local mouse = require(BASE .. 'mouse')
+local keyboard = require(BASE .. 'keyboard')
+
+-- {info = {checked = status, label = "", algin = "left"}, pos = {x, y}, size={w, h}, widgetHit=widgetHit, draw=draw}
+return function(w)
+ assert(type(w) == "table")
+ w.info.label = w.info.label or ""
+
+ local tight = w.size and (w.size[1] == 'tight' or w.size[2] == 'tight')
+ if tight then
+ local f = assert(love.graphics.getFont())
+ if w.size[1] == 'tight' then
+ w.size[1] = f:getWidth(w.info.label)
+ end
+ if w.size[2] == 'tight' then
+ w.size[2] = f:getHeight(w.info.label)
+ end
+ -- account for the checkbox
+ local bw = math.min(w.size[1] or group.size[1], w.size[2] or group.size[2])
+ w.size[1] = w.size[1] + bw + 4
+ end
-return function(info, x,y, w,h, widgetHit, draw)
local id = core.generateID()
+ local pos, size = group.getRect(w.pos, w.size)
- core.mouse.updateState(id, widgetHit or core.style.widgetHit, x,y,w,h)
- core.makeCyclable(id)
- core.registerDraw(id, draw or core.style.Checkbox, info.checked,x,y,w,h)
+ mouse.updateWidget(id, pos, size, w.widgetHit)
+ keyboard.makeCyclable(id)
- 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
+ local checked = w.info.checked
+ local key = keyboard.key
+ if mouse.releasedOn(id) or ((key == 'return' or key == ' ') and keyboard.hasFocus(id)) then
+ w.info.checked = not w.info.checked
end
- return info.checked ~= checked
+ core.registerDraw(id, w.draw or core.style.Checkbox,
+ w.info.checked, w.info.label, w.info.align or 'left', pos[1], pos[2], size[1], size[2])
+
+ return w.info.checked ~= checked
end
diff --git a/core.lua b/core.lua
index 74aebfb..371b114 100644
--- a/core.lua
+++ b/core.lua
@@ -24,81 +24,17 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]--
--- state
-local context = {maxid = 0}
-local draw_items = {n = 0}
local NO_WIDGET = function()end
+local BASE = (...):match("(.-)[^%.]+$")
+local group = require(BASE .. 'group')
+local mouse = require(BASE .. 'mouse')
+local keyboard = require(BASE .. 'keyboard')
-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
-
-local function disableKeyFocus() return setKeyFocus{} end
-local function clearKeyFocus() return setKeyFocus(nil) end
-
--- input
-local mouse = {x = 0, y = 0, down = false}
-local keyboard = {key = nil, code = -1}
-keyboard.cycle = {
- -- binding = {key = key, modifier1, modifier2, ...} XXX: modifiers are OR-ed!
- prev = {key = 'tab', 'lshift', 'rshift'},
- next = {key = 'tab'},
-}
-
-function mouse.updateState(id, widgetHit, ...)
- if widgetHit(mouse.x, mouse.y, ...) 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
- setKeyFocus(id)
- end
-end
-
-function keyboard.isBindingDown(bind)
- local modifiersDown = #bind == 0 or love.keyboard.isDown(unpack(bind))
- return keyboard.key == bind.key and modifiersDown
-end
+--
+-- Helper functions
+--
-local function makeCyclable(id)
- keyboard.tryGrab(id)
- if hasKeyFocus(id) then
- if keyboard.isBindingDown(keyboard.cycle.prev) then
- setKeyFocus(context.lastwidget)
- keyboard.key = nil
- elseif keyboard.isBindingDown(keyboard.cycle.next) then
- setKeyFocus(nil)
- keyboard.key = nil
- end
- end
- context.lastwidget = id
-end
-
--- helper functions
+-- evaluates all arguments
local function strictAnd(...)
local n = select("#", ...)
local ret = true
@@ -124,66 +60,76 @@ local function save_unpack(t, i)
return t[i], save_unpack(t, i+1)
end
+--
+-- Widget ID
+--
+local maxid = 0
+local function generateID()
+ maxid = maxid + 1
+ return maxid
+end
+
+--
+-- Drawing / Frame update
+--
+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 font = love.graphics.getFont()
+
local state = 'normal'
- if isHot(id) or hasKeyFocus(id) then
- state = isActive(id) and 'active' or 'hot'
+ if mouse.isHot(id) or keyboard.hasFocus(id) then
+ state = mouse.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
+ draw_items[draw_items.n] = function()
+ if font then love.graphics.setFont(font) end
+ 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
+ keyboard.endFrame()
+ mouse.endFrame()
+ group.endFrame()
+
+ -- save graphics state
+ local c = {love.graphics.getColor()}
+ local f = love.graphics.getFont()
+ local lw = love.graphics.getLineWidth()
+ local ls = love.graphics.getLineStyle()
for i = 1,draw_items.n do draw_items[i]() end
- -- prepare for next frame
- draw_items.n = 0
- context.maxid = 0
+ -- restore graphics state
+ love.graphics.setLine(lw, ls)
+ if f then love.graphics.setFont(f) end
+ love.graphics.setColor(c)
- -- update mouse status
- setHot(nil)
- mouse.x, mouse.y = love.mouse.getPosition()
- mouse.down = love.mouse.isDown('l')
+ draw_items.n = 0
+ maxid = 0
- keyboard.key, keyboard.code = nil, -1
+ group.beginFrame()
+ mouse.beginFrame()
+ keyboard.beginFrame()
end
+--
+-- The Module
+--
return {
- mouse = mouse,
- keyboard = keyboard,
-
- generateID = generateID,
- setHot = setHot,
- setActive = setActive,
- setKeyFocus = setKeyFocus,
- isHot = isHot,
- isActive = isActive,
- hasKeyFocus = hasKeyFocus,
-
- disableKeyFocus = disableKeyFocus,
- enableKeyFocus = clearKeyFocus,
- clearKeyFocus = clearKeyFocus,
- makeCyclable = makeCyclable,
-
- style = require((...):match("(.-)[^%.]+$") .. '.style-default'),
- color = color,
- registerDraw = registerDraw,
- draw = draw,
-
- strictAnd = strictAnd,
- strictOr = strictOr,
- save_pack = save_pack,
- save_unpack = save_unpack,
+ generateID = generateID,
+
+ style = require((...):match("(.-)[^%.]+$") .. 'style-default'),
+ registerDraw = registerDraw,
+ draw = draw,
+
+ strictAnd = strictAnd,
+ strictOr = strictOr,
+ save_pack = save_pack,
+ save_unpack = save_unpack,
}
diff --git a/group.lua b/group.lua
new file mode 100644
index 0000000..50e413e
--- /dev/null
+++ b/group.lua
@@ -0,0 +1,143 @@
+--[[
+Copyright (c) 2012 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.
+]]--
+
+local stack = {n = 0}
+local default = {
+ pos = {0,0},
+ grow = {0,0},
+ spacing = 2,
+ size = {100, 30},
+ upper_left = {0,0},
+ lower_right = {0,0},
+}
+local current = default
+
+local Grow = {
+ none = { 0, 0},
+ up = { 0, -1},
+ down = { 0, 1},
+ left = {-1, 0},
+ right = { 1, 0}
+}
+
+-- {grow = grow, spacing = spacing, size = size, pos = pos}
+local function push(info)
+ local grow = info.grow or "none"
+ local spacing = info.spacing or default.spacing
+
+ local size = {
+ info.size and info.size[1] or current.size[1],
+ info.size and info.size[2] or current.size[2]
+ }
+
+ local pos = {current.pos[1], current.pos[2]}
+ if info.pos then
+ pos[1] = pos[1] + (info.pos[1] or 0)
+ pos[2] = pos[2] + (info.pos[2] or 0)
+ end
+
+ assert(size, "Size neither specified nor derivable from parent group.")
+ assert(pos, "Position neither specified nor derivable from parent group.")
+ grow = assert(Grow[grow], "Invalid grow: " .. tostring(grow))
+
+ current = {
+ pos = pos,
+ grow = grow,
+ size = size,
+ spacing = spacing,
+ upper_left = { math.huge, math.huge},
+ lower_right = {-math.huge, -math.huge},
+ }
+ stack.n = stack.n + 1
+ stack[stack.n] = current
+end
+
+local function advance(pos, size)
+ current.upper_left[1] = math.min(current.upper_left[1], pos[1])
+ current.upper_left[2] = math.min(current.upper_left[2], pos[2])
+ current.lower_right[1] = math.max(current.lower_right[1], pos[1] + size[1])
+ current.lower_right[2] = math.max(current.lower_right[2], pos[2] + size[2])
+
+ if current.grow[1] ~= 0 then
+ current.pos[1] = pos[1] + current.grow[1] * (size[1] + current.spacing)
+ end
+ if current.grow[2] ~= 0 then
+ current.pos[2] = pos[2] + current.grow[2] * (size[2] + current.spacing)
+ end
+ return pos, size
+end
+
+local function getRect(pos, size)
+ pos = {pos and pos[1] or 0, pos and pos[2] or 0}
+ size = {size and size[1] or current.size[1], size and size[2] or current.size[2]}
+
+ -- growing left/up: update current position to account for differnt size
+ if current.grow[1] < 0 and current.size[1] ~= size[1] then
+ current.pos[1] = current.pos[1] + (current.size[1] - size[1])
+ end
+ if current.grow[2] < 0 and current.size[2] ~= size[2] then
+ current.pos[2] = current.pos[2] - (current.size[2] - size[2])
+ end
+
+ pos[1] = pos[1] + current.pos[1]
+ pos[2] = pos[2] + current.pos[2]
+
+ return advance(pos, size)
+end
+
+local function pop()
+ assert(stack.n > 0, "Group stack is empty.")
+ stack.n = stack.n - 1
+ local child = current
+ current = stack[stack.n] or default
+
+ local size = {
+ child.lower_right[1] - math.max(child.upper_left[1], current.pos[1]),
+ child.lower_right[2] - math.max(child.upper_left[2], current.pos[2])
+ }
+ advance(current.pos, size)
+end
+
+local function beginFrame()
+ current = default
+ stack.n = 0
+end
+
+local function endFrame()
+ -- future use?
+end
+
+return setmetatable({
+ push = push,
+ pop = pop,
+ getRect = getRect,
+ advance = advance,
+ beginFrame = beginFrame,
+ endFrame = endFrame,
+ default = default,
+}, {__index = function(_,k)
+ return ({size = current.size, pos = current.pos})[k]
+end})
diff --git a/init.lua b/init.lua
index c122ee5..11c5e92 100644
--- a/init.lua
+++ b/init.lua
@@ -29,6 +29,9 @@ assert(not BASE:match('%.init%.$'), "Invalid require path `"..(...).."' (drop th
return {
core = require(BASE .. 'core'),
+ group = require(BASE .. 'group'),
+ mouse = require(BASE .. 'mouse'),
+ keyboard = require(BASE .. 'keyboard'),
Button = require(BASE .. 'button'),
Slider = require(BASE .. 'slider'),
Slider2D = require(BASE .. 'slider2d'),
diff --git a/input.lua b/input.lua
index 3e83988..64f2ced 100644
--- a/input.lua
+++ b/input.lua
@@ -24,47 +24,56 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]--
-local core = require((...):match("(.-)[^%.]+$") .. 'core')
+local BASE = (...):match("(.-)[^%.]+$")
+local core = require(BASE .. 'core')
+local group = require(BASE .. 'group')
+local mouse = require(BASE .. 'mouse')
+local keyboard = require(BASE .. 'keyboard')
-return function(info, x,y,w,h, widgetHit, draw)
- info.text = info.text or ""
- info.cursor = math.min(info.cursor or info.text:len(), info.text:len())
+-- {info = {text = "", cursor = text:len()}, pos = {x, y}, size={w, h}, widgetHit=widgetHit, draw=draw}
+return function(w)
+ assert(type(w) == "table" and type(w.info) == "table", "Invalid argument")
+ w.info.text = w.info.text or ""
+ w.info.cursor = math.min(w.info.cursor or w.info.text:len(), w.info.text:len())
local id = core.generateID()
- core.mouse.updateState(id, widgetHit or core.style.widgetHit, x,y,w,h)
- core.makeCyclable(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)
- if not core.hasKeyFocus(id) then return false end
+ local pos, size = group.getRect(w.pos, w.size)
+ mouse.updateWidget(id, pos, size, w.widgetHit)
+ keyboard.makeCyclable(id)
+ if mouse.isActive(id) then keyboard.setFocus(id) end
local changed = false
+ if not keyboard.hasFocus(id) then
+ --[[nothing]]
-- 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)
+ elseif keyboard.key == 'backspace' then
+ w.info.text = w.info.text:sub(1,w.info.cursor-1) .. w.info.text:sub(w.info.cursor+1)
+ w.info.cursor = math.max(0, w.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)
+ elseif keyboard.key == 'delete' then
+ w.info.text = w.info.text:sub(1,w.info.cursor) .. w.info.text:sub(w.info.cursor+2)
+ w.info.cursor = math.min(w.info.text:len(), w.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
+ elseif keyboard.key == 'left' then
+ w.info.cursor = math.max(0, w.info.cursor-1)
+ elseif keyboard.key == 'right' then
+ w.info.cursor = math.min(w.info.text:len(), w.info.cursor+1)
+ elseif keyboard.key == 'home' then
+ w.info.cursor = 0
+ elseif keyboard.key == 'end' then
+ w.info.cursor = w.info.text:len()
+ -- info
+ elseif keyboard.code >= 32 and keyboard.code < 127 then
+ local left = w.info.text:sub(1,w.info.cursor)
+ local right = w.info.text:sub(w.info.cursor+1)
+ w.info.text = table.concat{left, string.char(keyboard.code), right}
+ w.info.cursor = w.info.cursor + 1
changed = true
end
+ core.registerDraw(id, w.draw or core.style.Input,
+ w.info.text, w.info.cursor, pos[1],pos[2], size[1],size[2])
+
return changed
end
diff --git a/keyboard.lua b/keyboard.lua
new file mode 100644
index 0000000..0b20570
--- /dev/null
+++ b/keyboard.lua
@@ -0,0 +1,89 @@
+--[[
+Copyright (c) 2012 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.
+]]--
+
+local key,code = nil, -1
+local focus, lastwidget
+
+local cycle = {
+ -- binding = {key = key, modifier1, modifier2, ...} XXX: modifiers are OR-ed!
+ prev = {key = 'tab', 'lshift', 'rshift'},
+ next = {key = 'tab'},
+}
+
+local function pressed(...) key, code = ... end
+local function setFocus(id) focus = id end
+local function disableFocus() focus = NO_WIDGET end
+local function clearFocus() focus = nil end
+local function hasFocus(id) return id == focus end
+
+local function tryGrab(id)
+ if not focus then
+ setFocus(id)
+ end
+end
+
+local function isBindingDown(bind)
+ local modifiersDown = #bind == 0 or love.keyboard.isDown(unpack(bind))
+ return key == bind.key and modifiersDown
+end
+
+local function makeCyclable(id)
+ tryGrab(id)
+ if hasFocus(id) then
+ if isBindingDown(cycle.prev) then
+ setFocus(lastwidget)
+ key = nil
+ elseif isBindingDown(cycle.next) then
+ setFocus(nil)
+ key = nil
+ end
+ end
+ lastwidget = id
+end
+
+local function beginFrame()
+ -- for future use?
+end
+
+local function endFrame()
+ key, code = nil, -1
+end
+
+return setmetatable({
+ cycle = cycle,
+ pressed = pressed,
+ tryGrab = tryGrab,
+ isBindingDown = isBindingDown,
+ setFocus = setFocus,
+ disableFocus = disableFocus,
+ enableFocus = clearFocus,
+ clearFocus = clearFocus,
+ hasFocus = hasFocus,
+ makeCyclable = makeCyclable,
+
+ beginFrame = beginFrame,
+ endFrame = endFrame,
+}, {__index = function(_,k) return ({key = key, code = code})[k] end})
diff --git a/label.lua b/label.lua
index ed7ad80..62b3950 100644
--- a/label.lua
+++ b/label.lua
@@ -24,12 +24,38 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]--
-local core = require((...):match("(.-)[^%.]+$") .. 'core')
+local BASE = (...):match("(.-)[^%.]+$")
+local core = require(BASE .. 'core')
+local group = require(BASE .. 'group')
+local mouse = require(BASE .. 'mouse')
+local keyboard = require(BASE .. 'keyboard')
+
+-- {text = text, align = align, pos = {x, y}, size={w, h}, widgetHit=widgetHit, draw=draw}
+return function(w)
+ assert(type(w) == "table" and w.text, "Invalid argument")
+ w.align = w.align or 'left'
+
+ local tight = w.size and (w.size[1] == 'tight' or w.size[2] == 'tight')
+ if tight then
+ local f = assert(love.graphics.getFont())
+ if w.size[1] == 'tight' then
+ w.size[1] = f:getWidth(w.text)
+ end
+ if w.size[2] == 'tight' then
+ w.size[2] = f:getHeight(w.text)
+ end
+ end
-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
+ local pos, size = group.getRect(w.pos, w.size)
+
+ if keyboard.hasFocus(id) then
+ keyboard.clearFocus()
+ end
+
+ core.registerDraw(id, draw or core.style.Label,
+ w.text, w.align, pos[1],pos[2], size[1],size[2])
+
+ return mouse.releasedOn(id)
end
diff --git a/mouse.lua b/mouse.lua
new file mode 100644
index 0000000..eac46f5
--- /dev/null
+++ b/mouse.lua
@@ -0,0 +1,86 @@
+--[[
+Copyright (c) 2012 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.
+]]--
+
+local _M -- holds the module. needed to make widgetHit overridable
+
+local x,y = 0,0
+local down = false
+local hot, active = nil, nil
+
+local function widgetHit(mouse, pos, size)
+ return mouse[1] >= pos[1] and mouse[1] <= pos[1] + size[1] and
+ mouse[2] >= pos[2] and mouse[2] <= pos[2] + size[2]
+end
+
+local function setHot(id) hot = id end
+local function setActive(id) active = id end
+local function isHot(id) return id == hot end
+local function isActive(id) return id == active end
+
+local function updateWidget(id, pos, size, hit)
+ hit = hit or _M.widgetHit
+
+ if hit({x,y}, pos, size) then
+ setHot(id)
+ if not active and down then
+ setActive(id)
+ end
+ end
+end
+
+local function releasedOn(id)
+ return not down and isHot(id) and isActive(id)
+end
+
+local function beginFrame()
+ hot = nil
+ x,y = love.mouse.getPosition()
+ down = love.mouse.isDown('l')
+end
+
+local function endFrame()
+ if not down then -- released
+ setActive(nil)
+ elseif not active then -- clicked outside
+ setActive(NO_WIDGET)
+ end
+end
+
+_M = {
+ widgetHit = widgetHit,
+ setHot = setHot,
+ setActive = setActive,
+ isHot = isHot,
+ isActive = isActive,
+ updateWidget = updateWidget,
+ releasedOn = releasedOn,
+
+ beginFrame = beginFrame,
+ endFrame = endFrame,
+}
+
+-- metatable provides getters to x, y and down
+return setmetatable(_M, {__index = function(_,k) return ({x = x, y = y, down = down})[k] end})
diff --git a/slider.lua b/slider.lua
index 4a88515..4561d4a 100644
--- a/slider.lua
+++ b/slider.lua
@@ -24,47 +24,56 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]--
-local core = require((...):match("(.-)[^%.]+$") .. 'core')
+local BASE = (...):match("(.-)[^%.]+$")
+local core = require(BASE .. 'core')
+local group = require(BASE .. 'group')
+local mouse = require(BASE .. 'mouse')
+local keyboard = require(BASE .. 'keyboard')
-return function(info, x,y,w,h, widgetHit, 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)
+-- {info = {value = v, min = 0, max = 1, step = (max-min)/20}, vertical = boolean, pos = {x, y}, size={w, h}, widgetHit=widgetHit, draw=draw}
+return function(w)
+ assert(type(w) == 'table' and type(w.info) == "table" and w.info.value, "Invalid argument.")
+ w.info.min = w.info.min or 0
+ w.info.max = w.info.max or math.max(w.info.value, 1)
+ w.info.step = w.info.step or (w.info.max - w.info.min) / 20
+ local fraction = (w.info.value - w.info.min) / (w.info.max - w.info.min)
local id = core.generateID()
- core.mouse.updateState(id, widgetHit or core.style.widgetHit, x,y,w,h)
- core.makeCyclable(id)
- core.registerDraw(id,draw or core.style.Slider, fraction, x,y,w,h, info.vertical)
+ local pos, size = group.getRect(w.pos, w.info.size)
+
+ mouse.updateWidget(id, pos, size, w.widgetHit)
+ keyboard.makeCyclable(id)
-- 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))
+ local changed = false
+ if mouse.isActive(id) then
+ keyboard.setFocus(id)
+ if w.vertical then
+ fraction = math.min(1, math.max(0, (pos[2] - mouse.y + size[2]) / size[2]))
else
- fraction = math.min(1, math.max(0, (core.mouse.x - x) / w))
+ fraction = math.min(1, math.max(0, (mouse.x - pos[1]) / size[1]))
end
- local v = fraction * (info.max - info.min) + info.min
- if v ~= info.value then
- info.value = v
- return true
+ local v = fraction * (w.info.max - w.info.min) + w.info.min
+ if v ~= w.info.value then
+ w.info.value = v
+ changed = 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)
+ if keyboard.hasFocus(id) then
+ local keys = w.vertical and {'up', 'down'} or {'right', 'left'}
+ if keyboard.key == keys[1] then
+ w.info.value = math.min(w.info.max, w.info.value + w.info.step)
changed = true
- elseif core.keyboard.key == keys[2] then
- info.value = math.max(info.min, info.value - info.step)
+ elseif keyboard.key == keys[2] then
+ w.info.value = math.max(w.info.min, w.info.value - w.info.step)
changed = true
end
end
+ core.registerDraw(id, w.draw or core.style.Slider,
+ fraction, w.vertical, pos[1],pos[2], size[1],size[2])
+
return changed
end
diff --git a/slider2d.lua b/slider2d.lua
index 20f063c..19abaf1 100644
--- a/slider2d.lua
+++ b/slider2d.lua
@@ -24,58 +24,76 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]--
-local core = require((...):match("(.-)[^%.]+$") .. 'core')
+local BASE = (...):match("(.-)[^%.]+$")
+local core = require(BASE .. 'core')
+local group = require(BASE .. 'group')
+local mouse = require(BASE .. 'mouse')
+local keyboard = require(BASE .. 'keyboard')
+
+-- {info = {value = {x,y}, min = {0,0}, max = {1,1}, step = (max-min)/20}, pos = {x, y}, size={w, h}, widgetHit=widgetHit, draw=draw}
+return function(w)
+ assert(type(w) == 'table' and type(w.info) == "table" and w.info.value, "Invalid argument.")
+ w.info.min = {
+ w.info.min and w.info.min[1] or 0,
+ w.info.min and w.info.min[2] or 0,
+ }
+ w.info.max = {
+ w.info.max and w.info.max[1] or math.max(1, w.info.value[1]),
+ w.info.max and w.info.max[2] or math.max(1, w.info.value[2]),
+ }
+ w.info.step = {
+ w.info.step and w.info.step[1] or (w.info.max[1] - w.info.min[1]) / 20,
+ w.info.step and w.info.step[2] or (w.info.max[2] - w.info.min[2]) / 20,
+ }
-return function(info, x,y,w,h, widgetHit, 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),
+ (w.info.value[1] - w.info.min[1]) / (w.info.max[1] - w.info.min[1]),
+ (w.info.value[2] - w.info.min[2]) / (w.info.max[2] - w.info.min[2]),
}
local id = core.generateID()
- core.mouse.updateState(id, widgetHit or core.style.widgetHit, x,y,w,h)
- core.makeCyclable(id)
- core.registerDraw(id,draw or core.style.Slider2D, fraction, x,y,w,h)
+ local pos, size = group.getRect(w.pos, w.size)
+
+ mouse.updateWidget(id, pos, size, w.widgetHit)
+ keyboard.makeCyclable(id)
-- update value
- if core.isActive(id) then
- core.setKeyFocus(id)
+ local changed = false
+ if mouse.isActive(id) then
+ keyboard.setFocus(id)
fraction = {
- x = (core.mouse.x - x) / w,
- y = (core.mouse.y - y) / h,
+ math.min(1, math.max(0, (mouse.x - pos[1]) / size[1])),
+ math.min(1, math.max(0, (mouse.y - pos[2]) / size[2])),
}
- 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,
+ fraction[1] * (w.info.max[1] - w.info.min[1]) + w.info.min[1],
+ fraction[2] * (w.info.max[2] - w.info.min[2]) + w.info.min[2],
}
- if v.x ~= info.value.x or v.y ~= info.value.y then
- info.value = v
- return true
+ if v[1] ~= w.info.value[1] or v[2] ~= w.info.value[2] then
+ w.info.value = v
+ changed = 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)
+ if keyboard.hasFocus(id) then
+ if keyboard.key == 'down' then
+ w.info.value[2] = math.min(w.info.max[2], w.info.value[2] + w.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)
+ elseif keyboard.key == 'up' then
+ w.info.value[2] = math.max(w.info.min[2], w.info.value[2] - w.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)
+ if keyboard.key == 'right' then
+ w.info.value[1] = math.min(w.info.max[1], w.info.value[1] + w.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)
+ elseif keyboard.key == 'left' then
+ w.info.value[1] = math.max(w.info.min[1], w.info.value[1] - w.info.step.x)
changed = true
end
end
+
+ core.registerDraw(id, w.draw or core.style.Slider2D,
+ fraction, pos[1],pos[2], size[1],size[2])
+
return changed
end