Swiftgram/submodules/Display/Sources/ListViewIntermediateState.swift
2020-03-01 13:59:30 +04:00

869 lines
34 KiB
Swift

import Foundation
import UIKit
import SwiftSignalKit
public enum ListViewCenterScrollPositionOverflow {
case top
case bottom
}
public enum ListViewScrollPosition: Equatable {
case top(CGFloat)
case bottom(CGFloat)
case center(ListViewCenterScrollPositionOverflow)
case visible
}
public enum ListViewScrollToItemDirectionHint {
case Up
case Down
}
public enum ListViewAnimationCurve {
case Spring(duration: Double)
case Default(duration: Double?)
}
public struct ListViewScrollToItem {
public let index: Int
public let position: ListViewScrollPosition
public let animated: Bool
public let curve: ListViewAnimationCurve
public let directionHint: ListViewScrollToItemDirectionHint
public init(index: Int, position: ListViewScrollPosition, animated: Bool, curve: ListViewAnimationCurve, directionHint: ListViewScrollToItemDirectionHint) {
self.index = index
self.position = position
self.animated = animated
self.curve = curve
self.directionHint = directionHint
}
}
public enum ListViewItemOperationDirectionHint {
case Up
case Down
}
public struct ListViewDeleteItem {
public let index: Int
public let directionHint: ListViewItemOperationDirectionHint?
public init(index: Int, directionHint: ListViewItemOperationDirectionHint?) {
self.index = index
self.directionHint = directionHint
}
}
public struct ListViewInsertItem {
public let index: Int
public let previousIndex: Int?
public let item: ListViewItem
public let directionHint: ListViewItemOperationDirectionHint?
public let forceAnimateInsertion: Bool
public init(index: Int, previousIndex: Int?, item: ListViewItem, directionHint: ListViewItemOperationDirectionHint?, forceAnimateInsertion: Bool = false) {
self.index = index
self.previousIndex = previousIndex
self.item = item
self.directionHint = directionHint
self.forceAnimateInsertion = forceAnimateInsertion
}
}
public struct ListViewUpdateItem {
public let index: Int
public let previousIndex: Int
public let item: ListViewItem
public let directionHint: ListViewItemOperationDirectionHint?
public init(index: Int, previousIndex: Int, item: ListViewItem, directionHint: ListViewItemOperationDirectionHint?) {
self.index = index
self.previousIndex = previousIndex
self.item = item
self.directionHint = directionHint
}
}
public struct ListViewDeleteAndInsertOptions: OptionSet {
public let rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
public static let AnimateInsertion = ListViewDeleteAndInsertOptions(rawValue: 1)
public static let AnimateAlpha = ListViewDeleteAndInsertOptions(rawValue: 2)
public static let LowLatency = ListViewDeleteAndInsertOptions(rawValue: 4)
public static let Synchronous = ListViewDeleteAndInsertOptions(rawValue: 8)
public static let RequestItemInsertionAnimations = ListViewDeleteAndInsertOptions(rawValue: 16)
public static let AnimateTopItemPosition = ListViewDeleteAndInsertOptions(rawValue: 32)
public static let PreferSynchronousDrawing = ListViewDeleteAndInsertOptions(rawValue: 64)
public static let PreferSynchronousResourceLoading = ListViewDeleteAndInsertOptions(rawValue: 128)
public static let AnimateCrossfade = ListViewDeleteAndInsertOptions(rawValue: 256)
}
public struct ListViewUpdateSizeAndInsets {
public let size: CGSize
public let insets: UIEdgeInsets
public let headerInsets: UIEdgeInsets?
public let scrollIndicatorInsets: UIEdgeInsets?
public let duration: Double
public let curve: ListViewAnimationCurve
public let ensureTopInsetForOverlayHighlightedItems: CGFloat?
public init(size: CGSize, insets: UIEdgeInsets, headerInsets: UIEdgeInsets? = nil, scrollIndicatorInsets: UIEdgeInsets? = nil, duration: Double, curve: ListViewAnimationCurve, ensureTopInsetForOverlayHighlightedItems: CGFloat? = nil) {
self.size = size
self.insets = insets
self.headerInsets = headerInsets
self.scrollIndicatorInsets = scrollIndicatorInsets
self.duration = duration
self.curve = curve
self.ensureTopInsetForOverlayHighlightedItems = ensureTopInsetForOverlayHighlightedItems
}
}
public struct ListViewItemRange: Equatable {
public let firstIndex: Int
public let lastIndex: Int
}
public struct ListViewVisibleItemRange: Equatable {
public let firstIndex: Int
public let firstIndexFullyVisible: Bool
public let lastIndex: Int
}
public struct ListViewDisplayedItemRange: Equatable {
public let loadedRange: ListViewItemRange?
public let visibleRange: ListViewVisibleItemRange?
}
struct IndexRange {
let first: Int
let last: Int
func contains(_ index: Int) -> Bool {
return index >= first && index <= last
}
var empty: Bool {
return first > last
}
}
struct OffsetRanges {
var offsets: [(IndexRange, CGFloat)] = []
mutating func append(_ other: OffsetRanges) {
self.offsets.append(contentsOf: other.offsets)
}
mutating func offset(_ indexRange: IndexRange, offset: CGFloat) {
self.offsets.append((indexRange, offset))
}
func offsetForIndex(_ index: Int) -> CGFloat {
var result: CGFloat = 0.0
for offset in self.offsets {
if offset.0.contains(index) {
result += offset.1
}
}
return result
}
}
func binarySearch(_ inputArr: [Int], searchItem: Int) -> Int? {
var lowerIndex = 0;
var upperIndex = inputArr.count - 1
if lowerIndex > upperIndex {
return nil
}
while (true) {
let currentIndex = (lowerIndex + upperIndex) / 2
if (inputArr[currentIndex] == searchItem) {
return currentIndex
} else if (lowerIndex > upperIndex) {
return nil
} else {
if (inputArr[currentIndex] > searchItem) {
upperIndex = currentIndex - 1
} else {
lowerIndex = currentIndex + 1
}
}
}
}
struct TransactionState {
let visibleSize: CGSize
let items: [ListViewItem]
}
struct PendingNode {
let index: Int
let node: QueueLocalObject<ListViewItemNode>
let apply: () -> (Signal<Void, NoError>?, () -> Void)
let frame: CGRect
let apparentHeight: CGFloat
}
enum ListViewStateNode {
case Node(index: Int, frame: CGRect, referenceNode: QueueLocalObject<ListViewItemNode>?)
case Placeholder(frame: CGRect)
var index: Int? {
switch self {
case .Node(let index, _, _):
return index
case .Placeholder(_):
return nil
}
}
var frame: CGRect {
get {
switch self {
case .Node(_, let frame, _):
return frame
case .Placeholder(let frame):
return frame
}
} set(value) {
switch self {
case let .Node(index, _, referenceNode):
self = .Node(index: index, frame: value, referenceNode: referenceNode)
case .Placeholder(_):
self = .Placeholder(frame: value)
}
}
}
}
enum ListViewInsertionOffsetDirection {
case Up
case Down
init(_ hint: ListViewItemOperationDirectionHint) {
switch hint {
case .Up:
self = .Up
case .Down:
self = .Down
}
}
func inverted() -> ListViewInsertionOffsetDirection {
switch self {
case .Up:
return .Down
case .Down:
return .Up
}
}
}
struct ListViewInsertionPoint {
let index: Int
let point: CGPoint
let direction: ListViewInsertionOffsetDirection
}
struct ListViewState {
var insets: UIEdgeInsets
var visibleSize: CGSize
let invisibleInset: CGFloat
var nodes: [ListViewStateNode]
var scrollPosition: (Int, ListViewScrollPosition)?
var stationaryOffset: (Int, CGFloat)?
let stackFromBottom: Bool
mutating func fixScrollPosition(_ itemCount: Int) {
if let (fixedIndex, fixedPosition) = self.scrollPosition {
for node in self.nodes {
if let index = node.index, index == fixedIndex {
let offset: CGFloat
switch fixedPosition {
case let .bottom(additionalOffset):
offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY + additionalOffset
case let .top(additionalOffset):
offset = self.insets.top - node.frame.minY + additionalOffset
case let .center(overflow):
let contentAreaHeight = self.visibleSize.height - self.insets.bottom - self.insets.top
if node.frame.size.height <= contentAreaHeight + CGFloat.ulpOfOne {
offset = self.insets.top + floor((contentAreaHeight - node.frame.size.height) / 2.0) - node.frame.minY
} else {
switch overflow {
case .top:
offset = self.insets.top - node.frame.minY
case .bottom:
offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY
}
}
case .visible:
if node.frame.maxY > self.visibleSize.height - self.insets.bottom {
offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY
} else if node.frame.minY < self.insets.top {
offset = self.insets.top - node.frame.minY
} else {
offset = 0.0
}
}
var minY: CGFloat = CGFloat.greatestFiniteMagnitude
var maxY: CGFloat = 0.0
for i in 0 ..< self.nodes.count {
var frame = self.nodes[i].frame
frame = frame.offsetBy(dx: 0.0, dy: offset)
self.nodes[i].frame = frame
minY = min(minY, frame.minY)
maxY = max(maxY, frame.maxY)
}
var additionalOffset: CGFloat = 0.0
if minY > self.insets.top {
additionalOffset = self.insets.top - minY
}
if abs(additionalOffset) > CGFloat.ulpOfOne {
for i in 0 ..< self.nodes.count {
var frame = self.nodes[i].frame
frame = frame.offsetBy(dx: 0.0, dy: additionalOffset)
self.nodes[i].frame = frame
}
}
self.snapToBounds(itemCount, snapTopItem: true, stackFromBottom: self.stackFromBottom)
break
}
}
} else if let (stationaryIndex, stationaryOffset) = self.stationaryOffset {
for node in self.nodes {
if node.index == stationaryIndex {
let offset = stationaryOffset - node.frame.minY
if abs(offset) > CGFloat.ulpOfOne {
for i in 0 ..< self.nodes.count {
var frame = self.nodes[i].frame
frame = frame.offsetBy(dx: 0.0, dy: offset)
self.nodes[i].frame = frame
}
}
break
}
}
}
}
mutating func setupStationaryOffset(_ index: Int, boundary: Int, frames: [Int: CGRect]) {
if index < boundary {
for node in self.nodes {
if let nodeIndex = node.index , nodeIndex >= index {
if let frame = frames[nodeIndex] {
self.stationaryOffset = (nodeIndex, frame.minY)
break
}
}
}
} else {
for node in self.nodes.reversed() {
if let nodeIndex = node.index , nodeIndex <= index {
if let frame = frames[nodeIndex] {
self.stationaryOffset = (nodeIndex, frame.minY)
break
}
}
}
}
}
mutating func snapToBounds(_ itemCount: Int, snapTopItem: Bool, stackFromBottom: Bool) {
var completeHeight: CGFloat = 0.0
var topItemFound = false
var bottomItemFound = false
var topItemEdge: CGFloat = 0.0
var bottomItemEdge: CGFloat = 0.0
for node in self.nodes {
if let index = node.index {
if index == 0 {
topItemFound = true
topItemEdge = node.frame.minY
}
break
}
}
for node in self.nodes.reversed() {
if let index = node.index {
if index == itemCount - 1 {
bottomItemFound = true
bottomItemEdge = node.frame.maxY
}
break
}
}
if topItemFound && bottomItemFound {
for node in self.nodes {
completeHeight += node.frame.size.height
}
}
let overscroll: CGFloat = 0.0
var offset: CGFloat = 0.0
if topItemFound && bottomItemFound {
let areaHeight = min(completeHeight, self.visibleSize.height - self.insets.bottom - self.insets.top)
if bottomItemEdge < self.insets.top + areaHeight - overscroll {
offset = self.insets.top + areaHeight - overscroll - bottomItemEdge
} else if topItemEdge > self.insets.top - overscroll && snapTopItem {
offset = (self.insets.top - overscroll) - topItemEdge
}
} else if topItemFound {
if topItemEdge > self.insets.top - overscroll && snapTopItem {
offset = (self.insets.top - overscroll) - topItemEdge
}
} else if bottomItemFound {
if bottomItemEdge < self.visibleSize.height - self.insets.bottom - overscroll {
offset = self.visibleSize.height - self.insets.bottom - overscroll - bottomItemEdge
}
}
if abs(offset) > CGFloat.ulpOfOne {
for i in 0 ..< self.nodes.count {
var frame = self.nodes[i].frame
frame.origin.y += offset
self.nodes[i].frame = frame
}
}
}
func insertionPoint(_ insertDirectionHints: [Int: ListViewItemOperationDirectionHint], itemCount: Int) -> ListViewInsertionPoint? {
var fixedNode: (nodeIndex: Int, index: Int, frame: CGRect)?
if let (fixedIndex, _) = self.scrollPosition {
for i in 0 ..< self.nodes.count {
let node = self.nodes[i]
if let index = node.index , index == fixedIndex {
fixedNode = (i, index, node.frame)
break
}
}
if fixedNode == nil {
return ListViewInsertionPoint(index: fixedIndex, point: CGPoint(), direction: .Down)
}
}
var fixedNodeIsStationary = false
if fixedNode == nil {
if let (fixedIndex, _) = self.stationaryOffset {
for i in 0 ..< self.nodes.count {
let node = self.nodes[i]
if let index = node.index , index == fixedIndex {
fixedNode = (i, index, node.frame)
fixedNodeIsStationary = true
break
}
}
}
}
if fixedNode == nil {
for i in 0 ..< self.nodes.count {
let node = self.nodes[i]
if let index = node.index , node.frame.maxY >= self.insets.top {
fixedNode = (i, index, node.frame)
break
}
}
}
if fixedNode == nil && self.nodes.count != 0 {
for i in (0 ..< self.nodes.count).reversed() {
let node = self.nodes[i]
if let index = node.index {
fixedNode = (i, index, node.frame)
break
}
}
}
if let fixedNode = fixedNode {
var currentUpperNode = fixedNode
for i in (0 ..< fixedNode.nodeIndex).reversed() {
let node = self.nodes[i]
if let index = node.index {
if index != currentUpperNode.index - 1 {
if currentUpperNode.frame.minY > -self.invisibleInset - CGFloat.ulpOfOne {
var directionHint: ListViewInsertionOffsetDirection?
if let hint = insertDirectionHints[currentUpperNode.index - 1] , currentUpperNode.frame.minY > self.insets.top - CGFloat.ulpOfOne {
directionHint = ListViewInsertionOffsetDirection(hint)
}
return ListViewInsertionPoint(index: currentUpperNode.index - 1, point: CGPoint(x: 0.0, y: currentUpperNode.frame.minY), direction: directionHint ?? .Up)
} else {
break
}
}
currentUpperNode = (i, index, node.frame)
}
}
if currentUpperNode.index != 0 && currentUpperNode.frame.minY > -self.invisibleInset - CGFloat.ulpOfOne {
var directionHint: ListViewInsertionOffsetDirection?
if let hint = insertDirectionHints[currentUpperNode.index - 1] {
if currentUpperNode.frame.minY >= self.insets.top - CGFloat.ulpOfOne {
directionHint = ListViewInsertionOffsetDirection(hint)
}
} else if currentUpperNode.frame.minY >= self.insets.top - CGFloat.ulpOfOne && !fixedNodeIsStationary {
directionHint = .Down
}
return ListViewInsertionPoint(index: currentUpperNode.index - 1, point: CGPoint(x: 0.0, y: currentUpperNode.frame.minY), direction: directionHint ?? .Up)
}
var currentLowerNode = fixedNode
if fixedNode.nodeIndex + 1 < self.nodes.count {
for i in (fixedNode.nodeIndex + 1) ..< self.nodes.count {
let node = self.nodes[i]
if let index = node.index {
if index != currentLowerNode.index + 1 {
if currentLowerNode.frame.maxY < self.visibleSize.height + self.invisibleInset - CGFloat.ulpOfOne {
var directionHint: ListViewInsertionOffsetDirection?
if let hint = insertDirectionHints[currentLowerNode.index + 1] , currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat.ulpOfOne {
directionHint = ListViewInsertionOffsetDirection(hint)
}
return ListViewInsertionPoint(index: currentLowerNode.index + 1, point: CGPoint(x: 0.0, y: currentLowerNode.frame.maxY), direction: directionHint ?? .Down)
} else {
break
}
}
currentLowerNode = (i, index, node.frame)
}
}
}
if currentLowerNode.index != itemCount - 1 && currentLowerNode.frame.maxY < self.visibleSize.height + self.invisibleInset - CGFloat.ulpOfOne {
var directionHint: ListViewInsertionOffsetDirection?
if let hint = insertDirectionHints[currentLowerNode.index + 1] , currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat.ulpOfOne {
directionHint = ListViewInsertionOffsetDirection(hint)
}
return ListViewInsertionPoint(index: currentLowerNode.index + 1, point: CGPoint(x: 0.0, y: currentLowerNode.frame.maxY), direction: directionHint ?? .Down)
}
} else if itemCount != 0 {
return ListViewInsertionPoint(index: 0, point: CGPoint(x: 0.0, y: self.insets.top), direction: .Down)
}
return nil
}
mutating func removeInvisibleNodes(_ operations: inout [ListViewStateOperation]) {
var i = 0
var visibleItemNodeHeight: CGFloat = 0.0
while i < self.nodes.count {
visibleItemNodeHeight += self.nodes[i].frame.height
i += 1
}
if visibleItemNodeHeight > (self.visibleSize.height + self.invisibleInset + self.invisibleInset) {
i = self.nodes.count - 1
while i >= 0 {
let itemNode = self.nodes[i]
let frame = itemNode.frame
//print("node \(i) frame \(frame)")
if frame.maxY < -self.invisibleInset || frame.origin.y > self.visibleSize.height + self.invisibleInset {
//print("remove invisible 1 \(i) frame \(frame)")
operations.append(.Remove(index: i, offsetDirection: frame.maxY < -self.invisibleInset ? .Down : .Up))
self.nodes.remove(at: i)
}
i -= 1
}
}
let upperBound = -self.invisibleInset + CGFloat.ulpOfOne
for i in 0 ..< self.nodes.count {
let node = self.nodes[i]
if let index = node.index , node.frame.maxY > upperBound {
if i != 0 {
var previousIndex = index
for j in (0 ..< i).reversed() {
if self.nodes[j].frame.maxY < upperBound {
if let index = self.nodes[j].index {
if index != previousIndex - 1 {
//print("remove monotonity \(j) (\(index))")
operations.append(.Remove(index: j, offsetDirection: .Down))
self.nodes.remove(at: j)
} else {
previousIndex = index
}
}
}
}
}
break
}
}
let lowerBound = self.visibleSize.height + self.invisibleInset - CGFloat.ulpOfOne
for i in (0 ..< self.nodes.count).reversed() {
let node = self.nodes[i]
if let index = node.index , node.frame.minY < lowerBound {
if i != self.nodes.count - 1 {
var previousIndex = index
var removeIndices: [Int] = []
for j in (i + 1) ..< self.nodes.count {
if self.nodes[j].frame.minY > lowerBound {
if let index = self.nodes[j].index {
if index != previousIndex + 1 {
removeIndices.append(j)
} else {
previousIndex = index
}
}
}
}
if !removeIndices.isEmpty {
for i in removeIndices.reversed() {
//print("remove monotonity \(i) (\(self.nodes[i].index!))")
operations.append(.Remove(index: i, offsetDirection: .Up))
self.nodes.remove(at: i)
}
}
}
break
}
}
}
func nodeInsertionPointAndIndex(_ itemIndex: Int) -> (CGPoint, Int) {
if self.nodes.count == 0 {
return (CGPoint(x: 0.0, y: self.insets.top), 0)
} else {
var index = 0
var lastNodeWithIndex = -1
for node in self.nodes {
if let nodeItemIndex = node.index {
if nodeItemIndex > itemIndex {
break
}
lastNodeWithIndex = index
}
index += 1
}
lastNodeWithIndex += 1
return (CGPoint(x: 0.0, y: lastNodeWithIndex == 0 ? self.nodes[0].frame.minY : self.nodes[lastNodeWithIndex - 1].frame.maxY), lastNodeWithIndex)
}
}
func continuousHeightRelativeToNodeIndex(_ fixedNodeIndex: Int) -> CGFloat {
let fixedIndex = self.nodes[fixedNodeIndex].index!
var height: CGFloat = 0.0
if fixedNodeIndex != 0 {
var upperIndex = fixedIndex
for i in (0 ..< fixedNodeIndex).reversed() {
if let index = self.nodes[i].index {
if index == upperIndex - 1 {
height += self.nodes[i].frame.size.height
upperIndex = index
} else {
break
}
}
}
}
if fixedNodeIndex != self.nodes.count - 1 {
var lowerIndex = fixedIndex
for i in (fixedNodeIndex + 1) ..< self.nodes.count {
if let index = self.nodes[i].index {
if index == lowerIndex + 1 {
height += self.nodes[i].frame.size.height
lowerIndex = index
} else {
break
}
}
}
}
return height
}
mutating func insertNode(_ itemIndex: Int, node: QueueLocalObject<ListViewItemNode>, layout: ListViewItemNodeLayout, apply: @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void), offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, operations: inout [ListViewStateOperation], itemCount: Int) {
let (insertionOrigin, insertionIndex) = self.nodeInsertionPointAndIndex(itemIndex)
let nodeOrigin: CGPoint
switch offsetDirection {
case .Up:
nodeOrigin = CGPoint(x: insertionOrigin.x, y: insertionOrigin.y - (animated ? 0.0 : layout.size.height))
case .Down:
nodeOrigin = insertionOrigin
}
let nodeFrame = CGRect(origin: nodeOrigin, size: CGSize(width: layout.size.width, height: animated ? 0.0 : layout.size.height))
operations.append(.InsertNode(index: insertionIndex, offsetDirection: offsetDirection, animated: animated, node: node, layout: layout, apply: apply))
self.nodes.insert(.Node(index: itemIndex, frame: nodeFrame, referenceNode: nil), at: insertionIndex)
if !animated {
switch offsetDirection {
case .Up:
var i = insertionIndex - 1
while i >= 0 {
var frame = self.nodes[i].frame
frame.origin.y -= nodeFrame.size.height
self.nodes[i].frame = frame
i -= 1
}
case .Down:
var i = insertionIndex + 1
while i < self.nodes.count {
var frame = self.nodes[i].frame
frame.origin.y += nodeFrame.size.height
self.nodes[i].frame = frame
i += 1
}
}
}
var previousIndex: Int?
for node in self.nodes {
if let index = node.index {
if let currentPreviousIndex = previousIndex {
if index <= currentPreviousIndex {
print("index <= previousIndex + 1")
break
}
previousIndex = index
} else {
previousIndex = index
}
}
}
if let _ = self.scrollPosition {
self.fixScrollPosition(itemCount)
}
}
mutating func removeNodeAtIndex(_ index: Int, direction: ListViewItemOperationDirectionHint?, animated: Bool, operations: inout [ListViewStateOperation]) {
let node = self.nodes[index]
if case let .Node(_, _, referenceNode) = node {
let nodeFrame = node.frame
self.nodes.remove(at: index)
let offsetDirection: ListViewInsertionOffsetDirection
if let direction = direction {
offsetDirection = ListViewInsertionOffsetDirection(direction)
} else {
if nodeFrame.maxY < self.insets.top + CGFloat.ulpOfOne {
offsetDirection = .Down
} else {
offsetDirection = .Up
}
}
operations.append(.Remove(index: index, offsetDirection: offsetDirection))
if let referenceNode = referenceNode , animated {
self.nodes.insert(.Placeholder(frame: nodeFrame), at: index)
operations.append(.InsertDisappearingPlaceholder(index: index, referenceNode: referenceNode, offsetDirection: offsetDirection.inverted()))
} else {
if nodeFrame.maxY > self.insets.top - CGFloat.ulpOfOne {
if let direction = direction , direction == .Down && node.frame.minY < self.visibleSize.height - self.insets.bottom + CGFloat.ulpOfOne {
for i in (0 ..< index).reversed() {
var frame = self.nodes[i].frame
frame.origin.y += nodeFrame.size.height
self.nodes[i].frame = frame
}
} else {
for i in index ..< self.nodes.count {
var frame = self.nodes[i].frame
frame.origin.y -= nodeFrame.size.height
self.nodes[i].frame = frame
}
}
} else if index != 0 {
for i in (0 ..< index).reversed() {
var frame = self.nodes[i].frame
frame.origin.y += nodeFrame.size.height
self.nodes[i].frame = frame
}
}
}
} else {
assertionFailure()
}
}
mutating func updateNodeAtItemIndex(_ itemIndex: Int, layout: ListViewItemNodeLayout, direction: ListViewItemOperationDirectionHint?, animation: ListViewItemUpdateAnimation, apply: @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void), operations: inout [ListViewStateOperation]) {
var i = -1
for node in self.nodes {
i += 1
if node.index == itemIndex {
switch animation {
case .None, .Crossfade:
let offsetDirection: ListViewInsertionOffsetDirection
if let direction = direction {
offsetDirection = ListViewInsertionOffsetDirection(direction)
} else {
if node.frame.maxY < self.insets.top + CGFloat.ulpOfOne {
offsetDirection = .Down
} else {
offsetDirection = .Up
}
}
switch offsetDirection {
case .Up:
let offsetDelta = -(layout.size.height - node.frame.size.height)
var updatedFrame = node.frame
updatedFrame.origin.y += offsetDelta
updatedFrame.size.height = layout.size.height
self.nodes[i].frame = updatedFrame
for j in 0 ..< i {
var frame = self.nodes[j].frame
frame.origin.y += offsetDelta
self.nodes[j].frame = frame
}
case .Down:
let offsetDelta = layout.size.height - node.frame.size.height
var updatedFrame = node.frame
updatedFrame.size.height = layout.size.height
self.nodes[i].frame = updatedFrame
for j in i + 1 ..< self.nodes.count {
var frame = self.nodes[j].frame
frame.origin.y += offsetDelta
self.nodes[j].frame = frame
}
}
operations.append(.UpdateLayout(index: i, layout: layout, apply: apply))
case .System:
operations.append(.UpdateLayout(index: i, layout: layout, apply: apply))
}
break
}
}
}
}
enum ListViewStateOperation {
case InsertNode(index: Int, offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, node: QueueLocalObject<ListViewItemNode>, layout: ListViewItemNodeLayout, apply: () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void))
case InsertDisappearingPlaceholder(index: Int, referenceNode: QueueLocalObject<ListViewItemNode>, offsetDirection: ListViewInsertionOffsetDirection)
case Remove(index: Int, offsetDirection: ListViewInsertionOffsetDirection)
case Remap([Int: Int])
case UpdateLayout(index: Int, layout: ListViewItemNodeLayout, apply: () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void))
}