Skip to content

Instantly share code, notes, and snippets.

Created June 21, 2015 13:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/97a330f75a95dc632a33 to your computer and use it in GitHub Desktop.
Save anonymous/97a330f75a95dc632a33 to your computer and use it in GitHub Desktop.
Gists Codea Upload
--# Main
-- components
--[[ the idea: define an Object class that
uses components
can be used as a component
so complex objects can be built out of simpler ones
the functions draw, touched, update (, delete, tostring) should work
the update function seems the most complex, because of 2 way interaction: top-down, bottom-up
problems are:
• Order of objects matters
• Conflict between objects, trying to set self.x to different values
• some object wants to reject an update request (itself or or its consequencies)
--]]
function setup()
comment = [[
Composite0 : only top-down messages are possible
The menu below is a toy project including 'bad' cases (multiple branches).
Drag boxes towards bottom or right and watch the result:
moving a box around will move its children only.
The updated parent data are correctly passed through the tree.
Boxes are bounded to the screen but cannot feedback
to their parent, so their relative positions may change.
This composite class is simple but limited.
]]
local a_menu = cMenu( {name="composite", x=100, y=HEIGHT-400, w=150, h=50 } )
world = Composite()
world:insert( a_menu )
end
function draw()
background(40, 40, 50)
strokeWidth(5)
textMode(CENTER)
fontSize(20)
fill(255, 184, 0, 255)
text(comment, WIDTH/2, HEIGHT-150)
world:update()
world:draw()
end
function touched(touch)
world:touched(touch)
end
--# Composite0
-- Comoposite0:
-- the simplest one: it passes top-down messages only, so:
-- end-leaf components cannot be components, if a bottom-up feedback is needed (like a touch)
-- so one must define a self.leaf field and manage it explicitely
-- the sub classes can re-implement:
-- drawBeforeComponents()
-- drawAfterComponents()
-- touchedAfterComponents(touch) (check the details to have touch interception working)
-- updateBeforeComponents(data,cData)
-- updateAfterComponents(data)
-- Top-down communication is managed via the ** interface ** table you define
-- when inserting a new child
Composite = class()
function Composite:init(data)
data = data or {}
self:updateDefaultFields(self.default)
self:updateDefaultFields(data)
self.components = {}
self.interface = {}
self.name = data.name or "?"
end
-- insert/remove components
function Composite:bindComponent(component)
-- set other references pointing to this component
end
function Composite:unbindComponent(component)
-- remove other references pointing to this component
end
function Composite:insert(component,interface)
-- make sure component isnt already here
for _,c in ipairs(self.components) do
if c == component then
error("Trying to insert same component twice")
end
end
-- then insert the component
table.insert(self.components, component)
-- interface is a table of key/key pairs: {componentKey="selfKey", etc.. }
self.interface[component] = interface
self:bindComponent(component)
end
function Composite:remove(component)
-- find and remove
local found = false
for i,c in ipairs(self.components) do
if c == component then
self.interface[component] = nil
table.remove(self.components,i)
self:unbindComponent(component)
found = true
break
end
end
return found
end
-- draw self and components
function Composite:drawComponents()
-- order matters: first ones drawn first
for _,c in ipairs(self.components) do c:draw() end
end
function Composite:draw()
self:drawBeforeComponents()
self:drawComponents()
self:drawAfterComponents()
end
-- your code:
function Composite:drawBeforeComponents() end
function Composite:drawAfterComponents() end
function Composite:touchedComponents(touch)
local touchIntercepted = false
-- order matters: LAST ones touched first
local c = self.components
local n = #self.components
if n>0 then
for i=0,n-1 do
touchIntercepted = c[n-i]:touched(touch)
if touchIntercepted then return touchIntercepted end
end
end
return touchIntercepted
end
function Composite:touched(touch)
local touchIntercepted = false
touchIntercepted = self:touchedComponents(touch)
if touchIntercepted then return touchIntercepted end
touchIntercepted = self:touchedAfterComponents(touch)
return touchIntercepted
end
-- your code:
function Composite:touchedAfterComponents(touch)
local touchIntercepted = false
-- you code here
return touchIntercepted
end
-- this table contains all the data fields of the composite
Composite.default = {}
-- this function makes sure only acceptable fields are copied into self
function Composite:updateDefaultFields(data)
if data then
for key,val in pairs(data) do
local default = self.default[key]
if default~=nil then
if val ~= nil then self[key] = val
elseif self[key] == nil then self[key] = default
end
end
end
end
end
-- this returns the internal fields needed to update the component
function Composite:convert(data,interface)
local cData,val = {}
for key,keyTop in pairs(interface) do
val = data[keyTop]
if val == nil then val = self[keyTop] end
if val == nil then error("key: "..keyTop.." not found") end
cData[key] = val
end
return cData
end
-- update each component
function Composite:updateComponents(cData)
-- order matters: first ones updated first
for _,c in ipairs(self.components) do
local data
if cData then -- only the fields you have defined in the interface are passed
data = self:convert(cData, self.interface[c] or {})
end
c:update(data)
end
end
function Composite:update(data)
self:updateDefaultFields(data) -- relevant fields are copied automatically
local cData = {} -- use this table to pass data to components
self:updateBeforeComponents(data,cData)
self:updateComponents(cData)
self:updateAfterComponents(data)
end
-- your code:
function Composite:updateBeforeComponents(data,cData)
-- use cData table to pass data to components
end
function Composite:updateAfterComponents(data)
end
--# Rect
Rect = class()
function Rect:init(x,y,w,h,txt)
self.x = x or 0
self.y = y or 0
self.w = w or 100
self.h = h or 100
self.txt = txt or "-"
self.xr = self.x + self.w/2
self.yr = self.y + self.h/2
self.wr = self.w/2
self.hr = self.h/2
end
function Rect:draw()
fill(200)
rectMode(CORNER)
rect(self.x,self.y,self.w,self.h)
fill(0)
textMode(CENTER)
text(self.txt,self.xr, self.yr)
end
local abs = math.abs
function Rect:touched(touch)
if abs(self.xr-touch.x)<=self.wr and abs(self.yr-touch.y)<=self.hr then
return true
end
end
--# cItem
cItem = class(Composite)
-- a menu item
function cItem:init(data)
self.leaf = Rect()
Composite.init(self,data)
end
cItem.default = {
name = "?",
xref=100,
yref=150,
dx=0,
dy=-50,
w=100,
h=50,
txt="?",
}
function cItem:boundX()
local ok = true
local val,size,mini,maxi = self.x, self.w, 0, WIDTH
if val < mini then self.x = mini ; ok = false end
if val + size > maxi then self.x = maxi-size ; ok = false end
return ok
end
function cItem:boundY()
local ok = true
local val,size,mini,maxi = self.y, self.h, 0, HEIGHT
if val < mini then self.y = mini ; ok = false end
if val + size > maxi then self.y = maxi-size ; ok = false end
return ok
end
function cItem:setTxt(txt)
self.txt = txt
self.leaf.txt = txt
end
function cItem:updateBeforeComponents(data,cData)
self.x = self.xref + self.dx
self.y = self.yref + self.dy
self:boundX()
self:boundY()
self.leaf:init(self.x,self.y,self.w,self.h,self.txt)
-- component data
cData.x=self.x
cData.y=self.y
end
function cItem:draw()
self.leaf:draw()
self:drawComponents()
end
function cItem:touchedAfterComponents(touch)
local touchIntercepted = false
if self.leaf:touched(touch) then
if touch.state == BEGAN then
elseif touch.state == MOVING then
local data = {}
data.dx = self.dx + touch.deltaX
data.dy = self.dy + touch.deltaY
self:update(data)
touchIntercepted = true
end
end
return touchIntercepted
end
--# cMenu
cMenu = class(Composite)
-- a toy project with all the 'bad' cases (multiple branches)
cMenu.default = {
name = "top",
x=100,
y=500,
w=100,
h=70,
txt="top",
}
function cMenu:init(data)
Composite.init(self,data)
local menu1 = self:createBox(self,0,0,"menu1")
local dy = -self.h-10
local child = self:createBox(menu1,0,dy,"item 1.1")
local child = self:createBox(child,0,dy,"item 1.2")
local menu2 = child
local child = self:createBox(child,0,dy,"item 1.3")
local child = self:createBox(child,0,dy,"item 1.4")
local child = self:createBox(menu2,160,0,"item 1.2.1")
local child = self:createBox(child,50,dy,"item 1.2.2")
local child = self:createBox(child,0,dy,"item 1.2.3")
end
function cMenu:createBox(parent,dx,dy,txt)
local x,y,w,h
x = parent.x
y = parent.y
w = parent.w
h = parent.h
local child = cItem( { dx=dx, dy=dy, xref=x, yref=y, w=w, h=h, txt=txt} )
local topDownInterface = {xref="x", yref="y",}
parent:insert(child, topDownInterface)
return child
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment