From 75c5962b7593932d6d0ded6581a0165de93b6683 Mon Sep 17 00:00:00 2001 From: Pascal Date: Fri, 19 Jun 2026 13:31:58 -0400 Subject: [PATCH 1/2] Stagger new rulers from occupied defaults --- Free Ruler/GroupedRulerWindow.swift | 15 ++++++++++++++- FreeRulerTests/RulerCoreTests.swift | 20 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Free Ruler/GroupedRulerWindow.swift b/Free Ruler/GroupedRulerWindow.swift index 88dcf98..5713f06 100644 --- a/Free Ruler/GroupedRulerWindow.swift +++ b/Free Ruler/GroupedRulerWindow.swift @@ -1693,10 +1693,11 @@ final class RulerManager { defaults: RulerSettings = RulerSettings(defaults: prefs), screenFrame: NSRect = defaultRulerScreenFrame() ) -> GroupedRulerController { - let state = RulerInstanceState.createFromDefaults( + let defaultState = RulerInstanceState.createFromDefaults( defaults: defaults, screenFrame: screenFrame ) + let state = staggeredState(from: defaultState) return addRuler(state: state) } @@ -1793,6 +1794,18 @@ final class RulerManager { } } + private func staggeredState(from defaultState: RulerInstanceState) -> RulerInstanceState { + var state = defaultState + let offset = Ruler.thickness / 2 + + while controllers.contains(where: { $0.state.layout.zeroPoint == state.layout.zeroPoint }) { + state.layout.zeroPoint.x -= offset + state.layout.zeroPoint.y -= offset + } + + return state + } + private func notifyStateChanged() { onStateChanged?(self) } diff --git a/FreeRulerTests/RulerCoreTests.swift b/FreeRulerTests/RulerCoreTests.swift index c1a9109..c49dac2 100644 --- a/FreeRulerTests/RulerCoreTests.swift +++ b/FreeRulerTests/RulerCoreTests.swift @@ -169,6 +169,26 @@ final class RulerCoreTests: XCTestCase { XCTAssertEqual(manager.states.map(\.settings.unit), [.inches]) } + func testRulerManagerStaggersNewRulersWhenDefaultPositionIsOccupied() { + let manager = RulerManager() + defer { + for controller in manager.controllers { + controller.hide() + } + } + + let screenFrame = NSRect(x: 0, y: 0, width: 1000, height: 800) + let first = manager.createRuler(screenFrame: screenFrame) + let second = manager.createRuler(screenFrame: screenFrame) + let third = manager.createRuler(screenFrame: screenFrame) + let staggerOffset = Ruler.thickness / 2 + + XCTAssertEqual(second.state.layout.zeroPoint.x, first.state.layout.zeroPoint.x - staggerOffset) + XCTAssertEqual(second.state.layout.zeroPoint.y, first.state.layout.zeroPoint.y - staggerOffset) + XCTAssertEqual(third.state.layout.zeroPoint.x, first.state.layout.zeroPoint.x - (staggerOffset * 2)) + XCTAssertEqual(third.state.layout.zeroPoint.y, first.state.layout.zeroPoint.y - (staggerOffset * 2)) + } + func testRulerContextMenuActivatesClickedRulerAndShowsSettingsCommand() { withInstalledAppDelegate { appDelegate in let manager = appDelegate.rulerManager From bb396a8762437443a87b25d599c4acead5a83c0e Mon Sep 17 00:00:00 2001 From: Pascal Date: Fri, 19 Jun 2026 14:45:04 -0400 Subject: [PATCH 2/2] Fix new ruler stagger direction --- Free Ruler/GroupedRulerWindow.swift | 2 +- FreeRulerTests/RulerCoreTests.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Free Ruler/GroupedRulerWindow.swift b/Free Ruler/GroupedRulerWindow.swift index 5713f06..2ed0e64 100644 --- a/Free Ruler/GroupedRulerWindow.swift +++ b/Free Ruler/GroupedRulerWindow.swift @@ -1799,7 +1799,7 @@ final class RulerManager { let offset = Ruler.thickness / 2 while controllers.contains(where: { $0.state.layout.zeroPoint == state.layout.zeroPoint }) { - state.layout.zeroPoint.x -= offset + state.layout.zeroPoint.x += offset state.layout.zeroPoint.y -= offset } diff --git a/FreeRulerTests/RulerCoreTests.swift b/FreeRulerTests/RulerCoreTests.swift index c49dac2..2a6d339 100644 --- a/FreeRulerTests/RulerCoreTests.swift +++ b/FreeRulerTests/RulerCoreTests.swift @@ -183,9 +183,9 @@ final class RulerCoreTests: XCTestCase { let third = manager.createRuler(screenFrame: screenFrame) let staggerOffset = Ruler.thickness / 2 - XCTAssertEqual(second.state.layout.zeroPoint.x, first.state.layout.zeroPoint.x - staggerOffset) + XCTAssertEqual(second.state.layout.zeroPoint.x, first.state.layout.zeroPoint.x + staggerOffset) XCTAssertEqual(second.state.layout.zeroPoint.y, first.state.layout.zeroPoint.y - staggerOffset) - XCTAssertEqual(third.state.layout.zeroPoint.x, first.state.layout.zeroPoint.x - (staggerOffset * 2)) + XCTAssertEqual(third.state.layout.zeroPoint.x, first.state.layout.zeroPoint.x + (staggerOffset * 2)) XCTAssertEqual(third.state.layout.zeroPoint.y, first.state.layout.zeroPoint.y - (staggerOffset * 2)) }