Mega update: Auto layout, code cleanup, api change.

Basically half a rewrite.
This commit is contained in:
Matthias Richter 2012-05-09 21:27:45 +02:00
parent 7b1b6e4176
commit adc7887587
11 changed files with 605 additions and 228 deletions

View file

@ -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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. 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 -- 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. -- Generate unique identifier for gui state update and querying.
local id = core.generateID() 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: -- The widget mouse-state can be:
-- hot (mouse over widget), -- hot (mouse over widget),
-- active (mouse pressed on widget) or -- active (mouse pressed on widget) or
-- normal (mouse not on widget and not pressed on widget). -- normal (mouse not on widget and not pressed on widget).
-- mouse.updateWidget(id, pos, size, w.widgetHit)
-- 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)
-- 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 -- in place (see core.keyboard.cycle). Cycle order is determied by the
-- order you call the widget functions. -- order you call the widget functions.
core.makeCyclable(id) keyboard.makeCyclable(id)
-- core.registerDraw(id, drawfunction, drawfunction-arguments...) -- core.registerDraw(id, drawfunction, drawfunction-arguments...)
-- shows widget when core.draw() is called. -- 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 return mouse.releasedOn(id) or (keyboard.key == 'return' and keyboard.hasFocus(id))
(core.keyboard.key == 'return' and core.hasKeyFocus(id))
end end

View file

@ -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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. 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 = {checked = status, label = "", algin = "left"}, pos = {x, y}, size={w, h}, widgetHit=widgetHit, draw=draw}
local id = core.generateID() return function(w)
assert(type(w) == "table")
w.info.label = w.info.label or ""
core.mouse.updateState(id, widgetHit or core.style.widgetHit, x,y,w,h) local tight = w.size and (w.size[1] == 'tight' or w.size[2] == 'tight')
core.makeCyclable(id) if tight then
core.registerDraw(id, draw or core.style.Checkbox, info.checked,x,y,w,h) local f = assert(love.graphics.getFont())
if w.size[1] == 'tight' then
local checked = info.checked w.size[1] = f:getWidth(w.info.label)
local key = core.keyboard.key end
if core.mouse.releasedOn(id) or if w.size[2] == 'tight' then
(core.hasKeyFocus(id) and key == 'return' or key == ' ') then w.size[2] = f:getHeight(w.info.label)
info.checked = not info.checked 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 end
return info.checked ~= checked local id = core.generateID()
local pos, size = group.getRect(w.pos, w.size)
mouse.updateWidget(id, pos, size, w.widgetHit)
keyboard.makeCyclable(id)
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
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 end

170
core.lua
View file

@ -24,81 +24,17 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
]]-- ]]--
-- state
local context = {maxid = 0}
local draw_items = {n = 0}
local NO_WIDGET = function()end 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 -- Helper functions
return context.maxid --
end
local function setHot(id) context.hot = id end -- evaluates all arguments
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
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
local function strictAnd(...) local function strictAnd(...)
local n = select("#", ...) local n = select("#", ...)
local ret = true local ret = true
@ -124,66 +60,76 @@ local function save_unpack(t, i)
return t[i], save_unpack(t, i+1) return t[i], save_unpack(t, i+1)
end 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, ...) local function registerDraw(id, f, ...)
assert(type(f) == 'function' or (getmetatable(f) or {}).__call, assert(type(f) == 'function' or (getmetatable(f) or {}).__call,
'Drawing function is not a callable type!') 'Drawing function is not a callable type!')
local font = love.graphics.getFont()
local state = 'normal' local state = 'normal'
if isHot(id) or hasKeyFocus(id) then if mouse.isHot(id) or keyboard.hasFocus(id) then
state = isActive(id) and 'active' or 'hot' state = mouse.isActive(id) and 'active' or 'hot'
end end
local rest = save_pack(...) local rest = save_pack(...)
draw_items.n = draw_items.n + 1 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 end
-- actually update-and-draw -- actually update-and-draw
local function draw() local function draw()
-- close frame state keyboard.endFrame()
if not mouse.down then -- released mouse.endFrame()
setActive(nil) group.endFrame()
elseif not context.active then -- clicked outside
setActive(NO_WIDGET) -- save graphics state
end 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 for i = 1,draw_items.n do draw_items[i]() end
-- prepare for next frame -- restore graphics state
love.graphics.setLine(lw, ls)
if f then love.graphics.setFont(f) end
love.graphics.setColor(c)
draw_items.n = 0 draw_items.n = 0
context.maxid = 0 maxid = 0
-- update mouse status group.beginFrame()
setHot(nil) mouse.beginFrame()
mouse.x, mouse.y = love.mouse.getPosition() keyboard.beginFrame()
mouse.down = love.mouse.isDown('l')
keyboard.key, keyboard.code = nil, -1
end end
--
-- The Module
--
return { return {
mouse = mouse, generateID = generateID,
keyboard = keyboard,
generateID = generateID, style = require((...):match("(.-)[^%.]+$") .. 'style-default'),
setHot = setHot, registerDraw = registerDraw,
setActive = setActive, draw = draw,
setKeyFocus = setKeyFocus,
isHot = isHot,
isActive = isActive,
hasKeyFocus = hasKeyFocus,
disableKeyFocus = disableKeyFocus, strictAnd = strictAnd,
enableKeyFocus = clearKeyFocus, strictOr = strictOr,
clearKeyFocus = clearKeyFocus, save_pack = save_pack,
makeCyclable = makeCyclable, save_unpack = save_unpack,
style = require((...):match("(.-)[^%.]+$") .. '.style-default'),
color = color,
registerDraw = registerDraw,
draw = draw,
strictAnd = strictAnd,
strictOr = strictOr,
save_pack = save_pack,
save_unpack = save_unpack,
} }

143
group.lua Normal file
View file

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

View file

@ -29,6 +29,9 @@ assert(not BASE:match('%.init%.$'), "Invalid require path `"..(...).."' (drop th
return { return {
core = require(BASE .. 'core'), core = require(BASE .. 'core'),
group = require(BASE .. 'group'),
mouse = require(BASE .. 'mouse'),
keyboard = require(BASE .. 'keyboard'),
Button = require(BASE .. 'button'), Button = require(BASE .. 'button'),
Slider = require(BASE .. 'slider'), Slider = require(BASE .. 'slider'),
Slider2D = require(BASE .. 'slider2d'), Slider2D = require(BASE .. 'slider2d'),

View file

@ -24,47 +24,56 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. 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 = "", cursor = text:len()}, pos = {x, y}, size={w, h}, widgetHit=widgetHit, draw=draw}
info.text = info.text or "" return function(w)
info.cursor = math.min(info.cursor or info.text:len(), info.text:len()) 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() local id = core.generateID()
core.mouse.updateState(id, widgetHit or core.style.widgetHit, x,y,w,h) local pos, size = group.getRect(w.pos, w.size)
core.makeCyclable(id) mouse.updateWidget(id, pos, size, w.widgetHit)
if core.isActive(id) then core.setKeyFocus(id) end keyboard.makeCyclable(id)
if mouse.isActive(id) then keyboard.setFocus(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 changed = false local changed = false
if not keyboard.hasFocus(id) then
--[[nothing]]
-- editing -- editing
if core.keyboard.key == 'backspace' then elseif keyboard.key == 'backspace' then
info.text = info.text:sub(1,info.cursor-1) .. info.text:sub(info.cursor+1) w.info.text = w.info.text:sub(1,w.info.cursor-1) .. w.info.text:sub(w.info.cursor+1)
info.cursor = math.max(0, info.cursor-1) w.info.cursor = math.max(0, w.info.cursor-1)
changed = true changed = true
elseif core.keyboard.key == 'delete' then elseif keyboard.key == 'delete' then
info.text = info.text:sub(1,info.cursor) .. info.text:sub(info.cursor+2) w.info.text = w.info.text:sub(1,w.info.cursor) .. w.info.text:sub(w.info.cursor+2)
info.cursor = math.min(info.text:len(), info.cursor) w.info.cursor = math.min(w.info.text:len(), w.info.cursor)
changed = true changed = true
-- movement -- movement
elseif core.keyboard.key == 'left' then elseif keyboard.key == 'left' then
info.cursor = math.max(0, info.cursor-1) w.info.cursor = math.max(0, w.info.cursor-1)
elseif core.keyboard.key == 'right' then elseif keyboard.key == 'right' then
info.cursor = math.min(info.text:len(), info.cursor+1) w.info.cursor = math.min(w.info.text:len(), w.info.cursor+1)
elseif core.keyboard.key == 'home' then elseif keyboard.key == 'home' then
info.cursor = 0 w.info.cursor = 0
elseif core.keyboard.key == 'end' then elseif keyboard.key == 'end' then
info.cursor = info.text:len() w.info.cursor = w.info.text:len()
-- input -- info
elseif core.keyboard.code >= 32 and core.keyboard.code < 127 then elseif keyboard.code >= 32 and keyboard.code < 127 then
local left = info.text:sub(1,info.cursor) local left = w.info.text:sub(1,w.info.cursor)
local right = info.text:sub(info.cursor+1) local right = w.info.text:sub(w.info.cursor+1)
info.text = table.concat{left, string.char(core.keyboard.code), right} w.info.text = table.concat{left, string.char(keyboard.code), right}
info.cursor = info.cursor + 1 w.info.cursor = w.info.cursor + 1
changed = true changed = true
end 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 return changed
end end

89
keyboard.lua Normal file
View file

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

View file

@ -24,12 +24,38 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. 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() local id = core.generateID()
w, h, align = w or 0, h or 0, align or 'left' local pos, size = group.getRect(w.pos, w.size)
core.registerDraw(id, draw or core.style.Label, text,x,y,w,h,align)
return false 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 end

86
mouse.lua Normal file
View file

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

View file

@ -24,47 +24,56 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. 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 = {value = v, min = 0, max = 1, step = (max-min)/20}, vertical = boolean, pos = {x, y}, size={w, h}, widgetHit=widgetHit, draw=draw}
assert(type(info) == 'table' and info.value, "Incomplete slider value info") return function(w)
info.min = info.min or 0 assert(type(w) == 'table' and type(w.info) == "table" and w.info.value, "Invalid argument.")
info.max = info.max or math.max(info.value, 1) w.info.min = w.info.min or 0
info.step = info.step or (info.max - info.min) / 50 w.info.max = w.info.max or math.max(w.info.value, 1)
local fraction = (info.value - info.min) / (info.max - info.min) 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() local id = core.generateID()
core.mouse.updateState(id, widgetHit or core.style.widgetHit, x,y,w,h) local pos, size = group.getRect(w.pos, w.info.size)
core.makeCyclable(id)
core.registerDraw(id,draw or core.style.Slider, fraction, x,y,w,h, info.vertical) mouse.updateWidget(id, pos, size, w.widgetHit)
keyboard.makeCyclable(id)
-- mouse update -- mouse update
if core.isActive(id) then local changed = false
core.setKeyFocus(id) if mouse.isActive(id) then
if info.vertical then keyboard.setFocus(id)
fraction = math.min(1, math.max(0, (y - core.mouse.y + h) / h)) if w.vertical then
fraction = math.min(1, math.max(0, (pos[2] - mouse.y + size[2]) / size[2]))
else 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 end
local v = fraction * (info.max - info.min) + info.min local v = fraction * (w.info.max - w.info.min) + w.info.min
if v ~= info.value then if v ~= w.info.value then
info.value = v w.info.value = v
return true changed = true
end end
end end
-- keyboard update -- keyboard update
local changed = false if keyboard.hasFocus(id) then
if core.hasKeyFocus(id) then local keys = w.vertical and {'up', 'down'} or {'right', 'left'}
local keys = info.vertical and {'up', 'down'} or {'right', 'left'} if keyboard.key == keys[1] then
if core.keyboard.key == keys[1] then w.info.value = math.min(w.info.max, w.info.value + w.info.step)
info.value = math.min(info.max, info.value + info.step)
changed = true changed = true
elseif core.keyboard.key == keys[2] then elseif keyboard.key == keys[2] then
info.value = math.max(info.min, info.value - info.step) w.info.value = math.max(w.info.min, w.info.value - w.info.step)
changed = true changed = true
end end
end end
core.registerDraw(id, w.draw or core.style.Slider,
fraction, w.vertical, pos[1],pos[2], size[1],size[2])
return changed return changed
end end

View file

@ -24,58 +24,76 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. 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 = { local fraction = {
x = (info.value.x - info.min.x) / (info.max.x - info.min.x), (w.info.value[1] - w.info.min[1]) / (w.info.max[1] - w.info.min[1]),
y = (info.value.y - info.min.y) / (info.max.y - info.min.y), (w.info.value[2] - w.info.min[2]) / (w.info.max[2] - w.info.min[2]),
} }
local id = core.generateID() local id = core.generateID()
core.mouse.updateState(id, widgetHit or core.style.widgetHit, x,y,w,h) local pos, size = group.getRect(w.pos, w.size)
core.makeCyclable(id)
core.registerDraw(id,draw or core.style.Slider2D, fraction, x,y,w,h) mouse.updateWidget(id, pos, size, w.widgetHit)
keyboard.makeCyclable(id)
-- update value -- update value
if core.isActive(id) then local changed = false
core.setKeyFocus(id) if mouse.isActive(id) then
keyboard.setFocus(id)
fraction = { fraction = {
x = (core.mouse.x - x) / w, math.min(1, math.max(0, (mouse.x - pos[1]) / size[1])),
y = (core.mouse.y - y) / h, 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 = { local v = {
x = fraction.x * (info.max.x - info.min.x) + info.min.x, fraction[1] * (w.info.max[1] - w.info.min[1]) + w.info.min[1],
y = fraction.y * (info.max.y - info.min.y) + info.min.y, 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 if v[1] ~= w.info.value[1] or v[2] ~= w.info.value[2] then
info.value = v w.info.value = v
return true changed = true
end end
end end
local changed = false if keyboard.hasFocus(id) then
if core.hasKeyFocus(id) then if keyboard.key == 'down' then
if core.keyboard.key == 'down' then w.info.value[2] = math.min(w.info.max[2], w.info.value[2] + w.info.step.y)
info.value.y = math.min(info.max.y, info.value.y + info.step.y)
changed = true changed = true
elseif core.keyboard.key == 'up' then elseif keyboard.key == 'up' then
info.value.y = math.max(info.min.y, info.value.y - info.step.y) w.info.value[2] = math.max(w.info.min[2], w.info.value[2] - w.info.step.y)
changed = true changed = true
end end
if core.keyboard.key == 'right' then if keyboard.key == 'right' then
info.value.x = math.min(info.max.x, info.value.x + info.step.x) w.info.value[1] = math.min(w.info.max[1], w.info.value[1] + w.info.step.x)
changed = true changed = true
elseif core.keyboard.key == 'left' then elseif keyboard.key == 'left' then
info.value.x = math.max(info.min.x, info.value.x - info.step.x) w.info.value[1] = math.max(w.info.min[1], w.info.value[1] - w.info.step.x)
changed = true changed = true
end end
end end
core.registerDraw(id, w.draw or core.style.Slider2D,
fraction, pos[1],pos[2], size[1],size[2])
return changed return changed
end end