diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d6a337f --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2016 Paul Liverman III + +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. + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4f26852 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# Pop.Box + +*Do not mix with [Cola][1].* + +Pop.Box is a GUI library for use in the [LÖVE][2] engine, designed to be easy to +use and require as little code as possible to set up. It is primarily designed +to make it easy to experiment with GUIs during development. + +Supports LÖVE versions 0.9.1 and higher. + +## Features + +- Quickly set up and align GUI elements. +- Fully customizable alignment / styling. +- Moving/resizing elements takes alignment into account. +- Mouse and key input handling. (**Note**: Work in progress.) +- Extensible: Make your own elements, skins, extensions, and everything is + automatically loaded. + +## Usage + +The basics: + +```lua +local pop = require "pop" +-- define LÖVE callbacks here (update, draw, textinput, mouse/key events) +local window = pop.window():align("center"):setTitle("Welcome!") +window:addChild(pop.text("Welcome to Pop.Box()!")) +``` + +**Note**: Due to this being so early in development...the above example doesn't +actually work as expected. `window` is a very new element. + +For more examples, see the code in `demo`. For documentation, see `docs`. + +# Documentation + +**Note**: Docs not written just yet. Will be soon. + +- [Pop Module][3] (The main module/interface.) +- [Elements][4] (Basic features of elements/types of elements.) +- [Skins][5] (A basic system for quickly applying settings to many elements.) +- [Extensions][7] (A way to load custom code in.) +- [Drawables][6] (Reference for what can be used as a background/color.) + +[1]: https://en.wikipedia.org/wiki/Cola_(programming_language) +[2]: https://love2d.org/ +[3]: ./docs/Pop.md +[4]: ./docs/Elements.md +[5]: ./docs/Skins.md +[6]: ./docs/Drawables.md +[7]: ./docs/Extensions.md diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..d9ec838 --- /dev/null +++ b/build.sh @@ -0,0 +1,5 @@ +#!/bin/bash +cd src +moonc -t ../lib . +cd .. +cp -rf ./lib/pop/* ./demo/pop/ diff --git a/demo/debug-lib/inspect.lua b/demo/debug-lib/inspect.lua new file mode 100644 index 0000000..4b654b2 --- /dev/null +++ b/demo/debug-lib/inspect.lua @@ -0,0 +1,344 @@ +local inspect ={ + _VERSION = 'inspect.lua 3.0.3', + _URL = 'http://github.com/kikito/inspect.lua', + _DESCRIPTION = 'human-readable representations of tables', + _LICENSE = [[ + MIT LICENSE + + Copyright (c) 2013 Enrique García Cota + + 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. + + 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. + ]] +} + +inspect.KEY = setmetatable({}, {__tostring = function() return 'inspect.KEY' end}) +inspect.METATABLE = setmetatable({}, {__tostring = function() return 'inspect.METATABLE' end}) + +-- Apostrophizes the string if it has quotes, but not aphostrophes +-- Otherwise, it returns a regular quoted string +local function smartQuote(str) + if str:match('"') and not str:match("'") then + return "'" .. str .. "'" + end + return '"' .. str:gsub('"', '\\"') .. '"' +end + +local controlCharsTranslation = { + ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", + ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v" +} + +local function escape(str) + local result = str:gsub("\\", "\\\\"):gsub("(%c)", controlCharsTranslation) + return result +end + +local function isIdentifier(str) + return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" ) +end + +local function isSequenceKey(k, sequenceLength) + return type(k) == 'number' + and 1 <= k + and k <= sequenceLength + and math.floor(k) == k +end + +local defaultTypeOrders = { + ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, + ['function'] = 5, ['userdata'] = 6, ['thread'] = 7 +} + +local function sortKeys(a, b) + local ta, tb = type(a), type(b) + + -- strings and numbers are sorted numerically/alphabetically + if ta == tb and (ta == 'string' or ta == 'number') then return a < b end + + local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb] + -- Two default types are compared according to the defaultTypeOrders table + if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb] + elseif dta then return true -- default types before custom ones + elseif dtb then return false -- custom types after default ones + end + + -- custom types are sorted out alphabetically + return ta < tb +end + +-- For implementation reasons, the behavior of rawlen & # is "undefined" when +-- tables aren't pure sequences. So we implement our own # operator. +local function getSequenceLength(t) + local len = 1 + local v = rawget(t,len) + while v ~= nil do + len = len + 1 + v = rawget(t,len) + end + return len - 1 +end + +local function getNonSequentialKeys(t) + local keys = {} + local sequenceLength = getSequenceLength(t) + for k,_ in pairs(t) do + if not isSequenceKey(k, sequenceLength) then table.insert(keys, k) end + end + table.sort(keys, sortKeys) + return keys, sequenceLength +end + +local function getToStringResultSafely(t, mt) + local __tostring = type(mt) == 'table' and rawget(mt, '__tostring') + local str, ok + if type(__tostring) == 'function' then + ok, str = pcall(__tostring, t) + str = ok and str or 'error: ' .. tostring(str) + end + if type(str) == 'string' and #str > 0 then return str end +end + +local maxIdsMetaTable = { + __index = function(self, typeName) + rawset(self, typeName, 0) + return 0 + end +} + +local idsMetaTable = { + __index = function (self, typeName) + local col = {} + rawset(self, typeName, col) + return col + end +} + +local function countTableAppearances(t, tableAppearances) + tableAppearances = tableAppearances or {} + + if type(t) == 'table' then + if not tableAppearances[t] then + tableAppearances[t] = 1 + for k,v in pairs(t) do + countTableAppearances(k, tableAppearances) + countTableAppearances(v, tableAppearances) + end + countTableAppearances(getmetatable(t), tableAppearances) + else + tableAppearances[t] = tableAppearances[t] + 1 + end + end + + return tableAppearances +end + +local copySequence = function(s) + local copy, len = {}, #s + for i=1, len do copy[i] = s[i] end + return copy, len +end + +local function makePath(path, ...) + local keys = {...} + local newPath, len = copySequence(path) + for i=1, #keys do + newPath[len + i] = keys[i] + end + return newPath +end + +local function processRecursive(process, item, path, visited) + + if item == nil then return nil end + if visited[item] then return visited[item] end + + local processed = process(item, path) + if type(processed) == 'table' then + local processedCopy = {} + visited[item] = processedCopy + local processedKey + + for k,v in pairs(processed) do + processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) + if processedKey ~= nil then + processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) + end + end + + local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) + setmetatable(processedCopy, mt) + processed = processedCopy + end + return processed +end + + + +------------------------------------------------------------------- + +local Inspector = {} +local Inspector_mt = {__index = Inspector} + +function Inspector:puts(...) + local args = {...} + local buffer = self.buffer + local len = #buffer + for i=1, #args do + len = len + 1 + buffer[len] = tostring(args[i]) + end +end + +function Inspector:down(f) + self.level = self.level + 1 + f() + self.level = self.level - 1 +end + +function Inspector:tabify() + self:puts(self.newline, string.rep(self.indent, self.level)) +end + +function Inspector:alreadyVisited(v) + return self.ids[type(v)][v] ~= nil +end + +function Inspector:getId(v) + local tv = type(v) + local id = self.ids[tv][v] + if not id then + id = self.maxIds[tv] + 1 + self.maxIds[tv] = id + self.ids[tv][v] = id + end + return id +end + +function Inspector:putKey(k) + if isIdentifier(k) then return self:puts(k) end + self:puts("[") + self:putValue(k) + self:puts("]") +end + +function Inspector:putTable(t) + if t == inspect.KEY or t == inspect.METATABLE then + self:puts(tostring(t)) + elseif self:alreadyVisited(t) then + self:puts('') + elseif self.level >= self.depth then + self:puts('{...}') + else + if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end + + local nonSequentialKeys, sequenceLength = getNonSequentialKeys(t) + local mt = getmetatable(t) + local toStringResult = getToStringResultSafely(t, mt) + + self:puts('{') + self:down(function() + if toStringResult then + self:puts(' -- ', escape(toStringResult)) + if sequenceLength >= 1 then self:tabify() end + end + + local count = 0 + for i=1, sequenceLength do + if count > 0 then self:puts(',') end + self:puts(' ') + self:putValue(t[i]) + count = count + 1 + end + + for _,k in ipairs(nonSequentialKeys) do + if count > 0 then self:puts(',') end + self:tabify() + self:putKey(k) + self:puts(' = ') + self:putValue(t[k]) + count = count + 1 + end + + if mt then + if count > 0 then self:puts(',') end + self:tabify() + self:puts(' = ') + self:putValue(mt) + end + end) + + if #nonSequentialKeys > 0 or mt then -- result is multi-lined. Justify closing } + self:tabify() + elseif sequenceLength > 0 then -- array tables have one extra space before closing } + self:puts(' ') + end + + self:puts('}') + end +end + +function Inspector:putValue(v) + local tv = type(v) + + if tv == 'string' then + self:puts(smartQuote(escape(v))) + elseif tv == 'number' or tv == 'boolean' or tv == 'nil' then + self:puts(tostring(v)) + elseif tv == 'table' then + self:putTable(v) + else + self:puts('<',tv,' ',self:getId(v),'>') + end +end + +------------------------------------------------------------------- + +function inspect.inspect(root, options) + options = options or {} + + local depth = options.depth or math.huge + local newline = options.newline or '\n' + local indent = options.indent or ' ' + local process = options.process + + if process then + root = processRecursive(process, root, {}, {}) + end + + local inspector = setmetatable({ + depth = depth, + buffer = {}, + level = 0, + ids = setmetatable({}, idsMetaTable), + maxIds = setmetatable({}, maxIdsMetaTable), + newline = newline, + indent = indent, + tableAppearances = countTableAppearances(root) + }, Inspector_mt) + + inspector:putValue(root) + + return table.concat(inspector.buffer) +end + +setmetatable(inspect, { __call = function(_, ...) return inspect.inspect(...) end }) + +return inspect + diff --git a/demo/main.lua b/demo/main.lua index e0f79b2..ff5e1f0 100644 --- a/demo/main.lua +++ b/demo/main.lua @@ -1,8 +1,14 @@ local lg = love.graphics -local pop +local pop, inspect + +local debugDraw = false function love.load() + print(love.getVersion()) + + inspect = require "debug-lib/inspect" pop = require "pop" + ---[[ local c = pop.box():align("center", "center"):setSize(300, 300) pop.box(c, {255, 0, 0, 255}):setSize(100, 50) pop.box(c, {0, 255, 0, 255}):align("center"):setSize(100, 100) @@ -17,6 +23,45 @@ function love.load() pop.skin(pop.text("Here's easier-to-code test text in the center!"):align("center", "center", true)) -- 'true' means align to pixel! w = pop.box(nil, {255, 255, 255, 255}):align(false, "bottom"):setSize(150, 150) b = pop.box(w, {0, 0, 0, 255}):setMargin(5):setSize(100, 100) + --]] + + --c:move(100) + pop.box({255, 0, 0, 255}):position(50, 500) -- testing streamlined_get_set extension & optional parents + --b:margin(2) -- testing streamlined_get_set extension + b:fill() -- testing fill! + + ---[[ + w2 = pop.window(nil, "Window") + w2:move(100, 100) + w2:setWidth(500) + w2:move(-50, 80) + w2:setHeight(500) + w2:move(0, -175) + w2.title:align("center") + w2:position(0, 0) + --w2:setAlignment("right") + w2:size(200, 120):position(90, 70) + --w2:align("center") + --w2:setAlignment("center"):align("center") + + --w2.child[1]:setBackground {100, 100, 100, 255} + --w2.child[3]:setBackground {160, 140, 40, 255} + --]] + + local test = lg.newImage("test.png") + G = pop.element():align("right"):move(-2, 2) + a = pop.box(G, test):align("right") + b = pop.box(G, test):align("right"):move(-25):setWidth(40) + c = pop.box(G, test):align("right"):move(0, 25):setHeight(40) + + print(a.horizontal, a.vertical) + print(b.horizontal, b.vertical) + print(c.horizontal, c.vertical) + + local window = pop.window():align("center", "center"):setTitle("Welcome!") + --window:addChild(pop.text("Welcome to Pop.Box()!")) + + --TODO make debugDraw better end function love.update(dt) @@ -25,7 +70,15 @@ end function love.draw() pop.draw() - --pop.debugDraw() + + if debugDraw then + pop.debugDraw() + --w2:debugDraw() + end +end + +function love.mousemoved(x, y, dx, dy) + pop.mousemoved(x, y, dx, dy) end function love.mousepressed(x, y, button) @@ -39,6 +92,10 @@ end function love.keypressed(key) local handled = pop.keypressed(key) + if (key == "d") and (not handled) then + debugDraw = not debugDraw + end + if (key == "escape") and (not handled) then love.event.quit() end diff --git a/demo/pop/elements/box.lua b/demo/pop/elements/box.lua index bd95152..6b9fb2f 100644 --- a/demo/pop/elements/box.lua +++ b/demo/pop/elements/box.lua @@ -21,6 +21,7 @@ do local w, h = self.background:getDimensions() w = self.w / w h = self.h / h + graphics.setColor(255, 255, 255, 255) graphics.draw(self.background, self.x, self.y, 0, w, h) end end @@ -70,11 +71,13 @@ do _base_0.__index = _base_0 setmetatable(_base_0, _parent_0.__base) _class_0 = setmetatable({ - __init = function(self, pop, parent, background) + __init = function(self, parent, background) if background == nil then background = false end - _class_0.__parent.__init(self, pop, parent) + _class_0.__parent.__init(self, parent) + self.w = 20 + self.h = 20 self.background = background end, __base = _base_0, diff --git a/demo/pop/elements/element.lua b/demo/pop/elements/element.lua index 163d7a3..e0d5301 100644 --- a/demo/pop/elements/element.lua +++ b/demo/pop/elements/element.lua @@ -16,6 +16,14 @@ do graphics.print("e", self.x, self.y) return self end, + addChild = function(self, child) + self.child[#self.child + 1] = child + child.parent = self + return self + end, + getChildren = function(self) + return self.child + end, move = function(self, x, y) if x then self.x = self.x + x @@ -105,6 +113,32 @@ do getSize = function(self) return self.w, self.h end, + setWidth = function(self, w) + local _exp_0 = self.horizontal + if "center" == _exp_0 then + self.x = self.x - ((w - self.w) / 2) + elseif "right" == _exp_0 then + self.x = self.x - (w - self.w) + end + self.w = w + return self + end, + getWidth = function(self) + return self.w + end, + setHeight = function(self, h) + local _exp_0 = self.vertical + if "center" == _exp_0 then + self.y = self.y - ((h - self.h) / 2) + elseif "bottom" == _exp_0 then + self.y = self.y - (h - self.h) + end + self.h = h + return self + end, + getHeight = function(self) + return self.h + end, adjustSize = function(self, w, h) local W, H = self:getSize() if w then @@ -136,7 +170,7 @@ do elseif "bottom" == _exp_1 then self.y = self.y + (self.parent.h - self.h - self.margin) end - if toPixel then + if toPixel or (toPixel == nil) then self.x = floor(self.x) self.y = floor(self.y) end @@ -158,6 +192,9 @@ do end return self end, + getAlignment = function(self) + return self.horizontal, self.vertical + end, setMargin = function(self, margin) self.margin = margin self:align() @@ -165,25 +202,31 @@ do end, getMargin = function(self) return self.margin + end, + fill = function(self) + self.x = self.parent.x + self.margin + self.y = self.parent.y + self.margin + self.w = self.parent.w - self.margin * 2 + self.h = self.parent.h - self.margin * 2 end } _base_0.__index = _base_0 _class_0 = setmetatable({ - __init = function(self, pop, parent) + __init = function(self, parent) self.parent = parent self.child = { } + self.w = 0 + self.h = 0 + self.margin = 0 if parent then - self.x = parent.x or 0 - self.y = parent.y or 0 + self.x = parent.x + self.y = parent.y else self.x = 0 self.y = 0 end - self.w = 10 - self.h = 10 self.horizontal = "left" self.vertical = "top" - self.margin = 0 end, __base = _base_0, __name = "element" diff --git a/demo/pop/elements/text.lua b/demo/pop/elements/text.lua index ba1cd78..9d5df18 100644 --- a/demo/pop/elements/text.lua +++ b/demo/pop/elements/text.lua @@ -12,19 +12,10 @@ do local _class_0 local _parent_0 = element local _base_0 = { - wrap = function(pop) - return function(parent, ...) - if type(parent) == "string" then - return pop.create("text", nil, parent, ...) - else - return pop.create("text", parent, ...) - end - end - end, draw = function(self) graphics.setColor(self.color) graphics.setFont(self.font) - graphics.print(self.text, self.x, self.y) + graphics.print(self.txt, self.x, self.y) return self end, debugDraw = function(self) @@ -38,8 +29,8 @@ do return self end, setSize = function(self) - local w = self.font:getWidth(self.text) - local h = self.font:getHeight() * (select(2, self.text:gsub("\n", "\n")) + 1) + local w = self.font:getWidth(self.txt) + local h = self.font:getHeight() * (select(2, self.txt:gsub("\n", "\n")) + 1) local _exp_0 = self.horizontal if "center" == _exp_0 then self.x = self.x - ((w - self.w) / 2) @@ -56,16 +47,24 @@ do self.h = h return self end, + setWidth = function(self) + self:setSize() + return self + end, + setHeight = function(self) + self:setSize() + return self + end, setText = function(self, text) if text == nil then text = "" end - self.text = text + self.txt = text self:setSize() return self end, getText = function(self) - return self.text + return self.txt end, setFont = function(self, font) self.font = font @@ -98,7 +97,7 @@ do _base_0.__index = _base_0 setmetatable(_base_0, _parent_0.__base) _class_0 = setmetatable({ - __init = function(self, pop, parent, text, color) + __init = function(self, parent, text, color) if text == nil then text = "" end @@ -110,7 +109,7 @@ do 255 } end - _class_0.__parent.__init(self, pop, parent) + _class_0.__parent.__init(self, parent) self.font = graphics.newFont(14) self:setText(text) self.color = color @@ -137,6 +136,16 @@ do end }) _base_0.__class = _class_0 + local self = _class_0 + self.wrap = function(pop) + return function(parent, ...) + if type(parent) == "string" then + return pop.create("text", nil, parent, ...) + else + return pop.create("text", parent, ...) + end + end + end if _parent_0.__inherited then _parent_0.__inherited(_parent_0, _class_0) end diff --git a/demo/pop/elements/window.lua b/demo/pop/elements/window.lua new file mode 100644 index 0000000..72b2033 --- /dev/null +++ b/demo/pop/elements/window.lua @@ -0,0 +1,251 @@ +local graphics +graphics = love.graphics +local sub, len +do + local _obj_0 = string + sub, len = _obj_0.sub, _obj_0.len +end +local path = sub(..., 1, len(...) - len("/window")) +local element = require(tostring(path) .. "/element") +local box = require(tostring(path) .. "/box") +local text = require(tostring(path) .. "/text") +local left = 1 +local mousemoved_event = true +do + local major, minor, revision = love.getVersion() + if (major == 0) and (minor == 10) and ((revision == 0) or (revision == 1)) then + left = 1 + elseif (major == 0) and (minor == 9) then + left = "l" + if revision == 1 then + mousemoved_event = false + end + else + print("elements/window: unrecognized LÖVE version: " .. tostring(major) .. "." .. tostring(minor) .. "." .. tostring(revision)) + print(" assuming LÖVE version > 0.10.1 (there may be bugs)") + end +end +local pop_ref = false +local window +do + local _class_0 + local _parent_0 = element + local _base_0 = { + load = function(pop) + pop_ref = pop + end, + debugDraw = function(self) + graphics.setLineWidth(0.5) + graphics.setColor(0, 0, 0, 100) + graphics.rectangle("fill", self.x, self.y, self.w, self.h) + graphics.setColor(200, 0, 200, 200) + graphics.rectangle("line", self.x, self.y, self.w, self.h) + graphics.setColor(255, 200, 255, 255) + graphics.print("w", self.x, self.y) + return self + end, + addChild = function(self, child) + self.window.child[#self.window.child + 1] = child + child.parent = self.window + return self + end, + getChildren = function(self) + return self.window.child + end, + align = function(self, horizontal, vertical, toPixel) + _class_0.__parent.__base.align(self, horizontal, vertical, toPixel) + for i = 1, #self.child do + self.child[i]:align() + end + self.window:move(nil, self.head:getHeight()) + return self + end, + setSize = function(self, w, h) + local x = 0 + local y = 0 + if w then + local _exp_0 = self.horizontal + if "center" == _exp_0 then + x = x - ((w - self.w) / 2) + elseif "right" == _exp_0 then + x = x - (w - self.w) + end + self.head:setWidth(w) + self.window:setWidth(w) + self.w = w + self.x = self.x + x + self.title:align() + end + if h then + h = h - self.head:getHeight() + local _exp_0 = self.vertical + if "center" == _exp_0 then + y = y - ((h - self.h) / 2) + elseif "right" == _exp_0 then + y = y - (h - self.h) + end + self.window:setHeight(h) + self.h = h + self.head:getHeight() + self.y = self.y + y + end + self.head:move(x, y) + self.window:move(x, y) + return self + end, + setWidth = function(self, w) + local x = 0 + local _exp_0 = self.horizontal + if "center" == _exp_0 then + x = x - ((w - self.w) / 2) + elseif "right" == _exp_0 then + x = x - (w - self.w) + end + self.head:setWidth(w) + self.window:setWidth(w) + self.w = w + self.x = self.x + x + self.title:align() + self.head:move(x) + self.window:move(x) + return self + end, + setHeight = function(self, h) + local y = 0 + h = h - self.head:getHeight() + local _exp_0 = self.vertical + if "center" == _exp_0 then + y = y - ((h - self.h) / 2) + elseif "right" == _exp_0 then + y = y - (h - self.h) + end + self.window:setHeight(h) + self.h = h + self.head:getHeight() + self.y = self.y + y + self.head:move(nil, y) + self.title:move(nil, y) + self.window:move(nil, y) + return self + end, + setTitle = function(self, title) + self.title:setText(title) + return self + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, parent, title, tBackground, tColor, wBackground) + if title == nil then + title = "window" + end + if tBackground == nil then + tBackground = { + 25, + 180, + 230, + 255 + } + end + if tColor == nil then + tColor = { + 255, + 255, + 255, + 255 + } + end + if wBackground == nil then + wBackground = { + 200, + 200, + 210, + 255 + } + end + _class_0.__parent.__init(self, parent) + self.head = box(self, tBackground) + self.title = text(self, title, tColor) + self.window = box(self, wBackground) + local height = self.title:getHeight() + self.head:setSize(self.w, height) + self.window:move(nil, height) + self:setSize(100, 80) + self.child = { + self.head, + self.title, + self.window + } + self.head.selected = false + if mousemoved_event then + self.head.mousemoved = function(self, x, y, dx, dy) + if self.selected then + self.parent:move(y, dx) + return true + end + return false + end + self.head.mousepressed = function(self, x, y, button) + if button == left then + self.selected = true + return true + end + return false + end + self.head.mousereleased = function(self, x, y, button) + if button == left then + self.selected = false + pop_ref.focused = false + return true + end + return false + end + else + self.head.mx = 0 + self.head.my = 0 + self.head.update = function(self) + return false + end + self.head.mousepressed = function(self, x, y, button) + if button == left then + self.selected = true + self.mx = x + self.my = y + end + end + self.head.mousereleased = function(self, x, y, button) + if button == left then + self.selected = false + return true + end + return false + end + end + end, + __base = _base_0, + __name = "window", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + window = _class_0 + return _class_0 +end diff --git a/demo/pop/extensions/streamlined_get_set.lua b/demo/pop/extensions/streamlined_get_set.lua new file mode 100644 index 0000000..b23a6d2 --- /dev/null +++ b/demo/pop/extensions/streamlined_get_set.lua @@ -0,0 +1,51 @@ +local graphics +graphics = love.graphics +local sub, len +do + local _obj_0 = string + sub, len = _obj_0.sub, _obj_0.len +end +local path = sub(..., 1, len(...) - len("/extensions/streamlined_get_set")) +local element = require(tostring(path) .. "/elements/element") +element.__base.position = function(self, x, y) + if x or y then + return self:setPosition(x, y) + else + return self:getPosition() + end +end +element.__base.size = function(self, w, h) + if w or h then + return self:setSize(w, h) + else + return self:getSize() + end +end +element.__base.width = function(self, w) + if w then + return self:setWidth(w) + else + return self:getWidth() + end +end +element.__base.height = function(self, h) + if h then + return self:setHeight(h) + else + return self:getHeight() + end +end +element.__base.alignment = function(self, horizontal, vertical) + if horizontal or vertical then + return self:setAlignment(horizontal, vertical) + else + return self:getAlignment() + end +end +element.__base.margin = function(self, m) + if m then + return self:setMargin(m) + else + return self:getMargin() + end +end diff --git a/demo/pop/init.lua b/demo/pop/init.lua index d77f6cc..fc09351 100644 --- a/demo/pop/init.lua +++ b/demo/pop/init.lua @@ -1,3 +1,6 @@ +if not (love.getVersion) then + error("Pop.Box only supports LÖVE versions >= 0.9.1") +end local filesystem, graphics do local _obj_0 = love @@ -5,11 +8,15 @@ do end local insert insert = table.insert +local inheritsFromElement +inheritsFromElement = require(tostring(...) .. "/util").inheritsFromElement local path = ... local pop = { } pop.elements = { } pop.skins = { } +pop.events = { } pop.screen = false +pop.focused = false pop.load = function() local elements = filesystem.getDirectoryItems(tostring(path) .. "/elements") for i = 1, #elements do @@ -21,6 +28,9 @@ pop.load = function() end local name = elements[i]:sub(1, -5) pop.elements[name] = require(tostring(path) .. "/elements/" .. tostring(name)) + if pop.elements[name].load then + pop.elements[name].load(pop) + end print("element loaded: \"" .. tostring(name) .. "\"") if not (pop[name]) then if pop.elements[name].wrap then @@ -55,6 +65,23 @@ pop.load = function() break end end + local extensions = filesystem.getDirectoryItems(tostring(path) .. "/extensions") + for i = 1, #extensions do + local _continue_0 = false + repeat + if not (extensions[i]:sub(-4) == ".lua") then + _continue_0 = true + break + end + local name = extensions[i]:sub(1, -5) + require(tostring(path) .. "/extensions/" .. tostring(name)) + print("extension loaded: \"" .. tostring(name) .. "\"") + _continue_0 = true + until true + if not _continue_0 then + break + end + end pop.screen = pop.create("element", false):setSize(graphics.getWidth(), graphics.getHeight()) return print("created \"pop.screen\"") end @@ -62,9 +89,14 @@ pop.create = function(element, parent, ...) if parent == nil then parent = pop.screen end - element = pop.elements[element](pop, parent, ...) - if parent then + if inheritsFromElement(parent) then + element = pop.elements[element](parent, ...) insert(parent.child, element) + elseif parent == false then + element = pop.elements[element](false, ...) + else + element = pop.elements[element](pop.screen, parent, ...) + insert(pop.screen.child, element) end return element end @@ -94,19 +126,62 @@ pop.draw = function(element) end end end -pop.mousepressed = function(x, y, button, element) - if element == nil then - element = pop.screen +pop.mousemoved = function(self, x, y, dx, dy) + if pop.focused and pop.focused.mousemoved then + return pop.focused:mousemoved(x, y, dx, dy) end - print("mousepressed", x, y, button, element) return false end -pop.mousereleased = function(x, y, button, element) - if element == nil then +pop.mousepressed = function(x, y, button, element) + if not (element) then + print("mousepressed", x, y, button) element = pop.screen end - print("mousereleased", x, y, button, element) - return false + local handled = false + if (x >= element.x) and (x <= element.x + element.w) and (y >= element.y) and (y <= element.y + element.h) then + if element.mousepressed then + handled = element:mousepressed(x - element.x, y - element.y, button) + end + if handled then + pop.focused = element + pop.events[button] = element + else + for i = 1, #element.child do + handled = pop.mousepressed(x, y, button, element.child[i]) + if handled then + break + end + end + end + end + return handled +end +pop.mousereleased = function(x, y, button) + print("mousereleased", x, y, button) + local clickedHandled = false + local mousereleasedHandled = false + do + local element = pop.events[button] + if element then + if element.clicked and (x >= element.x) and (x <= element.x + element.w) and (y >= element.y) and (y <= element.y + element.h) then + do + clickedHandled = element:clicked(x - element.x, y - element.y, button) + if clickedHandled then + pop.events[button] = nil + end + end + end + if element.mousereleased then + do + mousereleasedHandled = element:mousereleased(x - element.x, y - element.y, button) + if mousereleasedHandled then + pop.events[button] = nil + end + end + end + end + end + return clickedHandled, mousereleasedHandled end pop.keypressed = function(key) print("keypressed", key) diff --git a/demo/pop/util.lua b/demo/pop/util.lua new file mode 100644 index 0000000..0804d8b --- /dev/null +++ b/demo/pop/util.lua @@ -0,0 +1,19 @@ +local inheritsFromElement +inheritsFromElement = function(object) + if object and object.__class then + local cls = object.__class + if cls.__name == "element" then + return true + end + while cls.__parent do + cls = cls.__parent + if cls.__name == "element" then + return true + end + end + end + return false +end +return { + inheritsFromElement = inheritsFromElement +} diff --git a/demo/test.ogv b/demo/test.ogv new file mode 100644 index 0000000..1dcdf39 Binary files /dev/null and b/demo/test.ogv differ diff --git a/demo/test.png b/demo/test.png new file mode 100644 index 0000000..bf19d3b Binary files /dev/null and b/demo/test.png differ diff --git a/docs/Drawables.md b/docs/Drawables.md new file mode 100644 index 0000000..b1ed96e --- /dev/null +++ b/docs/Drawables.md @@ -0,0 +1,15 @@ +# Supported Drawables + +Pop.Box supports three [Drawables][1]: Canvas, Image, and Video. + +**Note**: Video and Canvas support are untested. + +Additionally, in the place of a Drawable, you can use `false` to not render +anything, or a table of color values. The color values should be in the format +LÖVE uses (`{red, green, blue, alpha}`, see [love.graphics.setColor][2]). + +(The alpha value is optional and will default to `255`, but not using an alpha + is likely to mess up your rendering if an alpha value is used *anywhere else*.) + +[1]: https://love2d.org/wiki/Drawable +[2]: https://love2d.org/wiki/love.graphics.setColor diff --git a/docs/Elements.md b/docs/Elements.md new file mode 100644 index 0000000..cc38df4 --- /dev/null +++ b/docs/Elements.md @@ -0,0 +1,31 @@ +# Elements + +Elements are the core of Pop.Box. + +Once `pop` has been required, you can create Elements and interact with them. +Most elements can be created like this: `local box = pop.box(...)` + +However, if an element's name clashes with a function name used in Pop.Box, you +will have to use `pop.create(type, ...)` where `type` is a string naming the +element type. (No standard elements do this.) + +When creating an element, the first argument is its parent element. If the first +argument is not an element, it will be treated as the second argument. If it is +`false`, then an element with no parent will be created. + +When no parent is specified, an element's parent is `pop.screen`, which is the +top-level element of Pop.Box. (This behavior can be modified by custom elements, +so check their documentation.) + +## Available Elements + +- [Element][1] (The base of all elements, and useful for alignment.) +- [Box][2] (A box, can be colored, or display a [supported Drawable][3].) +- [Text][4] (Plain text, no special features. Useful for basic labels and such.) +- [Window][5] (A moveable window. Has a title and area for other elements.) + +[1]: ./elements/element.md +[2]: ./elements/box.md +[3]: ./Drawables.md +[4]: ./elements/text.md +[5]: ./elements/window.md diff --git a/docs/Extensions.md b/docs/Extensions.md new file mode 100644 index 0000000..778c7b4 --- /dev/null +++ b/docs/Extensions.md @@ -0,0 +1,14 @@ +# Extensions + +Extensions are simply a way for custom code to be run after loading elements and +skins. Simply place a `.lua` file in the `extensions` directory, and it will be +required. + +## Standard Extensions + +There is only one standard extension, which modifies element classes to add a +more convenient getter/setter method to most get/set methods. For example, +instead of `element:getSize()`, just call `element:size()`. Instead of +`element:setSize(w, h)`, call `element:size(w, h)`. + +This is mostly for a demo of what can be possible, but also might be useful. diff --git a/docs/Pop.md b/docs/Pop.md new file mode 100644 index 0000000..79d1b69 --- /dev/null +++ b/docs/Pop.md @@ -0,0 +1,54 @@ +# Pop Module + +This is the main module that allows you to access everything else in Pop.Box. +Simply require it (`local pop = require "pop"`) and define LÖVE callbacks for: + +- `pop.update(dt)` +- `pop.draw()` +- `pop.mousemoved(x, y, dx, dy)` (when using LÖVE 0.10.0 or later) +- `pop.mousepressed(x, y, button)` +- `pop.mousereleased(x, y, button)` +- `pop.keypressed(key)` +- `pop.keyreleased(key)` +- `pop.textinput(text)` + +Every callback returns `true`/`false` for whether or not the event was handled. +For example, using the `mousepressed` event handler: + +```lua +function love.mousepressed(x, y, button) + local handled = pop.mousepressed(x, y, button) + if not handled then + -- do something useful + end +end +``` + +## Creating Elements + +Once `pop` has been required, you can create [Elements][1] and interact with +them. Most elements can be created like this: `local box = pop.box(...)` + +For more information, see the [Elements documentation][1]. + +## Skinning Elements + +See the [Skins][2] documentation. + +## Custom Elements/Skins/Extensions + +Any `.lua` file placed in the `elements`, `skins`, and `extensions` directories +within the module will be loaded and available as appropriate. See the +documentation on each for how to make them: + +- [Elements][1] +- [Skins][2] +- [Extensions][3] + +Also of use, there is a separate set of docs about how Pop.Box works under the +surface: [Pop Module (dev)][4] + +[1]: ./Elements.md +[2]: ./Skins.md +[3]: ./Extensions.md +[4]: ./dev/Pop.md diff --git a/docs/Skins.md b/docs/Skins.md new file mode 100644 index 0000000..9fdcf04 --- /dev/null +++ b/docs/Skins.md @@ -0,0 +1,19 @@ +# Skins + +**Note**: This system is mostly an after-thought right now, and will probably +be replaced with something else entirely. + +Skins are simple tables containing information to style a variety of elements. +Use `pop.skin()` to apply a skin to an element and its children (see +[Pop Module][3] documentation). Skins are loaded from the `skins` directory. + +- `color` - A table of RGBA values (see [love.graphics.setColor][2]), used as a + foreground color (currently for `text` elements only). +- `background` - A [supported Drawable][4], used for backgrounds (currently + used on `box` elements only). +- `font` - A [Font][5]. + +[2]: https://love2d.org/wiki/love.graphics.setColor +[3]: ./Pop.md +[4]: ./Drawables.md +[5]: https://love2d.org/wiki/Font diff --git a/docs/dev/Pop.md b/docs/dev/Pop.md new file mode 100644 index 0000000..36602fc --- /dev/null +++ b/docs/dev/Pop.md @@ -0,0 +1,8 @@ +# Pop Module (dev) + +(This document focuses on the structure and code of Pop.Box, not how to use it. + See the [regular documentation][1] for that.) + +TODO: Write me. + +[1]: ../Pop.md diff --git a/docs/elements/box.md b/docs/elements/box.md new file mode 100644 index 0000000..e2ec304 --- /dev/null +++ b/docs/elements/box.md @@ -0,0 +1,17 @@ +# box + +The box element is a rectangle that has a [supported Drawable][1] as its +background. + +## Methods + +- `setBackground(background)` Using a supported Drawable (see above), set the + background. +- `getBackground()` Returns the background in use. +- `setColor(red, green, blue, alpha)` Sets the background to the specified + color. `alpha` is optional and defaults to `255`. Alternately, pass a table of + color values (ex: `{red, green, blue, alpha}`). +- `getColor()` Returns red, green, blue, and alpha color values of background. + Errors if the background is not a color. + +[1]: ../Drawables.md diff --git a/docs/elements/element.md b/docs/elements/element.md new file mode 100644 index 0000000..eb87609 --- /dev/null +++ b/docs/elements/element.md @@ -0,0 +1,53 @@ +# element + +This is the base of all elements, and useful for alignment purposes. It is also +the element type used for `pop.screen` (the top-level element of Pop.Box). All +methods here are available on all other elements (any differences are noted on +their documentation pages). + +## Alignment + +All elements have a `horizontal` and `vertical` alignment property. These modify +how elements are aligned and how positions are handled. For example, an element +aligned to the top-right will return the position of the top-right corner when +calling `getPosition()`. + +- `horizontal` can be `left`, `center`, or `right`. Defaults to `left`. +- `vertical` can be `top`, `center`, or `bottom`. Defaults to `top`. + +## Methods + +- `addChild(child)` Adds a child element. +- `getChildren()` Returns a numerically indexed table of child elements. +- `move(x, y)` Moves the element (and its children) by specified values. + Parameters optional. +- `setPosition(x, y)` Sets position of the element (and its children) based on + the alignment of the element. Parameters optional. +- `getPosition()` Returns x/y position of the element based on its alignment. +- `setSize(w, h)` Sets the width/height of the element, element keeps "in-place" + based on its alignment. (For example, a right-aligned element will grow to the + left.) Parameters optional. +- `getSize()` Returns the width/height of the element. +- `setWidth(w)` Sets the width of the element. Element stays "in-place" based on + its alignment. +- `getWidth()` Returns the width of the element. +- `setHeight(h)` Sets the height of the element. Element stays "in-place" based + on its alignment. +- `getHeight()` Returns the height of the element. +- `adjustSize(w, h)` Grows the element by a relative width/height. Element stays + "in-place" based on its alignment. +- `align(horizontal, vertical, toPixel)` Aligns the element based on its margin + and parent. `toPixel` is a boolean for pixel-perfect alignment, defaulting to + true. See above section about alignment for valid values of `horizontal` and + `vertical`. A parent element is required for this method. +- `alignTo(element, horizontal, vertical, toPixel)` Works just like `align()`, + except that alignment is based on a specific element instead of the parent. + Does not require a parent element. +- `setAlignment(horizontal, vertical)` Sets alignment values on the element + *without* moving the element. +- `getAlignment()` Returns the `horizontal` and `vertical` alignment of the + element. +- `setMargin(m)` Sets a margin to be used when aligning the element. +- `getMargin()` Returns the current margin value. +- `fill()` Resizes and aligns the element to fill its parent's area, with the + element's margin taken into account on all sides. diff --git a/docs/elements/text.md b/docs/elements/text.md new file mode 100644 index 0000000..fd68c2b --- /dev/null +++ b/docs/elements/text.md @@ -0,0 +1,22 @@ +# text + +The text element is plain text in one color. Nothing special. + +(**Note**: Other, more advanced text elements are planned, with support for + things like line-wrapping and custom coloring and formatting of text.) + +## Methods + +- `setSize()` Unlike other elements, a size cannot be set. If this is called, it + will fix the size of the text if it somehow was modified incorrectly. +- `setWidth()` Width cannot be set. If called, will fix the size of the text. +- `setHeight()` Height cannot be set. If called, will fix the size of the text. +- `setText(text)` Sets the text of the element. Will resize element to fit text. + Newlines are supported. Defaults to an empty string. +- `getText()` Returns the text of the element. +- `setFont()` Sets the font to be used on this element. Will resize to fit the + text and font. +- `setColor(red, green, blue, alpha)` Sets color of text. `alpha` is optional + and defaults to `255`. Alternately, pass a table of color values (ex: `{red, + green, blue, alpha}`). +- `getColor()` Returns red, green, blue, and alpha values of color. diff --git a/docs/elements/window.md b/docs/elements/window.md new file mode 100644 index 0000000..dc443bd --- /dev/null +++ b/docs/elements/window.md @@ -0,0 +1,4 @@ +# window + +Documentation has not been written yet, as this element is still under heavy +development. diff --git a/dummy/main.lua b/dummy/main.lua new file mode 100644 index 0000000..c7744af --- /dev/null +++ b/dummy/main.lua @@ -0,0 +1,34 @@ +local lg = love.graphics +local pop, parts + +-- pretend parts has been defined + +function love.load() + pop = require "lib.pop" + local width4 = lg.getWidth()/4 + local height2 = lg.getHeight()/2 + local PartList = pop.scrollbox():setSize(width4, height2)--:setSizeControl(true) --defaults to true + local PartInfo = pop.scrollbox():setSize(width4, height2):move(nil, height2) + local CraftInfo = pop.box():setSize(width4, height2):move(lg.getWidth()*3/4) + local columns = math.floor(PartList:getWidth()/128) + local rows = math.floor(#parts/columns) + local grid = pop.grid(PartList, columns, rows) + for i = 1, #parts do + -- pretend that parts.gui is a box designed to fix in here properly + grid:add(parts[i].gui) -- pretend by default, adding something to a grid like this adds it to first available spot + -- also, grids auto-resize their children + end + PartList:add(grid) -- BULLSHIT ?! +end + +-- parts.gui is something like this: + +gui = pop.box(newImage()) -- assumes a box can take 'userdata' as first arg and know it is an image +gui.clicked = function(x, y, button) + -- don't care about x/y + if button == "l" then --left + selected = gui.partReference -- or something + elseif button == "r" then --right + displayPartInfo() -- something happens to display it in the proper spot + end +end diff --git a/lib/pop/elements/box.lua b/lib/pop/elements/box.lua index bd95152..6b9fb2f 100644 --- a/lib/pop/elements/box.lua +++ b/lib/pop/elements/box.lua @@ -21,6 +21,7 @@ do local w, h = self.background:getDimensions() w = self.w / w h = self.h / h + graphics.setColor(255, 255, 255, 255) graphics.draw(self.background, self.x, self.y, 0, w, h) end end @@ -70,11 +71,13 @@ do _base_0.__index = _base_0 setmetatable(_base_0, _parent_0.__base) _class_0 = setmetatable({ - __init = function(self, pop, parent, background) + __init = function(self, parent, background) if background == nil then background = false end - _class_0.__parent.__init(self, pop, parent) + _class_0.__parent.__init(self, parent) + self.w = 20 + self.h = 20 self.background = background end, __base = _base_0, diff --git a/lib/pop/elements/element.lua b/lib/pop/elements/element.lua index 163d7a3..e0d5301 100644 --- a/lib/pop/elements/element.lua +++ b/lib/pop/elements/element.lua @@ -16,6 +16,14 @@ do graphics.print("e", self.x, self.y) return self end, + addChild = function(self, child) + self.child[#self.child + 1] = child + child.parent = self + return self + end, + getChildren = function(self) + return self.child + end, move = function(self, x, y) if x then self.x = self.x + x @@ -105,6 +113,32 @@ do getSize = function(self) return self.w, self.h end, + setWidth = function(self, w) + local _exp_0 = self.horizontal + if "center" == _exp_0 then + self.x = self.x - ((w - self.w) / 2) + elseif "right" == _exp_0 then + self.x = self.x - (w - self.w) + end + self.w = w + return self + end, + getWidth = function(self) + return self.w + end, + setHeight = function(self, h) + local _exp_0 = self.vertical + if "center" == _exp_0 then + self.y = self.y - ((h - self.h) / 2) + elseif "bottom" == _exp_0 then + self.y = self.y - (h - self.h) + end + self.h = h + return self + end, + getHeight = function(self) + return self.h + end, adjustSize = function(self, w, h) local W, H = self:getSize() if w then @@ -136,7 +170,7 @@ do elseif "bottom" == _exp_1 then self.y = self.y + (self.parent.h - self.h - self.margin) end - if toPixel then + if toPixel or (toPixel == nil) then self.x = floor(self.x) self.y = floor(self.y) end @@ -158,6 +192,9 @@ do end return self end, + getAlignment = function(self) + return self.horizontal, self.vertical + end, setMargin = function(self, margin) self.margin = margin self:align() @@ -165,25 +202,31 @@ do end, getMargin = function(self) return self.margin + end, + fill = function(self) + self.x = self.parent.x + self.margin + self.y = self.parent.y + self.margin + self.w = self.parent.w - self.margin * 2 + self.h = self.parent.h - self.margin * 2 end } _base_0.__index = _base_0 _class_0 = setmetatable({ - __init = function(self, pop, parent) + __init = function(self, parent) self.parent = parent self.child = { } + self.w = 0 + self.h = 0 + self.margin = 0 if parent then - self.x = parent.x or 0 - self.y = parent.y or 0 + self.x = parent.x + self.y = parent.y else self.x = 0 self.y = 0 end - self.w = 10 - self.h = 10 self.horizontal = "left" self.vertical = "top" - self.margin = 0 end, __base = _base_0, __name = "element" diff --git a/lib/pop/elements/text.lua b/lib/pop/elements/text.lua index ba1cd78..9d5df18 100644 --- a/lib/pop/elements/text.lua +++ b/lib/pop/elements/text.lua @@ -12,19 +12,10 @@ do local _class_0 local _parent_0 = element local _base_0 = { - wrap = function(pop) - return function(parent, ...) - if type(parent) == "string" then - return pop.create("text", nil, parent, ...) - else - return pop.create("text", parent, ...) - end - end - end, draw = function(self) graphics.setColor(self.color) graphics.setFont(self.font) - graphics.print(self.text, self.x, self.y) + graphics.print(self.txt, self.x, self.y) return self end, debugDraw = function(self) @@ -38,8 +29,8 @@ do return self end, setSize = function(self) - local w = self.font:getWidth(self.text) - local h = self.font:getHeight() * (select(2, self.text:gsub("\n", "\n")) + 1) + local w = self.font:getWidth(self.txt) + local h = self.font:getHeight() * (select(2, self.txt:gsub("\n", "\n")) + 1) local _exp_0 = self.horizontal if "center" == _exp_0 then self.x = self.x - ((w - self.w) / 2) @@ -56,16 +47,24 @@ do self.h = h return self end, + setWidth = function(self) + self:setSize() + return self + end, + setHeight = function(self) + self:setSize() + return self + end, setText = function(self, text) if text == nil then text = "" end - self.text = text + self.txt = text self:setSize() return self end, getText = function(self) - return self.text + return self.txt end, setFont = function(self, font) self.font = font @@ -98,7 +97,7 @@ do _base_0.__index = _base_0 setmetatable(_base_0, _parent_0.__base) _class_0 = setmetatable({ - __init = function(self, pop, parent, text, color) + __init = function(self, parent, text, color) if text == nil then text = "" end @@ -110,7 +109,7 @@ do 255 } end - _class_0.__parent.__init(self, pop, parent) + _class_0.__parent.__init(self, parent) self.font = graphics.newFont(14) self:setText(text) self.color = color @@ -137,6 +136,16 @@ do end }) _base_0.__class = _class_0 + local self = _class_0 + self.wrap = function(pop) + return function(parent, ...) + if type(parent) == "string" then + return pop.create("text", nil, parent, ...) + else + return pop.create("text", parent, ...) + end + end + end if _parent_0.__inherited then _parent_0.__inherited(_parent_0, _class_0) end diff --git a/lib/pop/elements/window.lua b/lib/pop/elements/window.lua new file mode 100644 index 0000000..72b2033 --- /dev/null +++ b/lib/pop/elements/window.lua @@ -0,0 +1,251 @@ +local graphics +graphics = love.graphics +local sub, len +do + local _obj_0 = string + sub, len = _obj_0.sub, _obj_0.len +end +local path = sub(..., 1, len(...) - len("/window")) +local element = require(tostring(path) .. "/element") +local box = require(tostring(path) .. "/box") +local text = require(tostring(path) .. "/text") +local left = 1 +local mousemoved_event = true +do + local major, minor, revision = love.getVersion() + if (major == 0) and (minor == 10) and ((revision == 0) or (revision == 1)) then + left = 1 + elseif (major == 0) and (minor == 9) then + left = "l" + if revision == 1 then + mousemoved_event = false + end + else + print("elements/window: unrecognized LÖVE version: " .. tostring(major) .. "." .. tostring(minor) .. "." .. tostring(revision)) + print(" assuming LÖVE version > 0.10.1 (there may be bugs)") + end +end +local pop_ref = false +local window +do + local _class_0 + local _parent_0 = element + local _base_0 = { + load = function(pop) + pop_ref = pop + end, + debugDraw = function(self) + graphics.setLineWidth(0.5) + graphics.setColor(0, 0, 0, 100) + graphics.rectangle("fill", self.x, self.y, self.w, self.h) + graphics.setColor(200, 0, 200, 200) + graphics.rectangle("line", self.x, self.y, self.w, self.h) + graphics.setColor(255, 200, 255, 255) + graphics.print("w", self.x, self.y) + return self + end, + addChild = function(self, child) + self.window.child[#self.window.child + 1] = child + child.parent = self.window + return self + end, + getChildren = function(self) + return self.window.child + end, + align = function(self, horizontal, vertical, toPixel) + _class_0.__parent.__base.align(self, horizontal, vertical, toPixel) + for i = 1, #self.child do + self.child[i]:align() + end + self.window:move(nil, self.head:getHeight()) + return self + end, + setSize = function(self, w, h) + local x = 0 + local y = 0 + if w then + local _exp_0 = self.horizontal + if "center" == _exp_0 then + x = x - ((w - self.w) / 2) + elseif "right" == _exp_0 then + x = x - (w - self.w) + end + self.head:setWidth(w) + self.window:setWidth(w) + self.w = w + self.x = self.x + x + self.title:align() + end + if h then + h = h - self.head:getHeight() + local _exp_0 = self.vertical + if "center" == _exp_0 then + y = y - ((h - self.h) / 2) + elseif "right" == _exp_0 then + y = y - (h - self.h) + end + self.window:setHeight(h) + self.h = h + self.head:getHeight() + self.y = self.y + y + end + self.head:move(x, y) + self.window:move(x, y) + return self + end, + setWidth = function(self, w) + local x = 0 + local _exp_0 = self.horizontal + if "center" == _exp_0 then + x = x - ((w - self.w) / 2) + elseif "right" == _exp_0 then + x = x - (w - self.w) + end + self.head:setWidth(w) + self.window:setWidth(w) + self.w = w + self.x = self.x + x + self.title:align() + self.head:move(x) + self.window:move(x) + return self + end, + setHeight = function(self, h) + local y = 0 + h = h - self.head:getHeight() + local _exp_0 = self.vertical + if "center" == _exp_0 then + y = y - ((h - self.h) / 2) + elseif "right" == _exp_0 then + y = y - (h - self.h) + end + self.window:setHeight(h) + self.h = h + self.head:getHeight() + self.y = self.y + y + self.head:move(nil, y) + self.title:move(nil, y) + self.window:move(nil, y) + return self + end, + setTitle = function(self, title) + self.title:setText(title) + return self + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, parent, title, tBackground, tColor, wBackground) + if title == nil then + title = "window" + end + if tBackground == nil then + tBackground = { + 25, + 180, + 230, + 255 + } + end + if tColor == nil then + tColor = { + 255, + 255, + 255, + 255 + } + end + if wBackground == nil then + wBackground = { + 200, + 200, + 210, + 255 + } + end + _class_0.__parent.__init(self, parent) + self.head = box(self, tBackground) + self.title = text(self, title, tColor) + self.window = box(self, wBackground) + local height = self.title:getHeight() + self.head:setSize(self.w, height) + self.window:move(nil, height) + self:setSize(100, 80) + self.child = { + self.head, + self.title, + self.window + } + self.head.selected = false + if mousemoved_event then + self.head.mousemoved = function(self, x, y, dx, dy) + if self.selected then + self.parent:move(y, dx) + return true + end + return false + end + self.head.mousepressed = function(self, x, y, button) + if button == left then + self.selected = true + return true + end + return false + end + self.head.mousereleased = function(self, x, y, button) + if button == left then + self.selected = false + pop_ref.focused = false + return true + end + return false + end + else + self.head.mx = 0 + self.head.my = 0 + self.head.update = function(self) + return false + end + self.head.mousepressed = function(self, x, y, button) + if button == left then + self.selected = true + self.mx = x + self.my = y + end + end + self.head.mousereleased = function(self, x, y, button) + if button == left then + self.selected = false + return true + end + return false + end + end + end, + __base = _base_0, + __name = "window", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + window = _class_0 + return _class_0 +end diff --git a/lib/pop/extensions/streamlined_get_set.lua b/lib/pop/extensions/streamlined_get_set.lua new file mode 100644 index 0000000..b23a6d2 --- /dev/null +++ b/lib/pop/extensions/streamlined_get_set.lua @@ -0,0 +1,51 @@ +local graphics +graphics = love.graphics +local sub, len +do + local _obj_0 = string + sub, len = _obj_0.sub, _obj_0.len +end +local path = sub(..., 1, len(...) - len("/extensions/streamlined_get_set")) +local element = require(tostring(path) .. "/elements/element") +element.__base.position = function(self, x, y) + if x or y then + return self:setPosition(x, y) + else + return self:getPosition() + end +end +element.__base.size = function(self, w, h) + if w or h then + return self:setSize(w, h) + else + return self:getSize() + end +end +element.__base.width = function(self, w) + if w then + return self:setWidth(w) + else + return self:getWidth() + end +end +element.__base.height = function(self, h) + if h then + return self:setHeight(h) + else + return self:getHeight() + end +end +element.__base.alignment = function(self, horizontal, vertical) + if horizontal or vertical then + return self:setAlignment(horizontal, vertical) + else + return self:getAlignment() + end +end +element.__base.margin = function(self, m) + if m then + return self:setMargin(m) + else + return self:getMargin() + end +end diff --git a/lib/pop/init.lua b/lib/pop/init.lua index d77f6cc..fc09351 100644 --- a/lib/pop/init.lua +++ b/lib/pop/init.lua @@ -1,3 +1,6 @@ +if not (love.getVersion) then + error("Pop.Box only supports LÖVE versions >= 0.9.1") +end local filesystem, graphics do local _obj_0 = love @@ -5,11 +8,15 @@ do end local insert insert = table.insert +local inheritsFromElement +inheritsFromElement = require(tostring(...) .. "/util").inheritsFromElement local path = ... local pop = { } pop.elements = { } pop.skins = { } +pop.events = { } pop.screen = false +pop.focused = false pop.load = function() local elements = filesystem.getDirectoryItems(tostring(path) .. "/elements") for i = 1, #elements do @@ -21,6 +28,9 @@ pop.load = function() end local name = elements[i]:sub(1, -5) pop.elements[name] = require(tostring(path) .. "/elements/" .. tostring(name)) + if pop.elements[name].load then + pop.elements[name].load(pop) + end print("element loaded: \"" .. tostring(name) .. "\"") if not (pop[name]) then if pop.elements[name].wrap then @@ -55,6 +65,23 @@ pop.load = function() break end end + local extensions = filesystem.getDirectoryItems(tostring(path) .. "/extensions") + for i = 1, #extensions do + local _continue_0 = false + repeat + if not (extensions[i]:sub(-4) == ".lua") then + _continue_0 = true + break + end + local name = extensions[i]:sub(1, -5) + require(tostring(path) .. "/extensions/" .. tostring(name)) + print("extension loaded: \"" .. tostring(name) .. "\"") + _continue_0 = true + until true + if not _continue_0 then + break + end + end pop.screen = pop.create("element", false):setSize(graphics.getWidth(), graphics.getHeight()) return print("created \"pop.screen\"") end @@ -62,9 +89,14 @@ pop.create = function(element, parent, ...) if parent == nil then parent = pop.screen end - element = pop.elements[element](pop, parent, ...) - if parent then + if inheritsFromElement(parent) then + element = pop.elements[element](parent, ...) insert(parent.child, element) + elseif parent == false then + element = pop.elements[element](false, ...) + else + element = pop.elements[element](pop.screen, parent, ...) + insert(pop.screen.child, element) end return element end @@ -94,19 +126,62 @@ pop.draw = function(element) end end end -pop.mousepressed = function(x, y, button, element) - if element == nil then - element = pop.screen +pop.mousemoved = function(self, x, y, dx, dy) + if pop.focused and pop.focused.mousemoved then + return pop.focused:mousemoved(x, y, dx, dy) end - print("mousepressed", x, y, button, element) return false end -pop.mousereleased = function(x, y, button, element) - if element == nil then +pop.mousepressed = function(x, y, button, element) + if not (element) then + print("mousepressed", x, y, button) element = pop.screen end - print("mousereleased", x, y, button, element) - return false + local handled = false + if (x >= element.x) and (x <= element.x + element.w) and (y >= element.y) and (y <= element.y + element.h) then + if element.mousepressed then + handled = element:mousepressed(x - element.x, y - element.y, button) + end + if handled then + pop.focused = element + pop.events[button] = element + else + for i = 1, #element.child do + handled = pop.mousepressed(x, y, button, element.child[i]) + if handled then + break + end + end + end + end + return handled +end +pop.mousereleased = function(x, y, button) + print("mousereleased", x, y, button) + local clickedHandled = false + local mousereleasedHandled = false + do + local element = pop.events[button] + if element then + if element.clicked and (x >= element.x) and (x <= element.x + element.w) and (y >= element.y) and (y <= element.y + element.h) then + do + clickedHandled = element:clicked(x - element.x, y - element.y, button) + if clickedHandled then + pop.events[button] = nil + end + end + end + if element.mousereleased then + do + mousereleasedHandled = element:mousereleased(x - element.x, y - element.y, button) + if mousereleasedHandled then + pop.events[button] = nil + end + end + end + end + end + return clickedHandled, mousereleasedHandled end pop.keypressed = function(key) print("keypressed", key) diff --git a/lib/pop/util.lua b/lib/pop/util.lua new file mode 100644 index 0000000..0804d8b --- /dev/null +++ b/lib/pop/util.lua @@ -0,0 +1,19 @@ +local inheritsFromElement +inheritsFromElement = function(object) + if object and object.__class then + local cls = object.__class + if cls.__name == "element" then + return true + end + while cls.__parent do + cls = cls.__parent + if cls.__name == "element" then + return true + end + end + end + return false +end +return { + inheritsFromElement = inheritsFromElement +} diff --git a/src/pop/elements/box.moon b/src/pop/elements/box.moon index c70b496..2450eb3 100644 --- a/src/pop/elements/box.moon +++ b/src/pop/elements/box.moon @@ -5,8 +5,11 @@ path = sub ..., 1, len(...) - len "/box" element = require "#{path}/element" class box extends element - new: (pop, parent, background=false) => - super pop, parent + new: (parent, background=false) => + super parent + + @w = 20 + @h = 20 @background = background @@ -19,6 +22,7 @@ class box extends element w, h = @background\getDimensions! w = @w / w h = @h / h + graphics.setColor 255, 255, 255, 255 graphics.draw @background, @x, @y, 0, w, h return @ diff --git a/src/pop/elements/element.moon b/src/pop/elements/element.moon index 9a4265b..be36652 100644 --- a/src/pop/elements/element.moon +++ b/src/pop/elements/element.moon @@ -2,23 +2,29 @@ import graphics from love import floor from math class element - new: (pop, parent) => + new: (parent) => @parent = parent @child = {} + @w = 0 + @h = 0 + + @margin = 0 + if parent - @x = parent.x or 0 - @y = parent.y or 0 + @x = parent.x + @y = parent.y + --@horizontal = parent.horizontal + --@vertical = parent.vertical + --@align! else @x = 0 @y = 0 - - @w = 10 - @h = 10 + --@horizontal = "left" + --@vertical = "top" @horizontal = "left" @vertical = "top" - @margin = 0 debugDraw: => graphics.setLineWidth 0.5 @@ -31,6 +37,15 @@ class element return @ + addChild: (child) => + @child[#@child+1] = child + child.parent = @ + + return @ + + getChildren: => + return @child + move: (x, y) => if x @x = @x + x @@ -117,6 +132,34 @@ class element getSize: => return @w, @h + setWidth: (w) => + switch @horizontal + when "center" + @x -= (w - @w)/2 + when "right" + @x -= w - @w + + @w = w + + return @ + + getWidth: => + return @w + + setHeight: (h) => + switch @vertical + when "center" + @y -= (h - @h)/2 + when "bottom" + @y -= h - @h + + @h = h + + return @ + + getHeight: => + return @h + adjustSize: (w, h) => W, H = @getSize! @@ -130,7 +173,7 @@ class element return @ --TODO note that align requires a parent! - align: (horizontal, vertical, toPixel) => + align: (horizontal, vertical, toPixel=true) => @setAlignment horizontal, vertical @x = @parent.x @@ -158,11 +201,11 @@ class element return @ - alignTo: (element, horizontal, vertical) => + alignTo: (element, horizontal, vertical, toPixel=true) => parent = @parent @parent = element - @align horizontal, vertical + @align horizontal, vertical, toPixel @parent = parent @@ -176,6 +219,9 @@ class element return @ + getAlignment: => + return @horizontal, @vertical + setMargin: (margin) => @margin = margin @align! @@ -183,3 +229,9 @@ class element getMargin: => return @margin + + fill: => + @x = @parent.x + @margin + @y = @parent.y + @margin + @w = @parent.w - @margin*2 + @h = @parent.h - @margin*2 diff --git a/src/pop/elements/text.moon b/src/pop/elements/text.moon index fc7e6af..80d611e 100644 --- a/src/pop/elements/text.moon +++ b/src/pop/elements/text.moon @@ -5,15 +5,16 @@ path = sub ..., 1, len(...) - len "/box" element = require "#{path}/element" class text extends element - wrap: (pop) -> + -- this should be completely unneccessary, but I'm keeping it just in case + @wrap = (pop) -> return (parent, ...) -> if type(parent) == "string" return pop.create("text", nil, parent, ...) else return pop.create("text", parent, ...) - new: (pop, parent, text="", color={255,255,255,255}) => - super pop, parent + new: (parent, text="", color={255,255,255,255}) => + super parent @font = graphics.newFont 14 @setText text @@ -22,7 +23,7 @@ class text extends element draw: => graphics.setColor @color graphics.setFont @font - graphics.print @text, @x, @y + graphics.print @txt, @x, @y return @ @@ -39,8 +40,8 @@ class text extends element -- unlike most elements, you cannot set a size for text elements setSize: => - w = @font\getWidth @text - h = @font\getHeight! * (select(2, @text\gsub("\n", "\n")) + 1) --hack to get height of multiple lines + w = @font\getWidth @txt + h = @font\getHeight! * (select(2, @txt\gsub("\n", "\n")) + 1) --hack to get height of multiple lines switch @horizontal when "center" @@ -59,13 +60,23 @@ class text extends element return @ + -- cannot set width! + setWidth: => + @setSize! + return @ + + -- cannot set height! + setHeight: => + @setSize! + return @ + setText: (text="") => - @text = text + @txt = text @setSize! return @ getText: => - return @text + return @txt setFont: (font) => @font = font diff --git a/src/pop/elements/window.moon b/src/pop/elements/window.moon new file mode 100644 index 0000000..9a4aa8c --- /dev/null +++ b/src/pop/elements/window.moon @@ -0,0 +1,223 @@ +import graphics from love +import sub, len from string + +path = sub ..., 1, len(...) - len "/window" +element = require "#{path}/element" +box = require "#{path}/box" +text = require "#{path}/text" + +-- version compatibility +left = 1 -- what is the left mouse button? +mousemoved_event = true -- is the mousemoved event available? + +do + major, minor, revision = love.getVersion! + if (major == 0) and (minor == 10) and ((revision == 0) or (revision == 1)) + left = 1 -- redundant, but whatever + elseif (major == 0) and (minor == 9) + left = "l" + if revision == 1 + mousemoved_event = false + else + print "elements/window: unrecognized LOVE version: #{major}.#{minor}.#{revision}" + print " assuming LOVE version > 0.10.1 (there may be bugs)" + +pop_ref = false -- reference to pop, loaded by pop.load! + +class window extends element + load: (pop) -> + pop_ref = pop + + --wrap: (pop) -> + -- pop_ref = pop -- set our reference to pop (needed for mouse handling) + -- return (...) -> -- standard wrapper, nothing special needed + -- return pop.create("window", ...) + + new: (parent, title="window", tBackground={25, 180, 230, 255}, tColor={255, 255, 255, 255}, wBackground={200, 200, 210, 255}) => + super parent + + @head = box @, tBackground -- title box at top + @title = text @, title, tColor -- text at top + @window = box @, wBackground -- main window area + + -- correct placement / sizes of elements + height = @title\getHeight! + @head\setSize @w, height + @window\move nil, height + @setSize 100, 80 + + -- our child elements are still child elements + --TODO change title to be a child of head ? + @child = { + @head, @title, @window + } + + --@selected = false -- whether or not the window title (and thus, the window) has been selected + --NOTE all of these commented out, because I realized these event handlers should be attached to the title element + + @head.selected = false -- whether or not the window title (and thus, the window) has been selected + + if mousemoved_event + @head.mousemoved = (x, y, dx, dy) => + if @selected + -- for some reason, y and dx are actually dx and dy...what the fuck? (note: in version 0.10.0) + @parent\move y, dx --dx, dy + return true + return false + + @head.mousepressed = (x, y, button) => + if button == left + @selected = true + return true + return false + + @head.mousereleased = (x, y, button) => + if button == left + @selected = false + pop_ref.focused = false -- clear our focus + return true + return false + + else + @head.mx = 0 -- local mouse coordinates when selected + @head.my = 0 + + @head.update = => + --TODO write me! + return false + + @head.mousepressed = (x, y, button) => + if button == left + @selected = true + @mx = x + @my = y + + @head.mousereleased = (x, y, button) => -- this is actually the same for both versions... + if button == left + @selected = false + return true + return false + + debugDraw: => + graphics.setLineWidth 0.5 + graphics.setColor 0, 0, 0, 100 + graphics.rectangle "fill", @x, @y, @w, @h + graphics.setColor 200, 0, 200, 200 + graphics.rectangle "line", @x, @y, @w, @h + graphics.setColor 255, 200, 255, 255 + graphics.print "w", @x, @y + + return @ + + addChild: (child) => + @window.child[#@window.child+1] = child + child.parent = @window + + return @ + + getChildren: => + return @window.child + + align: (horizontal, vertical, toPixel) => + super horizontal, vertical, toPixel + + for i = 1, #@child + @child[i]\align! + + @window\move nil, @head\getHeight! + + return @ + + --update: => + -- if selected, set position based on current mouse position relative to position it was when mousepressed + + --mousemoved: (x, y, dx, dy) => + -- if selected, set position based on new mouse position relative to position it was when mousepressed + + --mousepressed: (x, y, button) => + -- if button == "l" -> selected = true, mouse position saved + + --mousereleased: (x, y, button) => + -- if button == "l" -> set position based on position relative to when mousepressed, selected == false + + setSize: (w, h) => + x = 0 + y = 0 + + if w + switch @horizontal + when "center" + x -= (w - @w)/2 + when "right" + x -= w - @w + + @head\setWidth w + @window\setWidth w + @w = w + @x += x + + @title\align! + + if h + h = h - @head\getHeight! + switch @vertical + when "center" + y -= (h - @h)/2 + when "right" + y -= h - @h + + @window\setHeight h + @h = h + @head\getHeight! + @y += y + + @head\move x, y + --@title\move x, y + @window\move x, y + + return @ + + setWidth: (w) => + x = 0 + + switch @horizontal + when "center" + x -= (w - @w)/2 + when "right" + x -= w - @w + + @head\setWidth w + @window\setWidth w + @w = w + @x += x + + @title\align! + + @head\move x + --@title\move x + @window\move x + + return @ + + setHeight: (h) => + y = 0 + + h = h - @head\getHeight! + switch @vertical + when "center" + y -= (h - @h)/2 + when "right" + y -= h - @h + + @window\setHeight h + @h = h + @head\getHeight! + @y += y + + @head\move nil, y + @title\move nil, y + @window\move nil, y + + return @ + + setTitle: (title) => + @title\setText title + return @ diff --git a/src/pop/extensions/streamlined_get_set.moon b/src/pop/extensions/streamlined_get_set.moon new file mode 100644 index 0000000..5391baf --- /dev/null +++ b/src/pop/extensions/streamlined_get_set.moon @@ -0,0 +1,86 @@ +-- adds methods to elements using a single function for get and set operations +-- ex: instead of getWidth() and setWidth(val), use width() and width(val) + +import graphics from love +import sub, len from string + +path = sub ..., 1, len(...) - len "/extensions/streamlined_get_set" +element = require "#{path}/elements/element" +box = require "#{path}/elements/box" +text = require "#{path}/elements/text" + +element.__base.position = (x, y) => + if x or y + return @setPosition x, y + else + return @getPosition! + +element.__base.size = (w, h) => + if w or h + return @setSize w, h + else + return @getSize! + +element.__base.width = (w) => + if w + return @setWidth w + else + return @getWidth! + +element.__base.height = (h) => + if h + return @setHeight h + else + return @getHeight! + +element.__base.alignment = (horizontal, vertical) => + if horizontal or vertical + return @setAlignment horizontal, vertical + else + return @getAlignment! + +-- why is this bit here? Oo +element.__base.margin = (m) => + if m + return @setMargin m + else + return @getMargin! + +--oldinit = element.__init +-- +--element.__init = (...) -> +-- object = oldinit ... +-- value = object.margin +-- +-- object.margin = setmetatable {:value}, { +-- __call: (...) -> +-- print ... +-- } + +element.__base.resize = element.__base.adjustSize + +-- box.__base.background -- can't be done! + +box.__base.color = (r, g, b, a) => + if r or g or b or a + return @setColor r, g, b, a + else + return @getColor! + +text.__base.text = (text) => + if text + return @setText text + else + return @getText! + +text.__base.font = (font) => + if font + return @setFont font + else + return @getFont! + +text.__base.color = (r, g, b, a) => + if r or g or b or a + return @setColor r, g, b, a + else + return @getColor! diff --git a/src/pop/init.moon b/src/pop/init.moon index 8420818..73c8370 100644 --- a/src/pop/init.moon +++ b/src/pop/init.moon @@ -1,5 +1,9 @@ +unless love.getVersion + error "Pop.Box only supports LOVE versions >= 0.9.1" + import filesystem, graphics from love import insert from table +import inheritsFromElement from require "#{...}/util" path = ... @@ -7,13 +11,15 @@ pop = {} pop.elements = {} pop.skins = {} +pop.events = {} pop.screen = false -- initialized in pop.load() ---pop.focused ? +pop.focused = false -- loads elements and skins, creates pop.screen (intended to only be called once at the beginning) pop.load = -> elements = filesystem.getDirectoryItems "#{path}/elements" + for i = 1, #elements -- only attempt to load lua files unless elements[i]\sub(-4) == ".lua" @@ -22,6 +28,10 @@ pop.load = -> -- load into pop.elements table name = elements[i]\sub 1, -5 pop.elements[name] = require "#{path}/elements/#{name}" + + if pop.elements[name].load + pop.elements[name].load pop + print "element loaded: \"#{name}\"" -- create pop.element() wrapper if possible @@ -36,27 +46,47 @@ pop.load = -> -- works just like above, except no wrappers skins = filesystem.getDirectoryItems "#{path}/skins" + for i = 1, #skins unless skins[i]\sub(-4) == ".lua" continue + name = skins[i]\sub 1, -5 pop.skins[name] = require "#{path}/skins/#{name}" + print "skin loaded: \"#{name}\"" + -- load extensions by just running them via require + extensions = filesystem.getDirectoryItems "#{path}/extensions" + + for i = 1, #extensions + unless extensions[i]\sub(-4) == ".lua" + continue + + name = extensions[i]\sub 1, -5 + require "#{path}/extensions/#{name}" + + print "extension loaded: \"#{name}\"" + -- main window (called screen because there will be a window element class) pop.screen = pop.create("element", false)\setSize(graphics.getWidth!, graphics.getHeight!) print "created \"pop.screen\"" --- creates an element with specified parent (parent can be false) +-- creates an element with specified parent (parent can be false or non-existent) pop.create = (element, parent=pop.screen, ...) -> - element = pop.elements[element](pop, parent, ...) - - if parent + if inheritsFromElement parent + element = pop.elements[element](parent, ...) insert parent.child, element + elseif parent == false + element = pop.elements[element](false, ...) + else + element = pop.elements[element](pop.screen, parent, ...) + insert pop.screen.child, element return element pop.update = (dt, element=pop.screen) -> + --pop.screen\update dt unless element.excludeUpdate if element.update element\update dt @@ -64,19 +94,56 @@ pop.update = (dt, element=pop.screen) -> pop.update dt, element.child[i] pop.draw = (element=pop.screen) -> + --pop.screen\draw! unless element.excludeDraw if element.draw element\draw! for i = 1, #element.child pop.draw element.child[i] -pop.mousepressed = (x, y, button, element=pop.screen) -> - print "mousepressed", x, y, button, element - return false --TODO event handlers return if they have handled the event! +pop.mousemoved = (x, y, dx, dy) => + if pop.focused and pop.focused.mousemoved + return pop.focused\mousemoved x, y, dx, dy -pop.mousereleased = (x, y, button, element=pop.screen) -> - print "mousereleased", x, y, button, element - return false --TODO event handlers return if they have handled the event! + return false + +pop.mousepressed = (x, y, button, element) -> + unless element + print "mousepressed", x, y, button + element = pop.screen + + handled = false + + if (x >= element.x) and (x <= element.x + element.w) and (y >= element.y) and (y <= element.y + element.h) + if element.mousepressed + handled = element\mousepressed x - element.x, y - element.y, button + if handled + pop.focused = element + pop.events[button] = element + else + for i = 1, #element.child + handled = pop.mousepressed x, y, button, element.child[i] + if handled + break + + return handled + +pop.mousereleased = (x, y, button) -> + print "mousereleased", x, y, button + + clickedHandled = false + mousereleasedHandled = false + + if element = pop.events[button] + if element.clicked and (x >= element.x) and (x <= element.x + element.w) and (y >= element.y) and (y <= element.y + element.h) + if clickedHandled = element\clicked x - element.x, y - element.y, button + pop.events[button] = nil + + if element.mousereleased + if mousereleasedHandled = element\mousereleased x - element.x, y - element.y, button + pop.events[button] = nil + + return clickedHandled, mousereleasedHandled pop.keypressed = (key) -> print "keypressed", key @@ -90,6 +157,8 @@ pop.textinput = (text) -> print "textinput", text return false --TODO event handlers return if they have handled the event! +--TODO rewrite skin system to not rely on knowing internals of elements, +-- instead call functions like setColor and setBackground -- skins an element (and its children unless depth == true or 0) -- depth can be an integer for how many levels to go down when skinning -- defaults to pop.screen and the default skin diff --git a/src/pop/skins/default.moon b/src/pop/skins/default.moon index cb1aa5a..197849e 100644 --- a/src/pop/skins/default.moon +++ b/src/pop/skins/default.moon @@ -1,3 +1,6 @@ +-- Note that the "default" name is a bit of a misnomer, as this does not +-- specify the defaults used in Pop.Box elements (they define their own) + import graphics from love return { diff --git a/src/pop/util.moon b/src/pop/util.moon new file mode 100644 index 0000000..b985e13 --- /dev/null +++ b/src/pop/util.moon @@ -0,0 +1,17 @@ +inheritsFromElement = (object) -> + if object and object.__class + cls = object.__class + + if cls.__name == "element" + return true + + while cls.__parent + cls = cls.__parent + if cls.__name == "element" + return true + + return false + +return { + :inheritsFromElement +}