From bb6193431d350c024edeac4e27e82dd55ef9343d Mon Sep 17 00:00:00 2001 From: Pascal Date: Fri, 12 Jun 2026 23:42:02 -0400 Subject: [PATCH 1/2] Persist zero corner preference --- Free Ruler/AppDelegate.swift | 2 + Free Ruler/Prefs.swift | 12 +++++- Free Ruler/Ruler.swift | 7 ++++ FreeRulerTests/RulerCoreTests.swift | 65 +++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 1 deletion(-) diff --git a/Free Ruler/AppDelegate.swift b/Free Ruler/AppDelegate.swift index 41c91ee..b69c122 100644 --- a/Free Ruler/AppDelegate.swift +++ b/Free Ruler/AppDelegate.swift @@ -262,6 +262,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { "backgroundOpacity", "rulerColor", "unit", + "zeroCorner", "NSWindow Frame horizontal-ruler", "NSWindow Frame vertical-ruler", "NSWindow Frame preferencesWindow", @@ -274,6 +275,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { prefs.backgroundOpacity = 50 prefs.rulerColor = Prefs.defaultRulerFillColor prefs.unit = .pixels + prefs.zeroCorner = .topLeft } func createRulersIfNeeded() { diff --git a/Free Ruler/Prefs.swift b/Free Ruler/Prefs.swift index 73426de..62fc51a 100644 --- a/Free Ruler/Prefs.swift +++ b/Free Ruler/Prefs.swift @@ -32,6 +32,7 @@ class Prefs: NSObject { @objc dynamic var backgroundOpacity : Int @objc dynamic var rulerColor : NSColor @objc dynamic var unit : Unit + @objc dynamic var zeroCorner : ZeroCorner // MARK: - public save method func save() { @@ -58,7 +59,8 @@ class Prefs: NSObject { "rulerShadow": false, "foregroundOpacity": 90, "backgroundOpacity": 50, - "unit": Unit.pixels.rawValue + "unit": Unit.pixels.rawValue, + "zeroCorner": ZeroCorner.topLeft.rawValue ] if let defaultRulerColorData = Prefs.defaultRulerColorData { @@ -78,6 +80,7 @@ class Prefs: NSObject { backgroundOpacity = defaults.integer(forKey: "backgroundOpacity") rulerColor = Prefs.rulerFillColor(fromArchivedData: defaults.data(forKey: "rulerColor")) unit = Unit(rawValue: defaults.integer(forKey: "unit")) ?? .pixels + zeroCorner = Prefs.zeroCorner(fromRawValue: defaults.integer(forKey: "zeroCorner")) super.init() @@ -117,6 +120,9 @@ class Prefs: NSObject { observe(\Prefs.unit, options: .new) { prefs, changed in self.defaults.set(prefs.unit.rawValue, forKey: "unit") }, + observe(\Prefs.zeroCorner, options: .new) { prefs, changed in + self.defaults.set(prefs.zeroCorner.rawValue, forKey: "zeroCorner") + }, ] } @@ -131,6 +137,10 @@ extension Prefs { return normalizedRulerColor(unarchiveColor(data) ?? defaultRulerColor) } + static func zeroCorner(fromRawValue rawValue: Int) -> ZeroCorner { + return ZeroCorner(rawValue: rawValue) ?? .topLeft + } + private static func archivedColorData(_ color: NSColor) -> Data? { return try? NSKeyedArchiver.archivedData( withRootObject: color, diff --git a/Free Ruler/Ruler.swift b/Free Ruler/Ruler.swift index 339046d..1ec8e8d 100644 --- a/Free Ruler/Ruler.swift +++ b/Free Ruler/Ruler.swift @@ -5,6 +5,13 @@ enum Orientation: String { case vertical } +@objc enum ZeroCorner: Int { + case topLeft + case topRight + case bottomLeft + case bottomRight +} + class Ruler { static let thickness: CGFloat = 40 diff --git a/FreeRulerTests/RulerCoreTests.swift b/FreeRulerTests/RulerCoreTests.swift index 4e0f697..3076427 100644 --- a/FreeRulerTests/RulerCoreTests.swift +++ b/FreeRulerTests/RulerCoreTests.swift @@ -19,6 +19,43 @@ final class RulerCoreTests: XCTestCase { XCTAssertEqual(ruler.name, "test-ruler") } + func testZeroCornerRawValuesPreservePersistedOrder() { + XCTAssertEqual(ZeroCorner.topLeft.rawValue, 0) + XCTAssertEqual(ZeroCorner.topRight.rawValue, 1) + XCTAssertEqual(ZeroCorner.bottomLeft.rawValue, 2) + XCTAssertEqual(ZeroCorner.bottomRight.rawValue, 3) + } + + func testZeroCornerLoadsFromRawValue() { + XCTAssertEqual(Prefs.zeroCorner(fromRawValue: ZeroCorner.topLeft.rawValue), .topLeft) + XCTAssertEqual(Prefs.zeroCorner(fromRawValue: ZeroCorner.topRight.rawValue), .topRight) + XCTAssertEqual(Prefs.zeroCorner(fromRawValue: ZeroCorner.bottomLeft.rawValue), .bottomLeft) + XCTAssertEqual(Prefs.zeroCorner(fromRawValue: ZeroCorner.bottomRight.rawValue), .bottomRight) + } + + func testZeroCornerDefaultsToTopLeftForUnknownRawValue() { + XCTAssertEqual(Prefs.zeroCorner(fromRawValue: -1), .topLeft) + XCTAssertEqual(Prefs.zeroCorner(fromRawValue: 99), .topLeft) + } + + func testZeroCornerPreferencePersistsToUserDefaults() { + withRestoredZeroCornerPreference { + prefs.zeroCorner = .bottomRight + + XCTAssertEqual( + UserDefaults.standard.integer(forKey: "zeroCorner"), + ZeroCorner.bottomRight.rawValue + ) + + prefs.zeroCorner = .topRight + + XCTAssertEqual( + UserDefaults.standard.integer(forKey: "zeroCorner"), + ZeroCorner.topRight.rawValue + ) + } + } + func testMinAndMaxSizesMatchRulerOrientation() { let horizontal = Ruler(.horizontal, frame: NSRect(x: 0, y: 0, width: 300, height: 40)) let vertical = Ruler(.vertical, frame: NSRect(x: 0, y: 0, width: 40, height: 300)) @@ -706,6 +743,34 @@ final class RulerCoreTests: XCTestCase { try test() } + + private func withRestoredZeroCornerPreference(_ test: () throws -> Void) rethrows { + let defaults = UserDefaults.standard + let previousZeroCorner = prefs.zeroCorner + let domainName = Bundle.main.bundleIdentifier + let previousDomainValue = domainName + .flatMap { defaults.persistentDomain(forName: $0)?["zeroCorner"] } + + defer { + prefs.zeroCorner = previousZeroCorner + + if let domainName = domainName { + var domain = defaults.persistentDomain(forName: domainName) ?? [:] + if let previousDomainValue = previousDomainValue { + domain["zeroCorner"] = previousDomainValue + } else { + domain.removeValue(forKey: "zeroCorner") + } + defaults.setPersistentDomain(domain, forName: domainName) + } else { + if previousDomainValue == nil { + defaults.removeObject(forKey: "zeroCorner") + } + } + } + + try test() + } } private final class ChildAttachingRulerWindow: RulerWindow { From 151fd6efb6139579a764d3085b62af55d4128b27 Mon Sep 17 00:00:00 2001 From: Pascal Date: Fri, 12 Jun 2026 23:57:28 -0400 Subject: [PATCH 2/2] Assign explicit zero corner raw values --- Free Ruler/Ruler.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Free Ruler/Ruler.swift b/Free Ruler/Ruler.swift index 1ec8e8d..7b66982 100644 --- a/Free Ruler/Ruler.swift +++ b/Free Ruler/Ruler.swift @@ -6,10 +6,10 @@ enum Orientation: String { } @objc enum ZeroCorner: Int { - case topLeft - case topRight - case bottomLeft - case bottomRight + case topLeft = 0 + case topRight = 1 + case bottomLeft = 2 + case bottomRight = 3 } class Ruler {