LeadKit/TIMapUtils/Sources/Drawing/Operations/CurrentLocationDrawingOpera...

164 lines
5.8 KiB
Swift

//
// Copyright (c) 2023 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics
public struct CurrentLocationDrawingOperation: DrawingOperation {
public enum Defaults {
public static var iconSize: CGSize {
CGSize(width: 27, height: 27)
}
public static var borderWidth: CGFloat {
2
}
}
public var iconSize: CGSize
public var borderWidth: CGFloat
public var mainColor: CGColor
public var borderColor: CGColor
public var backgroundColor: CGColor
public var showHeadingArrow: Bool
private var innerCircleSize: CGSize {
CGSize(width: innerBorderSize.width - borderWidth,
height: innerBorderSize.height - borderWidth)
}
private var innerBorderSize: CGSize {
CGSize(width: outerBorderSize.width - borderWidth,
height: outerBorderSize.height - borderWidth)
}
private var outerBorderSize: CGSize {
CGSize(width: iconSize.width - 8,
height: iconSize.height - 8)
}
private var innerCircleOrigin: CGPoint {
CGPoint(x: (iconSize.width - innerCircleSize.width) / 2,
y: (iconSize.height - innerCircleSize.height) / 2)
}
private var innerBorderOrigin: CGPoint {
CGPoint(x: (iconSize.width - innerBorderSize.width) / 2,
y: (iconSize.height - innerBorderSize.height) / 2)
}
private var outerBorderOrigin: CGPoint {
CGPoint(x: (iconSize.width - outerBorderSize.width) / 2,
y: (iconSize.height - outerBorderSize.height) / 2)
}
private var iconRect: CGRect {
CGRect(origin: .zero,
size: iconSize)
}
private var innerCircleRect: CGRect {
CGRect(origin: innerCircleOrigin,
size: innerCircleSize)
}
private var innerBorderRect: CGRect {
CGRect(origin: innerBorderOrigin,
size: innerBorderSize)
}
private var outerBorderRect: CGRect {
CGRect(origin: outerBorderOrigin,
size: outerBorderSize)
}
public init(iconSize: CGSize = Defaults.iconSize,
borderWidth: CGFloat = Defaults.borderWidth,
mainColor: CGColor,
borderColor: CGColor,
showHeadingArrow: Bool = true) {
self.iconSize = iconSize
self.borderWidth = borderWidth
self.mainColor = mainColor
self.borderColor = borderColor
self.backgroundColor = mainColor.copy(alpha: 0.3) ?? mainColor
self.showHeadingArrow = showHeadingArrow
}
public func affectedArea(in context: CGContext? = nil) -> CGRect {
iconRect
}
public func apply(in context: CGContext) {
let backgroundDrawing = SolidFillDrawingOperation(color: backgroundColor,
ellipseRect: iconRect)
let innerCircleDrawing = SolidFillDrawingOperation(color: mainColor,
ellipseRect: innerCircleRect)
let innerBorderDrawing = BorderDrawingOperation(frameableContentRect: innerBorderRect,
border: borderWidth,
color: borderColor,
radius: min(innerBorderSize.width, innerBorderSize.height) / 2,
exteriorBorder: false)
let outerBorderDrawing = BorderDrawingOperation(frameableContentRect: outerBorderRect,
border: borderWidth,
color: backgroundColor,
radius: min(outerBorderSize.width, outerBorderSize.height) / 2,
exteriorBorder: false)
// flip horizontally
context.translateBy(x: 0, y: iconSize.height)
context.scaleBy(x: 1, y: -1)
backgroundDrawing.apply(in: context)
context.setFillColor(mainColor)
context.setLineWidth(.zero)
guard showHeadingArrow else {
innerCircleDrawing.apply(in: context)
innerBorderDrawing.apply(in: context)
return
}
context.addLines(between: [
CGPoint(x: innerBorderRect.minX, y: innerBorderRect.midY),
CGPoint(x: iconRect.midX, y: iconRect.maxY),
CGPoint(x: innerBorderRect.maxX, y: innerBorderRect.midY)
])
context.drawPath(using: .fillStroke)
innerCircleDrawing.apply(in: context)
innerBorderDrawing.apply(in: context)
context.setBlendMode(.destinationAtop)
outerBorderDrawing.apply(in: context)
}
}