From adc78875875a64d40b4cf65a8618fb49a77e8080 Mon Sep 17 00:00:00 2001 From: Matthias Richter Date: Wed, 9 May 2012 21:27:45 +0200 Subject: Mega update: Auto layout, code cleanup, api change. Basically half a rewrite. --- button.lua | 44 +++++++++++---- checkbox.lua | 46 ++++++++++++---- core.lua | 174 +++++++++++++++++++++-------------------------------------- group.lua | 143 ++++++++++++++++++++++++++++++++++++++++++++++++ init.lua | 3 ++ input.lua | 69 +++++++++++++----------- keyboard.lua | 89 ++++++++++++++++++++++++++++++ label.lua | 36 +++++++++++-- mouse.lua | 86 +++++++++++++++++++++++++++++ slider.lua | 61 ++++++++++++--------- slider2d.lua | 82 +++++++++++++++++----------- 11 files changed, 605 insertions(+), 228 deletions(-) create mode 100644 group.lua create mode 100644 keyboard.lua create mode 100644 mouse.lua diff --git a/button.lua b/button.lua index b55f35c..fd0cff4 100644 --- a/button.lua +++ b/button.lua @@ -23,31 +23,55 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]]-- -local core = require((...):match("(.-)[^%.]+$") .. 'core') +local BASE = (...):match("(.-)[^%.]+$") +local core = require(BASE .. 'core') +local group = require(BASE .. 'group') +local mouse = require(BASE .. 'mouse') +local keyboard = require(BASE .. 'keyboard') -- the widget -return function(title, x,y, w,h, widgetHit, draw) +-- {text = text, pos = {x, y}, size={w, h}, widgetHit=widgetHit, draw=draw} +return function(w) + assert(type(w) == "table" and w.text, "Invalid argument") + + -- if tight fit requested, compute the size according to text size + -- have a 2px margin around the text + local tight = w.size and (w.size[1] == 'tight' or w.size[2] == 'tight') + if tight then + local f = assert(love.graphics.getFont()) + if w.size[1] == 'tight' then + w.size[1] = f:getWidth(w.text) + 4 + end + if w.size[2] == 'tight' then + w.size[2] = f:getHeight(w.text) + 4 + end + end + -- Generate unique identifier for gui state update and querying. local id = core.generateID() + -- group.getRect determines the position and size of the widget according + -- to the currently active group. Both arguments may be omitted. + local pos, size = group.getRect(w.pos, w.size) + + -- mouse.updateWidget(id, {x,y}, {w,h}, widgetHit) updates the state for this widget. + -- widgetHit may be nil, in which case mouse.widgetHit will be used. -- The widget mouse-state can be: -- hot (mouse over widget), -- active (mouse pressed on widget) or -- normal (mouse not on widget and not pressed on widget). - -- - -- core.mouse.updateState(id, widgetHit, x,y,w,h) updates the state for this widget. - core.mouse.updateState(id, widgetHit or core.style.widgetHit, x,y,w,h) + mouse.updateWidget(id, pos, size, w.widgetHit) - -- core.makeCyclable makes the item focus on tab or whatever binding is + -- keyboard.makeCyclable makes the item focus on tab or whatever binding is -- in place (see core.keyboard.cycle). Cycle order is determied by the -- order you call the widget functions. - core.makeCyclable(id) + keyboard.makeCyclable(id) -- core.registerDraw(id, drawfunction, drawfunction-arguments...) -- shows widget when core.draw() is called. - core.registerDraw(id, draw or core.style.Button, title,x,y,w,h) + core.registerDraw(id, w.draw or core.style.Button, + w.text, pos[1],pos[2], size[1],size[2]) - return core.mouse.releasedOn(id) or - (core.keyboard.key == 'return' and core.hasKeyFocus(id)) + return mouse.releasedOn(id) or (keyboard.key == 'return' and keyboard.hasFocus(id)) end diff --git a/checkbox.lua b/checkbox.lua index 22c325a..e04f6a6 100644 --- a/checkbox.lua +++ b/checkbox.lua @@ -23,22 +23,46 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]]-- -local core = require((...):match("(.-)[^%.]+$") .. 'core') +local BASE = (...):match("(.-)[^%.]+$") +local core = require(BASE .. 'core') +local group = require(BASE .. 'group') +local mouse = require(BASE .. 'mouse') +local keyboard = require(BASE .. 'keyboard') + +-- {info = {checked = status, label = "", algin = "left"}, pos = {x, y}, size={w, h}, widgetHit=widgetHit, draw=draw} +return function(w) + assert(type(w) == "table") + w.info.label = w.info.label or "" + + local tight = w.size and (w.size[1] == 'tight' or w.size[2] == 'tight') + if tight then + local f = assert(love.graphics.getFont()) + if w.size[1] == 'tight' then + w.size[1] = f:getWidth(w.info.label) + end + if w.size[2] == 'tight' then + w.size[2] = f:getHeight(w.info.label) + end + -- account for the checkbox + local bw = math.min(w.size[1] or group.size[1], w.size[2] or group.size[2]) + w.size[1] = w.size[1] + bw + 4 + end -return function(info, x,y, w,h, widgetHit, draw) local id = core.generateID() + local pos, size = group.getRect(w.pos, w.size) - core.mouse.updateState(id, widgetHit or core.style.widgetHit, x,y,w,h) - core.makeCyclable(id) - core.registerDraw(id, draw or core.style.Checkbox, info.checked,x,y,w,h) + mouse.updateWidget(id, pos, size, w.widgetHit) + keyboard.makeCyclable(id) - local checked = info.checked - local key = core.keyboard.key - if core.mouse.releasedOn(id) or - (core.hasKeyFocus(id) and key == 'return' or key == ' ') then - info.checked = not info.checked + local checked = w.info.checked + local key = keyboard.key + if mouse.releasedOn(id) or ((key == 'return' or key == ' ') and keyboard.hasFocus(id)) then + w.info.checked = not w.info.checked end - return info.checked ~= checked + core.registerDraw(id, w.draw or core.style.Checkbox, + w.info.checked, w.info.label, w.info.align or 'left', pos[1], pos[2], size[1], size[2]) + + return w.info.checked ~= checked end diff --git a/core.lua b/core.lua index 74aebfb..371b114 100644 --- a/core.lua +++ b/core.lua @@ -24,81 +24,17 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]]-- --- state -local context = {maxid = 0} -local draw_items = {n = 0} local NO_WIDGET = function()end +local BASE = (...):match("(.-)[^%.]+$") +local group = require(BASE .. 'group') +local mouse = require(BASE .. 'mouse') +local keyboard = require(BASE .. 'keyboard') -local function generateID() - context.maxid = context.maxid + 1 - return context.maxid -end - -local function setHot(id) context.hot = id end -local function isHot(id) return context.hot == id end - -local function setActive(id) context.active = id end -local function isActive(id) return context.active == id end - -local function setKeyFocus(id) context.keyfocus = id end -local function hasKeyFocus(id) return context.keyfocus == id end - -local function disableKeyFocus() return setKeyFocus{} end -local function clearKeyFocus() return setKeyFocus(nil) end - --- input -local mouse = {x = 0, y = 0, down = false} -local keyboard = {key = nil, code = -1} -keyboard.cycle = { - -- binding = {key = key, modifier1, modifier2, ...} XXX: modifiers are OR-ed! - prev = {key = 'tab', 'lshift', 'rshift'}, - next = {key = 'tab'}, -} - -function mouse.updateState(id, widgetHit, ...) - if widgetHit(mouse.x, mouse.y, ...) then - setHot(id) - if not context.active and mouse.down then - setActive(id) - end - end -end - -function mouse.releasedOn(id) - return not mouse.down and isHot(id) and isActive(id) -end - -function keyboard.pressed(key, code) - keyboard.key = key - keyboard.code = code -end - -function keyboard.tryGrab(id) - if not context.keyfocus then - setKeyFocus(id) - end -end - -function keyboard.isBindingDown(bind) - local modifiersDown = #bind == 0 or love.keyboard.isDown(unpack(bind)) - return keyboard.key == bind.key and modifiersDown -end +-- +-- Helper functions +-- -local function makeCyclable(id) - keyboard.tryGrab(id) - if hasKeyFocus(id) then - if keyboard.isBindingDown(keyboard.cycle.prev) then - setKeyFocus(context.lastwidget) - keyboard.key = nil - elseif keyboard.isBindingDown(keyboard.cycle.next) then - setKeyFocus(nil) - keyboard.key = nil - end - end - context.lastwidget = id -end - --- helper functions +-- evaluates all arguments local function strictAnd(...) local n = select("#", ...) local ret = true @@ -124,66 +60,76 @@ local function save_unpack(t, i) return t[i], save_unpack(t, i+1) end +-- +-- Widget ID +-- +local maxid = 0 +local function generateID() + maxid = maxid + 1 + return maxid +end + +-- +-- Drawing / Frame update +-- +local draw_items = {n = 0} local function registerDraw(id, f, ...) assert(type(f) == 'function' or (getmetatable(f) or {}).__call, 'Drawing function is not a callable type!') + local font = love.graphics.getFont() + local state = 'normal' - if isHot(id) or hasKeyFocus(id) then - state = isActive(id) and 'active' or 'hot' + if mouse.isHot(id) or keyboard.hasFocus(id) then + state = mouse.isActive(id) and 'active' or 'hot' end local rest = save_pack(...) draw_items.n = draw_items.n + 1 - draw_items[draw_items.n] = function() f(state, save_unpack(rest)) end + draw_items[draw_items.n] = function() + if font then love.graphics.setFont(font) end + f(state, save_unpack(rest)) + end end -- actually update-and-draw local function draw() - -- close frame state - if not mouse.down then -- released - setActive(nil) - elseif not context.active then -- clicked outside - setActive(NO_WIDGET) - end + keyboard.endFrame() + mouse.endFrame() + group.endFrame() + + -- save graphics state + local c = {love.graphics.getColor()} + local f = love.graphics.getFont() + local lw = love.graphics.getLineWidth() + local ls = love.graphics.getLineStyle() for i = 1,draw_items.n do draw_items[i]() end - -- prepare for next frame - draw_items.n = 0 - context.maxid = 0 + -- restore graphics state + love.graphics.setLine(lw, ls) + if f then love.graphics.setFont(f) end + love.graphics.setColor(c) - -- update mouse status - setHot(nil) - mouse.x, mouse.y = love.mouse.getPosition() - mouse.down = love.mouse.isDown('l') + draw_items.n = 0 + maxid = 0 - keyboard.key, keyboard.code = nil, -1 + group.beginFrame() + mouse.beginFrame() + keyboard.beginFrame() end +-- +-- The Module +-- return { - mouse = mouse, - keyboard = keyboard, - - generateID = generateID, - setHot = setHot, - setActive = setActive, - setKeyFocus = setKeyFocus, - isHot = isHot, - isActive = isActive, - hasKeyFocus = hasKeyFocus, - - disableKeyFocus = disableKeyFocus, - enableKeyFocus = clearKeyFocus, - clearKeyFocus = clearKeyFocus, - makeCyclable = makeCyclable, - - style = require((...):match("(.-)[^%.]+$") .. '.style-default'), - color = color, - registerDraw = registerDraw, - draw = draw, - - strictAnd = strictAnd, - strictOr = strictOr, - save_pack = save_pack, - save_unpack = save_unpack, + generateID = generateID, + + style = require((...):match("(.-)[^%.]+$") .. 'style-default'), + registerDraw = registerDraw, + draw = draw, + + strictAnd = strictAnd, + strictOr = strictOr, + save_pack = save_pack, + save_unpack = save_unpack, } diff --git a/group.lua b/group.lua new file mode 100644 index 0000000..50e413e --- /dev/null +++ b/group.lua @@ -0,0 +1,143 @@ +--[[ +Copyright (c) 2012 Matthias Richter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +]]-- + +local stack = {n = 0} +local default = { + pos = {0,0}, + grow = {0,0}, + spacing = 2, + size = {100, 30}, + upper_left = {0,0}, + lower_right = {0,0}, +} +local current = default + +local Grow = { + none = { 0, 0}, + up = { 0, -1}, + down = { 0, 1}, + left = {-1, 0}, + right = { 1, 0} +} + +-- {grow = grow, spacing = spacing, size = size, pos = pos} +local function push(info) + local grow = info.grow or "none" + local spacing = info.spacing or default.spacing + + local size = { + info.size and info.size[1] or current.size[1], + info.size and info.size[2] or current.size[2] + } + + local pos = {current.pos[1], current.pos[2]} + if info.pos then + pos[1] = pos[1] + (info.pos[1] or 0) + pos[2] = pos[2] + (info.pos[2] or 0) + end + + assert(size, "Size neither specified nor derivable from parent group.") + assert(pos, "Position neither specified nor derivable from parent group.") + grow = assert(Grow[grow], "Invalid grow: " .. tostring(grow)) + + current = { + pos = pos, + grow = grow, + size = size, + spacing = spacing, + upper_left = { math.huge, math.huge}, + lower_right = {-math.huge, -math.huge}, + } + stack.n = stack.n + 1 + stack[stack.n] = current +end + +local function advance(pos, size) + current.upper_left[1] = math.min(current.upper_left[1], pos[1]) + current.upper_left[2] = math.min(current.upper_left[2], pos[2]) + current.lower_right[1] = math.max(current.lower_right[1], pos[1] + size[1]) + current.lower_right[2] = math.max(current.lower_right[2], pos[2] + size[2]) + + if current.grow[1] ~= 0 then + current.pos[1] = pos[1] + current.grow[1] * (size[1] + current.spacing) + end + if current.grow[2] ~= 0 then + current.pos[2] = pos[2] + current.grow[2] * (size[2] + current.spacing) + end + return pos, size +end + +local function getRect(pos, size) + pos = {pos and pos[1] or 0, pos and pos[2] or 0} + size = {size and size[1] or current.size[1], size and size[2] or current.size[2]} + + -- growing left/up: update current position to account for differnt size + if current.grow[1] < 0 and current.size[1] ~= size[1] then + current.pos[1] = current.pos[1] + (current.size[1] - size[1]) + end + if current.grow[2] < 0 and current.size[2] ~= size[2] then + current.pos[2] = current.pos[2] - (current.size[2] - size[2]) + end + + pos[1] = pos[1] + current.pos[1] + pos[2] = pos[2] + current.pos[2] + + return advance(pos, size) +end + +local function pop() + assert(stack.n > 0, "Group stack is empty.") + stack.n = stack.n - 1 + local child = current + current = stack[stack.n] or default + + local size = { + child.lower_right[1] - math.max(child.upper_left[1], current.pos[1]), + child.lower_right[2] - math.max(child.upper_left[2], current.pos[2]) + } + advance(current.pos, size) +end + +local function beginFrame() + current = default + stack.n = 0 +end + +local function endFrame() + -- future use? +end + +return setmetatable({ + push = push, + pop = pop, + getRect = getRect, + advance = advance, + beginFrame = beginFrame, + endFrame = endFrame, + default = default, +}, {__index = function(_,k) + return ({size = current.size, pos = current.pos})[k] +end}) diff --git a/init.lua b/init.lua index c122ee5..11c5e92 100644 --- a/init.lua +++ b/init.lua @@ -29,6 +29,9 @@ assert(not BASE:match('%.init%.$'), "Invalid require path `"..(...).."' (drop th return { core = require(BASE .. 'core'), + group = require(BASE .. 'group'), + mouse = require(BASE .. 'mouse'), + keyboard = require(BASE .. 'keyboard'), Button = require(BASE .. 'button'), Slider = require(BASE .. 'slider'), Slider2D = require(BASE .. 'slider2d'), diff --git a/input.lua b/input.lua index 3e83988..64f2ced 100644 --- a/input.lua +++ b/input.lua @@ -24,47 +24,56 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]]-- -local core = require((...):match("(.-)[^%.]+$") .. 'core') +local BASE = (...):match("(.-)[^%.]+$") +local core = require(BASE .. 'core') +local group = require(BASE .. 'group') +local mouse = require(BASE .. 'mouse') +local keyboard = require(BASE .. 'keyboard') -return function(info, x,y,w,h, widgetHit, draw) - info.text = info.text or "" - info.cursor = math.min(info.cursor or info.text:len(), info.text:len()) +-- {info = {text = "", cursor = text:len()}, pos = {x, y}, size={w, h}, widgetHit=widgetHit, draw=draw} +return function(w) + assert(type(w) == "table" and type(w.info) == "table", "Invalid argument") + w.info.text = w.info.text or "" + w.info.cursor = math.min(w.info.cursor or w.info.text:len(), w.info.text:len()) local id = core.generateID() - core.mouse.updateState(id, widgetHit or core.style.widgetHit, x,y,w,h) - core.makeCyclable(id) - if core.isActive(id) then core.setKeyFocus(id) end - - core.registerDraw(id, draw or core.style.Input, info.text, info.cursor, x,y,w,h) - if not core.hasKeyFocus(id) then return false end + local pos, size = group.getRect(w.pos, w.size) + mouse.updateWidget(id, pos, size, w.widgetHit) + keyboard.makeCyclable(id) + if mouse.isActive(id) then keyboard.setFocus(id) end local changed = false + if not keyboard.hasFocus(id) then + --[[nothing]] -- editing - if core.keyboard.key == 'backspace' then - info.text = info.text:sub(1,info.cursor-1) .. info.text:sub(info.cursor+1) - info.cursor = math.max(0, info.cursor-1) + elseif keyboard.key == 'backspace' then + w.info.text = w.info.text:sub(1,w.info.cursor-1) .. w.info.text:sub(w.info.cursor+1) + w.info.cursor = math.max(0, w.info.cursor-1) changed = true - elseif core.keyboard.key == 'delete' then - info.text = info.text:sub(1,info.cursor) .. info.text:sub(info.cursor+2) - info.cursor = math.min(info.text:len(), info.cursor) + elseif keyboard.key == 'delete' then + w.info.text = w.info.text:sub(1,w.info.cursor) .. w.info.text:sub(w.info.cursor+2) + w.info.cursor = math.min(w.info.text:len(), w.info.cursor) changed = true -- movement - elseif core.keyboard.key == 'left' then - info.cursor = math.max(0, info.cursor-1) - elseif core.keyboard.key == 'right' then - info.cursor = math.min(info.text:len(), info.cursor+1) - elseif core.keyboard.key == 'home' then - info.cursor = 0 - elseif core.keyboard.key == 'end' then - info.cursor = info.text:len() - -- input - elseif core.keyboard.code >= 32 and core.keyboard.code < 127 then - local left = info.text:sub(1,info.cursor) - local right = info.text:sub(info.cursor+1) - info.text = table.concat{left, string.char(core.keyboard.code), right} - info.cursor = info.cursor + 1 + elseif keyboard.key == 'left' then + w.info.cursor = math.max(0, w.info.cursor-1) + elseif keyboard.key == 'right' then + w.info.cursor = math.min(w.info.text:len(), w.info.cursor+1) + elseif keyboard.key == 'home' then + w.info.cursor = 0 + elseif keyboard.key == 'end' then + w.info.cursor = w.info.text:len() + -- info + elseif keyboard.code >= 32 and keyboard.code < 127 then + local left = w.info.text:sub(1,w.info.cursor) + local right = w.info.text:sub(w.info.cursor+1) + w.info.text = table.concat{left, string.char(keyboard.code), right} + w.info.cursor = w.info.cursor + 1 changed = true end + core.registerDraw(id, w.draw or core.style.Input, + w.info.text, w.info.cursor, pos[1],pos[2], size[1],size[2]) + return changed end diff --git a/keyboard.lua b/keyboard.lua new file mode 100644 index 0000000..0b20570 --- /dev/null +++ b/keyboard.lua @@ -0,0 +1,89 @@ +--[[ +Copyright (c) 2012 Matthias Richter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +]]-- + +local key,code = nil, -1 +local focus, lastwidget + +local cycle = { + -- binding = {key = key, modifier1, modifier2, ...} XXX: modifiers are OR-ed! + prev = {key = 'tab', 'lshift', 'rshift'}, + next = {key = 'tab'}, +} + +local function pressed(...) key, code = ... end +local function setFocus(id) focus = id end +local function disableFocus() focus = NO_WIDGET end +local function clearFocus() focus = nil end +local function hasFocus(id) return id == focus end + +local function tryGrab(id) + if not focus then + setFocus(id) + end +end + +local function isBindingDown(bind) + local modifiersDown = #bind == 0 or love.keyboard.isDown(unpack(bind)) + return key == bind.key and modifiersDown +end + +local function makeCyclable(id) + tryGrab(id) + if hasFocus(id) then + if isBindingDown(cycle.prev) then + setFocus(lastwidget) + key = nil + elseif isBindingDown(cycle.next) then + setFocus(nil) + key = nil + end + end + lastwidget = id +end + +local function beginFrame() + -- for future use? +end + +local function endFrame() + key, code = nil, -1 +end + +return setmetatable({ + cycle = cycle, + pressed = pressed, + tryGrab = tryGrab, + isBindingDown = isBindingDown, + setFocus = setFocus, + disableFocus = disableFocus, + enableFocus = clearFocus, + clearFocus = clearFocus, + hasFocus = hasFocus, + makeCyclable = makeCyclable, + + beginFrame = beginFrame, + endFrame = endFrame, +}, {__index = function(_,k) return ({key = key, code = code})[k] end}) diff --git a/label.lua b/label.lua index ed7ad80..62b3950 100644 --- a/label.lua +++ b/label.lua @@ -24,12 +24,38 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]]-- -local core = require((...):match("(.-)[^%.]+$") .. 'core') +local BASE = (...):match("(.-)[^%.]+$") +local core = require(BASE .. 'core') +local group = require(BASE .. 'group') +local mouse = require(BASE .. 'mouse') +local keyboard = require(BASE .. 'keyboard') + +-- {text = text, align = align, pos = {x, y}, size={w, h}, widgetHit=widgetHit, draw=draw} +return function(w) + assert(type(w) == "table" and w.text, "Invalid argument") + w.align = w.align or 'left' + + local tight = w.size and (w.size[1] == 'tight' or w.size[2] == 'tight') + if tight then + local f = assert(love.graphics.getFont()) + if w.size[1] == 'tight' then + w.size[1] = f:getWidth(w.text) + end + if w.size[2] == 'tight' then + w.size[2] = f:getHeight(w.text) + end + end -return function(text, x,y,w,h,align, draw) local id = core.generateID() - w, h, align = w or 0, h or 0, align or 'left' - core.registerDraw(id, draw or core.style.Label, text,x,y,w,h,align) - return false + local pos, size = group.getRect(w.pos, w.size) + + if keyboard.hasFocus(id) then + keyboard.clearFocus() + end + + core.registerDraw(id, draw or core.style.Label, + w.text, w.align, pos[1],pos[2], size[1],size[2]) + + return mouse.releasedOn(id) end diff --git a/mouse.lua b/mouse.lua new file mode 100644 index 0000000..eac46f5 --- /dev/null +++ b/mouse.lua @@ -0,0 +1,86 @@ +--[[ +Copyright (c) 2012 Matthias Richter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +Except as contained in this notice, the name(s) of the above copyright holders +shall not be used in advertising or otherwise to promote the sale, use or +other dealings in this Software without prior written authorization. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +]]-- + +local _M -- holds the module. needed to make widgetHit overridable + +local x,y = 0,0 +local down = false +local hot, active = nil, nil + +local function widgetHit(mouse, pos, size) + return mouse[1] >= pos[1] and mouse[1] <= pos[1] + size[1] and + mouse[2] >= pos[2] and mouse[2] <= pos[2] + size[2] +end + +local function setHot(id) hot = id end +local function setActive(id) active = id end +local function isHot(id) return id == hot end +local function isActive(id) return id == active end + +local function updateWidget(id, pos, size, hit) + hit = hit or _M.widgetHit + + if hit({x,y}, pos, size) then + setHot(id) + if not active and down then + setActive(id) + end + end +end + +local function releasedOn(id) + return not down and isHot(id) and isActive(id) +end + +local function beginFrame() + hot = nil + x,y = love.mouse.getPosition() + down = love.mouse.isDown('l') +end + +local function endFrame() + if not down then -- released + setActive(nil) + elseif not active then -- clicked outside + setActive(NO_WIDGET) + end +end + +_M = { + widgetHit = widgetHit, + setHot = setHot, + setActive = setActive, + isHot = isHot, + isActive = isActive, + updateWidget = updateWidget, + releasedOn = releasedOn, + + beginFrame = beginFrame, + endFrame = endFrame, +} + +-- metatable provides getters to x, y and down +return setmetatable(_M, {__index = function(_,k) return ({x = x, y = y, down = down})[k] end}) diff --git a/slider.lua b/slider.lua index 4a88515..4561d4a 100644 --- a/slider.lua +++ b/slider.lua @@ -24,47 +24,56 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]]-- -local core = require((...):match("(.-)[^%.]+$") .. 'core') +local BASE = (...):match("(.-)[^%.]+$") +local core = require(BASE .. 'core') +local group = require(BASE .. 'group') +local mouse = require(BASE .. 'mouse') +local keyboard = require(BASE .. 'keyboard') -return function(info, x,y,w,h, widgetHit, draw) - assert(type(info) == 'table' and info.value, "Incomplete slider value info") - info.min = info.min or 0 - info.max = info.max or math.max(info.value, 1) - info.step = info.step or (info.max - info.min) / 50 - local fraction = (info.value - info.min) / (info.max - info.min) +-- {info = {value = v, min = 0, max = 1, step = (max-min)/20}, vertical = boolean, pos = {x, y}, size={w, h}, widgetHit=widgetHit, draw=draw} +return function(w) + assert(type(w) == 'table' and type(w.info) == "table" and w.info.value, "Invalid argument.") + w.info.min = w.info.min or 0 + w.info.max = w.info.max or math.max(w.info.value, 1) + w.info.step = w.info.step or (w.info.max - w.info.min) / 20 + local fraction = (w.info.value - w.info.min) / (w.info.max - w.info.min) local id = core.generateID() - core.mouse.updateState(id, widgetHit or core.style.widgetHit, x,y,w,h) - core.makeCyclable(id) - core.registerDraw(id,draw or core.style.Slider, fraction, x,y,w,h, info.vertical) + local pos, size = group.getRect(w.pos, w.info.size) + + mouse.updateWidget(id, pos, size, w.widgetHit) + keyboard.makeCyclable(id) -- mouse update - if core.isActive(id) then - core.setKeyFocus(id) - if info.vertical then - fraction = math.min(1, math.max(0, (y - core.mouse.y + h) / h)) + local changed = false + if mouse.isActive(id) then + keyboard.setFocus(id) + if w.vertical then + fraction = math.min(1, math.max(0, (pos[2] - mouse.y + size[2]) / size[2])) else - fraction = math.min(1, math.max(0, (core.mouse.x - x) / w)) + fraction = math.min(1, math.max(0, (mouse.x - pos[1]) / size[1])) end - local v = fraction * (info.max - info.min) + info.min - if v ~= info.value then - info.value = v - return true + local v = fraction * (w.info.max - w.info.min) + w.info.min + if v ~= w.info.value then + w.info.value = v + changed = true end end -- keyboard update - local changed = false - if core.hasKeyFocus(id) then - local keys = info.vertical and {'up', 'down'} or {'right', 'left'} - if core.keyboard.key == keys[1] then - info.value = math.min(info.max, info.value + info.step) + if keyboard.hasFocus(id) then + local keys = w.vertical and {'up', 'down'} or {'right', 'left'} + if keyboard.key == keys[1] then + w.info.value = math.min(w.info.max, w.info.value + w.info.step) changed = true - elseif core.keyboard.key == keys[2] then - info.value = math.max(info.min, info.value - info.step) + elseif keyboard.key == keys[2] then + w.info.value = math.max(w.info.min, w.info.value - w.info.step) changed = true end end + core.registerDraw(id, w.draw or core.style.Slider, + fraction, w.vertical, pos[1],pos[2], size[1],size[2]) + return changed end diff --git a/slider2d.lua b/slider2d.lua index 20f063c..19abaf1 100644 --- a/slider2d.lua +++ b/slider2d.lua @@ -24,58 +24,76 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]]-- -local core = require((...):match("(.-)[^%.]+$") .. 'core') +local BASE = (...):match("(.-)[^%.]+$") +local core = require(BASE .. 'core') +local group = require(BASE .. 'group') +local mouse = require(BASE .. 'mouse') +local keyboard = require(BASE .. 'keyboard') + +-- {info = {value = {x,y}, min = {0,0}, max = {1,1}, step = (max-min)/20}, pos = {x, y}, size={w, h}, widgetHit=widgetHit, draw=draw} +return function(w) + assert(type(w) == 'table' and type(w.info) == "table" and w.info.value, "Invalid argument.") + w.info.min = { + w.info.min and w.info.min[1] or 0, + w.info.min and w.info.min[2] or 0, + } + w.info.max = { + w.info.max and w.info.max[1] or math.max(1, w.info.value[1]), + w.info.max and w.info.max[2] or math.max(1, w.info.value[2]), + } + w.info.step = { + w.info.step and w.info.step[1] or (w.info.max[1] - w.info.min[1]) / 20, + w.info.step and w.info.step[2] or (w.info.max[2] - w.info.min[2]) / 20, + } -return function(info, x,y,w,h, widgetHit, draw) - assert(type(info) == 'table' and type(info.value) == "table", "Incomplete slider value info") - info.min = info.min or {x = 0, y = 0} - info.max = info.max or {x = math.max(info.value.x or 0, 1), y = math.max(info.value.y or 0, 1)} - info.step = info.step or {x = (info.max.x - info.min.x)/50, y = (info.max.y - info.min.y)/50} local fraction = { - x = (info.value.x - info.min.x) / (info.max.x - info.min.x), - y = (info.value.y - info.min.y) / (info.max.y - info.min.y), + (w.info.value[1] - w.info.min[1]) / (w.info.max[1] - w.info.min[1]), + (w.info.value[2] - w.info.min[2]) / (w.info.max[2] - w.info.min[2]), } local id = core.generateID() - core.mouse.updateState(id, widgetHit or core.style.widgetHit, x,y,w,h) - core.makeCyclable(id) - core.registerDraw(id,draw or core.style.Slider2D, fraction, x,y,w,h) + local pos, size = group.getRect(w.pos, w.size) + + mouse.updateWidget(id, pos, size, w.widgetHit) + keyboard.makeCyclable(id) -- update value - if core.isActive(id) then - core.setKeyFocus(id) + local changed = false + if mouse.isActive(id) then + keyboard.setFocus(id) fraction = { - x = (core.mouse.x - x) / w, - y = (core.mouse.y - y) / h, + math.min(1, math.max(0, (mouse.x - pos[1]) / size[1])), + math.min(1, math.max(0, (mouse.y - pos[2]) / size[2])), } - fraction.x = math.min(1, math.max(0, fraction.x)) - fraction.y = math.min(1, math.max(0, fraction.y)) local v = { - x = fraction.x * (info.max.x - info.min.x) + info.min.x, - y = fraction.y * (info.max.y - info.min.y) + info.min.y, + fraction[1] * (w.info.max[1] - w.info.min[1]) + w.info.min[1], + fraction[2] * (w.info.max[2] - w.info.min[2]) + w.info.min[2], } - if v.x ~= info.value.x or v.y ~= info.value.y then - info.value = v - return true + if v[1] ~= w.info.value[1] or v[2] ~= w.info.value[2] then + w.info.value = v + changed = true end end - local changed = false - if core.hasKeyFocus(id) then - if core.keyboard.key == 'down' then - info.value.y = math.min(info.max.y, info.value.y + info.step.y) + if keyboard.hasFocus(id) then + if keyboard.key == 'down' then + w.info.value[2] = math.min(w.info.max[2], w.info.value[2] + w.info.step.y) changed = true - elseif core.keyboard.key == 'up' then - info.value.y = math.max(info.min.y, info.value.y - info.step.y) + elseif keyboard.key == 'up' then + w.info.value[2] = math.max(w.info.min[2], w.info.value[2] - w.info.step.y) changed = true end - if core.keyboard.key == 'right' then - info.value.x = math.min(info.max.x, info.value.x + info.step.x) + if keyboard.key == 'right' then + w.info.value[1] = math.min(w.info.max[1], w.info.value[1] + w.info.step.x) changed = true - elseif core.keyboard.key == 'left' then - info.value.x = math.max(info.min.x, info.value.x - info.step.x) + elseif keyboard.key == 'left' then + w.info.value[1] = math.max(w.info.min[1], w.info.value[1] - w.info.step.x) changed = true end end + + core.registerDraw(id, w.draw or core.style.Slider2D, + fraction, pos[1],pos[2], size[1],size[2]) + return changed end -- cgit v1.2.3-54-g00ecf