neft
Version:
Universal Platform
488 lines (421 loc) • 15.1 kB
text/coffeescript
'use strict'
assert = require 'src/assert'
log = require 'src/log'
utils = require 'src/utils'
eventLoop = require 'src/eventLoop'
log = log.scope 'Renderer', 'Anchors'
{isArray} = Array
module.exports = (impl) ->
GET_ZERO = -> 0
MAX_LOOPS = 10
queueIndex = 0
queues = [[], []]
queue = queues[queueIndex]
pending = false
updateItems = ->
pending = false
currentQueue = queue
queue = queues[++queueIndex % queues.length]
while currentQueue.length
anchor = currentQueue.pop()
anchor.pending = false
anchor.update()
return
update = ->
if
return
= true
queue.push @
unless pending
pending = true
eventLoop.setImmediate updateItems
return
getItemProp =
left: 'x'
top: 'y'
right: 'x'
bottom: 'y'
horizontalCenter: 'x'
verticalCenter: 'y'
fillWidthSize: 'width'
fillHeightSize: 'height'
getSourceWatchProps =
left: ['onMarginChange']
top: ['onMarginChange']
right: ['onMarginChange', 'onWidthChange']
bottom: ['onMarginChange', 'onHeightChange']
horizontalCenter: ['onMarginChange', 'onWidthChange']
verticalCenter: ['onMarginChange', 'onHeightChange']
fillWidthSize: ['onMarginChange']
fillHeightSize: ['onMarginChange']
getTargetWatchProps =
left:
parent: []
children: []
sibling: ['onXChange']
top:
parent: []
children: []
sibling: ['onYChange']
right:
parent: ['onWidthChange']
sibling: ['onXChange', 'onWidthChange']
bottom:
parent: ['onHeightChange']
sibling: ['onYChange', 'onHeightChange']
horizontalCenter:
parent: ['onWidthChange']
sibling: ['onXChange', 'onWidthChange']
verticalCenter:
parent: ['onHeightChange']
sibling: ['onYChange', 'onHeightChange']
fillWidthSize:
parent: ['onWidthChange']
children: []
sibling: ['onWidthChange']
fillHeightSize:
parent: ['onHeightChange']
children: []
sibling: ['onHeightChange']
getSourceValue =
left: (item) ->
0
top: (item) ->
0
right: (item) ->
-item._width
bottom: (item) ->
-item._height
horizontalCenter: (item) ->
-item._width / 2
verticalCenter: (item) ->
-item._height / 2
fillWidthSize: (item) ->
0
fillHeightSize: (item) ->
0
getTargetValue =
left:
parent: (target) ->
0
children: (target) ->
0
sibling: (target) ->
target._x
top:
parent: (target) ->
0
children: (target) ->
0
sibling: (target) ->
target._y
right:
parent: (target) ->
target._width
sibling: (target) ->
target._x + target._width
bottom:
parent: (target) ->
target._height
sibling: (target) ->
target._y + target._height
horizontalCenter:
parent: (target) ->
target._width / 2
sibling: (target) ->
target._x + target._width / 2
verticalCenter:
parent: (target) ->
target._height / 2
sibling: (target) ->
target._y + target._height / 2
fillWidthSize:
parent: (target) ->
target._width
children: (target) ->
tmp = 0
size = 0
child = target.firstChild
while child
if child._visible
tmp = child._x + child._width
if tmp > size
size = tmp
child = child.nextSibling
size
sibling: (target) ->
target._width
fillHeightSize:
parent: (target) ->
target._height
children: (target) ->
tmp = 0
size = 0
child = target.firstChild
while child
if child._visible
tmp = child._y + child._height
if tmp > size
size = tmp
child = child.nextSibling
size
sibling: (target) ->
target._height
getMarginValue =
left: (margin) ->
margin._left
top: (margin) ->
margin._top
right: (margin) ->
-margin._right
bottom: (margin) ->
-margin._bottom
horizontalCenter: (margin) ->
margin._left - margin._right
verticalCenter: (margin) ->
margin._top - margin._bottom
fillWidthSize: (margin) ->
-margin._left - margin._right
fillHeightSize: (margin) ->
-margin._top - margin._bottom
onParentChange = (oldVal) ->
if oldVal
for handler in getTargetWatchProps[].parent
oldVal[handler].disconnect update, @
if val = = ._parent
for handler in getTargetWatchProps[].parent
val[handler] update, @
update.call @
return
onNextSiblingChange = (oldVal) ->
if oldVal
for handler in getTargetWatchProps[].sibling
oldVal[handler].disconnect update, @
if val = = ._nextSibling
for handler in getTargetWatchProps[].sibling
val[handler] update, @
update.call @
return
onPreviousSiblingChange = (oldVal) ->
if oldVal
for handler in getTargetWatchProps[].sibling
oldVal[handler].disconnect update, @
if val = = ._previousSibling
for handler in getTargetWatchProps[].sibling
val[handler] update, @
update.call @
return
onChildInsert = (child) ->
child.onVisibleChange update, @
if is 'fillWidthSize'
child.onXChange update, @
child.onWidthChange update, @
if is 'fillHeightSize'
child.onYChange update, @
child.onHeightChange update, @
update.call @
return
onChildPop = (child) ->
child.onVisibleChange.disconnect update, @
if is 'fillWidthSize'
child.onXChange.disconnect update, @
child.onWidthChange.disconnect update, @
if is 'fillHeightSize'
child.onYChange.disconnect update, @
child.onHeightChange.disconnect update, @
update.call @
return
onChildrenChange = (added, removed) ->
if added
onChildInsert.call @, added
if removed
onChildPop.call @, removed
class Anchor
pool = []
= (item, source, def) ->
if pool.length > 0 and (elem = pool.pop())
Anchor.call elem, item, source, def
elem
else
new Anchor item, source, def
constructor: (, , def) ->
item =
source =
[target, line] = def
line ?= source
= target
= line
= false
= 0
if target is 'parent' or item._parent is target
= 'parent'
else if target is 'children'
= 'children'
else
= 'sibling'
for handler in getSourceWatchProps[source]
item[handler] update, @
= getItemProp[source]
= getSourceValue[source]
= getTargetValue[line][]
= null
Object.seal @
if typeof isnt 'function'
= GET_ZERO
log.error "Unknown anchor `#{@}` given"
switch target
when 'parent'
= item._parent
item.onParentChange onParentChange, @
onParentChange.call @, null
when 'children'
= item.children
item.onChildrenChange onChildrenChange, @
child = .firstChild
while child
onChildInsert.call @, child
child = child.nextSibling
when 'nextSibling'
= item._nextSibling
item.onNextSiblingChange onNextSiblingChange, @
onNextSiblingChange.call @, null
when 'previousSibling'
= item._previousSibling
item.onPreviousSiblingChange onPreviousSiblingChange, @
onPreviousSiblingChange.call @, null
else
if not utils.isObject(target) or handler not of target
log.error "Unknown anchor `#{@}` given"
return
if = target
for handler in getTargetWatchProps[line][]
[handler] update, @
update.call @
update: ->
# sometimes it can be already destroyed
if not or >= MAX_LOOPS
return
# targetItem can be possibly different than actual value;
# e.g. when Anchor listener to change targetItem is not called firstly
switch
when 'parent'
targetItem = ._parent
when 'children'
targetItem = .children
when 'nextSibling'
targetItem = ._nextSibling
when 'previousSibling'
targetItem = ._previousSibling
else
{targetItem} = @
if not targetItem or targetItem isnt
return
if targetItem
`//<development>`
fails = ._parent
fails &&= targetItem isnt ._children
fails &&= ._parent isnt targetItem
fails &&= ._parent isnt targetItem._parent
if fails
log.error """
Invalid anchor point; \
you can anchor only to a parent or a sibling; \
item '#{@item.toString()}.anchors.#{@source}: #{@target}'
"""
`//</development>`
r = +
else
r = 0
if margin = ._margin
r += getMarginValue[] margin
++
[] = r
if is MAX_LOOPS
log.error """
Potential anchors loop detected; \
recalculating on this anchor (#{@}) has been disabled
"""
++
else if < MAX_LOOPS
--
return
destroy: ->
switch
when 'parent'
.onParentChange.disconnect onParentChange, @
when 'children'
.onChildrenChange.disconnect onChildrenChange, @
child = .children.firstChild
while child
onChildPop.call @, child, -1
child = child.nextSibling
when 'nextSibling'
.onNextSiblingChange.disconnect onNextSiblingChange, @
when 'previousSibling'
.onPreviousSiblingChange.disconnect onPreviousSiblingChange, @
for handler in getSourceWatchProps[]
[handler].disconnect update, @
if
for handler in getTargetWatchProps[][]
[handler].disconnect update, @
= = null
pool.push @
return
toString: ->
"#{@item.toString()}.anchors.#{@source}: #{@target}.#{@line}"
getBaseAnchors =
centerIn: ['horizontalCenter', 'verticalCenter']
fillWidth: ['fillWidthSize', 'left']
fillHeight: ['fillHeightSize', 'top']
fill: ['fillWidthSize', 'fillHeightSize', 'left', 'top']
getBaseAnchorsPerAnchorType =
__proto__: null
isMultiAnchor = (source) ->
!!getBaseAnchors[source]
class MultiAnchor
pool = []
= (item, source, def) ->
if elem = pool.pop()
MultiAnchor.call elem, item, source, def
elem
else
new MultiAnchor item, source, def
constructor: (item, source, def) ->
assert.lengthOf def, 1
= []
def = [def[0], '']
= false
baseAnchors = getBaseAnchorsPerAnchorType[def[0]]?[source]
baseAnchors ?= getBaseAnchors[source]
for line in baseAnchors
def[1] = line
anchor = Anchor.factory item, line, def
.push anchor
return
update: ->
for anchor in
anchor.update()
return
destroy: ->
for anchor in
anchor.destroy()
pool.push @
return
createAnchor = (item, source, def) ->
if isMultiAnchor(source)
MultiAnchor.factory item, source, def
else
Anchor.factory item, source, def
exports =
setItemAnchor: (type, val) ->
if val isnt null
assert.isArray val
anchors = .anchors ?= {}
if not val and not anchors[type]
return
if anchors[type]
anchors[type].destroy()
anchors[type] = null
if val
anchors[type] = createAnchor(@, type, val)
return