X-Git-Url: https://git.mdrn.pl/wl-app.git/blobdiff_plain/53b27422d140022594fc241cca91c3183be57bca..48b2fe9f7c2dc3d9aeaaa6dbfb27c7da4f3235ff:/iOS/Pods/FolioReaderKit/Vendor/HAControls/HADiscreteSlider.swift diff --git a/iOS/Pods/FolioReaderKit/Vendor/HAControls/HADiscreteSlider.swift b/iOS/Pods/FolioReaderKit/Vendor/HAControls/HADiscreteSlider.swift new file mode 100644 index 0000000..8c65c5b --- /dev/null +++ b/iOS/Pods/FolioReaderKit/Vendor/HAControls/HADiscreteSlider.swift @@ -0,0 +1,444 @@ +// +// HADiscreteSlider.swift +// FolioReaderKit +// +// Created by Heberti Almeida on 12/02/15. +// Copyright (c) 2015 Folio Reader. All rights reserved. +// + +import UIKit + +enum ComponentStyle: Int { + case ios + case rectangular + case rounded + case invisible + case image +} + +let iOSThumbShadowRadius: CGFloat = 4.0 +let iosThumbShadowOffset = CGSize(width: 0, height: 3) + +class HADiscreteSlider : UIControl { + + func ticksDistanceChanged(_ ticksDistance: CGFloat, sender: AnyObject) { } + func valueChanged(_ value: CGFloat) { } + + // MARK: properties + + var tickStyle: ComponentStyle = ComponentStyle.rectangular { + didSet { self.layoutTrack() } + } + + var tickSize: CGSize = CGSize(width: 1.0, height: 4.0) { + willSet (value) { + self.tickSize.width = max(0, value.width) + self.tickSize.height = max(0, value.height) + self.layoutTrack() + } + } + + var tickCount: Int = 11 { + willSet (value) { + self.tickCount = max(2, value) + self.layoutTrack() + } + } + + var ticksDistance: CGFloat { + get { + assert(self.tickCount > 1, "2 ticks minimum \(self.tickCount)") + let segments = CGFloat(max(1, self.tickCount-1)) + return (self.trackRectangle!.size.width/segments) + } + } + + var tickImage: String? { + didSet { self.layoutTrack() } + } + + var trackStyle: ComponentStyle = ComponentStyle.ios { + didSet { self.layoutTrack() } + } + + var trackThickness: CGFloat = 2.0 { + willSet (value) { + self.trackThickness = max(0, value) + self.layoutTrack() + } + } + + var trackImage: String? { + didSet { self.layoutTrack() } + } + + var thumbStyle: ComponentStyle = ComponentStyle.ios { + didSet { self.layoutTrack() } + } + + var thumbSize: CGSize = CGSize(width: 10.0, height: 10.0) { + willSet (value) { + self.thumbSize.width = max(1, value.width) + self.thumbSize.height = max(1, value.height) + self.layoutTrack() + } + } + + var thumbShadowRadius: CGFloat = 0.0 { + didSet { self.layoutTrack() } + } + + var thumbImage: String? { + willSet (value) { + self.thumbImage = value + // Associate image to layer + if let imageName = value { + let image: UIImage = UIImage(named: imageName)! + self.thumbLayer!.contents = image.cgImage + } + self.layoutTrack() + } + } + + // AKA: UISlider value (as CGFloat for compatibility with UISlider API, but expected to contain integers) + var minimumValue: CGFloat { + get { return CGFloat(self._intMinimumValue!) } // calculated property, with a float-to-int adapter + set (value) { + _intMinimumValue = Int(value); + self.layoutTrack() + } + } + + var value: CGFloat { + get { return CGFloat(self._intValue!) } + set (value) { + let rootValue = ((value - self.minimumValue) / self.incrementValue) + _intValue = Int(self.minimumValue+(rootValue * self.incrementValue)) + self.layoutTrack() + } + } + + var incrementValue: CGFloat = 1 { + willSet (value) { + self.incrementValue = value + if 0 == incrementValue { + self.incrementValue = 1 // nonZeroIncrement + } + self.layoutTrack() + } + } + + var thumbColor: UIColor? + var thumbShadowOffset: CGSize? + var _intValue: Int? + var _intMinimumValue: Int? + var ticksAbscisses = [CGPoint]() + var thumbAbscisse: CGFloat? + var thumbLayer: CALayer? + var colorTrackLayer: CALayer? + var trackRectangle: CGRect! + + // When bounds change, recalculate layout +// func setBounds(bounds: CGRect) { +// super.bounds = bounds +// self.layoutTrack() +// self.setNeedsDisplay() +// } + + override init(frame: CGRect) { + super.init(frame: frame) + self.initProperties() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func draw(_ rect: CGRect) { + self.drawTrack() + self.drawThumb() + } + + func sendActionsForControlEvents() { + self.sendActions(for: UIControlEvents.valueChanged) + } + + // MARK: HADiscreteSlider + + func initProperties() { + self.thumbColor = UIColor.lightGray + self.thumbShadowOffset = CGSize.zero + _intMinimumValue = -5 + _intValue = 0 + self.thumbAbscisse = 0.0 + self.trackRectangle = CGRect.zero + // In case we need a colored track, initialize it now + // There may be a more elegant way to do this than with a CALayer, + // but then again CALayer brings free animation and will animate along the thumb + self.colorTrackLayer = CALayer() + self.colorTrackLayer!.backgroundColor = UIColor(hue: 211.0/360.0, saturation: 1, brightness: 1, alpha: 1).cgColor + self.colorTrackLayer!.cornerRadius = 2.0 + self.layer.addSublayer(self.colorTrackLayer!) + // The thumb is its own CALayer, which brings in free animation + self.thumbLayer = CALayer() + self.layer.addSublayer(self.thumbLayer!) + self.isMultipleTouchEnabled = false + self.layoutTrack() + } + + func drawTrack() { + let ctx = UIGraphicsGetCurrentContext() + // Track + switch self.trackStyle { + case .rectangular: + ctx!.addRect(self.trackRectangle) + break + case .image: + + // Draw image if exists + if let imageName = self.trackImage { + let image = UIImage(named:imageName)! + let centered = CGRect(x: (self.frame.size.width/2)-(image.size.width/2), y: (self.frame.size.height/2)-(image.size.height/2), width: image.size.width, height: image.size.height) + ctx!.draw(image.cgImage!, in: centered) + } + break + + case .invisible, .rounded, .ios: + let path: UIBezierPath = UIBezierPath(roundedRect: self.trackRectangle, cornerRadius: self.trackRectangle.size.height/2) + ctx!.addPath(path.cgPath) + break + } + + // Ticks + if .ios != self.tickStyle { + for originValue in self.ticksAbscisses { + let originPoint = originValue + let rectangle = CGRect(x: originPoint.x-(self.tickSize.width/2), y: originPoint.y-(self.tickSize.height/2), width: self.tickSize.width, height: self.tickSize.height) + switch self.tickStyle { + case .rounded: + let path = UIBezierPath(roundedRect: rectangle, cornerRadius: rectangle.size.height/2) + ctx!.addPath(path.cgPath) + break + case .rectangular: + ctx!.addRect(rectangle) + break + case .image: + // Draw image if exists + + if let imageName = self.tickImage { + let image = UIImage(named: imageName)! + let centered = CGRect(x: rectangle.origin.x+(rectangle.size.width/2)-(image.size.width/2), y: rectangle.origin.y+(rectangle.size.height/2)-(image.size.height/2), width: image.size.width, height: image.size.height) + ctx!.draw(image.cgImage!, in: centered) + } + break + + case .invisible: break + case .ios: break + } + } + } + + // iOS UISlider aka .IOS does not have ticks + ctx!.setFillColor(self.tintColor.cgColor) + ctx!.fillPath() + // For colored track, we overlay a CALayer, which will animate along with the cursor + if .ios == self.trackStyle { + var frame = self.trackRectangle + frame?.size.width = self.thumbAbscisse!-self.trackRectangle.minX + self.colorTrackLayer!.frame = frame! + } else { + self.colorTrackLayer!.frame = CGRect.zero + } + } + + func drawThumb() { + if self.value >= self.minimumValue { + // Feature: hide the thumb when below range + let thumbSizeForStyle = self.thumbSizeIncludingShadow() + let thumbWidth = thumbSizeForStyle.width + let thumbHeight = thumbSizeForStyle.height + let rectangle = CGRect(x: self.thumbAbscisse!-(thumbWidth/2), y: (self.frame.size.height-thumbHeight)/2, width: thumbWidth, height: thumbHeight) + let shadowRadius = ((self.thumbStyle == .ios) ? iOSThumbShadowRadius : self.thumbShadowRadius) + let shadowOffset = ((self.thumbStyle == .ios) ? iosThumbShadowOffset : self.thumbShadowOffset) + // Ignore offset if there is no shadow + self.thumbLayer!.frame = ((shadowRadius != 0.0) ? rectangle.insetBy(dx: shadowRadius+shadowOffset!.width, dy: shadowRadius+shadowOffset!.height) : rectangle.insetBy(dx: shadowRadius, dy: shadowRadius)) + switch self.thumbStyle { + case .rounded: + // A rounded thumb is circular + self.thumbLayer!.backgroundColor = self.thumbColor!.cgColor + self.thumbLayer!.borderColor = UIColor.clear.cgColor + self.thumbLayer!.borderWidth = 0.0 + self.thumbLayer!.cornerRadius = self.thumbLayer!.frame.size.width/2 + self.thumbLayer!.allowsEdgeAntialiasing = true + break + + case .image: + // image is set using layer.contents + self.thumbLayer!.backgroundColor = UIColor.clear.cgColor + self.thumbLayer!.borderColor = UIColor.clear.cgColor + self.thumbLayer!.borderWidth = 0.0 + self.thumbLayer!.cornerRadius = 0.0 + self.thumbLayer!.allowsEdgeAntialiasing = false + break + + case .rectangular: + self.thumbLayer!.backgroundColor = self.thumbColor!.cgColor + self.thumbLayer!.borderColor = UIColor.clear.cgColor + self.thumbLayer!.borderWidth = 0.0 + self.thumbLayer!.cornerRadius = 0.0 + self.thumbLayer!.allowsEdgeAntialiasing = false + break + + case .invisible: + self.thumbLayer!.backgroundColor = UIColor.clear.cgColor + self.thumbLayer!.cornerRadius = 0.0 + break + + case .ios: + self.thumbLayer!.backgroundColor = UIColor.white.cgColor + self.thumbLayer!.borderColor = UIColor(hue: 0, saturation: 0, brightness: 0.8, alpha: 1).cgColor + self.thumbLayer!.borderWidth = 0.5 + self.thumbLayer!.cornerRadius = self.thumbLayer!.frame.size.width/2 + self.thumbLayer!.allowsEdgeAntialiasing = true + break + } + + + // Shadow + if shadowRadius != 0.0 { + self.thumbLayer!.shadowOffset = shadowOffset! + self.thumbLayer!.shadowRadius = shadowRadius + self.thumbLayer!.shadowColor = UIColor.black.cgColor + self.thumbLayer!.shadowOpacity = 0.15 + } else { + self.thumbLayer!.shadowRadius = 0.0 + self.thumbLayer!.shadowOffset = CGSize.zero + self.thumbLayer!.shadowColor = UIColor.clear.cgColor + self.thumbLayer!.shadowOpacity = 0.0 + } + } + } + + func layoutTrack() { + assert(self.tickCount > 1, "2 ticks minimum \(self.tickCount)") + let segments = max(1, self.tickCount-1) + let thumbWidth = self.thumbSizeIncludingShadow().width + + // Calculate the track ticks positions + let trackHeight = ((.ios == self.trackStyle) ? 2.0 : self.trackThickness) + var trackSize = CGSize(width: self.frame.size.width-thumbWidth, height: trackHeight) + if .image == self.trackStyle { + if let imageName = self.trackImage { + let image = UIImage(named: imageName)! + trackSize.width = image.size.width-thumbWidth + } + } + self.trackRectangle = CGRect(x: (self.frame.size.width-trackSize.width)/2, y: (self.frame.size.height-trackSize.height)/2, width: trackSize.width, height: trackSize.height) + let trackY = self.frame.size.height/2 + + self.ticksAbscisses.removeAll() + + for i in 0...segments { + let ratio = Double(i) / Double(segments) + let originX = self.trackRectangle.origin.x+(trackSize.width * CGFloat(ratio)) + let point = CGPoint(x:originX, y:trackY) + self.ticksAbscisses.append(point) + } + + self.layoutThumb() + } + + func layoutThumb() { + assert(self.tickCount > 1, "2 ticks minimum \(self.tickCount)") + let segments = max(1, self.tickCount-1) + // Calculate the thumb position + var thumbRatio = (self.value-self.minimumValue) / CGFloat(segments) * self.incrementValue + thumbRatio = max(0.0, min(thumbRatio, 1.0)) + // Normalized + self.thumbAbscisse = self.trackRectangle.origin.x+(self.trackRectangle.size.width*thumbRatio) + } + + func thumbSizeIncludingShadow() -> CGSize { + switch self.thumbStyle { + case .invisible: break + case .rectangular: break + case .rounded: + return ((self.thumbShadowRadius != 0.0) ? CGSize(width: self.thumbSize.width+(self.thumbShadowRadius*2)+(self.thumbShadowOffset!.width*2), height: self.thumbSize.height+(self.thumbShadowRadius*2)+(self.thumbShadowOffset!.height*2)) : self.thumbSize) + case .ios: + return CGSize(width: 33.0+(iOSThumbShadowRadius*2)+(iosThumbShadowOffset.width*2), height: 33.0+(iOSThumbShadowRadius*2)+(iosThumbShadowOffset.height*2)) + case .image: + if let imageName = self.thumbImage { + return UIImage(named: imageName)!.size + } + } + return CGSize(width: 33.0, height: 33.0) + } + + // MARK: Touches + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + self.touchDown(touches, duration: 0.1) + } + + override func touchesMoved(_ touches: Set, with event: UIEvent?) { + self.touchDown(touches, duration: 0.0) + } + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + self.touchUp(touches) + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + self.touchUp(touches) + } + + func touchDown(_ touches: Set, duration: TimeInterval) { + guard let touch = touches.first else { return } + let location = touch.location(in: touch.view) + self.moveThumbTo(location.x, duration: duration) + } + + func touchUp(_ touches: Set) { + guard let touch = touches.first else { return } + let location = touch.location(in: touch.view) + let tick = self.pickTickFromSliderPosition(location.x) + self.moveThumbToTick(tick) + } + + // MARK: Notifications + + func moveThumbToTick(_ tick: Int) { + let intValue = Int(self.minimumValue)+(tick * Int(self.incrementValue)) + if intValue != _intValue { + _intValue = intValue + self.sendActionsForControlEvents() + } + self.layoutThumb() + self.setNeedsDisplay() + } + + func moveThumbTo(_ abscisse: CGFloat, duration: CFTimeInterval) { + let leftMost = self.trackRectangle.minX + let rightMost = self.trackRectangle.maxX + self.thumbAbscisse = max(leftMost, min(abscisse, rightMost)) + CATransaction.setAnimationDuration(duration) + let tick = self.pickTickFromSliderPosition(self.thumbAbscisse!) + let intValue = Int(self.minimumValue)+(tick * Int(self.incrementValue)) + if intValue != _intValue { + _intValue = intValue + self.sendActionsForControlEvents() + } + self.setNeedsDisplay() + } + + func pickTickFromSliderPosition(_ abscisse: CGFloat) -> Int { + let leftMost = self.trackRectangle.minX + let rightMost = self.trackRectangle.maxX + let clampedAbscisse = max(leftMost, min(abscisse, rightMost)) + let ratio = Double(clampedAbscisse-leftMost) / Double(rightMost-leftMost) + let segments = Double(max(1, self.tickCount-1)) + return Int(round(segments*ratio)) + } + +} +