148 lines
3.9 KiB
Plaintext
148 lines
3.9 KiB
Plaintext
// clay_input.cm - Input handling for clay UI
|
|
// Separates input concerns from layout/rendering
|
|
|
|
var geometry = use('geometry')
|
|
var point = use('point')
|
|
var clay = use('prosperon/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
|
|
}
|
|
|
|
clay_input.hit = function hit(tree_root, pos, prev_state = {}) {
|
|
function find_path(node, path) {
|
|
if (!pointer_enabled(node)) return null
|
|
if (!rect_contains(node, pos)) return null
|
|
|
|
var next_path = path.concat(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 = node[clay.CHILDREN].length - 1; i >= 0; i--) {
|
|
var child = node[clay.CHILDREN][i]
|
|
var child_path = find_path(child, next_path)
|
|
if (child_path) return child_path
|
|
}
|
|
}
|
|
|
|
if (should_skip_self(node)) return null
|
|
return next_path
|
|
}
|
|
|
|
var path = find_path(tree_root, []) || []
|
|
var deepest = path.length ? path[path.length - 1] : null
|
|
|
|
function nearest_actionable(path) {
|
|
for (var i = path.length - 1; i >= 0; i--) {
|
|
var n = path[i]
|
|
if (n && n.config && typeof n.config.action == 'function') return n
|
|
}
|
|
return null
|
|
}
|
|
|
|
var action_target = nearest_actionable(path)
|
|
|
|
// Hover bookkeeping: let child hover, and optionally parent hover via hover_includes_children
|
|
var new_hover_chain = []
|
|
if (deepest) {
|
|
if (deepest.config && deepest.config.hovered) {
|
|
deepest.state = deepest.state || {}
|
|
deepest.state.hovered = true
|
|
new_hover_chain.push(deepest)
|
|
}
|
|
|
|
for (var i = path.length - 2; i >= 0; i--) {
|
|
var anc = path[i]
|
|
if (anc.config && anc.config.hovered) {
|
|
anc.state = anc.state || {}
|
|
anc.state.hovered = true
|
|
new_hover_chain.push(anc)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear previous hovers not in the new chain
|
|
if (prev_state.hover_chain) {
|
|
for (var i = 0; i < prev_state.hover_chain.length; i++) {
|
|
var n = prev_state.hover_chain[i]
|
|
if (new_hover_chain.indexOf(n) == -1) {
|
|
n.state = n.state || {}
|
|
n.state.hovered = false
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
path: path,
|
|
deepest: deepest,
|
|
action_target: action_target,
|
|
hover_chain: new_hover_chain
|
|
}
|
|
}
|
|
|
|
clay_input.click = function click(tree_root, mousepos, button = 'left') {
|
|
var hit_result = clay_input.hit(tree_root, mousepos, {})
|
|
var target = hit_result.action_target
|
|
if (target && target.config.action) target.config.action()
|
|
return target || null
|
|
}
|
|
|
|
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])
|
|
for (var child of node[clay.CHILDREN]) walk(child)
|
|
}
|
|
walk(tree_root)
|
|
return actionable
|
|
}
|
|
|
|
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])
|
|
for (var child of node[clay.CHILDREN]) {
|
|
var f = rec(child)
|
|
if (f) return f
|
|
}
|
|
return null
|
|
}
|
|
return rec(tree_root)
|
|
}
|
|
|
|
clay_input.filter = function filter(tree_root, predicate) {
|
|
var results = []
|
|
function rec(node) {
|
|
if (predicate(node)) results.push(node)
|
|
if (node[clay.CHILDREN])
|
|
for (var child of node[clay.CHILDREN]) rec(child)
|
|
}
|
|
rec(tree_root)
|
|
return results
|
|
}
|
|
|
|
return clay_input
|