Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
315 changes: 311 additions & 4 deletions Free Ruler/Ruler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,311 @@ enum RulerVerticalSide: Equatable {
}
}

struct RulerSettings: Equatable, Codable {
var unit: Unit
var rulerColor: NSColor
var foregroundOpacity: Int
var backgroundOpacity: Int
var floatRulers: Bool
var rulerShadow: Bool
var zeroCorner: ZeroCorner

init(
unit: Unit = .pixels,
rulerColor: NSColor = Prefs.defaultRulerFillColor,
foregroundOpacity: Int = 90,
backgroundOpacity: Int = 50,
floatRulers: Bool = true,
rulerShadow: Bool = false,
zeroCorner: ZeroCorner = Prefs.defaultZeroCorner
) {
self.unit = unit
self.rulerColor = RulerSettings.normalizedColor(rulerColor)
self.foregroundOpacity = foregroundOpacity
self.backgroundOpacity = backgroundOpacity
self.floatRulers = floatRulers
self.rulerShadow = rulerShadow
self.zeroCorner = zeroCorner
}

init(defaults: Prefs = prefs) {
self.init(
unit: defaults.unit,
rulerColor: defaults.rulerColor,
foregroundOpacity: defaults.foregroundOpacity,
backgroundOpacity: defaults.backgroundOpacity,
floatRulers: defaults.floatRulers,
rulerShadow: defaults.rulerShadow,
zeroCorner: defaults.zeroCorner
)
}

static func == (lhs: RulerSettings, rhs: RulerSettings) -> Bool {
return lhs.unit == rhs.unit
&& Prefs.colorsMatch(lhs.rulerColor, rhs.rulerColor)
&& lhs.foregroundOpacity == rhs.foregroundOpacity
&& lhs.backgroundOpacity == rhs.backgroundOpacity
&& lhs.floatRulers == rhs.floatRulers
&& lhs.rulerShadow == rhs.rulerShadow
&& lhs.zeroCorner == rhs.zeroCorner
}

private enum CodingKeys: String, CodingKey {
case unit
case rulerColor
case foregroundOpacity
case backgroundOpacity
case floatRulers
case rulerShadow
case zeroCorner
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let unitRawValue = try container.decodeIfPresent(Int.self, forKey: .unit) ?? Unit.pixels.rawValue
let zeroCornerRawValue = try container.decodeIfPresent(Int.self, forKey: .zeroCorner)
?? Prefs.defaultZeroCorner.rawValue
let colorComponents = try container.decodeIfPresent(RulerColorComponents.self, forKey: .rulerColor)

self.init(
unit: Unit(rawValue: unitRawValue) ?? .pixels,
rulerColor: colorComponents?.color ?? Prefs.defaultRulerFillColor,
foregroundOpacity: try container.decodeIfPresent(Int.self, forKey: .foregroundOpacity) ?? 90,
backgroundOpacity: try container.decodeIfPresent(Int.self, forKey: .backgroundOpacity) ?? 50,
floatRulers: try container.decodeIfPresent(Bool.self, forKey: .floatRulers) ?? true,
rulerShadow: try container.decodeIfPresent(Bool.self, forKey: .rulerShadow) ?? false,
zeroCorner: Prefs.zeroCorner(fromRawValue: zeroCornerRawValue)
)
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)

try container.encode(unit.rawValue, forKey: .unit)
try container.encode(RulerColorComponents(color: rulerColor), forKey: .rulerColor)
try container.encode(foregroundOpacity, forKey: .foregroundOpacity)
try container.encode(backgroundOpacity, forKey: .backgroundOpacity)
try container.encode(floatRulers, forKey: .floatRulers)
try container.encode(rulerShadow, forKey: .rulerShadow)
try container.encode(zeroCorner.rawValue, forKey: .zeroCorner)
}

private static func normalizedColor(_ color: NSColor) -> NSColor {
guard let rgbColor = color.usingColorSpace(.deviceRGB) else {
return Prefs.defaultRulerFillColor
}

return NSColor(
deviceRed: rgbColor.redComponent,
green: rgbColor.greenComponent,
blue: rgbColor.blueComponent,
alpha: 1
)
}
}

private struct RulerColorComponents: Equatable, Codable {
let red: CGFloat
let green: CGFloat
let blue: CGFloat
let alpha: CGFloat

init(color: NSColor) {
let rgbColor = color.usingColorSpace(.deviceRGB) ?? Prefs.defaultRulerFillColor

red = rgbColor.redComponent
green = rgbColor.greenComponent
blue = rgbColor.blueComponent
alpha = rgbColor.alphaComponent
}

var color: NSColor {
return NSColor(
deviceRed: red,
green: green,
blue: blue,
alpha: alpha
)
}
}

struct RulerWingVisibility: Equatable, Codable {
private(set) var showsHorizontal: Bool
private(set) var showsVertical: Bool

init(horizontal: Bool = true, vertical: Bool = true) {
if horizontal || vertical {
showsHorizontal = horizontal
showsVertical = vertical
} else {
showsHorizontal = true
showsVertical = true
}
}

var hasVisibleWing: Bool {
return showsHorizontal || showsVertical
}

func isVisible(_ orientation: Orientation) -> Bool {
switch orientation {
case .horizontal:
return showsHorizontal
case .vertical:
return showsVertical
}
}

@discardableResult
mutating func toggle(_ orientation: Orientation) -> Bool {
return set(orientation, isVisible: !isVisible(orientation))
}

@discardableResult
mutating func set(_ orientation: Orientation, isVisible: Bool) -> Bool {
guard canSet(orientation, isVisible: isVisible) else { return false }

switch orientation {
case .horizontal:
showsHorizontal = isVisible
case .vertical:
showsVertical = isVisible
}

return true
}

private func canSet(_ orientation: Orientation, isVisible: Bool) -> Bool {
guard !isVisible, self.isVisible(orientation) else { return true }

switch orientation {
case .horizontal:
return showsVertical
case .vertical:
return showsHorizontal
}
}

private enum CodingKeys: String, CodingKey {
case showsHorizontal
case showsVertical
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

self.init(
horizontal: try container.decodeIfPresent(Bool.self, forKey: .showsHorizontal) ?? true,
vertical: try container.decodeIfPresent(Bool.self, forKey: .showsVertical) ?? true
)
}
}

struct RulerLayoutState: Equatable, Codable {
var zeroPoint: NSPoint
var horizontalLength: CGFloat
var verticalLength: CGFloat

init(
zeroPoint: NSPoint,
horizontalLength: CGFloat,
verticalLength: CGFloat
) {
self.zeroPoint = zeroPoint
self.horizontalLength = max(0, horizontalLength)
self.verticalLength = max(0, verticalLength)
}

init(
horizontalFrame: NSRect,
verticalFrame: NSRect,
zeroCorner: ZeroCorner
) {
self.init(
zeroPoint: ZeroCornerGeometry(zeroCorner: zeroCorner).zeroPoint(
in: horizontalFrame,
for: .horizontal
),
horizontalLength: horizontalFrame.width,
verticalLength: verticalFrame.height
)
}

static func defaults(
zeroCorner: ZeroCorner,
screenFrame: NSRect = defaultRulerScreenFrame()
) -> RulerLayoutState {
let geometry = ZeroCornerGeometry(zeroCorner: zeroCorner)

return RulerLayoutState(
horizontalFrame: geometry.defaultFrame(for: .horizontal, screenFrame: screenFrame),
verticalFrame: geometry.defaultFrame(for: .vertical, screenFrame: screenFrame),
zeroCorner: zeroCorner
)
}

func layout(zeroCorner: ZeroCorner) -> GroupedRulerLayout {
return GroupedRulerLayout.layout(
horizontalLength: horizontalLength,
verticalLength: verticalLength,
zeroPoint: zeroPoint,
zeroCorner: zeroCorner
)
}
}

struct RulerInstanceState: Identifiable, Equatable, Codable {
var id: UUID
var settings: RulerSettings
var visibility: RulerWingVisibility
var layout: RulerLayoutState

init(
id: UUID = UUID(),
settings: RulerSettings,
visibility: RulerWingVisibility = RulerWingVisibility(),
layout: RulerLayoutState
) {
self.id = id
self.settings = settings
self.visibility = visibility
self.layout = layout
}

static func createFromDefaults(
id: UUID = UUID(),
defaults: RulerSettings = RulerSettings(defaults: prefs),
screenFrame: NSRect = defaultRulerScreenFrame()
) -> RulerInstanceState {
return RulerInstanceState(
id: id,
settings: defaults,
layout: RulerLayoutState.defaults(
zeroCorner: defaults.zeroCorner,
screenFrame: screenFrame
)
)
}

var hasVisibleWing: Bool {
return visibility.hasVisibleWing
}

func isWingVisible(_ orientation: Orientation) -> Bool {
return visibility.isVisible(orientation)
}

@discardableResult
mutating func toggleWing(_ orientation: Orientation) -> Bool {
return visibility.toggle(orientation)
}

@discardableResult
mutating func setWing(_ orientation: Orientation, isVisible: Bool) -> Bool {
return visibility.set(orientation, isVisible: isVisible)
}
}

struct RulerCornerPlacement: Equatable {
let xSide: RulerHorizontalSide
let ySide: RulerVerticalSide
Expand Down Expand Up @@ -271,15 +576,17 @@ func getDefaultContentRect(orientation: Orientation) -> NSRect {
}

func getDefaultContentRect(orientation: Orientation, zeroCorner: ZeroCorner) -> NSRect {
let fallbackScreenFrame = NSRect(x: 0, y: 0, width: 1000, height: 800)
let screenFrame = NSScreen.main?.frame ?? fallbackScreenFrame

return ZeroCornerGeometry(zeroCorner: zeroCorner).defaultFrame(
for: orientation,
screenFrame: screenFrame
screenFrame: defaultRulerScreenFrame()
)
}

func defaultRulerScreenFrame() -> NSRect {
let fallbackScreenFrame = NSRect(x: 0, y: 0, width: 1000, height: 800)
return NSScreen.main?.frame ?? fallbackScreenFrame
}

func getMinSize(ruler: Ruler) -> NSSize {
switch ruler.orientation {
case .horizontal:
Expand Down
Loading