// clay_input.cm - Input handling for clay UI // Separates input concerns from layout/rendering var geometry = use('geometry') var clay = use('clay') var clay_input = {} function rect_contains(node, pos) { var bb = geometry.rect_move(node.boundingbox, node.config.offset || {x: 0, y: 0}) return geometry.rect_point_inside(bb, pos) } function pointer_enabled(node) { var p = node.config.pointer_events if (!p || p == 'auto') return true if (p == 'none') return false return true } function should_skip_children(node) { var p = node.config.pointer_events if (p == 'box-only') return true return false } function should_skip_self(node) { var p = node.config.pointer_events if (p == 'box-none') return true return false } function find_path(node, path, pos) { if (!pointer_enabled(node)) return null if (!rect_contains(node, pos)) return null var next_path = array(path, node) if (node[clay.CHILDREN] && !should_skip_children(node)) { // Children drawn later should be tested first; reverse if your render order differs for (var i = length(node[clay.CHILDREN]) - 1; i >= 0; i--) { var child = node[clay.CHILDREN][i] var child_path = find_path(child, next_path, pos) if (child_path) return child_path } } if (should_skip_self(node)) return null return next_path } clay_input.deepest = function deepest(tree_root, pos) { var path = find_path(tree_root, [], pos) || [] var deepest = length(path) ? path[length(path) - 1] : null return deepest } clay_input.bubble = function bubble(deepest, prop) { var current = deepest while (current) { if (current.config && current.config[prop]) return current current = current[clay.PARENT] } return null } clay_input.click = function click(tree_root, mousepos, button = 'left') { var deepest = clay_input.deepest(tree_root, mousepos) var action_target = clay_input.bubble(deepest, 'action') if (action_target && action_target.config.action) action_target.config.action() } clay_input.get_actionable = function get_actionable(tree_root) { var actionable = [] function walk(node) { if (node.config.action) actionable.push(node) if (node[clay.CHILDREN]) arrfor(node[clay.CHILDREN], walk) } walk(tree_root) return actionable } clay_input.filter = function filter(tree_root, predicate) { var results = [] function rec(node) { if (predicate(node)) results.push(node) if (node[clay.CHILDREN]) arrfor(node[clay.CHILDREN], rec) } rec(tree_root) return results } clay_input.find_by_id = function find_by_id(tree_root, id) { function rec(node) { if (node.id == id) return node if (node[clay.CHILDREN]) arrfor(node[clay.CHILDREN], function(child) { var f = rec(child) if (f) return f }) return null } return rec(tree_root) } return clay_input