2016-06-21 05:52:44 +00:00
--- The Pop.Box GUI itself.
--- @module pop
--- @copyright Paul Liverman III (2015-2016)
--- @license The MIT License (MIT)
--- @release v0.0.0
2016-04-18 06:22:40 +00:00
pop = {
2016-04-20 21:12:32 +00:00
_VERSION: 'Pop.Box v0.0.0'
_DESCRIPTION: 'GUI library for LOVE, designed for ease of use'
_URL: 'http://github.com/Guard13007/Pop.Box'
_LICENSE: 'The MIT License (MIT)'
_AUTHOR: 'Paul Liverman III'
2016-04-18 06:22:40 +00:00
}
2016-04-02 03:45:43 +00:00
unless love.getVersion
2016-04-05 03:59:42 +00:00
error "Pop.Box only supports LOVE versions >= 0.9.1"
2016-04-02 03:45:43 +00:00
2016-06-21 05:52:44 +00:00
--- @todo Find out what happens if someone requires the `init.lua` / `init.moon` file instead of the directory, add an error message for this.
2016-02-23 03:55:05 +00:00
import filesystem, graphics from love
import insert from table
2016-04-02 03:45:43 +00:00
import inheritsFromElement from require "#{...}/util"
2016-02-23 03:55:05 +00:00
path = ...
2016-06-21 05:52:44 +00:00
--- @table pop
--- @field elements All GUI classes are stored here.
--- @field skins All skins are stored here.
--- @field screen The top level GUI element. Represents the game screen. Initialized in `pop.load()`
--- @see pop.load
--- @field focused The currently focused GUI element (or false if none is focused).
2016-02-25 01:02:56 +00:00
pop.elements = {}
2016-03-29 00:59:12 +00:00
pop.skins = {}
2016-06-21 05:52:44 +00:00
pop.screen = false
2016-03-30 17:50:33 +00:00
pop.focused = false
2016-03-29 00:59:12 +00:00
2016-06-21 05:52:44 +00:00
--- Loads elements, skins, extensions, and initializes `pop.screen`. **IMPORTANT**: Intended to only be called once, and is automatically called when you require Pop.Box.
--- @function load
--- @see pop
--- @todo @see Elements
--- @todo @see Skins
--- @todo @see Extensions
2016-02-25 01:02:56 +00:00
pop.load = ->
elements = filesystem.getDirectoryItems "#{path}/elements"
for i = 1, #elements
2016-06-21 05:52:44 +00:00
-- ignore non-Lua files
2016-03-29 00:59:12 +00:00
unless elements[i]\sub(-4) == ".lua"
continue
2016-06-21 05:52:44 +00:00
-- require into pop.elements table by filename
2016-02-25 01:02:56 +00:00
name = elements[i]\sub 1, -5
pop.elements[name] = require "#{path}/elements/#{name}"
2016-04-03 07:27:15 +00:00
2016-06-21 05:52:44 +00:00
-- call the element's load function if it exists
2016-04-03 07:27:15 +00:00
if pop.elements[name].load
pop.elements[name].load pop
2016-03-29 00:59:12 +00:00
print "element loaded: \"#{name}\""
2016-06-21 05:52:44 +00:00
-- create "pop.element()" function wrapper if possible
2016-03-29 00:59:12 +00:00
unless pop[name]
if pop.elements[name].wrap
pop[name] = pop.elements[name].wrap pop
else
pop[name] = (...) ->
return pop.create(name, ...)
2016-02-25 01:02:56 +00:00
2016-03-29 00:59:12 +00:00
print "wrapper created: \"pop.#{name}()\""
2016-02-25 01:02:56 +00:00
2016-03-30 21:01:15 +00:00
2016-06-21 05:52:44 +00:00
skins = filesystem.getDirectoryItems "#{path}/skins"
2016-03-29 00:59:12 +00:00
for i = 1, #skins
2016-06-21 05:52:44 +00:00
-- ignore non-Lua files
2016-03-29 00:59:12 +00:00
unless skins[i]\sub(-4) == ".lua"
continue
2016-03-30 21:01:15 +00:00
2016-06-21 05:52:44 +00:00
-- require into pop.skins table by filename
2016-03-29 00:59:12 +00:00
name = skins[i]\sub 1, -5
pop.skins[name] = require "#{path}/skins/#{name}"
2016-03-30 21:01:15 +00:00
2016-06-21 05:52:44 +00:00
-- call the skin's load function if it exists
if pop.skins[name].load
pop.skins[name].load pop
2016-03-29 00:59:12 +00:00
print "skin loaded: \"#{name}\""
2016-02-25 01:02:56 +00:00
2016-04-01 21:14:24 +00:00
2016-06-21 05:52:44 +00:00
extensions = filesystem.getDirectoryItems "#{path}/extensions"
2016-04-01 21:14:24 +00:00
for i = 1, #extensions
2016-06-21 05:52:44 +00:00
-- ignore non-Lua files
2016-04-01 21:14:24 +00:00
unless extensions[i]\sub(-4) == ".lua"
continue
2016-06-21 05:52:44 +00:00
--- @todo Determine if extensions should have a reference saved (and the possibility of a load function?)
-- require into pop.extensions by filename
2016-04-01 21:14:24 +00:00
name = extensions[i]\sub 1, -5
require "#{path}/extensions/#{name}"
2016-04-02 03:45:43 +00:00
print "extension loaded: \"#{name}\""
2016-06-21 05:52:44 +00:00
-- Initialize pop.screen (top element, GUI area)
2016-03-29 00:59:12 +00:00
pop.screen = pop.create("element", false)\setSize(graphics.getWidth!, graphics.getHeight!)
print "created \"pop.screen\""
2016-02-25 01:02:56 +00:00
2016-06-21 05:52:44 +00:00
--- Creates an element.
--- @function create
--- @param element A string naming the element class to use.
--- @param parent *Optional* The parent element. If `false`, an element is created with no parent. If `nil`, defaults to `pop.screen`.
--- (**Note**: An element with no parent will not be handled by Pop.Box's event handlers unless you handle it explicitly.)
--- @see pop
--- @todo @see Elements
2016-03-29 00:59:12 +00:00
pop.create = (element, parent=pop.screen, ...) ->
2016-06-21 05:52:44 +00:00
-- if valid parent element, use it
2016-04-02 03:45:43 +00:00
if inheritsFromElement parent
2016-04-03 06:33:18 +00:00
element = pop.elements[element](parent, ...)
2016-04-01 22:48:58 +00:00
insert parent.child, element
2016-06-21 05:52:44 +00:00
insert parent.data.child, element.data
-- if explicitly no parent, just create the element
2016-04-02 03:45:43 +00:00
elseif parent == false
2016-04-03 06:33:18 +00:00
element = pop.elements[element](false, ...)
2016-06-21 05:52:44 +00:00
-- else use pop.screen (and "parent" is actually the first argument)
2016-04-01 01:59:16 +00:00
else
2016-04-03 06:33:18 +00:00
element = pop.elements[element](pop.screen, parent, ...)
2016-04-01 22:48:58 +00:00
insert pop.screen.child, element
2016-06-21 05:52:44 +00:00
insert pop.screen.data.child, element.data
2016-03-29 00:59:12 +00:00
return element
2016-06-21 05:52:44 +00:00
2016-06-21 07:58:08 +00:00
--- Event handler for `love.update()`.
2016-06-21 05:52:44 +00:00
--- @function update
--- @param dt The amount of time passed since the last call to update, in seconds.
--- @param element *Optional* The element to update. Defaults to `pop.screen` (and loops through all its children).
--- @todo Define Elements and @see that documentation from here. Generic documentation, not specifically element!
2016-03-29 00:59:12 +00:00
pop.update = (dt, element=pop.screen) ->
2016-06-21 05:52:44 +00:00
-- data.update boolean controls an element and its children being updated
if element.data.update
2016-02-25 01:02:56 +00:00
if element.update
element\update dt
for i = 1, #element.child
pop.update dt, element.child[i]
2016-02-23 03:55:05 +00:00
2016-06-21 05:52:44 +00:00
2016-06-21 07:58:08 +00:00
--- Event handler for `love.draw()`.
2016-06-21 05:52:44 +00:00
--- @function draw
--- @param element *Optional* The element to draw. Defaults to `pop.screen` (and loops through all its children).
--- @todo @see Elements
2016-03-29 00:59:12 +00:00
pop.draw = (element=pop.screen) ->
2016-06-21 05:52:44 +00:00
-- data.draw boolean controls an element and its children being drawn
if element.data.draw
2016-02-25 01:02:56 +00:00
if element.draw
2016-03-29 00:59:12 +00:00
element\draw!
2016-02-25 01:02:56 +00:00
for i = 1, #element.child
2016-03-29 00:59:12 +00:00
pop.draw element.child[i]
2016-02-25 01:02:56 +00:00
2016-06-21 05:52:44 +00:00
2016-06-21 07:58:08 +00:00
--- Event handler for `love.mousemoved()`. (*LÖVE >= 0.10.0*)
2016-06-21 05:52:44 +00:00
--- @function mousemoved
--- @param x The x coordinate of the mouse.
--- @param y The y coordinate of the mouse.
--- @param dx The distance on the x axis the mouse was moved.
--- @param dy The distance on the y axis the mouse was moved.
--- @return `true` / `false`: Was the event handled?
--- @todo Implement a way for an element to attach itself to `love.mousemoved()` events?
2016-04-17 08:27:06 +00:00
pop.mousemoved = (x, y, dx, dy) ->
2016-04-02 03:45:43 +00:00
if pop.focused and pop.focused.mousemoved
return pop.focused\mousemoved x, y, dx, dy
return false
2016-06-21 05:52:44 +00:00
2016-06-21 07:58:08 +00:00
--- Event handler for `love.mousepressed()`.
2016-06-21 05:52:44 +00:00
--- @function mousepressed
--- @param x The x coordinate of the mouse press.
--- @param y The y coordinate of the mouse press.
--- @param button The mouse button pressed.
--- @param element *Optional* The element to check for event handling. Defaults to `pop.screen` (and loops through all its children).
--- @return `true` / `false`: Was the event handled?
2016-04-02 03:45:43 +00:00
pop.mousepressed = (x, y, button, element) ->
2016-04-20 21:12:32 +00:00
-- start at the screen, print that we received an event
2016-04-02 03:45:43 +00:00
unless element
print "mousepressed", x, y, button
element = pop.screen
2016-03-30 21:38:46 +00:00
2016-04-20 21:12:32 +00:00
-- have we handled the event?
2016-03-30 17:50:33 +00:00
handled = false
2016-04-02 03:45:43 +00:00
2016-06-21 05:52:44 +00:00
-- if it is inside the current element..
if (x >= element.data.x) and (x <= element.data.x + element.data.w) and (y >= element.data.y) and (y <= element.data.y + element.data.h)
2016-04-20 21:12:32 +00:00
-- check its child elements in reverse order, returning if something handles it
2016-04-18 04:57:32 +00:00
for i = #element.child, 1, -1
2016-04-20 21:12:32 +00:00
if handled = pop.mousepressed x, y, button, element.child[i]
return handled
2016-04-18 04:57:32 +00:00
2016-06-21 05:52:44 +00:00
-- if a child hasn't handled it yet (note: this check doesn't seem neccessary)
2016-04-18 04:57:32 +00:00
unless handled
2016-04-20 21:12:32 +00:00
-- if we can handle it and are visible, try to handle it, and set pop.focused
2016-06-21 05:52:44 +00:00
if element.mousepressed and element.data.draw
if handled = element\mousepressed x - element.data.x, y - element.data.y, button
2016-04-18 04:57:32 +00:00
pop.focused = element
2016-02-25 01:02:56 +00:00
2016-04-27 17:53:19 +00:00
-- return whether or not we have handled the event
2016-04-02 03:45:43 +00:00
return handled
2016-03-30 21:38:46 +00:00
2016-06-21 05:52:44 +00:00
2016-06-21 07:58:08 +00:00
--- Event handler for `love.mousereleased()`.
2016-06-21 05:52:44 +00:00
--- @function mousereleased
--- @param x The x coordinate of the mouse release.
--- @param y The y coordinate of the mouse release.
--- @param button The mouse button released.
--- @param element *Optional* The element to check for event handling. Defaults to `pop.screen` (and loops through all its children).
--- @return `true` / `false`: Was a click handled?
--- @return `true` / `false`: Was a mouse release handled?
2016-04-18 03:37:55 +00:00
pop.mousereleased = (x, y, button, element) ->
2016-04-20 21:12:32 +00:00
-- we are trying to handle a clicked or mousereleased event
2016-04-02 03:45:43 +00:00
clickedHandled = false
mousereleasedHandled = false
2016-03-30 21:38:46 +00:00
2016-04-20 21:12:32 +00:00
-- if we have an element, and are within its bounds
2016-04-18 03:37:55 +00:00
if element
2016-06-21 05:52:44 +00:00
if (x >= element.data.x) and (x <= element.data.x + element.data.w) and (y >= element.data.y) and (y <= element.data.y + element.data.h)
2016-04-20 21:12:32 +00:00
-- check its children in reverse for handling a clicked or mousereleased event
2016-04-18 04:57:32 +00:00
for i = #element.child, 1, -1
clickedHandled, mousereleasedHandled = pop.mousereleased x, y, button, element.child[i]
if clickedHandled or mousereleasedHandled
2016-04-20 21:12:32 +00:00
return clickedHandled, mousereleasedHandled
2016-04-18 03:37:55 +00:00
2016-06-21 05:52:44 +00:00
-- if that doesn't work, we try to handle it ourselves (note: again, this check seems unneccessary)
2016-04-18 04:57:32 +00:00
unless clickedHandled or mousereleasedHandled
2016-04-20 21:12:32 +00:00
-- clicked only happens on visible elements, mousereleased happens either way
2016-06-21 05:52:44 +00:00
if element.clicked and element.data.draw
clickedHandled = element\clicked x - element.data.x, y - element.data.y, button
2016-04-18 04:57:32 +00:00
if element.mousereleased
2016-06-21 05:52:44 +00:00
mousereleasedHandled = element\mousereleased x - element.data.x, y - element.data.y, button
2016-03-30 21:38:46 +00:00
2016-04-20 21:12:32 +00:00
-- if we clicked, we're focused!
2016-04-18 04:57:32 +00:00
if clickedHandled
pop.focused = element
2016-06-21 05:52:44 +00:00
--- @todo Figure out how to bring a focused element to the front of view (aka the first element in its parent's children).
--- (If I do it right here, the for loop above may break! I need to test/figure this out.)
2016-04-27 17:53:19 +00:00
--NOTE this might cause an error in the above for loop!
-- basically, move focused element to front of its parent's child
--element.parent\focusChild element
--table.insert element.parent, element.parent\removeChild(element),
2016-04-18 03:37:55 +00:00
2016-04-20 21:12:32 +00:00
-- else, default to pop.screen to begin! (and print that we received an event)
else
print "mousereleased", x, y, button
pop.mousereleased x, y, button, pop.screen
2016-03-30 21:38:46 +00:00
2016-04-02 03:45:43 +00:00
return clickedHandled, mousereleasedHandled
2016-02-25 01:02:56 +00:00
2016-06-21 07:58:08 +00:00
--- Event handler for `love.keypressed()`.
--- @function keypressed
--- @param key The key that was pressed.
--- @return `true` / `false`: Was the event handled?
2016-02-25 01:02:56 +00:00
pop.keypressed = (key) ->
2016-03-29 00:59:12 +00:00
print "keypressed", key
2016-04-17 07:29:27 +00:00
2016-04-20 21:12:32 +00:00
-- keypressed events must be on visible elements
2016-04-17 07:29:27 +00:00
element = pop.focused
2016-06-21 07:58:08 +00:00
if element and element.keypressed and element.data.draw
2016-04-17 07:29:27 +00:00
return element.keypressed key
return false
2016-02-25 01:02:56 +00:00
2016-06-21 07:58:08 +00:00
--- Event handler for `love.keyreleased()`.
--- @function keyreleased
--- @param key The key that was released.
--- @return `true` / `false`: Was the event handled?
2016-02-25 01:02:56 +00:00
pop.keyreleased = (key) ->
2016-03-29 00:59:12 +00:00
print "keyreleased", key
2016-04-17 07:29:27 +00:00
2016-04-20 21:12:32 +00:00
-- keyreleased events are always called
2016-04-17 07:29:27 +00:00
element = pop.focused
if element and element.keyreleased
return element.keyreleased key
return false
2016-02-25 01:02:56 +00:00
2016-06-21 07:58:08 +00:00
--- Event handler for `love.textinput()`.
--- @function textinput
--- @param text The text that was typed.
--- @return `true` / `false`: Was the text input handled?
2016-02-25 01:02:56 +00:00
pop.textinput = (text) ->
2016-03-29 00:59:12 +00:00
print "textinput", text
2016-04-17 07:29:27 +00:00
2016-04-20 21:12:32 +00:00
-- textinput events must be on visible elements
2016-04-17 07:29:27 +00:00
element = pop.focused
2016-06-21 07:58:08 +00:00
if element and element.textinput and element.data.draw
2016-04-17 07:29:27 +00:00
return element.textinput text
return false
2016-03-29 00:59:12 +00:00
2016-06-21 07:58:08 +00:00
--- Applies skins to elements. (**NOTE^*: This function will be rewritten and change at some point...)
--- @function skin
--- @param element The element to skin. Defaults to `pop.screen` (and loops through all its children).
--- @param skin The skin to use, can be a string or an actual skin object, defaults to a default skin that is part of Pop.Box.
--- @param depth Can be an integer for how many levels to go skinning. Alternately, if `true`, will skin all children.
2016-04-04 08:16:32 +00:00
--TODO rewrite skin system to not rely on knowing internals of elements,
-- instead call functions like setColor and setBackground
2016-03-29 00:59:12 +00:00
-- 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
pop.skin = (element=pop.screen, skin=pop.skins.default, depth) ->
if element.background and skin.background
2016-02-25 01:02:56 +00:00
element.background = skin.background
2016-03-29 00:59:12 +00:00
if element.color and skin.color
2016-02-25 01:02:56 +00:00
element.color = skin.color
2016-03-29 00:59:12 +00:00
if element.font and skin.font
2016-02-25 01:02:56 +00:00
element.font = skin.font
2016-02-23 03:55:05 +00:00
2016-03-29 00:59:12 +00:00
unless depth or (depth == 0)
if depth == tonumber depth
for i = 1, #element.child
pop.skin element.child[i], skin, depth - 1
else
for i = 1, #element.child
pop.skin element.child[i], skin, false
2016-02-25 01:02:56 +00:00
2016-03-29 00:59:12 +00:00
pop.debugDraw = (element=pop.screen) ->
2016-02-25 01:02:56 +00:00
if element.debugDraw
element\debugDraw!
else
graphics.setLineWidth 1
2016-03-29 00:59:12 +00:00
graphics.setLineColor 0, 0, 0, 100
2016-02-25 01:02:56 +00:00
graphics.rectangle "fill", element.x, element.y, element.w, element.h
graphics.setColor 150, 150, 150, 150
graphics.rectangle "line", element.x, element.y, element.w, element.h
graphics.setColor 200, 200, 200, 255
graphics.print ".", element.x, element.y
for i = 1, #element.child
pop.debugDraw element.child[i]
2016-04-20 21:12:32 +00:00
pop.printElementTree = (element=pop.screen, depth=0) ->
2016-04-18 04:57:32 +00:00
cls = element.__class.__name
if cls == "text"
cls = cls .. " (\"#{element\getText!\gsub "\n", "\\n"}\")"
elseif cls == "box"
bg = element\getBackground!
if type(bg) == "table"
bg = "#{bg[1]}, #{bg[2]}, #{bg[3]}, #{bg[4]}"
cls = cls .. " (#{bg})"
print string.rep("-", depth) .. " #{cls}"
for i = 1, #element.child
pop.printElementStack element.child[i], depth + 1
2016-02-25 01:02:56 +00:00
pop.load!
return pop