diff --git a/.travis.yml b/.travis.yml index da64fef..2f961f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,3 +20,7 @@ install: script: - busted + +branches: + only: + - master diff --git a/scene.lua b/scene.lua index dc23ffb..a9ff374 100644 --- a/scene.lua +++ b/scene.lua @@ -12,6 +12,12 @@ local function min_bounds(x, y, half_width, half_height) return x - half_width, y - half_height end +local function insert_all(src, dest) + for _, value in ipairs(src) do + table.insert(dest, value) + end +end + --[[ A node representing a local transform. --]] @@ -36,6 +42,7 @@ function node:init(params) self.halfw = (params and params.halfw) or 0 self.halfh = (params and params.halfh) or 0 self._id = (params and params.id) or nil + self.listeners = {} end -- Adds id to id tables of all ancestors @@ -187,6 +194,83 @@ function node:calc_bounds() end end +function node:on(event_type, listener) + self.listeners[event_type] = self.listeners[event_type] or {} + table.insert(self.listeners[event_type], listener) + return listener +end + +function node:off(event_type, listener) + if listener == nil then + self.listeners[event_type] = nil + else + local pos = nil + for i, value in ipairs(self.listeners[event_type]) do + if value == listener then + pos = i + break + end + end + + if pos ~= nil then + table.remove(self.listeners[event_type], pos) + end + end +end + +-- Sends event to current node only +function node:fire(event_type, ...) + for _, listener in ipairs(self.listeners[event_type]) do + local stopLocal, stopGlobal = listener(...) + if stopGlobal then + return false + elseif stopLocal then + break + end + end + + return true +end + +-- Sends event to root +function node:emit(event_type, ...) + if not self:fire(event_type, ...) then + return + end + + if self.parent then + self.parent:emit(event_type, ...) + end +end + +-- Sends event to descendents +function node:broadcast(event_type, ...) + if not self:fire(event_type, ...) then + return + end + + if not self.children then + return + end + + -- Breadth first traversal of scene tree + local queue = {} + local qpos = 1 + insert_all(self.children, queue) + while qpos <= #queue do + local next = queue[qpos] + if not next:fire(event_type, ...) then + return + end + + if next.children then + insert_all(next.children, queue) + end + + qpos = qpos + 1 + end +end + scene.node = node ----------------------------------------------- diff --git a/spec/event_spec.lua b/spec/event_spec.lua new file mode 100644 index 0000000..b423c02 --- /dev/null +++ b/spec/event_spec.lua @@ -0,0 +1,74 @@ +local scene = require('scene') + +local describe = require('busted').describe +local it = require('busted').it + +describe("event listeners", function() + it("can be attached to nodes", function() + local node = scene.node() + node:on("test", function(obj) + obj.test_value = true + end) + + local event = {test_value=false} + node:fire("test", event) + assert.is_true(event.test_value) + end) + + it("can be removed", function() + local node = scene.node() + local listener = node:on("test", function() + print("will be removed") + end) + + assert.is_equal(#node.listeners["test"], 1) + node:off("test", listener) + assert.is_equal(#node.listeners["test"], 0) + end) + + it("can be emitted", function() + local node1 = scene.node() + local node2 = scene.node() + local node3 = scene.node() + + node1:attach(node2) + node2:attach(node3) + + local listener = node1:on("test", function(obj) + obj.test_value = obj.test_value + 1 + end) + node2:on("test", listener) + node3:on("test", listener) + + local test_obj = {test_value=0} + node3:emit("test", test_obj) + + assert.is_equal(test_obj.test_value, 3) + end) + + it("can be broadcasted", function() + local node1 = scene.node{id="1"} + local node2 = scene.node{id="2"} + local node3 = scene.node{id="3"} + + node1:attach(node2) + node1:attach(node3) + + node1:on("test", function(obj) + table.insert(obj.received, node1) + end) + node2:on("test", function(obj) + table.insert(obj.received, node2) + end) + node3:on("test", function(obj) + table.insert(obj.received, node3) + end) + + local test_obj = {received={}} + node1:broadcast("test", test_obj) + + assert.is_equal(test_obj.received[1], node1) + assert.is_equal(test_obj.received[2], node2) + assert.is_equal(test_obj.received[3], node3) + end) +end)