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
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
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()
```
## Using
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.
Docs: [pop Module][3], [Elements][4], [Skins][5]
[1]: https://en.wikipedia.org/wiki/Cola_(programming_language)
[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 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:
- `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.
- `getPosition()` - Returns `x` and `y` position based on current alignment.
- `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.
- `align(alignment)` - Sets alignment based on the parent's position and size.
`alignment` is a string specifying how to align: `top-left`, `top-center`,
`top-right`, `left-center`, `center`, `right-center`, `bottom-left`,
`bottom-center`, `bottom-right`
- `alignTo(element, alignment)` - Sets alignment based on an element's position
and size. Same `alignment`'s as `align()`.
- `setAlignment(alignment)` - Sets alignment *value* to this, but does not move
the element.
- `align(horizontal, vertical)` - Sets alignment based on the parent's position
and size. Valid `horizontal` strings: `left`, `center`, `right`. Valid
`vertical` strings: `top`, `center`, `bottom`.
- `alignTo(element, horizontal, vertical)` - Sets alignment based on an
element's position and size. Same `horizontal`/`vertical` strings as `align()`
- `setAlignment(horizontal, vertical)` - Sets alignment *values* to this, but
does not move the element. Same `horizontal`/`vertical` strings as `align()`
- `setSkin(skin)` - Sets the skin (see [Skins.md][1]) used for this element.
**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 is the simplest element, a rectangular area that can be styled or used for
alignment.
Box is the simplest element, a rectangular area.
`pop.box(parent, skin)`
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?
## Text Element
## Text Element (NOT DEFINED YET!)
Text is used to draw text. Its styling is based on its skin, see [Skins.md][1]
for information on how to set that up.
Text is used to draw text.
`pop.text(parent, text, skin)`
If `parent` not specified, uses `pop.window` (the top level element).
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 use setting size on text to actually calculate what
font size will make that work?
# 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
standard drawing method.
**Note**: Supported Drawables: Canvas, Image, Video
TODO various text style infos!
[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 class = require(path .. "/lib/middleclass")
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)
element.initialize(self, pop, parent, skin)
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

View File

@ -1,7 +1,7 @@
local path = string.sub(..., 1, string.len(...) - string.len("/elements/element"))
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)
self.parent = parent
@ -13,152 +13,109 @@ function element:initialize(pop, parent, skin)
self.h = 10
self.skin = pop.skins[skin] or pop.skins[pop.currentSkin]
self.alignment = "top-left"
self.horizontal = "left"
self.vertical = "top"
end
function element:move(x, y)
self.x = self.x + x
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
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.y = y
elseif self.alignment == "top-center" then
elseif self.horizontal == "center" then
self.x = x - self.w/2
self.y = y
elseif self.alignment == "top-right" then
elseif self.horizontal == "right" then
self.x = x - self.w
end
if self.vertical == "top" then
self.y = y
elseif self.alignment == "left-center" then
self.x = x
elseif self.vertical == "center" then
self.y = y - self.h/2
elseif self.alignment == "center" 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
elseif self.vertical == "bottom" then
self.y = y - self.h
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
function element:getPosition()
if self.alignment == "top-left" then
return self.x, self.y
elseif self.alignment == "top-center" then
return self.x + self.w/2, self.y
elseif self.alignment == "top-right" then
return self.x + self.w, self.y
elseif self.alignment == "left-center" then
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
local resultX = self.x
local resultY = self.y
if self.horizontal == "center" then
resultX = resultX + self.w/2
elseif self.horizontal == "right" then
resultX = resultX + self.w
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
function element:setSize(w, h)
if self.alignment == "top-left" then
self.w = w
self.h = h
elseif self.alignment == "top-center" then
-- x minus half difference to expand horizontally
if self.horizontal == "center" then
self.x = self.x - (w - self.w)/2
self.w = w
self.h = h
elseif self.alignment == "top-right" then
-- x minus difference to expand left
elseif self.horizontal == "right" then
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
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
function element:getSize()
return self.w, self.h
end
function element:align(alignment)
self.alignment = alignment
function element:align(horizontal, vertical)
self:setAlignment(horizontal, vertical)
if self.alignment == "top-left" then
self.x = self.parent.x
self.y = self.parent.y
elseif self.alignment == "top-center" then
-- parent's x plus half of difference in width to center
self.x = self.parent.x + (self.parent.w - self.w)/2
self.y = self.parent.y
elseif self.alignment == "top-right" then
-- parent's x plus difference in width to align right
self.x = self.parent.x + (self.parent.w - self.w)
self.y = self.parent.y
elseif self.alignment == "left-center" then
self.x = self.parent.x
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)
if self.horizontal == "center" then
self.x = self.x + (self.parent.w - self.w)/2
elseif self.horizontal == "right" then
self.x = self.x + (self.parent.w - self.w)
end
if self.vertical == "center" then
self.y = self.y + (self.parent.h - self.h)/2
elseif self.vertical == "bottom" then
self.y = self.y + (self.parent.h - self.h)
end
end
function element:alignTo(element, alignment)
function element:alignTo(element, horizontal, vertical)
local realParent = self.parent
self.parent = element
@ -167,8 +124,13 @@ function element:alignTo(element, alignment)
self.parent = realParent
end
function element:setAlignment(alignment)
self.alignment = alignment
function element:setAlignment(horizontal, vertical)
if horizontal then
self.horizontal = horizontal
end
if vertical then
self.vertical = vertical
end
end
function element:setSkin(skin)

View File

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