signage: bring in from external repo

This is b28e6f07aa48f1e2f01eb37bffa180f97a7b03bd from
https://code.hackerspace.pl/q3k/love2d-signage/. We only keep code
commited by inf and q3k, and we're both now licensing this code under
the ISC license, as per COPYING in the root of hscloud.

Change-Id: Ibeee2e6923605e4b1a17a1d295867c056863ef59
Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1335
Reviewed-by: informatic <informatic@hackerspace.pl>
Reviewed-by: q3k <q3k@hackerspace.pl>
diff --git a/hswaw/signage/core/node-manager.lua b/hswaw/signage/core/node-manager.lua
new file mode 100644
index 0000000..69af96b
--- /dev/null
+++ b/hswaw/signage/core/node-manager.lua
@@ -0,0 +1,114 @@
+local NodeManager = class('NodeManager', {
+  state = 'running',
+  stateTime = 0,
+  currentNode = nil,
+})
+
+function NodeManager:init(config)
+  self.config = config
+  self.nodes = {}
+  self.nodeConfs = {}
+
+  self.currentNode = nil
+
+  print('Initializing NodeManager')
+end
+
+function NodeManager:load()
+  self:configChanged()
+end
+
+function NodeManager:resize(w, h)
+  self.secondaryCanvas = love.graphics.newCanvas(w, h)
+end
+
+function NodeManager:configChanged()
+  local cnt = {}
+  local newNodes = {}
+  local newNodeConfs = {}
+
+  for _, c_ in ipairs(self.config.nodes) do
+    local nodeConfig = lume.clone(c_)
+    local hash = inspect(nodeConfig)
+    if cnt[hash] == nil then cnt[hash] = 0 end
+    cnt[hash] = cnt[hash] + 1
+    hash = hash .. '-' .. tostring(cnt[hash])
+
+    local nodeName = nodeConfig[1]
+    table.remove(nodeConfig, 1)
+
+    if self.nodeConfs[hash] then
+      print('Using existing node:', self.nodeConfs[hash], hash)
+      newNodes[#newNodes + 1] = self.nodeConfs[hash]
+    else
+      print('Creating new node.', nodeName, inspect(nodeConfig))
+      local status, err = pcall(function()
+        newNodes[#newNodes + 1] = require(nodeName)(nodeConfig)
+      end)
+      if err then
+        print("Error occured while loading", nodeName, err)
+        return
+      end
+    end
+
+    newNodeConfs[hash] = newNodes[#newNodes]
+  end
+
+  self.nodes = newNodes
+  self.nodeConfs = newNodeConfs
+end
+
+function NodeManager:render()
+  if not self.currentNode then self.currentNode = self.nodes[1] end
+  if not self.currentNode then return end
+
+  -- love.graphics.print('state: ' .. self.state .. '; counter: ' .. tostring(self.stateTime), 50, 50)
+
+  self.currentNode:render()
+
+  if self.state == 'transitioning' and self.currentNode ~= self.nextNode and self.nextNode then
+    self.secondaryCanvas:renderTo(function()
+      self.nextNode:render()
+    end)
+    love.graphics.setColor(1.0, 1.0, 1.0, 1.0 * (self.stateTime / self.config.transitionTime))
+    love.graphics.draw(self.secondaryCanvas, 0, 0)
+  end
+
+  love.graphics.setColor(1.0, 1.0, 1.0, 0.3)
+
+  if self.config.showProgress and self.state == 'running' then
+    local stateTime
+    stateTime = self.currentNode.displayTime or self.config.displayTime
+    local h = 5
+    love.graphics.rectangle("fill", 0, love.graphics.getHeight() - h, (self.stateTime / stateTime) * love.graphics.getWidth(), h)
+  end
+end
+
+function NodeManager:update(dt)
+  if not self.currentNode then self.currentNode = self.nodes[1] end
+  if not self.currentNode then return end
+
+  self.stateTime = self.stateTime + dt
+
+  if self.state == 'transitioning' and self.stateTime >= self.config.transitionTime then
+    self.stateTime = 0
+    self.state = 'running'
+    self.currentNode:afterExit()
+    -- self.currentNode, self.nextNode = self.nextNode, self.nodes[(lume.find(self.nodes, self.nextNode) or 1) % #self.nodes + 1]
+    self.currentNode = self.nextNode
+    self.currentNode:afterEnter()
+  elseif self.state == 'running' and self.stateTime >= (self.currentNode.displayTime or self.config.displayTime) then
+    self.stateTime = 0
+    self.state = 'transitioning'
+    self.currentNode:beforeExit()
+    self.nextNode = self.nodes[(lume.find(self.nodes, self.currentNode) or 1) % #self.nodes + 1]
+    self.nextNode:beforeEnter()
+  end
+
+  self.currentNode:update(dt)
+  if self.state == 'transitioning' and self.currentNode ~= self.nextNode and self.nextNode then
+    self.nextNode:update(dt)
+  end
+end
+
+return NodeManager