--[[
-- This is an abstract class and represents the idea of what is a draw (short for drawable).
-- It's not just an interface as it also defines some properties and implements many methods.
-- All the visibility, positioning and collision detection support is implemented in this class.
-- The only abstract method is [draw] and all derived sub-classes must implement it.
-- The [gui.lua#draws] maintains a collection of these objects demanding that any other draw should derive from this class.
--
-- Positioning System
-- The area avaiable for a Draw is always relative to its parent, this means that its (0,0) is in its parent Draw (0,0) and not in main window's (0,0).
-- It's possible to set positioning for a Draw in two places: when creating it through the constructor or using [set] method.
-- Absolute and relative positionings are avaiable (both relative to parent).
--
-- Absolute positioning:
-- p1
: left/top most point
-- pc
: center/middle point
-- p2
: right/bottom most point
-- d
: dimension
--
-- Example:
-- draw = TextDraw{ x={pc=200}, y={p2=100} , ... }
--
--
--
--
--
--
-- Relative positioning:
-- Here the values varies from 0 to 1000 ranging proporcionally from 0% to 100% to parent's dimensions.
-- pp1
: left/top most point of draw (p1) is positioned in this percentage relative to parent dimension.
-- ppc
: center/middle point
-- pp2
: right/bottom most point
-- pk
: constant adjustment
-- dd
: dimension of draw is set to this percentage of parent's dimension
--
-- The following examples corresponds to the image on the right:
--
-- -- Example 1:
-- draw:set{ x={pp1=0}, y={pp1=0} }
-- -- Example 2:
-- draw:set{ x={ppc=500}, y={ppc=500} }
-- -- Example 3:
-- draw:set{ x={ppc=1000}, y={pp1=500} }
-- -- Example 4:
-- -- also sets to half the parent's width
-- draw:set{ x={pp1=0, dd=500}, y={pp2=1000} }
-- -- Example 5:
-- -- also adjusts 10 pixels left
-- draw:set{ x={pp2=1000, pk=-10}, y={pp1=1000} }
--
--
--
--]]
local _G = _G
local oo = require 'oo'
module (...)
local gui = _G.require(_PACKAGE..'gui')
local event = _G.require(_PACKAGE..'event')
--[[
-- Maintains self positioning.
-- See the Postioning section for more information.
--]]
x = {p1,pc,p2,pp1,ppc,pp2, d,dd, P={v,t}, D={v,t}}
-- The same as in [x].
y = {p1,pc,p2,pp1,ppc,pp2,pk=0, d,dd,dk=0, P={v,t}, D={v,t}}
--[[
-- Every draw has as parent and all positioning are relative to it.
-- The default parent for every draw is the main window.
--]]
parent = nil -- TEMP was: gui.parent
--[[
-- Current visibility status for self.
-- Not visibles draws are not subject for drawing and collision detection.
--]]
visible = true
--[[
-- Current collision status for self.
-- If false
then self isn't subject for collision detection.
-- If is a function
value it is not only checked for collisions but also this function is called when collided with other draw.
-- The signature of the function is:
--
-- function (self, draw2) end
--
-- Where draw2 is the collided draw with self.
-- Any other non-nil value assumes self as subject for collision detection and takes no action.
--]]
collide = false
--[[
-- Method responsible for drawing it on screen.
-- Here is an abstract method required to be implemented for sub-classes.
-- This method is called for every draw in [gui.lua#draws] whenever a call to [set] generates 'draw' event [event.lua#post].
-- It's signature is:
--
-- function draw (self) end
--
--]]
draw = oo._REQ
local _INT = {}
local _SHIFT = {
left = 0, center = -1, right = -2,
top = 0, middle = -1, bottom = -2,
}
--[[
-- Returns pc, p1 and p2, given:
-- p: position
-- dp: parent's dimension
-- l: type of alignment ('left'/'top', 'center'/'middle', 'right'/'bottom)
--]]
function _shift (p, dp, l)
l = l or 'center' -- ou middle, tanto faz
local p1 = p + (_SHIFT[l] * dp / 2)
local p2 = p1 + dp
local pc = p1 + dp/2
return pc, p1, p2
end
--[[
-- Checks if self intersects with Draw B.
-- B: Draw being checked against self
-- returns true/false
--]]
function getIntersection (self, B)--, int)
local Ax1, Ax2, Ay1, Ay2 = self.x.p1, self.x.p2, self.y.p1, self.y.p2
local Bx1, Bx2, By1, By2 = B.x.p1, B.x.p2, B.y.p1, B.y.p2
if (not self) or (not B) or
Ax1 > Bx2 or
Ax2 < Bx1 or
Ay1 > By2 or
Ay2 < By1 then
return false
end
-- so esta interessado se colidiu
if not int then return true end
--[[
-- retangulo de intersecao
int.x1 = math.max( Ax1, Bx1 )
int.x2 = math.min( Ax2, Bx2 )
int.dx = int.x2 - int.x1
int.y1 = math.max( Ay1, By1 )
int.y2 = math.min( Ay2, By2 )
int.dy = int.y2 - int.y1
int.offX = int.x1 - Ax1
int.offY = int.y1 - Ay1
-- intersecao completa: se int == (self ou B)
--int.complete = (int.x1 == Ax1) and (int.x2 == Ax2) and (int.y1 == A1.y1) and (int.y2 == A1.y2)
--int.complete = int.complete or (int.x1 == B.x1) and (int.x2 == B.x2) and (int.y1 == B.y1) and (int.y2 == B.y2)
return int
]]--
end
--[[
--
-- Default constructor for all Draws.
-- Calls [set] automatically.
-- set: (false) used for classes requiring initialization prior to calling [set].
-- In this case, self is required to call [set] later in its derived constructor.
--]]
function new (cls, obj, set)
set = set or (set == nil)
local self = _SUPER.new(cls, obj)
self.parent = self.parent or gui.parent
if set then
self:set(self)
end
return self
end
local _R = {x=nil,y=nil,xp=true}
--[[
-- Moves self dx
, dy
pixels.
--]]
function move (self, dx, dy)
_R.x, _R.y = dx+self.x.pc, dy+self.y.pc
self:set(_R)
end
local _NIL = {}
local _D = {
d = {pct=false}, dd = {pct=true}
}
local _P = {
p1 = {pct=false, align='top'}, pc = {pct=false, align='center'}, p2 = {pct=false, align='bottom'},
pp1 = {pct=true, align='top'}, ppc = {pct=true, align='center'}, pp2 = {pct=true, align='bottom'},
}
--[[
-- Controls self state.
-- All the positioning and visibility is set through this method.
-- Sub-classes requiring to set other properties should do this extending this method (see [oo.lua#TEMP]).
-- Generates a 'draw' event calling [event.lua#post].
-- T
can be:
--
-- - nil: TEMP
-- - false: sets self visibility to false.
-- - table: expects the format as in Positioning System section.
--
--]]
function set (self, T)
event.post('draw', self)
if T == false then
self.visible = false
return
end
T = T or self -- nil should continue below
self.visible = true
if T == true then
return
end
-- assume mudanca de posicao centralizada (xp=express)
-- considera apenas t.x/t.y
-- TEMP: nao testei depois de incluir parent.p1
if T.xp
then
local X, Y = self.x, self.y
local dx2, dy2 = X.d/2, Y.d/2
local x, y = T.x, T.y
if x then
x = x + self.parent.x.p1
X.p1, X.pc, X.p2 = x-dx2, x, x+dx2
X.P.v, X.P.t = x, _P.pc
end
if y then
y = y + self.parent.y.p1
Y.p1, Y.pc, Y.p2 = y-dy2, y, y+dy2
Y.P.v, Y.P.t = y, _P.pc
end
-- SET() PADRAO
else
-- Em x e y:
for _, axis in _G.ipairs(gui._AXIS)
do
local t, s = T[axis] or _NIL, self[axis]
s.pk = t.pk or s.pk
s.dk = t.dk or s.dk
local parentD = self.parent[axis].d
-- POSICAO
local P = s.P
if not P.v then
P.v, P.t = -10000, _P.p2 -- default: fora da tela
end
for p, _t in _G.pairs(_P) do -- itera entre p1,pp1,pc,etc
if t[p] then -- pega o primeiro que encontrar
P.v = t[p]
P.t = _t
break
end
end
local pv = _G.assert(P.v, axis..': '..self.id)
if P.t.pct then
if pv >= 0 then pv = (parentD or 0) * pv / 1000
elseif pv < 0 then pv = (parentD or 0) + pv end
end
if s.pk then pv = pv + s.pk end
-- TAMANHO
local D = s.D
for d, _t in _G.pairs(_D) do
if t[d] then
D.v = t[d]
D.t = _t
break
end
end
local dv = _G.assert(D.v, 'missing "'..axis..'" dimension for '..self.id)
if D.t.pct then
if dv >= 0 then dv = (parentD or 0) * dv / 1000
elseif dv < 0 then dv = (parentD or 0) + dv end
end
if s.dk then dv = dv + s.dk end
-- ALTERA
s.d, s.pc, s.p1, s.p2 = dv, _shift(pv, dv, P.t.align)
end
end
end
return oo.class(_M)