Skip to content

Instantly share code, notes, and snippets.

Created June 21, 2015 13:18
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/f5b396dc34f312a27b1a to your computer and use it in GitHub Desktop.
Save anonymous/f5b396dc34f312a27b1a 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 = [[
Composite1 : full top-down messages, and 1 bottom-up message
(update rejection).
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 down through the tree.
Boxes are bounded to the screen and feedback the change rejection
to their parent if needed, so their relative positions remain correct.
This composite class is more complex but still limited: you cannot
pass complex information to the parent.
]]
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
--# Composite1
-- Comoposite1:
-- more complex: it passes top-down messages only, but chidren can refuse the update 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,feedback)
-- updateAfterComponents(data,feedback)
-- Top-down communication is managed via the ** interface ** table you define
-- when inserting a new child
-- update rejection is managed via:
-- - a feedback table passed around
-- - saving the component state before updating
-- - and restoring this state if the update is rejected by a sub component
-- modified functions:
-- update each component
function Composite:updateComponents(data,feedback)
-- order matters: first ones updated first
for _,c in ipairs(self.components) do
local cData
if data then
cData = self:convert(data,self.interface[c] or {})
end
c:update(cData,feedback)
end
end
-- update fonctions, to be overwritten by each sub class
function Composite:updateBeforeComponents(data,cData,feedback) end
function Composite:updateAfterComponents(data,feedback) end
function Composite:update(data, feedback)
local feedback = feedback or {}
self:saveState()
self:updateDefaultFields(data) -- relevant fields are copied automatically
local cData = {}
self:updateBeforeComponents( data, cData,feedback)
self:updateComponents(cData,feedback)
self:updateAfterComponents( data, feedback )
if self:updateRefused(feedback) then
self:restoreState()
end
end
-- new functions:
function Composite:refuseUpdate(data)
data.updateRefused = true
end
function Composite:updateRefused(data)
return data.updateRefused
end
function Composite:saveState()
self.clone = self.clone or {}
for key,val in pairs(self.default) do
self.clone[key] = self[key]
end
end
function Composite:restoreState()
for key,val in pairs(self.default) do
self[key] = self.clone[key]
end
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)
self:update(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,feedback)
self.x = self.xref + self.dx
self.y = self.yref + self.dy
local ok = true
ok = ok and self:boundX()
ok = ok and self:boundY()
if not ok then
self:refuseUpdate(feedback)
end
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