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
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

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
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
View file

@ -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
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 {
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'),

View file

@ -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
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.
]]--
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
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.
]]--
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

View file

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