446 lines
14 KiB
JavaScript
Executable File
446 lines
14 KiB
JavaScript
Executable File
import { logger, Direction, EventType } from './jsmind.common.js'
|
|
|
|
export class LayoutProvider {
|
|
constructor(jm, options) {
|
|
this.opts = options
|
|
this.jm = jm
|
|
this.isside = this.opts.mode == 'side'
|
|
this.bounds = null
|
|
|
|
this.cache_valid = false
|
|
}
|
|
init() {
|
|
logger.debug('layout.init')
|
|
}
|
|
reset() {
|
|
logger.debug('layout.reset')
|
|
this.bounds = { n: 0, s: 0, w: 0, e: 0 }
|
|
}
|
|
calculate_next_child_direction(node) {
|
|
if (this.isside) {
|
|
return Direction.right
|
|
}
|
|
var children = node.children || []
|
|
var children_len = children.length
|
|
var r = 0
|
|
for (var i = 0; i < children_len; i++) {
|
|
if (children[i].direction === Direction.left) {
|
|
r--
|
|
} else {
|
|
r++
|
|
}
|
|
}
|
|
return children_len > 1 && r > 0 ? Direction.left : Direction.right
|
|
}
|
|
layout() {
|
|
logger.debug('layout.layout')
|
|
this.layout_direction()
|
|
this.layout_offset()
|
|
}
|
|
layout_direction() {
|
|
this._layout_direction_root()
|
|
}
|
|
_layout_direction_root() {
|
|
var node = this.jm.mind.root
|
|
var layout_data = null
|
|
if ('layout' in node._data) {
|
|
layout_data = node._data.layout
|
|
} else {
|
|
layout_data = {}
|
|
node._data.layout = layout_data
|
|
}
|
|
var children = node.children
|
|
var children_count = children.length
|
|
layout_data.direction = Direction.center
|
|
layout_data.side_index = 0
|
|
if (this.isside) {
|
|
var i = children_count
|
|
while (i--) {
|
|
this._layout_direction_side(children[i], Direction.right, i)
|
|
}
|
|
} else {
|
|
var i = children_count
|
|
var subnode = null
|
|
while (i--) {
|
|
subnode = children[i]
|
|
if (subnode.direction == Direction.left) {
|
|
this._layout_direction_side(subnode, Direction.left, i)
|
|
} else {
|
|
this._layout_direction_side(subnode, Direction.right, i)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_layout_direction_side(node, direction, side_index) {
|
|
var layout_data = null
|
|
if ('layout' in node._data) {
|
|
layout_data = node._data.layout
|
|
} else {
|
|
layout_data = {}
|
|
node._data.layout = layout_data
|
|
}
|
|
var children = node.children
|
|
var children_count = children.length
|
|
|
|
layout_data.direction = direction
|
|
layout_data.side_index = side_index
|
|
var i = children_count
|
|
while (i--) {
|
|
this._layout_direction_side(children[i], direction, i)
|
|
}
|
|
}
|
|
layout_offset() {
|
|
var node = this.jm.mind.root
|
|
var layout_data = node._data.layout
|
|
layout_data.offset_x = 0
|
|
layout_data.offset_y = 0
|
|
layout_data.outer_height = 0
|
|
var children = node.children
|
|
var i = children.length
|
|
var left_nodes = []
|
|
var right_nodes = []
|
|
var subnode = null
|
|
while (i--) {
|
|
subnode = children[i]
|
|
if (subnode._data.layout.direction == Direction.right) {
|
|
right_nodes.unshift(subnode)
|
|
} else {
|
|
left_nodes.unshift(subnode)
|
|
}
|
|
}
|
|
layout_data.left_nodes = left_nodes
|
|
layout_data.right_nodes = right_nodes
|
|
layout_data.outer_height_left = this._layout_offset_subnodes(left_nodes)
|
|
layout_data.outer_height_right = this._layout_offset_subnodes(right_nodes)
|
|
this.bounds.e = node._data.view.width / 2
|
|
this.bounds.w = 0 - this.bounds.e
|
|
this.bounds.n = 0
|
|
this.bounds.s = Math.max(layout_data.outer_height_left, layout_data.outer_height_right)
|
|
}
|
|
// layout both the x and y axis
|
|
_layout_offset_subnodes(nodes) {
|
|
var total_height = 0
|
|
var nodes_count = nodes.length
|
|
var i = nodes_count
|
|
var node = null
|
|
var node_outer_height = 0
|
|
var layout_data = null
|
|
var base_y = 0
|
|
var pd = null // parent._data
|
|
while (i--) {
|
|
node = nodes[i]
|
|
layout_data = node._data.layout
|
|
if (pd == null) {
|
|
pd = node.parent._data
|
|
}
|
|
|
|
node_outer_height = this._layout_offset_subnodes(node.children)
|
|
if (!node.expanded) {
|
|
node_outer_height = 0
|
|
this.set_visible(node.children, false)
|
|
}
|
|
node_outer_height = Math.max(node._data.view.height, node_outer_height)
|
|
if (node.children.length > 1) {
|
|
node_outer_height += this.opts.cousin_space
|
|
}
|
|
|
|
layout_data.outer_height = node_outer_height
|
|
layout_data.offset_y = base_y - node_outer_height / 2
|
|
layout_data.offset_x =
|
|
this.opts.hspace * layout_data.direction +
|
|
(pd.view.width * (pd.layout.direction + layout_data.direction)) / 2
|
|
if (!node.parent.isroot) {
|
|
layout_data.offset_x += this.opts.pspace * layout_data.direction
|
|
}
|
|
|
|
base_y = base_y - node_outer_height - this.opts.vspace
|
|
total_height += node_outer_height
|
|
}
|
|
if (nodes_count > 1) {
|
|
total_height += this.opts.vspace * (nodes_count - 1)
|
|
}
|
|
i = nodes_count
|
|
var middle_height = total_height / 2
|
|
while (i--) {
|
|
node = nodes[i]
|
|
node._data.layout.offset_y += middle_height
|
|
}
|
|
return total_height
|
|
}
|
|
// layout the y axis only, for collapse/expand a node
|
|
_layout_offset_subnodes_height(nodes) {
|
|
var total_height = 0
|
|
var nodes_count = nodes.length
|
|
var i = nodes_count
|
|
var node = null
|
|
var node_outer_height = 0
|
|
var layout_data = null
|
|
var base_y = 0
|
|
var pd = null // parent._data
|
|
while (i--) {
|
|
node = nodes[i]
|
|
layout_data = node._data.layout
|
|
if (pd == null) {
|
|
pd = node.parent._data
|
|
}
|
|
|
|
node_outer_height = this._layout_offset_subnodes_height(node.children)
|
|
if (!node.expanded) {
|
|
node_outer_height = 0
|
|
}
|
|
node_outer_height = Math.max(node._data.view.height, node_outer_height)
|
|
if (node.children.length > 1) {
|
|
node_outer_height += this.opts.cousin_space
|
|
}
|
|
|
|
layout_data.outer_height = node_outer_height
|
|
layout_data.offset_y = base_y - node_outer_height / 2
|
|
base_y = base_y - node_outer_height - this.opts.vspace
|
|
total_height += node_outer_height
|
|
}
|
|
if (nodes_count > 1) {
|
|
total_height += this.opts.vspace * (nodes_count - 1)
|
|
}
|
|
i = nodes_count
|
|
var middle_height = total_height / 2
|
|
while (i--) {
|
|
node = nodes[i]
|
|
node._data.layout.offset_y += middle_height
|
|
}
|
|
return total_height
|
|
}
|
|
get_node_offset(node) {
|
|
var layout_data = node._data.layout
|
|
var offset_cache = null
|
|
if ('_offset_' in layout_data && this.cache_valid) {
|
|
offset_cache = layout_data._offset_
|
|
} else {
|
|
offset_cache = { x: -1, y: -1 }
|
|
layout_data._offset_ = offset_cache
|
|
}
|
|
if (offset_cache.x == -1 || offset_cache.y == -1) {
|
|
var x = layout_data.offset_x
|
|
var y = layout_data.offset_y
|
|
if (!node.isroot) {
|
|
var offset_p = this.get_node_offset(node.parent)
|
|
x += offset_p.x
|
|
y += offset_p.y
|
|
}
|
|
offset_cache.x = x
|
|
offset_cache.y = y
|
|
}
|
|
return offset_cache
|
|
}
|
|
get_node_point(node) {
|
|
var view_data = node._data.view
|
|
var offset_p = this.get_node_offset(node)
|
|
var p = {}
|
|
p.x = offset_p.x + (view_data.width * (node._data.layout.direction - 1)) / 2
|
|
p.y = offset_p.y - view_data.height / 2
|
|
return p
|
|
}
|
|
get_node_point_in(node) {
|
|
var p = this.get_node_offset(node)
|
|
return p
|
|
}
|
|
get_node_point_out(node) {
|
|
var layout_data = node._data.layout
|
|
var pout_cache = null
|
|
if ('_pout_' in layout_data && this.cache_valid) {
|
|
pout_cache = layout_data._pout_
|
|
} else {
|
|
pout_cache = { x: -1, y: -1 }
|
|
layout_data._pout_ = pout_cache
|
|
}
|
|
if (pout_cache.x == -1 || pout_cache.y == -1) {
|
|
if (node.isroot) {
|
|
pout_cache.x = 0
|
|
pout_cache.y = 0
|
|
} else {
|
|
var view_data = node._data.view
|
|
var offset_p = this.get_node_offset(node)
|
|
pout_cache.x =
|
|
offset_p.x + (view_data.width + this.opts.pspace) * node._data.layout.direction
|
|
pout_cache.y = offset_p.y
|
|
}
|
|
}
|
|
return pout_cache
|
|
}
|
|
get_expander_point(node) {
|
|
var p = this.get_node_point_out(node)
|
|
var ex_p = {}
|
|
if (node._data.layout.direction == Direction.right) {
|
|
ex_p.x = p.x - this.opts.pspace
|
|
} else {
|
|
ex_p.x = p.x
|
|
}
|
|
ex_p.y = p.y - Math.ceil(this.opts.pspace / 2)
|
|
return ex_p
|
|
}
|
|
get_min_size() {
|
|
var nodes = this.jm.mind.nodes
|
|
var node = null
|
|
var pout = null
|
|
for (var node_id in nodes) {
|
|
node = nodes[node_id]
|
|
pout = this.get_node_point_out(node)
|
|
if (pout.x > this.bounds.e) {
|
|
this.bounds.e = pout.x
|
|
}
|
|
if (pout.x < this.bounds.w) {
|
|
this.bounds.w = pout.x
|
|
}
|
|
}
|
|
return {
|
|
w: this.bounds.e - this.bounds.w,
|
|
h: this.bounds.s - this.bounds.n,
|
|
}
|
|
}
|
|
toggle_node(node) {
|
|
if (node.isroot) {
|
|
return
|
|
}
|
|
if (node.expanded) {
|
|
this.collapse_node(node)
|
|
} else {
|
|
this.expand_node(node)
|
|
}
|
|
}
|
|
expand_node(node) {
|
|
node.expanded = true
|
|
this.part_layout(node)
|
|
this.set_visible(node.children, true)
|
|
this.jm.invoke_event_handle(EventType.show, {
|
|
evt: 'expand_node',
|
|
data: [],
|
|
node: node.id,
|
|
})
|
|
}
|
|
collapse_node(node) {
|
|
node.expanded = false
|
|
this.part_layout(node)
|
|
this.set_visible(node.children, false)
|
|
this.jm.invoke_event_handle(EventType.show, {
|
|
evt: 'collapse_node',
|
|
data: [],
|
|
node: node.id,
|
|
})
|
|
}
|
|
expand_all() {
|
|
var nodes = this.jm.mind.nodes
|
|
var c = 0
|
|
var node
|
|
for (var node_id in nodes) {
|
|
node = nodes[node_id]
|
|
if (!node.expanded) {
|
|
node.expanded = true
|
|
c++
|
|
}
|
|
}
|
|
if (c > 0) {
|
|
var root = this.jm.mind.root
|
|
this.part_layout(root)
|
|
this.set_visible(root.children, true)
|
|
}
|
|
}
|
|
collapse_all() {
|
|
var nodes = this.jm.mind.nodes
|
|
var c = 0
|
|
var node
|
|
for (var node_id in nodes) {
|
|
node = nodes[node_id]
|
|
if (node.expanded && !node.isroot) {
|
|
node.expanded = false
|
|
c++
|
|
}
|
|
}
|
|
if (c > 0) {
|
|
var root = this.jm.mind.root
|
|
this.part_layout(root)
|
|
this.set_visible(root.children, true)
|
|
}
|
|
}
|
|
expand_to_depth(target_depth, curr_nodes, curr_depth) {
|
|
if (target_depth < 1) {
|
|
return
|
|
}
|
|
var nodes = curr_nodes || this.jm.mind.root.children
|
|
var depth = curr_depth || 1
|
|
var i = nodes.length
|
|
var node = null
|
|
while (i--) {
|
|
node = nodes[i]
|
|
if (depth < target_depth) {
|
|
if (!node.expanded) {
|
|
this.expand_node(node)
|
|
}
|
|
this.expand_to_depth(target_depth, node.children, depth + 1)
|
|
}
|
|
if (depth == target_depth) {
|
|
if (node.expanded) {
|
|
this.collapse_node(node)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
part_layout(node) {
|
|
var root = this.jm.mind.root
|
|
if (!!root) {
|
|
var root_layout_data = root._data.layout
|
|
if (node.isroot) {
|
|
root_layout_data.outer_height_right = this._layout_offset_subnodes_height(
|
|
root_layout_data.right_nodes
|
|
)
|
|
root_layout_data.outer_height_left = this._layout_offset_subnodes_height(
|
|
root_layout_data.left_nodes
|
|
)
|
|
} else {
|
|
if (node._data.layout.direction == Direction.right) {
|
|
root_layout_data.outer_height_right = this._layout_offset_subnodes_height(
|
|
root_layout_data.right_nodes
|
|
)
|
|
} else {
|
|
root_layout_data.outer_height_left = this._layout_offset_subnodes_height(
|
|
root_layout_data.left_nodes
|
|
)
|
|
}
|
|
}
|
|
this.bounds.s = Math.max(
|
|
root_layout_data.outer_height_left,
|
|
root_layout_data.outer_height_right
|
|
)
|
|
this.cache_valid = false
|
|
} else {
|
|
logger.warn('can not found root node')
|
|
}
|
|
}
|
|
set_visible(nodes, visible) {
|
|
var i = nodes.length
|
|
var node = null
|
|
var layout_data = null
|
|
while (i--) {
|
|
node = nodes[i]
|
|
layout_data = node._data.layout
|
|
if (node.expanded) {
|
|
this.set_visible(node.children, visible)
|
|
} else {
|
|
this.set_visible(node.children, false)
|
|
}
|
|
if (!node.isroot) {
|
|
node._data.layout.visible = visible
|
|
}
|
|
}
|
|
}
|
|
is_expand(node) {
|
|
return node.expanded
|
|
}
|
|
is_visible(node) {
|
|
var layout_data = node._data.layout
|
|
if ('visible' in layout_data && !layout_data.visible) {
|
|
return false
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
}
|