align shit done badly

This commit is contained in:
Fox 2016-01-20 14:34:25 -08:00
parent 00fe7f2617
commit 145533195d
10 changed files with 339 additions and 216 deletions

54
Elements.md Normal file
View File

@ -0,0 +1,54 @@
# Elements
All elements have the following standard methods:
- `move(x, y)` - Moves from current position 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.
- `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.
- `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.
## Box Element
Box is the simplest element, a rectangular area that can be styled or used for
alignment.
`pop.box(parent, 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 skin?
## Text Element
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.
`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?
# Excluding Movement/Rendering
If you set `excludeMovement` to `true` on any element, it and its children will
not be moved unless its own movement methods are used.
If you set `excludeRendering` to `true` on any element, it and its children will
not be rendered.
[1]: ./Skins.md

View File

@ -8,17 +8,30 @@ 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
```lua
local pop = require "pop"
-- define love callbacks here
-- define love callbacks here (update, draw, textinput, mouse/key events)
local box = pop.box()
```
* `box` is a box (class) for containing things.
* `text` is a class for handling text.
* Nothing else! Is alpha, just started.
## 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.
[1]: https://en.wikipedia.org/wiki/Cola_(programming_language)
[2]: https://love2d.org/
[3]: ./Elements.md

17
Skins.md Normal file
View File

@ -0,0 +1,17 @@
# Skins
Skins are simply tables containing information on how to draw elements that have
been assigned them. Skins are loaded from Pop's `skins` directory when you
require it.
- `background` - A [Drawable][1] drawn before the `foreground`, or if a table,
assumed to be a [color][2] and that color is used (if `false`, is ignored).
- `foreground` - A [Drawable][1] drawn after the `background`, or if a table,
assumed to be a [color][2] and that color is used (if `false`, is ignored).
- `draw(element)` - If defined, will be used to render an element instead of the
standard drawing method.
TODO various text style infos!
[1]: https://love2d.org/wiki/Drawable
[2]: https://love2d.org/wiki/love.graphics.setColor

View File

@ -1,14 +1,22 @@
local pop = require "pop" --TODO tell user that pop must be required with SLASHES
local pop = require "pop"
function love.load()
pop.box() -- returns the box element
--pop.box() -- returns the box element
-- or pop.create("box") (this is what is actually called when you call pop.box())
end
function love.update(dt)
pop.update(dt)
end
function love.draw()
pop.draw()
end
function love.textinput(text)
pop.textinput(text)
end
function love.mousepressed(button, x, y)
pop.mousepressed(button, x, y)
end
@ -20,5 +28,11 @@ end
function love.keypressed(key)
if key == "escape" then
love.event.quit()
else
pop.keypressed(key)
end
end
function love.keyreleased(key)
pop.keyreleased(key)
end

View File

@ -1,24 +1,11 @@
local path = string.sub(..., 1, string.len(...) - string.len(".elements.box"))
local class = require(path .. ".lib.middleclass")
local element = require(path .. ".elements.element")
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)
local box = class("pop.box", element) --TODO follow middleclass standards!?@@R/
function box:initialize(pop, parent)
element.initialize(self, pop, parent)
self.sizeControl = "specified"
self.outerWidth = 300
self.outerHeight = 250
self.innerWidth = self.outerWidth - self.skin.style.borderSize
self.innerHeight = self.outerHeight - self.skin.style.borderSize
end
function box:update()
--
end
function box:draw()
--TODO find a way for relative x/y to be passed here, because else, we won't have proper coords for drawing
function box:initialize(pop, parent, skin)
element.initialize(self, pop, parent, skin)
end
return box

View File

@ -1,121 +1,182 @@
local path = string.sub(..., 1, string.len(...) - string.len(".elements.element"))
local class = require(path .. ".lib.middleclass")
--TODO determine if these requires can break because of slashes / subdirectories
local path = string.sub(..., 1, string.len(...) - string.len("/elements/element"))
local class = require(path .. "/lib/middleclass")
local element = class("pop.element")
function element:initialize(pop, parent)
self.ax = 0 -- absolute locations
self.ay = 0
self.rx = 0 -- relative to parent locations
self.ry = 0
self.sizeControl = "fromInner" -- fromInner, fromOuter, specified
self.outerWidth = 0
self.outerHeight = 0
self.innerWidth = 0
self.innerHeight = 0
self.skin = pop.skins[pop.currentSkin]
self.visible = true
local element = class("pop.element") --TODO follow middleclass standards!?@@R/
function element:initialize(pop, parent, skin)
self.parent = parent
self.child = {}
parent.child[self] = self -- add ourselves to the parent's children
self.x = parent.x or 0
self.y = parent.y or 0
self.w = 10
self.h = 10
self.skin = pop.skins[skin] or pop.skins[pop.currentSkin]
self.alignment = "top-left"
end
--TODO completely redefine interface based on what we should expect users to do
-- REMEMBER the goal is minimal effort on their part
-- THEREFORE, we should reduce this interface, they should rely on skins for borderSize (and thus, differences in outer/inner sizes)
-- all calls should be based on sizing the outside, and update() should update inners (including children!) based on outers
function element:move(x, y)
self.x = self.x + x
self.y = self.y + y
end
function element:setPosition(x, y)
if self.alignment == "top-left" then
self.x = x
self.y = y
elseif self.alignment == "top-center" then
self.x = x - self.w/2
self.y = y
elseif self.alignment == "top-right" then
self.x = x - self.w
self.y = y
elseif self.alignment == "left-center" then
self.x = x
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
self.y = y - self.h
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
end
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
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
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
function element:getSize()
return self.outerWidth, self.outerHeight
end
function element:setSize(width, height)
assert(width > 0, "width must be above 0")
assert(height > 0, "height must be above 0")
self.outerWidth = width
self.outerHeight = height
self.sizeControl = "specified"
return self.w, self.h
end
function element:getOuterWidth()
return self.outerWidth
function element:align(alignment)
self.alignment = alignment
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)
end
function element:setOuterWidth(width)
assert(width > 0, "width must be above 0")
self.outerWidth = width
self.sizeControl = "specified"
--TODO needs to update() to update inner size based on borderSize ???
end
function element:getOuterHeight()
return self.outerHeight()
end
function element:setOuterHeight(height)
assert(height > 0, "height must be above 0")
self.outerHeight = height
self.sizeControl = "specified"
--TODO needs to update() to update inner size based on borderSize ???
function element:alignTo(element, alignment)
local realParent = self.parent
self.parent = element
self:align(alignment)
self.parent = realParent
end
function element:getInnerWidth()
return self.innerWidth
end
function element:setInnerWidth(width)
assert(width > 0, "width must be above 0")
self.innerWidth = width
self.sizeControl = "specified"
--TODO needs to update outerWidth ???
function element:setAlignment(alignment)
self.alignment = alignment
end
function element:getInnerHeight()
return self.innerHeight
function element:setSkin(skin)
if type(skin) == "string" then
self.skin = pop.skins[skin]
else
self.skin = skin
end
function element:setInnerHeight(height)
assert(height > 0, "height must be above 0")
self.innerHeight = height
self.sizeControl = "specified"
--TODO needs to update outerHeight ??
end
--[[ TODO determine how to write these better (consistency motherfucker)
function element:getStyle()
return self.style.name
end
function element:setStyle(style)
self.style = style
end
]]
function element:getVisible()
return self.visible
end
function element:setVisible(bool)
self.visible = bool
end
function element:getParent()
return self.parent
end
function element:setParent(parent)
self.parent.child[self] = nil
self.parent = parent
self.parent.child[self] = self
end
--TODO figure out how getting and setting children might work? or no??
function element:update()
--TODO a proper error message
print("update() not deifnenfei")
end
function element:draw()
--TODO figure out how to get class name
print("Attempting to use element, or did not overwrite element's :draw() method.")
end
return element

View File

@ -1,99 +1,100 @@
local lf = love.filesystem
local lg = love.graphics
local path = ...
local pop = {}
local path = ... --NOTE Pop.Box must be required as its directory name (w SLASHES)!
pop.elementClasses = {}
--pop.elements = {}
pop.window = false --top level element, defined in pop.load()
pop.skins = {}
pop.currentSkin = "clear"
-- elements are local
local box = require(path .. ".elements.box")
local text = require(path .. ".elements.text")
--TODO require these how skins are required
pop.elements = {}
local elements = lf.getDirectoryItems(path .. "/elements") --NOTE Pop.Box must be required with SLASHES!
for _, v in ipairs(elements) do
local name = v:sub(1,-5)
pop.elements[name] = require(path .. "/elements/" .. name) --TODO test this actually works right
function pop.load()
-- load element classes
local elementList = lf.getDirectoryItems(path .. "/elements")
for i=0, #elementList do
local name = elementList[i]:sub(1, -5)
pop.elementClasses[name] = require(path .. "/elements/" .. name)
-- wrapper to be able to call pop.element() to create elements
if not pop[name] then
pop[name] = function(...) return pop.create(name, ...) end
end
end
-- skins define how elements are drawn
pop.skins = {}
local skins = lf.getDirectoryItems(path .. "/skins") --NOTE Pop.Box must be required with SLASHES!
for _, v in ipairs(skins) do
local name = v:sub(1,-5)
-- load skins
local skinList = lf.getDirectoryItems(path .. "/skins")
for i=0, #skinList do
local name = skinList[i]:sub(1, -5)
pop.skins[name] = require(path .. "/skins/" .. name)
pop.skins[name].name = name
end
pop.currentSkin = "clearspace" --default skin
-- everything has one parent element (initialized at the end)
pop.parentElement = false
-- set top element
pop.window = pop.create("box"):setSize(lg.getWidth(), lg.getHeight())
end
-- this function actually creates elements based on what type of element is requested
function pop.create(elementType, parent, ...)
if not parent then
parent = pop.parentElement
parent = pop.window
end
local newElement
--TODO replace these with calls to pop.elements.ELEMENTNAME
if elementType == "box" then
newElement = box(pop, parent, ...)
elseif elementType == "text" then
newElement = text(pop, parent, ...)
else
error("Invalid element type: " .. elementType)
end
local newElement = pop.elementClasses[elementType](pop, parent, ...)
table.insert(parent.child, newElement) --NOTE pop.window is its own parent!
return newElement
end
-- pretty wrappers to call pop.element() instead of pop.create("element")
pop.box = function(...) return pop.create("box", ...) end
pop.text = function(...) return pop.create("text", ...) end
-- called every frame to draw elements
function pop.draw(element)
function pop.update(dt, element)
if not element then
element = pop.parentElement
element = pop.window
end
element:draw()
if element.update then
element:update(dt)
end
--TODO redo this loop
for _, childElement in pairs(element.child) do
pop.update(dt, childElement)
end
end
function pop.draw(element)
if not element then
element = pop.window
end
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, element.x, element.y)
pop.draw(childElement)
end
end
-- TODO decide if we should track mouse movement
-- these are functions to be overwritten by user if they want a global event handler
function pop.onMousePress(button, x, y) end
function pop.onMouseRelease(button, x, y) end
function pop.mousepressed(button, x, y)
--TODO find which element it belongs to and if that element has a callback set,
-- if it does, use that, else, use the global callback (defined above..as nil)
--TODO find element, if it has a callback, call with button and LOCAL x/y
end
function pop.mousereleased(button, x, y)
--TODO find which element it belongs to and if that element has a callback set,
-- if it does, use that, else, use the global callback (defined above..as nil)
--TODO find element, if it has a callback, call with button and LOCAL x/y
end
function pop.keypressed(key, unicode)
--TODO handle this in some manner when needed
function pop.keypressed(key)
--TODO no idea what to do with this
end
function pop.keyreleased(key)
--TODO handle this when needed
--TODO no idea what to do with this
end
-- initialize the top element
pop.parentElement = box(pop, {child={}}) -- dummy object because it has no parent
--pop.parentElement:setSizeControl("specified") -- make it not try to update itself (TODO or based on outro, and custom code to check top parent against screen size)
--pop.parentElement:setSize(lg.getWidth(), lg.getHeight()) -- fill window size
--pop.parentElement:setVisible(false) -- uneeded since its clear...
pop.load()
return pop

View File

@ -1,10 +0,0 @@
local skin = {}
skin.style = {
background = {255,255,255,1}, -- color table, image, or false
foreground = {0,0,0,1}, -- color table
borderSize = 2, -- integer (minimum 0)
borderStyle = {255,255,255,1} -- color table, image, or false
}
return skin

View File

@ -1,10 +1,6 @@
local skin = {}
skin.style = {
background = false, -- color table, image, or false
foreground = {0,0,0,1}, -- color table
borderSize = 0, -- integer (minimum 0)
borderStyle = false -- color table, image, or false
}
skin.background = false, -- Drawable, color table, or false
skin.foreground = {255,255,255,255} -- Drawable, color table, or false
return skin

View File

@ -1,10 +0,0 @@
local skin = {}
skin.style = {
background = false, -- color table, image, or false
foreground = {0,0,0,1}, -- color table
borderSize = 2, -- integer (minimum 0)
borderStyle = false -- color table, image, or false
}
return skin