neft
Version:
Universal Platform
440 lines (364 loc) • 12.9 kB
text/coffeescript
'use strict'
MAX_LOOPS = 100
utils = require 'src/utils'
log = require 'src/log'
TypedArray = require 'src/typed-array'
log = log.scope 'Renderer'
queueIndex = 0
queues = [[], []]
queue = queues[queueIndex]
pending = false
visibleChildren = new TypedArray.Uint8 64
columnsSizes = new TypedArray.Uint32 64
columnsFills = new TypedArray.Uint8 64
rowsSizes = new TypedArray.Uint32 64
rowsFills = new TypedArray.Uint8 64
unusedFills = new TypedArray.Uint8 64
getArray = (arr, len) ->
if arr.length < len
new arr.constructor len * 1.4 | 0
else
arr
getCleanArray = (arr, len) ->
newArr = getArray arr, len
if newArr is arr
for i in [0...len] by 1
arr[i] = 0
arr
else
newArr
ALIGNMENT_TO_POINT =
left: 0
center: 0.5
right: 1
top: 0
bottom: 1
updateItem = (item) ->
unless effectItem = item._effectItem
return
{includeBorderMargins} = item
{children} = effectItem
firstChild = children.firstChild
data = item._impl
{gridType} = data
# get config
{autoWidth, autoHeight} = data
columnSpacing = rowSpacing = 0
if layout = effectItem._layout
autoWidth &&= !layout._fillWidth
autoHeight &&= !layout._fillHeight
if gridType is ALL
columnsLen = item.columns
rowsLen = item.rows
columnSpacing = item.spacing.column
rowSpacing = item.spacing.row
else if gridType is COLUMN
rowSpacing = item.spacing
columnsLen = 1
rowsLen = Infinity
else if gridType is ROW
columnSpacing = item.spacing
columnsLen = Infinity
rowsLen = 1
if alignment = item._alignment
alignH = ALIGNMENT_TO_POINT[alignment._horizontal]
alignV = ALIGNMENT_TO_POINT[alignment._vertical]
else
alignH = 0
alignV = 0
if padding = item._padding
topPadding = padding._top
rightPadding = padding._right
bottomPadding = padding._bottom
leftPadding = padding._left
else
topPadding = rightPadding = bottomPadding = leftPadding = 0
# get tmp variables
maxColumnsLen = if columnsLen is Infinity then children.length else columnsLen
columnsSizes = getCleanArray columnsSizes, maxColumnsLen
columnsFills = getCleanArray columnsFills, maxColumnsLen
maxRowsLen = if rowsLen is Infinity then Math.ceil(children.length / columnsLen) else rowsLen
rowsSizes = getCleanArray rowsSizes, maxRowsLen
rowsFills = getCleanArray rowsFills, maxRowsLen
visibleChildren = getArray visibleChildren, children.length
# get last column and last row
i = lastColumn = 0
lastRow = -1
nextChild = firstChild
childIndex = -1
while child = nextChild
childIndex += 1
nextChild = child.nextSibling
# check visibility
if not child._visible or (child._layout and not child._layout._enabled)
visibleChildren[childIndex] = 0
continue
visibleChildren[childIndex] = 1
# get max column and max row
column = i % columnsLen
row = Math.floor(i / columnsLen) % rowsLen
if column > lastColumn
lastColumn = column
if row > lastRow
lastRow = row
i++
# get columns and rows sizes
i = columnsFillsSum = rowsFillsSum = 0
nextChild = firstChild
childIndex = -1
while child = nextChild
childIndex += 1
nextChild = child.nextSibling
unless visibleChildren[childIndex]
continue
# child
width = child._width
height = child._height
margin = child._margin
layout = child._layout
column = i % columnsLen
row = Math.floor(i / columnsLen) % rowsLen
if layout
if layout._fillWidth
width = 0
columnsFillsSum++
columnsFills[column] = 1
if layout._fillHeight
height = 0
rowsFillsSum++
rowsFills[row] = 1
# margins
if margin
if includeBorderMargins or column isnt 0
width += margin._left
if includeBorderMargins or column isnt lastColumn
width += margin._right
if includeBorderMargins or row isnt 0
height += margin._top
if includeBorderMargins or row isnt lastRow
height += margin._bottom
# save
if width > columnsSizes[column]
columnsSizes[column] = width
if height > rowsSizes[row]
rowsSizes[row] = height
i++
# get grid size
gridWidth = 0
if autoWidth or columnsFillsSum > 0 or alignH isnt 0
for i in [0..lastColumn] by 1
gridWidth += columnsSizes[i]
gridHeight = 0
if autoHeight or rowsFillsSum > 0 or alignV isnt 0
for i in [0..lastRow] by 1
gridHeight += rowsSizes[i]
# expand filled cells
if not autoWidth
freeWidthSpace = effectItem._width - columnSpacing * lastColumn - leftPadding - rightPadding - gridWidth
if freeWidthSpace > 0 and columnsFillsSum > 0
unusedFills = getCleanArray unusedFills, lastColumn+1
length = lastColumn+1
perCell = (gridWidth + freeWidthSpace) / length
update = true
while update
update = false
for i in [0..lastColumn] by 1
if unusedFills[i] is 0 and (columnsFills[i] is 0 or columnsSizes[i] > perCell)
length--
perCell -= (columnsSizes[i] - perCell) / length
unusedFills[i] = 1
update = true
for i in [0..lastColumn] by 1
if unusedFills[i] is 0
columnsSizes[i] = perCell
freeWidthSpace = 0
if not autoHeight
freeHeightSpace = effectItem._height - rowSpacing * lastRow - topPadding - bottomPadding - gridHeight
if freeHeightSpace > 0 and rowsFillsSum > 0
unusedFills = getCleanArray unusedFills, lastRow+1
length = lastRow+1
perCell = (gridHeight + freeHeightSpace) / length
update = true
while update
update = false
for i in [0..lastRow] by 1
if unusedFills[i] is 0 and (rowsFills[i] is 0 or rowsSizes[i] > perCell)
length--
perCell -= (rowsSizes[i] - perCell) / length
unusedFills[i] = 1
update = true
for i in [0..lastRow] by 1
if unusedFills[i] is 0
rowsSizes[i] = perCell
freeHeightSpace = 0
# get grid content margin
if autoWidth or alignH is 0
plusX = 0
else
plusX = freeWidthSpace * alignH
if autoHeight or alignV is 0
plusY = 0
else
plusY = freeHeightSpace * alignV
# set children positions and sizes
i = cellX = cellY = 0
nextChild = firstChild
childIndex = -1
while child = nextChild
childIndex += 1
nextChild = child.nextSibling
unless visibleChildren[childIndex]
continue
margin = child._margin
layout = child._layout
anchors = child._anchors
column = i % columnsLen
row = Math.floor(i / columnsLen) % rowsLen
# get cell position
if column is 0
cellX = 0
if row is 0
cellY = 0
else
cellY += rowsSizes[row-1] + rowSpacing
else
cellX += columnsSizes[column-1] + columnSpacing
# get child margins
leftMargin = rightMargin = 0
if margin
if includeBorderMargins or column isnt 0
leftMargin = margin._left
if includeBorderMargins or column isnt lastColumn
rightMargin = margin._right
topMargin = bottomMargin = 0
if margin
if includeBorderMargins or row isnt 0
topMargin = margin._top
if includeBorderMargins or row isnt lastRow
bottomMargin = margin._bottom
# set sizes
if layout
# set width
if layout._fillWidth
width = columnsSizes[column] - leftMargin - rightMargin
child.width = width
# set height
if layout._fillHeight
height = rowsSizes[row] - topMargin - bottomMargin
child.height = height
# set x
unless anchors?._autoX
child.x = cellX + plusX + leftMargin + leftPadding + columnsSizes[column] * alignH - (child._width + leftMargin + rightMargin) * alignH
# set y
unless anchors?._autoY
child.y = cellY + plusY + topMargin + topPadding + rowsSizes[row] * alignV - (child._height + topMargin + bottomMargin) * alignV
i++
# set effect item size
if autoWidth
effectItem.width = gridWidth + columnSpacing * lastColumn + leftPadding + rightPadding
if autoHeight
effectItem.height = gridHeight + rowSpacing * lastRow + topPadding + bottomPadding
return
updateItems = ->
pending = false
currentQueue = queue
queue = queues[++queueIndex % queues.length]
while currentQueue.length
item = currentQueue.pop()
item._impl.pending = false
item._impl.updatePending = true
updateItem item
item._impl.updatePending = false
return
update = ->
data =
if data.pending or not ?._visible
return
data.pending = true
if data.updatePending
if data.gridUpdateLoops > MAX_LOOPS
return
if ++data.gridUpdateLoops is MAX_LOOPS
log.error "Potential Grid/Column/Row loop detected. Recalculating on this item (#{@toString()}) has been disabled."
return
else
data.gridUpdateLoops = 0
queue.push @
unless pending
setImmediate updateItems
pending = true
return
updateSize = ->
if not .updatePending
update.call @
return
onWidthChange = (oldVal) ->
if and not .updatePending and (not (layout = ._layout) or not layout._fillWidth)
.autoWidth = ._width is 0 and oldVal isnt -1
updateSize.call @
onHeightChange = (oldVal) ->
if and not .updatePending and (not (layout = ._layout) or not layout._fillHeight)
.autoHeight = ._height is 0 and oldVal isnt -1
updateSize.call @
enableChild = (child) ->
child.onVisibleChange update, @
child.onWidthChange update, @
child.onHeightChange update, @
child.onMarginChange update, @
child.onAnchorsChange update, @
child.onLayoutChange update, @
disableChild = (child) ->
child.onVisibleChange.disconnect update, @
child.onWidthChange.disconnect update, @
child.onHeightChange.disconnect update, @
child.onMarginChange.disconnect update, @
child.onAnchorsChange.disconnect update, @
child.onLayoutChange.disconnect update, @
onChildrenChange = (added, removed) ->
if added
enableChild.call @, added
if removed
disableChild.call @, removed
update.call @
COLUMN = exports.COLUMN = 1<<0
ROW = exports.ROW = 1<<1
ALL = exports.ALL = (1<<2) - 1
exports.DATA =
pending: false
updatePending: false
gridType: 0
gridUpdateLoops: 0
autoWidth: true
autoHeight: true
exports.create = (item, type) ->
item._impl.gridType = type
item.onAlignmentChange updateSize
item.onPaddingChange updateSize
exports.update = update
exports.setEffectItem = (item, oldItem) ->
if oldItem
oldItem.onVisibleChange.disconnect update, @
oldItem.onChildrenChange.disconnect onChildrenChange, @
oldItem.onLayoutChange.disconnect update, @
oldItem.onWidthChange.disconnect onWidthChange, @
oldItem.onHeightChange.disconnect onHeightChange, @
child = oldItem.children.firstChild
while child
disableChild.call @, child
child = child.nextSibling
if item
if .autoWidth = item.width is 0
item.width = -1
if .autoHeight = item.height is 0
item.height = -1
item.onVisibleChange update, @
item.onChildrenChange onChildrenChange, @
item.onLayoutChange update, @
item.onWidthChange onWidthChange, @
item.onHeightChange onHeightChange, @
child = item.children.firstChild
while child
enableChild.call @, child
child = child.nextSibling
update.call @
return