<commit_before>//
//  CarouselView.swift
//  UIPlayground
//
//  Created by <NAME> on 8/18/16.
//  Copyright © 2016 <NAME>. All rights reserved.
//

import UIKit
import QuartzCore

public class CarouselView: UIView, UIGestureRecognizerDelegate {

    public weak var dataSource: CarouselViewDataSource?
    public weak var delegate: CarouselViewDelegate?
    
    // A 1 here means that the resitance increases linearly from 0 to 1 between the item origin and item offscreen
    public var itemViewPanDownResistance = CGFloat(1.8)
    public var itemViewPanDownResistanceMax = CGFloat(0.9)
    public var itemViewPanDownResistanceMin = CGFloat(0.0)
    
    public var itemViewRemovalEscapeVelocity = CGFloat(-100)
    public var itemViewRemovalAnimationDuration = 0.15
    public var itemViewRemovalFailureAnimationDuration = 0.25
    
    private var itemViews: [UIView] = []
    private var absoluteOffset = CGFloat(0)
    private var viewPositions: [UIView: Point3D] = [:]
    private var horizontalPanGesture: UIPanGestureRecognizer!
    private var decelerateDisplayLinkProgressor: DisplayLinkProgressor?
    
    public override init(frame: CGRect) {
        super.init(frame: frame)
        
        setup()
    }
    
    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        
        setup()
    }
    
    private func setup() {
        horizontalPanGesture = UIPanGestureRecognizer(target: self, action: #selector(viewWasPanned))
        addGestureRecognizer(horizontalPanGesture)
        horizontalPanGesture.enabled = true
        horizontalPanGesture.delegate = self
    }
    
    // MARK: - Handling gestures
    
    func viewWasPanned(recognizer: UIPanGestureRecognizer) {
        switch recognizer.state {
        case .Possible:
            break
            
        case .Began:
            stopDecelerationAnimation()
            
        case .Changed:
            let translation = recognizer.translationInView(self)
            recognizer.setTranslation(CGPointZero, inView: self)

            // Find which view the user's finger is currently panning over
            // and shift all views relative to that view
            let pannedView = itemViews.findFirst({ (view) -> Bool in
                let localPoint = recognizer.locationInView(view)
                return view.pointInside(localPoint, withEvent: nil)
            })

            if let pannedView = pannedView {
                didPanHorizontally(byOffset: translation.x, forView: pannedView)
            }
            
        case .Ended:
            let velocity = recognizer.velocityInView(self)
            animatePanEndDeceleration(withVelocity: velocity)
            
        case .Cancelled, .Failed:
            break
        }
    }
    
    func itemViewDidPan(recognizer: UIPanGestureRecognizer) {
        let pannedView = recognizer.view!
        let viewPosition = viewPositions[pannedView]!
        
        switch recognizer.state {
        case .Possible:
            break
            
        case .Began:
            break
            
        case .Changed:
            let translation = recognizer.translationInView(self)
            recognizer.setTranslation(CGPointZero, inView: self)
            
            let resistance: CGFloat = {
                // If the view is being pulled down past the resting spot,
                // add a resistance to the dragging to simulate it being on a rubber band
                // TODO: make the resting location common code
                if translation.y >= 0 && pannedView.frame.midY > bounds.midY {
                    let originY = bounds.midY - (pannedView.frame.height / 2.0)
                    let offScreenDistance = bounds.maxY - originY
                    let currentDistance = pannedView.frame.origin.y - originY
                    let resistance = currentDistance / (offScreenDistance / itemViewPanDownResistance)
                    
                    return min(max(resistance, itemViewPanDownResistanceMin), itemViewPanDownResistanceMax)
                }
                return 0
            }()
            
            var newPosition = viewPosition
            newPosition.y += translation.y * (1 - resistance)
            
            updateItemView(pannedView, withPosition: newPosition)
            
        case .Ended:
            let velocity = recognizer.velocityInView(self)
            
            // If the user swipes up with a great enough velocity or
            // if the panned view has been moved up enough, then remove it
            if velocity.y < itemViewRemovalEscapeVelocity || pannedView.frame.maxY < center.y {
                animateRemoval(ofItemView: pannedView)
            }
            // Otherwise animate a snapping back of the view into its position
            else {
                UIView.animateWithDuration(itemViewRemovalFailureAnimationDuration, animations: {
                    var newPosition = viewPosition
                    // TODO: make the resting location common code
                    newPosition.y = self.bounds.height / 2.0 - pannedView.bounds.height / 2.0
                    self.updateItemView(pannedView, withPosition: newPosition)
                })
            }
            
        case .Cancelled, .Failed:
            break
        }
    }
    
    public override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        stopDecelerationAnimation()
    }
    
    // MARK: - Working with Data
    
    public func reloadData() {
        // TODO: could put this all in a struct and have it all reset properly each time rather than trying to maintain the state here
        for itemView in itemViews {
            itemView.removeFromSuperview()
        }
        viewPositions.removeAll()
        absoluteOffset = 0
        
        itemViews = {
            guard let dataSource = dataSource else {
                return []
            }
            
            let numberOfItems = dataSource.numberOfItemsInCarouselView(self)
            
            return (0..<numberOfItems).map { (index) -> UIView in
                dataSource.carouselView(self, viewForItemAtIndex: index)
            }
        }()
        
        for itemView in itemViews {
            insertSubview(itemView, atIndex: 0)
            
            let panGesture = UIPanGestureRecognizer(target: self, action: #selector(itemViewDidPan))
            itemView.addGestureRecognizer(panGesture)
            panGesture.enabled = true
            // Give presedence to the horizontal pan gesture <commit_msg>Not much gained from this todo and will require more code.<commit_after>//
//  CarouselView.swift
//  UIPlayground
//
//  Created by <NAME> on 8/18/16.
//  Copyright © 2016 <NAME>. All rights reserved.
//

import UIKit
import QuartzCore

public class CarouselView: UIView, UIGestureRecognizerDelegate {

    public weak var dataSource: CarouselViewDataSource?
    public weak var delegate: CarouselViewDelegate?
    
    // A 1 here means that the resitance increases linearly from 0 to 1 between the item origin and item offscreen
    public var itemViewPanDownResistance = CGFloat(1.8)
    public var itemViewPanDownResistanceMax = CGFloat(0.9)
    public var itemViewPanDownResistanceMin = CGFloat(0.0)
    
    public var itemViewRemovalEscapeVelocity = CGFloat(-100)
    public var itemViewRemovalAnimationDuration = 0.15
    public var itemViewRemovalFailureAnimationDuration = 0.25
    
    private var itemViews: [UIView] = []
    private var absoluteOffset = CGFloat(0)
    private var viewPositions: [UIView: Point3D] = [:]
    private var horizontalPanGesture: UIPanGestureRecognizer!
    private var decelerateDisplayLinkProgressor: DisplayLinkProgressor?
    
    public override init(frame: CGRect) {
        super.init(frame: frame)
        
        setup()
    }
    
    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        
        setup()
    }
    
    private func setup() {
        horizontalPanGesture = UIPanGestureRecognizer(target: self, action: #selector(viewWasPanned))
        addGestureRecognizer(horizontalPanGesture)
        horizontalPanGesture.enabled = true
        horizontalPanGesture.delegate = self
    }
    
    // MARK: - Handling gestures
    
    func viewWasPanned(recognizer: UIPanGestureRecognizer) {
        switch recognizer.state {
        case .Possible:
            break
            
        case .Began:
            stopDecelerationAnimation()
            
        case .Changed:
            let translation = recognizer.translationInView(self)
            recognizer.setTranslation(CGPointZero, inView: self)

            // Find which view the user's finger is currently panning over
            // and shift all views relative to that view
            let pannedView = itemViews.findFirst({ (view) -> Bool in
                let localPoint = recognizer.locationInView(view)
                return view.pointInside(localPoint, withEvent: nil)
            })

            if let pannedView = pannedView {
                didPanHorizontally(byOffset: translation.x, forView: pannedView)
            }
            
        case .Ended:
            let velocity = recognizer.velocityInView(self)
            animatePanEndDeceleration(withVelocity: velocity)
            
        case .Cancelled, .Failed:
            break
        }
    }
    
    func itemViewDidPan(recognizer: UIPanGestureRecognizer) {
        let pannedView = recognizer.view!
        let viewPosition = viewPositions[pannedView]!
        
        switch recognizer.state {
        case .Possible:
            break
            
        case .Began:
            break
            
        case .Changed:
            let translation = recognizer.translationInView(self)
            recognizer.setTranslation(CGPointZero, inView: self)
            
            let resistance: CGFloat = {
                // If the view is being pulled down past the resting spot,
                // add a resistance to the dragging to simulate it being on a rubber band
                // TODO: make the resting location common code
                if translation.y >= 0 && pannedView.frame.midY > bounds.midY {
                    let originY = bounds.midY - (pannedView.frame.height / 2.0)
                    let offScreenDistance = bounds.maxY - originY
                    let currentDistance = pannedView.frame.origin.y - originY
                    let resistance = currentDistance / (offScreenDistance / itemViewPanDownResistance)
                    
                    return min(max(resistance, itemViewPanDownResistanceMin), itemViewPanDownResistanceMax)
                }
                return 0
            }()
            
            var newPosition = viewPosition
            newPosition.y += translation.y * (1 - resistance)
            
            updateItemView(pannedView, withPosition: newPosition)
            
        case .Ended:
            let velocity = recognizer.velocityInView(self)
            
            // If the user swipes up with a great enough velocity or
            // if the panned view has been moved up enough, then remove it
            if velocity.y < itemViewRemovalEscapeVelocity || pannedView.frame.maxY < center.y {
                animateRemoval(ofItemView: pannedView)
            }
            // Otherwise animate a snapping back of the view into its position
            else {
                UIView.animateWithDuration(itemViewRemovalFailureAnimationDuration, animations: {
                    var newPosition = viewPosition
                    // TODO: make the resting location common code
                    newPosition.y = self.bounds.height / 2.0 - pannedView.bounds.height / 2.0
                    self.updateItemView(pannedView, withPosition: newPosition)
                })
            }
            
        case .Cancelled, .Failed:
            break
        }
    }
    
    public override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        stopDecelerationAnimation()
    }
    
    // MARK: - Working with Data
    
    public func reloadData() {
        for itemView in itemViews {
            itemView.removeFromSuperview()
        }
        viewPositions.removeAll()
        absoluteOffset = 0
        
        itemViews = {
            guard let dataSource = dataSource else {
                return []
            }
            
            let numberOfItems = dataSource.numberOfItemsInCarouselView(self)
            
            return (0..<numberOfItems).map { (index) -> UIView in
                dataSource.carouselView(self, viewForItemAtIndex: index)
            }
        }()
        
        for itemView in itemViews {
            insertSubview(itemView, atIndex: 0)
            
            let panGesture = UIPanGestureRecognizer(target: self, action: #selector(itemViewDidPan))
            itemView.addGestureRecognizer(panGesture)
            panGesture.enabled = true
            // Give presedence to the horizontal pan gesture 
{'fraction_non_alphanumeric': 0.06602475928473177, 'fraction_numerical': 0.006266238728412044, 'mean_word_length': 1.8235167206040992, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5436145', 'n_tokens_mistral': 3506, 'n_tokens_neox': 3297, 'n_words': 902}