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. I've never written a GUI library before..so we'll see how that goes.
## Features
```lua ```lua
local pop = require "pop" local pop = require "pop"
-- define love callbacks here -- define love callbacks here (update, draw, textinput, mouse/key events)
local box = pop.box() local box = pop.box()
``` ```
* `box` is a box (class) for containing things. ## Using
* `text` is a class for handling text.
* Nothing else! Is alpha, just started. 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

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() 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()) -- or pop.create("box") (this is what is actually called when you call pop.box())
end end
function love.update(dt)
pop.update(dt)
end
function love.draw() function love.draw()
pop.draw() pop.draw()
end end
function love.textinput(text)
pop.textinput(text)
end
function love.mousepressed(button, x, y) function love.mousepressed(button, x, y)
pop.mousepressed(button, x, y) pop.mousepressed(button, x, y)
end end
@ -20,5 +28,11 @@ end
function love.keypressed(key) function love.keypressed(key)
if key == "escape" then if key == "escape" then
love.event.quit() love.event.quit()
else
pop.keypressed(key)
end end
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 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) local box = class("pop.box", element) --TODO follow middleclass standards!?@@R/
function box:initialize(pop, parent) function box:initialize(pop, parent, skin)
element.initialize(self, pop, parent) element.initialize(self, pop, parent, skin)
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
end end
return box return box

View File

@ -1,121 +1,182 @@
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")
--TODO determine if these requires can break because of slashes / subdirectories
local element = class("pop.element") local element = class("pop.element") --TODO follow middleclass standards!?@@R/
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
function element:initialize(pop, parent, skin)
self.parent = parent self.parent = parent
self.child = {} 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 end
--TODO completely redefine interface based on what we should expect users to do function element:move(x, y)
-- REMEMBER the goal is minimal effort on their part self.x = self.x + x
-- THEREFORE, we should reduce this interface, they should rely on skins for borderSize (and thus, differences in outer/inner sizes) self.y = self.y + y
-- all calls should be based on sizing the outside, and update() should update inners (including children!) based on outers 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() function element:getSize()
return self.outerWidth, self.outerHeight return self.w, self.h
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"
end end
function element:getOuterWidth() function element:align(alignment)
return self.outerWidth self.alignment = alignment
end
function element:setOuterWidth(width) if self.alignment == "top-left" then
assert(width > 0, "width must be above 0") self.x = self.parent.x
self.outerWidth = width self.y = self.parent.y
self.sizeControl = "specified" elseif self.alignment == "top-center" then
--TODO needs to update() to update inner size based on borderSize ??? -- 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
end end
function element:getOuterHeight() function element:alignTo(element, alignment)
return self.outerHeight() local realParent = self.parent
end self.parent = element
function element:setOuterHeight(height)
assert(height > 0, "height must be above 0") self:align(alignment)
self.outerHeight = height
self.sizeControl = "specified" self.parent = realParent
--TODO needs to update() to update inner size based on borderSize ???
end end
function element:getInnerWidth() function element:setAlignment(alignment)
return self.innerWidth self.alignment = alignment
end
function element:setInnerWidth(width)
assert(width > 0, "width must be above 0")
self.innerWidth = width
self.sizeControl = "specified"
--TODO needs to update outerWidth ???
end end
function element:getInnerHeight() function element:setSkin(skin)
return self.innerHeight if type(skin) == "string" then
end self.skin = pop.skins[skin]
function element:setInnerHeight(height) else
assert(height > 0, "height must be above 0") self.skin = skin
self.innerHeight = height end
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 end
return element return element

View File

@ -1,99 +1,100 @@
local lf = love.filesystem local lf = love.filesystem
local lg = love.graphics local lg = love.graphics
local path = ...
local pop = {} local pop = {}
local path = ... --NOTE Pop.Box must be required as its directory name (w SLASHES)! pop.elementClasses = {}
--pop.elements = {}
-- elements are local pop.window = false --top level element, defined in pop.load()
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
end
-- skins define how elements are drawn
pop.skins = {} pop.skins = {}
local skins = lf.getDirectoryItems(path .. "/skins") --NOTE Pop.Box must be required with SLASHES! pop.currentSkin = "clear"
for _, v in ipairs(skins) do
local name = v:sub(1,-5) 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
-- 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] = require(path .. "/skins/" .. name)
pop.skins[name].name = name pop.skins[name].name = name
end
-- set top element
pop.window = pop.create("box"):setSize(lg.getWidth(), lg.getHeight())
end end
pop.currentSkin = "clearspace" --default skin
-- everything has one parent element (initialized at the end)
pop.parentElement = false
-- this function actually creates elements based on what type of element is requested
function pop.create(elementType, parent, ...) function pop.create(elementType, parent, ...)
if not parent then if not parent then
parent = pop.parentElement parent = pop.window
end end
local newElement local newElement = pop.elementClasses[elementType](pop, parent, ...)
table.insert(parent.child, newElement) --NOTE pop.window is its own parent!
--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
return newElement return newElement
end end
-- pretty wrappers to call pop.element() instead of pop.create("element") function pop.update(dt, 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)
if not element then if not element then
element = pop.parentElement element = pop.window
end 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 for _, childElement in pairs(element.child) do
--pop.draw(childElement, element.x, element.y)
pop.draw(childElement) pop.draw(childElement)
end end
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) function pop.mousepressed(button, x, y)
--TODO find which element it belongs to and if that element has a callback set, --TODO find element, if it has a callback, call with button and LOCAL x/y
-- if it does, use that, else, use the global callback (defined above..as nil)
end end
function pop.mousereleased(button, x, y) function pop.mousereleased(button, x, y)
--TODO find which element it belongs to and if that element has a callback set, --TODO find element, if it has a callback, call with button and LOCAL x/y
-- if it does, use that, else, use the global callback (defined above..as nil)
end end
function pop.keypressed(key, unicode) function pop.keypressed(key)
--TODO handle this in some manner when needed --TODO no idea what to do with this
end end
function pop.keyreleased(key) function pop.keyreleased(key)
--TODO handle this when needed --TODO no idea what to do with this
end end
-- initialize the top element pop.load()
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...
return pop 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 = {} local skin = {}
skin.style = { skin.background = false, -- Drawable, color table, or false
background = false, -- color table, image, or false skin.foreground = {255,255,255,255} -- Drawable, color table, or false
foreground = {0,0,0,1}, -- color table
borderSize = 0, -- integer (minimum 0)
borderStyle = false -- color table, image, or false
}
return skin 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