UNPKG

neft

Version:

Universal Platform

299 lines (239 loc) 7.37 kB
'use strict' {utils, signal} = Neft {Impl} = Neft.Renderer {Item} = Impl.Types WHEEL_DIVISOR = 3 MIN_POINTER_DELTA = 7 ### Scroll container by given x and y deltas ### scroll = (item, x = 0, y = 0) -> deltaX = getDeltaX item, x deltaY = getDeltaY item, y x = Math.round item._contentX - deltaX y = Math.round item._contentY - deltaY x = getLimitedX item, x y = getLimitedY item, y if item._contentX isnt x or item._contentY isnt y item.contentX = x item.contentY = y return true false getDeltaX = (item, x) -> x / item._impl.globalScale getDeltaY = (item, y) -> y / item._impl.globalScale getLimitedX = (item, x) -> max = item._impl.contentItem._width - item._width Math.round Math.max(0, Math.min(max, x)) getLimitedY = (item, y) -> max = item._impl.contentItem._height - item._height Math.round Math.max(0, Math.min(max, y)) getItemGlobalScale = (item) -> val = item.scale while item = item.parent val *= item.scale val createContinuous = (item, prop) -> velocity = 0 amplitude = 0 timestamp = 0 target = 0 reversed = false scrollAxis = do -> switch prop when 'x' (val) -> scroll item, val, 0 when 'y' (val) -> scroll item, 0, val contentProp = do -> switch prop when 'x' '_contentX' when 'y' '_contentY' positionProp = do -> switch prop when 'x' '_x' when 'y' '_y' sizeProp = do -> switch prop when 'x' '_width' when 'y' '_height' anim = -> if amplitude isnt 0 elapsed = Date.now() - timestamp delta = -amplitude * 0.7 * Math.exp(-elapsed / 325) if delta > 0.5 or delta < -0.5 scrollAxis delta requestAnimationFrame anim else scrollAxis targetDelta return press: -> velocity = amplitude = 0 reversed = false timestamp = Date.now() release: -> data = item._impl if Math.abs(velocity) < 5 return amplitude = 0.8 * velocity timestamp = Date.now() target = item[contentProp] + amplitude * 4 shouldAnimate = Math.abs(velocity) > 10 if shouldAnimate anim() return update: (val) -> now = Date.now() elapsed = now - timestamp timestamp = now v = 100 * -val / (1 + elapsed) velocity = 0.8 * v + 0.2 * velocity return DELTA_VALIDATION_PENDING = 1 pointerWindowMoveListeners = [] onImplReady = (windowItem) -> windowItem.pointer.onMove (e) -> stop = false for listener in pointerWindowMoveListeners r = listener(e) if r is signal.STOP_PROPAGATION stop = true break if r is DELTA_VALIDATION_PENDING stop = true if stop signal.STOP_PROPAGATION Impl.onWindowItemReady onImplReady pointerUsed = false usePointer = (item) -> horizontalContinuous = createContinuous item, 'x' verticalContinuous = createContinuous item, 'y' focus = false listen = false dx = dy = 0 moveMovement = (e) -> unless scroll(item, e.movementX + dx, e.movementY + dy) e.stopPropagation = false onImplReady = (windowItem) -> pointerWindowMoveListeners.push (e) -> if not listen return if not focus if pointerUsed return dx += e.movementX dy += e.movementY limitedX = getLimitedX(item, -dx) limitedY = getLimitedY(item, -dy) if limitedX isnt item._contentX or limitedY isnt item._contentY proceed = Math.abs(limitedX - item._contentX) < MIN_POINTER_DELTA proceed &&= Math.abs(limitedY - item._contentY) < MIN_POINTER_DELTA if proceed return DELTA_VALIDATION_PENDING dx = dy = 0 if moveMovement(e) is signal.STOP_PROPAGATION focus = true pointerUsed = true horizontalContinuous.update dx + e.movementX verticalContinuous.update dy + e.movementY signal.STOP_PROPAGATION windowItem.pointer.onRelease (e) -> listen = false dx = dy = 0 return unless focus focus = false pointerUsed = false moveMovement e horizontalContinuous.release() verticalContinuous.release() return Impl.onWindowItemReady onImplReady item.pointer.onPress (e) -> listen = true item._impl.globalScale = getItemGlobalScale item horizontalContinuous.press() verticalContinuous.press() return wheelUsed = false lastActionTimestamp = 0 useWheel = (item) -> i = 0 used = false accepts = false pending = false clear = true lastAcceptedActionTimestamp = 0 horizontalContinuous = createContinuous item, 'x' verticalContinuous = createContinuous item, 'y' x = y = 0 minX = minY = maxX = maxY = 0 timer = -> now = Date.now() if accepts or now - lastAcceptedActionTimestamp > 70 pending = false accepts = true horizontalContinuous.update x verticalContinuous.update y horizontalContinuous.release() verticalContinuous.release() else requestAnimationFrame timer return item.pointer.onWheel (e) -> x = e.deltaX / WHEEL_DIVISOR y = e.deltaY / WHEEL_DIVISOR item._impl.globalScale = getItemGlobalScale item unless scroll(item, x, y) e.stopPropagation = false return onWidthChange = (oldVal) -> if @contentItem.width < oldVal scroll @ return onHeightChange = (oldVal) -> if @contentItem.height < oldVal scroll @ return exports.create = (data) -> Item.create.call @, data Item.setItemClip.call @, true # signals usePointer @ useWheel @ return exports.createData = -> utils.merge contentItem: null globalScale: 1 , Item.DATA exports.setScrollableContentItem = (val) -> if oldVal = @_impl.contentItem Item.setItemParent.call oldVal, null oldVal.onWidthChange.disconnect onWidthChange, @ oldVal.onHeightChange.disconnect onHeightChange, @ if val if @children.length > 0 Item.insertItemBefore.call val, @children.first else Item.setItemParent.call val, @ @_impl.contentItem = val val.onWidthChange onWidthChange, @ val.onHeightChange onHeightChange, @ return exports.setScrollableContentX = (val) -> if item = @_impl.contentItem Item.setItemX.call item, -val return exports.setScrollableContentY = (val) -> if item = @_impl.contentItem Item.setItemY.call item, -val return