pretty much ready (hopefully)

This commit is contained in:
Fox 2016-01-21 14:18:28 -08:00
parent 145533195d
commit 1ba7314243
7 changed files with 212 additions and 165 deletions

View File

@ -6,32 +6,27 @@ Pop.Box attempts to make a GUI system for use in the [LÖVE][2] engine that is
easy to use, requiring as little code as possible to get working, but also easy to use, requiring as little code as possible to get working, but also
extensible, allowing for complex interfaces to be built in it. extensible, allowing for complex interfaces to be built in it.
I've never written a GUI library before..so we'll see how that goes. ## Features
- Quickly set up and align GUI elements.
- Fully customizable alignment / styling.
- Supports moving/resizing things in ways that take the alignment headache away,
without trying to do too much and become bloated.
- Supports custom elements/skins, make your own and move them into the
appropriate directories for them to be automatically loaded.
## Usage
```lua ```lua
local pop = require "pop" local pop = require "pop"
-- define love callbacks here (update, draw, textinput, mouse/key events) -- define LÖVE callbacks here (update, draw, textinput, mouse/key events)
local box = pop.box() local box = pop.box()
``` ```
## Using Docs: [pop Module][3], [Elements][4], [Skins][5]
Elements store position, size, and child elements. When moved, an element's
children also move. Elements have simple methods for adjusting their position
and size.
`pop.window` - An element representing the game window. It will not auto-resize.
Any element (and its children) with `excludeMovement == true` will not be moved
except when its `move()` or `setPosition()` are called.
Children render on top of their parents. (Rendering starts at `pop.window` and
loops down.) Any element (and its children) with `excludeRendering == true` will
not be rendered.
See [Elements.md][3] for the standard methods each element has, and what
elements are available.
[1]: https://en.wikipedia.org/wiki/Cola_(programming_language) [1]: https://en.wikipedia.org/wiki/Cola_(programming_language)
[2]: https://love2d.org/ [2]: https://love2d.org/
[3]: ./Elements.md [3]: ./docs/Pop.md
[4]: ./docs/Elements.md
[5]: ./docs/Skins.md

View File

@ -1,30 +1,41 @@
# Elements # Elements
Elements are the core of Pop.Box.
- Elements are arranged hierarchically.
- When an element is moved, its child elements move with it.
- When an element is resized, it resizes in a way that makes sense based on its
alignment.
- Elements are drawn from the top down (meaning child elements will always draw
on top of their parents.)
The alignment stuff is much easier explained by experimenting or running the
demo, please check it out!
All elements have the following standard methods: All elements have the following standard methods:
- `move(x, y)` - Moves from current position by `x`/`y`. - `move(x, y)` - Moves the element by `x`/`y`.
- `setPosition(x, y)` - Sets the `x`/`y` position based on current alignment. - `setPosition(x, y)` - Sets the `x`/`y` position based on current alignment.
- `getPosition()` - Returns `x` and `y` position based on current alignment. - `getPosition()` - Returns `x` and `y` position based on current alignment.
- `setSize(x, y)` - Sets the witdh/height of the element. Will stretch based on - `setSize(x, y)` - Sets the witdh/height of the element. Will stretch based on
alignment. alignment (run the demo to see an example).
- `getSize()` - Returns width and height of the element. - `getSize()` - Returns width and height of the element.
- `align(alignment)` - Sets alignment based on the parent's position and size. - `align(horizontal, vertical)` - Sets alignment based on the parent's position
`alignment` is a string specifying how to align: `top-left`, `top-center`, and size. Valid `horizontal` strings: `left`, `center`, `right`. Valid
`top-right`, `left-center`, `center`, `right-center`, `bottom-left`, `vertical` strings: `top`, `center`, `bottom`.
`bottom-center`, `bottom-right` - `alignTo(element, horizontal, vertical)` - Sets alignment based on an
- `alignTo(element, alignment)` - Sets alignment based on an element's position element's position and size. Same `horizontal`/`vertical` strings as `align()`
and size. Same `alignment`'s as `align()`. - `setAlignment(horizontal, vertical)` - Sets alignment *values* to this, but
- `setAlignment(alignment)` - Sets alignment *value* to this, but does not move does not move the element. Same `horizontal`/`vertical` strings as `align()`
the element.
- `setSkin(skin)` - Sets the skin (see [Skins.md][1]) used for this element. - `setSkin(skin)` - Sets the skin (see [Skins.md][1]) used for this element.
**Note**! Calls to `align()`, `alignTo()`, and `setAlignment()` change what **Note**! Calls to `align()`, `alignTo()`, and `setAlignment()` change what
positions will be returned, and how positioning and resizing will work. positions will be returned, and how positioning and resizing will work. Run the
demo to see how these affect things.
## Box Element ## Box Element
Box is the simplest element, a rectangular area that can be styled or used for Box is the simplest element, a rectangular area.
alignment.
`pop.box(parent, skin)` `pop.box(parent, skin)`
If `parent` not specified, uses `pop.window` (the top level element). If `parent` not specified, uses `pop.window` (the top level element).
@ -32,16 +43,17 @@ If `skin` is not specified, uses `pop.currentSkin` (see [Skins.md][1]).
TODO Make it possible to just specify skin? TODO Make it possible to just specify skin?
## Text Element ## Text Element (NOT DEFINED YET!)
Text is used to draw text. Its styling is based on its skin, see [Skins.md][1] Text is used to draw text.
for information on how to set that up.
`pop.text(parent, text, skin)` `pop.text(parent, text, skin)`
If `parent` not specified, uses `pop.window` (the top level element). If `parent` not specified, uses `pop.window` (the top level element).
If `skin` is not specified, uses `pop.currentSkin` (see [Skins.md][1]). If `skin` is not specified, uses `pop.currentSkin` (see [Skins.md][1]).
TODO Make it possible to just specify text, or just text and skin? TODO Make it possible to just specify text, or just text and skin?
TODO Make it possible to use setting size on text to actually calculate what
font size will make that work?
# Excluding Movement/Rendering # Excluding Movement/Rendering

47
docs/Pop.md Normal file
View File

@ -0,0 +1,47 @@
# `pop` Module
`pop` is the name of the Pop.Box module. You are expected to require it and then
define the following callbacks in LÖVE's callbacks: `pop.update(dt)`,
`pop.draw()`, `pop.textinput(text)`, `pop.mousepressed(button, x, y)`,
`pop.mousereleased(button, x, y)`, `pop.keypressed(key)`, `pop.keyreleased(key)`
Once that has been done (or at the very least, `pop` has been required and a
callback is set up for `pop.draw`), you can start creating [Elements][1] and
drawing them.
Also look into [Skins][2], which control how elements are rendered.
## `pop` Values / Methods
- `pop.window` is the top level element. It essentially represents the game
window.
- `pop.currentSkin` holds a string specifying the currently in-use skin.
Basically, it's a shortcut so you don't have to specify a skin with every
call to construct an element.
- `pop.create(element, parent, ...)` is how elements are actually created,
`element` is a string naming the desired element. There are wrappers around
any element that doesn't conflict with a key in the `pop` module so that you
can call `pop.element(parent, ...)` instead.
- `pop.load()` loads elements and skins, and sets up `pop.window`. This is used
internally, and will probably lead to issues if you use it (namely, destroying
Pop.Box's access to any existing GUI).
## `pop` Callbacks
- `pop.update(dt)` is used so that any element can have a frame-by-frame update
attached to it.
- `pop.draw()` is used to draw everything.
- `pop.textinput(text)` is used to grab text input for any focused element that
can accept it.
- `pop.mousepressed(button, x, y)` is used to detect and handle when an element
is clicked on. (Not actually used yet.)
- `pop.mousereleased(button, x, y)` is not used yet, but probably will be used
in the future.
- `pop.keypressed(key)` is not used yet, but probably will be used in the
future.
- `pop.keyreleased(key)` is also not used yet, but probably will be used in the
future.
[1]: ./Elements.md
[2]: ./Skins.md

View File

@ -11,6 +11,8 @@ require it.
- `draw(element)` - If defined, will be used to render an element instead of the - `draw(element)` - If defined, will be used to render an element instead of the
standard drawing method. standard drawing method.
**Note**: Supported Drawables: Canvas, Image, Video
TODO various text style infos! TODO various text style infos!
[1]: https://love2d.org/wiki/Drawable [1]: https://love2d.org/wiki/Drawable

View File

@ -1,11 +1,39 @@
local lg = love.graphics
local path = string.sub(..., 1, string.len(...) - string.len("/elements/box")) local path = string.sub(..., 1, string.len(...) - string.len("/elements/box"))
local class = require(path .. "/lib/middleclass") local class = require(path .. "/lib/middleclass")
local element = require(path .. "/elements/element") local element = require(path .. "/elements/element")
local box = class("pop.box", element) --TODO follow middleclass standards!?@@R/ local box = class("pop.box", element)
function box:initialize(pop, parent, skin) function box:initialize(pop, parent, skin)
element.initialize(self, pop, parent, skin) element.initialize(self, pop, parent, skin)
end end
function box:draw() --TODO these ifs are probably wrong
if type(self.skin.background) == "table" then
lg.setColor(self.skin.background)
lg.rectangle("fill", self.x, self.y, self.w, self.h)
else
lg.setColor(255, 255, 255, 255)
local w, h = self.skin.background:getDimensions()
-- scale!
w = self.w/w
h = self.h/h
lg.draw(self.skin.background, self.x, self.y, 0, w, h)
end
if type(self.skin.foreground) == "table" then
lg.setColor(self.skin.foreground)
lg.rectangle("fill", self.x, self.y, self.w, self.h)
else
lg.setColor(255, 255, 255, 255)
local w, h = self.skin.foreground:getDimensions()
-- scale!
w = self.w/w
h = self.h/h
lg.draw(self.skin.foreground, self.x, self.y, 0, w, h)
end
end
return box return box

View File

@ -1,7 +1,7 @@
local path = string.sub(..., 1, string.len(...) - string.len("/elements/element")) local path = string.sub(..., 1, string.len(...) - string.len("/elements/element"))
local class = require(path .. "/lib/middleclass") local class = require(path .. "/lib/middleclass")
local element = class("pop.element") --TODO follow middleclass standards!?@@R/ local element = class("pop.element")
function element:initialize(pop, parent, skin) function element:initialize(pop, parent, skin)
self.parent = parent self.parent = parent
@ -13,152 +13,109 @@ function element:initialize(pop, parent, skin)
self.h = 10 self.h = 10
self.skin = pop.skins[skin] or pop.skins[pop.currentSkin] self.skin = pop.skins[skin] or pop.skins[pop.currentSkin]
self.alignment = "top-left"
self.horizontal = "left"
self.vertical = "top"
end end
function element:move(x, y) function element:move(x, y)
self.x = self.x + x self.x = self.x + x
self.y = self.y + y self.y = self.y + y
for i=1,#element.child do
if not element.child[i].excludeMovement then
element.child[i]:move(x - oldX, y - oldY)
end
end
end end
function element:setPosition(x, y) function element:setPosition(x, y)
if self.alignment == "top-left" then local oldX = self.x
local oldY = self.y
if self.horizontal == "left" then
self.x = x self.x = x
self.y = y elseif self.horizontal == "center" then
elseif self.alignment == "top-center" then
self.x = x - self.w/2 self.x = x - self.w/2
self.y = y elseif self.horizontal == "right" then
elseif self.alignment == "top-right" then
self.x = x - self.w self.x = x - self.w
end
if self.vertical == "top" then
self.y = y self.y = y
elseif self.alignment == "left-center" then elseif self.vertical == "center" then
self.x = x
self.y = y - self.h/2 self.y = y - self.h/2
elseif self.alignment == "center" then elseif self.vertical == "bottom" then
self.x = x - self.w/2
self.y = y - self.h/2
elseif self.alignment == "right-center" then
self.x = x - self.w
self.y = y - self.h/2
elseif self.alignment == "bottom-left" then
self.x = x
self.y = y - self.h
elseif self.alignment == "bottom-center" then
self.x = x - self.w/2
self.y = y
elseif self.alignment == "bottom-right" then
self.x = x - self.w
self.y = y - self.h self.y = y - self.h
end end
for i=1,#element.child do
if not element.child[i].excludeMovement then
element.child[i]:move(x - oldX, y - oldY)
end
end
end end
function element:getPosition() function element:getPosition()
if self.alignment == "top-left" then local resultX = self.x
return self.x, self.y local resultY = self.y
elseif self.alignment == "top-center" then
return self.x + self.w/2, self.y if self.horizontal == "center" then
elseif self.alignment == "top-right" then resultX = resultX + self.w/2
return self.x + self.w, self.y elseif self.horizontal == "right" then
elseif self.alignment == "left-center" then resultX = resultX + self.w
return self.x, self.y + self.h/2
elseif self.alignment == "center" then
return self.x + self.w/2, self.y + self.h/2
elseif self.alignment == "right-center" then
return self.x + self.w, self.y + self.h/2
elseif self.alignment == "bottom-left" then
return self.x, self.y + self.h
elseif self.alignment == "bottom-center" then
return self.x + self.w/2, self.y
elseif self.alignment == "bottom-right" then
return self.x + self.w, self.y + self.h
end end
if self.vertical == "center" then
resultY = resultY + self.h/2
elseif self.vertical == "bottom" then
resultY = resultY + self.h
end
return resultX, resultY
end end
function element:setSize(w, h) function element:setSize(w, h)
if self.alignment == "top-left" then if self.horizontal == "center" then
self.w = w
self.h = h
elseif self.alignment == "top-center" then
-- x minus half difference to expand horizontally
self.x = self.x - (w - self.w)/2 self.x = self.x - (w - self.w)/2
self.w = w elseif self.horizontal == "right" then
self.h = h
elseif self.alignment == "top-right" then
-- x minus difference to expand left
self.x = self.x - (w - self.w) self.x = self.x - (w - self.w)
self.w = w
self.h = h
elseif self.alignment == "left-center" then
self.y = self.y - (h - self.h)/2
self.w = w
self.h = h
elseif self.alignment == "center" then
self.x = self.x - (w - self.w)/2
self.y = self.y - (h - self.h)/2
self.w = w
self.h = h
elseif self.alignment == "right-center" then
self.x = self.x - (w - self.w)
self.y = self.y - (h - self.h)/2
self.w = w
self.h = h
elseif self.alignment == "bottom-left" then
self.y = self.y - (h - self.h)
self.w = w
self.h = h
elseif self.alignment == "bottom-center" then
self.x = self.x - (w - self.w)/2
self.y = self.y - (h - self.h)
self.w = w
self.h = h
elseif self.alignment == "bottom-right" then
self.x = self.x - (w - self.w)
self.y = self.y - (h - self.h)
self.w = w
self.h = h
end end
if self.vertical == "center" then
self.y = self.y - (h - self.h)/2
elseif self.vertical == "bottom" then
self.y = self.y - (h - self.h)
end
self.w = w
self.h = h
end end
function element:getSize() function element:getSize()
return self.w, self.h return self.w, self.h
end end
function element:align(alignment) function element:align(horizontal, vertical)
self.alignment = alignment self:setAlignment(horizontal, vertical)
if self.alignment == "top-left" then self.x = self.parent.x
self.x = self.parent.x self.y = self.parent.y
self.y = self.parent.y
elseif self.alignment == "top-center" then if self.horizontal == "center" then
-- parent's x plus half of difference in width to center self.x = self.x + (self.parent.w - self.w)/2
self.x = self.parent.x + (self.parent.w - self.w)/2 elseif self.horizontal == "right" then
self.y = self.parent.y self.x = self.x + (self.parent.w - self.w)
elseif self.alignment == "top-right" then end
-- parent's x plus difference in width to align right
self.x = self.parent.x + (self.parent.w - self.w) if self.vertical == "center" then
self.y = self.parent.y self.y = self.y + (self.parent.h - self.h)/2
elseif self.alignment == "left-center" then elseif self.vertical == "bottom" then
self.x = self.parent.x self.y = self.y + (self.parent.h - self.h)
self.y = self.parent.y + (self.parent.h - self.h)/2
elseif self.alignment == "center" then
self.x = self.parent.x + (self.parent.w - self.w)/2
self.y = self.parent.y + (self.parent.h - self.h)/2
elseif self.alignment == "right-center" then
self.x = self.parent.x + (self.parent.w - self.w)
self.y = self.parent.y + (self.parent.h - self.h)/2
elseif self.alignment == "bottom-left" then
self.x = self.parent.x
self.y = self.parent.y + (self.parent.h - self.h)
elseif self.alignment == "bottom-center" then
self.x = self.parent.x + (self.parent.w - self.w)/2
self.y = self.parent.y + (self.parent.h - self.h)
elseif self.alignment == "bottom-right" then
self.x = self.parent.x + (self.parent.w - self.w)
self.y = self.parent.y + (self.parent.h - self.h)
end end
end end
function element:alignTo(element, alignment) function element:alignTo(element, horizontal, vertical)
local realParent = self.parent local realParent = self.parent
self.parent = element self.parent = element
@ -167,8 +124,13 @@ function element:alignTo(element, alignment)
self.parent = realParent self.parent = realParent
end end
function element:setAlignment(alignment) function element:setAlignment(horizontal, vertical)
self.alignment = alignment if horizontal then
self.horizontal = horizontal
end
if vertical then
self.vertical = vertical
end
end end
function element:setSkin(skin) function element:setSkin(skin)

View File

@ -8,12 +8,13 @@ pop.elementClasses = {}
pop.window = false --top level element, defined in pop.load() pop.window = false --top level element, defined in pop.load()
pop.skins = {} pop.skins = {}
pop.currentSkin = "clear" pop.currentSkin = "clear"
--TODO we need a "focused" element for textinput or whatever
function pop.load() function pop.load()
-- load element classes -- load element classes
local elementList = lf.getDirectoryItems(path .. "/elements") local elementList = lf.getDirectoryItems(path .. "/elements")
for i=0, #elementList do for i=1, #elementList do
local name = elementList[i]:sub(1, -5) local name = elementList[i]:sub(1, -5)
pop.elementClasses[name] = require(path .. "/elements/" .. name) pop.elementClasses[name] = require(path .. "/elements/" .. name)
@ -26,14 +27,14 @@ function pop.load()
-- load skins -- load skins
local skinList = lf.getDirectoryItems(path .. "/skins") local skinList = lf.getDirectoryItems(path .. "/skins")
for i=0, #skinList do for i=1, #skinList do
local name = skinList[i]:sub(1, -5) local name = skinList[i]:sub(1, -5)
pop.skins[name] = require(path .. "/skins/" .. name) pop.skins[name] = require(path .. "/skins/" .. name)
pop.skins[name].name = name pop.skins[name].name = name
end end
-- set top element -- set top element
pop.window = pop.create("box"):setSize(lg.getWidth(), lg.getHeight()) pop.window = pop.create("element"):setSize(lg.getWidth(), lg.getHeight())
end end
function pop.create(elementType, parent, ...) function pop.create(elementType, parent, ...)
@ -56,9 +57,8 @@ function pop.update(dt, element)
element:update(dt) element:update(dt)
end end
--TODO redo this loop for i=1,#element.child do
for _, childElement in pairs(element.child) do pop.update(dt, element.child[i])
pop.update(dt, childElement)
end end
end end
@ -67,15 +67,16 @@ function pop.draw(element)
element = pop.window element = pop.window
end end
if element.skin.draw and element.skin.draw(element) then if not element.excludeRendering then
-- do nothing... if element.skin.draw and element.skin.draw(element) then
elseif element.draw then -- do nothing...
element:draw() elseif element.draw then
end element:draw()
end
--TODO redo this loop for i=1,#element.child do
for _, childElement in pairs(element.child) do pop.draw(element.child[i])
pop.draw(childElement) end
end end
end end