Mega update: Auto layout, code cleanup, api change.
Basically half a rewrite.
This commit is contained in:
parent
7b1b6e4176
commit
adc7887587
11 changed files with 605 additions and 228 deletions
44
button.lua
44
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
|
||||
|
||||
|
|
50
checkbox.lua
50
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')
|
||||
|
||||
return function(info, x,y, w,h, widgetHit, draw)
|
||||
local id = core.generateID()
|
||||
-- {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 ""
|
||||
|
||||
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)
|
||||
|
||||
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 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 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
|
||||
|
||||
|
|
170
core.lua
170
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
|
||||
--
|
||||
-- Helper functions
|
||||
--
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
-- restore graphics state
|
||||
love.graphics.setLine(lw, ls)
|
||||
if f then love.graphics.setFont(f) end
|
||||
love.graphics.setColor(c)
|
||||
|
||||
draw_items.n = 0
|
||||
context.maxid = 0
|
||||
maxid = 0
|
||||
|
||||
-- update mouse status
|
||||
setHot(nil)
|
||||
mouse.x, mouse.y = love.mouse.getPosition()
|
||||
mouse.down = love.mouse.isDown('l')
|
||||
|
||||
keyboard.key, keyboard.code = nil, -1
|
||||
group.beginFrame()
|
||||
mouse.beginFrame()
|
||||
keyboard.beginFrame()
|
||||
end
|
||||
|
||||
--
|
||||
-- The Module
|
||||
--
|
||||
return {
|
||||
mouse = mouse,
|
||||
keyboard = keyboard,
|
||||
generateID = generateID,
|
||||
|
||||
generateID = generateID,
|
||||
setHot = setHot,
|
||||
setActive = setActive,
|
||||
setKeyFocus = setKeyFocus,
|
||||
isHot = isHot,
|
||||
isActive = isActive,
|
||||
hasKeyFocus = hasKeyFocus,
|
||||
style = require((...):match("(.-)[^%.]+$") .. 'style-default'),
|
||||
registerDraw = registerDraw,
|
||||
draw = draw,
|
||||
|
||||
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,
|
||||
strictAnd = strictAnd,
|
||||
strictOr = strictOr,
|
||||
save_pack = save_pack,
|
||||
save_unpack = save_unpack,
|
||||
}
|
||||
|
|
143
group.lua
Normal file
143
group.lua
Normal 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})
|
3
init.lua
3
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'),
|
||||
|
|
69
input.lua
69
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
|
||||
|
|
89
keyboard.lua
Normal file
89
keyboard.lua
Normal 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})
|
36
label.lua
36
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
|
||||
|
||||
|
|
86
mouse.lua
Normal file
86
mouse.lua
Normal 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})
|
61
slider.lua
61
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
|
||||
|
|
82
slider2d.lua
82
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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue