second commit
This commit is contained in:
@@ -18,6 +18,7 @@ enum ToolKind: String, CaseIterable {
|
||||
case highlight
|
||||
case obfuscate
|
||||
case counter
|
||||
case dropper
|
||||
case crop
|
||||
|
||||
var title: String {
|
||||
@@ -32,6 +33,7 @@ enum ToolKind: String, CaseIterable {
|
||||
case .highlight: return "Highlight"
|
||||
case .obfuscate: return "Obfuscate"
|
||||
case .counter: return "Step counter"
|
||||
case .dropper: return "Colour picker"
|
||||
case .crop: return "Crop"
|
||||
}
|
||||
}
|
||||
@@ -39,15 +41,16 @@ enum ToolKind: String, CaseIterable {
|
||||
var symbolName: String {
|
||||
switch self {
|
||||
case .select: return "cursorarrow"
|
||||
case .rectangle: return "rectangle"
|
||||
case .rectangle: return "square"
|
||||
case .ellipse: return "circle"
|
||||
case .line: return "line.diagonal"
|
||||
case .arrow: return "arrow.up.right"
|
||||
case .freehand: return "scribble"
|
||||
case .text: return "textformat"
|
||||
case .arrow: return "arrow.up.forward"
|
||||
case .freehand: return "scribble.variable"
|
||||
case .text: return "character.textbox"
|
||||
case .highlight: return "highlighter"
|
||||
case .obfuscate: return "square.grid.3x3.fill"
|
||||
case .counter: return "1.circle"
|
||||
case .counter: return "number.circle"
|
||||
case .dropper: return "eyedropper"
|
||||
case .crop: return "crop"
|
||||
}
|
||||
}
|
||||
@@ -64,6 +67,7 @@ enum ToolKind: String, CaseIterable {
|
||||
case .highlight: return "h"
|
||||
case .obfuscate: return "o"
|
||||
case .counter: return "c"
|
||||
case .dropper: return "i"
|
||||
case .crop: return "k"
|
||||
}
|
||||
}
|
||||
@@ -118,6 +122,8 @@ struct StyleCapabilities: OptionSet, Hashable {
|
||||
class Element {
|
||||
var frame: CGRect
|
||||
var style: ElementStyle
|
||||
/// Where the drag that created this element started; nil once the drag has finished.
|
||||
var creationAnchor: CGPoint?
|
||||
|
||||
init(frame: CGRect, style: ElementStyle) {
|
||||
self.frame = frame
|
||||
@@ -217,13 +223,61 @@ final class EllipseElement: Element {
|
||||
override func copyElement() -> Element { EllipseElement(frame: frame, style: style) }
|
||||
}
|
||||
|
||||
/// A straight line; `frame` stores start in origin and end in origin + size, so the size may be negative.
|
||||
/// A straight line between two explicit points.
|
||||
///
|
||||
/// The end points are stored separately rather than being derived from `frame`: CGRect
|
||||
/// reports `width` and `height` as absolute values, so a rect built with a negative size
|
||||
/// cannot describe a line that runs right-to-left or bottom-to-top. `frame` is kept in
|
||||
/// sync as the bounding box so selection and hit testing behave like every other element.
|
||||
class LineElement: Element {
|
||||
var start: CGPoint { frame.origin }
|
||||
var end: CGPoint { CGPoint(x: frame.origin.x + frame.width, y: frame.origin.y + frame.height) }
|
||||
private(set) var start: CGPoint
|
||||
private(set) var end: CGPoint
|
||||
|
||||
init(start: CGPoint, end: CGPoint, style: ElementStyle) {
|
||||
self.start = start
|
||||
self.end = end
|
||||
super.init(frame: LineElement.bounds(start, end), style: style)
|
||||
}
|
||||
|
||||
static func bounds(_ a: CGPoint, _ b: CGPoint) -> CGRect {
|
||||
CGRect(x: min(a.x, b.x), y: min(a.y, b.y),
|
||||
width: abs(b.x - a.x), height: abs(b.y - a.y))
|
||||
}
|
||||
|
||||
/// Moves the free end while the line is being drawn.
|
||||
func setEnd(_ point: CGPoint) {
|
||||
end = point
|
||||
frame = LineElement.bounds(start, end)
|
||||
}
|
||||
|
||||
var length: CGFloat { hypot(end.x - start.x, end.y - start.y) }
|
||||
|
||||
override var capabilities: StyleCapabilities { [.lineColor, .lineThickness, .shadow] }
|
||||
|
||||
override func move(by delta: CGVector) {
|
||||
start = CGPoint(x: start.x + delta.dx, y: start.y + delta.dy)
|
||||
end = CGPoint(x: end.x + delta.dx, y: end.y + delta.dy)
|
||||
frame = LineElement.bounds(start, end)
|
||||
}
|
||||
|
||||
/// Maps both end points into the new bounding box, preserving the line's direction.
|
||||
override func resize(to rect: CGRect) {
|
||||
let old = frame
|
||||
let target = CGRect(x: min(rect.minX, rect.maxX), y: min(rect.minY, rect.maxY),
|
||||
width: abs(rect.width), height: abs(rect.height))
|
||||
func map(_ point: CGPoint) -> CGPoint {
|
||||
let fractionX = old.width > 0 ? (point.x - old.minX) / old.width : 0
|
||||
let fractionY = old.height > 0 ? (point.y - old.minY) / old.height : 0
|
||||
return CGPoint(x: target.minX + fractionX * target.width,
|
||||
y: target.minY + fractionY * target.height)
|
||||
}
|
||||
let newStart = map(start)
|
||||
let newEnd = map(end)
|
||||
start = newStart
|
||||
end = newEnd
|
||||
frame = LineElement.bounds(start, end)
|
||||
}
|
||||
|
||||
override func draw(in context: CGContext, baseImage: CGImage?, imageSize: CGSize) {
|
||||
context.saveGState()
|
||||
applyShadow(context)
|
||||
@@ -249,7 +303,7 @@ class LineElement: Element {
|
||||
return hypot(point.x - (a.x + t * dx), point.y - (a.y + t * dy))
|
||||
}
|
||||
|
||||
override func copyElement() -> Element { LineElement(frame: frame, style: style) }
|
||||
override func copyElement() -> Element { LineElement(start: start, end: end, style: style) }
|
||||
}
|
||||
|
||||
final class ArrowElement: LineElement {
|
||||
@@ -282,7 +336,7 @@ final class ArrowElement: LineElement {
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
override func copyElement() -> Element { ArrowElement(frame: frame, style: style) }
|
||||
override func copyElement() -> Element { ArrowElement(start: start, end: end, style: style) }
|
||||
}
|
||||
|
||||
final class FreehandElement: Element {
|
||||
|
||||
Reference in New Issue
Block a user