From d70f1f07e48afa96a111f3ae85ce96d425c7214e Mon Sep 17 00:00:00 2001 From: Pascal Date: Fri, 19 Jun 2026 16:00:42 -0400 Subject: [PATCH 1/2] Refactor ruler management by replacing GroupedRulerController with RulerController, updating related window handling and layout structures. Rename grouped ruler references to align with new architecture, enhancing clarity and maintainability. Remove LegacyRulerWindow and adjust associated tests for consistency. --- AGENTS.md | 5 +- Free Ruler/AppDelegate.swift | 490 ++----------- Free Ruler/AppStoreScreenshotPreview.swift | 50 +- Free Ruler/LegacyRulerWindow.swift | 160 ----- Free Ruler/PreferencesController.swift | 24 +- Free Ruler/Ruler.swift | 17 +- Free Ruler/RulerController.swift | 290 -------- Free Ruler/RulerWindow.swift | 413 +++++------ FreeRulerTests/RulerCoreTests.swift | 772 ++++++--------------- FreeRulerTests/RulerSnapshotTests.swift | 28 +- 10 files changed, 486 insertions(+), 1763 deletions(-) delete mode 100644 Free Ruler/LegacyRulerWindow.swift delete mode 100644 Free Ruler/RulerController.swift diff --git a/AGENTS.md b/AGENTS.md index 4ce80c5..4860476 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,9 +18,8 @@ The main app target lives in `Free Ruler/` and the Xcode project is hotkey behavior, timer policy wiring, and UI test state reset. - `Ruler.swift`: ruler model plus default placement and min/max size helpers. -- `RulerController.swift`, `RulerWindow.swift`, `RuleView.swift`, - `HorizontalRule.swift`, `VerticalRule.swift`: ruler window/view behavior and - drawing. +- `RulerWindow.swift`, `RuleView.swift`, `HorizontalRule.swift`, + `VerticalRule.swift`: ruler window/controller/view behavior and drawing. - `Prefs.swift`: persisted app preferences. - `Base.lproj/*.xib`: AppKit interface files for menus/preferences. - `*.lproj/*.strings` and `Localizable.xcstrings`: diff --git a/Free Ruler/AppDelegate.swift b/Free Ruler/AppDelegate.swift index 50fe4ca..fce4836 100644 --- a/Free Ruler/AppDelegate.swift +++ b/Free Ruler/AppDelegate.swift @@ -60,14 +60,11 @@ class AppDelegate: NSObject, NSApplicationDelegate { var observers: [NSKeyValueObservation] = [] - var rulers: [RulerController] = [] - var groupedRulerController: GroupedRulerController? lazy var rulerManager: RulerManager = { let manager = RulerManager() manager.onActiveControllerChanged = { [weak self] controller in guard let self = self else { return } - self.groupedRulerController = controller self.updateDisplay() guard let settingsController = self.rulerSettingsController, @@ -125,54 +122,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { private var updaterController: SPUStandardUpdaterController? #endif - private enum RulerWindowMode { - case grouped - case separate - } - - private struct RulerVisibility { - var horizontal = true - var vertical = true - - var hasVisibleRuler: Bool { - return horizontal || vertical - } - - mutating func showAll() { - horizontal = true - vertical = true - } - - mutating func hideAll() { - horizontal = false - vertical = false - } - - mutating func toggle(_ orientation: Orientation) { - set(orientation, isVisible: !isVisible(orientation)) - } - - mutating func set(_ orientation: Orientation, isVisible: Bool) { - switch orientation { - case .horizontal: - horizontal = isVisible - case .vertical: - vertical = isVisible - } - } - - func isVisible(_ orientation: Orientation) -> Bool { - switch orientation { - case .horizontal: - return horizontal - case .vertical: - return vertical - } - } - } - - private var rulerVisibility = RulerVisibility() - // MARK: - Lifecycle func applicationDidFinishLaunching(_ aNotification: Notification) { @@ -302,17 +251,14 @@ class AppDelegate: NSObject, NSApplicationDelegate { }, prefs.observe(\Prefs.floatRulers, options: .new) { prefs, changed in self.updateFloatRulersMenuItem() - self.legacyGroupedRulerController?.updateIsFloatingPanel() self.uiTestSupport?.writePreferencesState() }, prefs.observe(\Prefs.groupRulers, options: .new) { prefs, changed in self.updateGroupRulersMenuItem() - self.applyRulerWindowMode() self.uiTestSupport?.writePreferencesState() }, prefs.observe(\Prefs.rulerShadow, options: .new) { prefs, changed in self.updateRulerShadowMenuItem() - self.legacyGroupedRulerController?.updateHasShadow() self.uiTestSupport?.writePreferencesState() }, prefs.observe(\Prefs.rulerColor, options: .new) { prefs, changed in @@ -339,20 +285,13 @@ class AppDelegate: NSObject, NSApplicationDelegate { } func redrawRulers() { - for ruler in rulers { - ruler.rulerWindow.rule.redrawForPreferenceChange() - } for controller in rulerManager.controllers { controller.redrawForPreferenceChange() } - legacyGroupedRulerController?.redrawForPreferenceChange() } func redrawDefaultBackedRulers() { - for ruler in rulers { - ruler.rulerWindow.rule.redrawForPreferenceChange() - } - legacyGroupedRulerController?.redrawForPreferenceChange() + redrawRulers() } func updateFloatRulersMenuItem() { @@ -386,27 +325,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { rulerManager.createRuler() } - private func createLegacyRulersIfNeeded() { - guard rulers.isEmpty else { return } - - rulers = [ - RulerController(Ruler(.vertical, name: "vertical-ruler")), - RulerController(Ruler(.horizontal, name: "horizontal-ruler")), - ] - - // let rulers know about each other - // TODO: provide each ruler with otherRulers: [LegacyRulerWindow] - rulers[0].otherWindow = rulers[1].rulerWindow - rulers[1].otherWindow = rulers[0].rulerWindow - - let groupedFrame = GroupedRulerLayout.joined( - horizontalFrame: rulers[1].rulerWindow.frame, - verticalFrame: rulers[0].rulerWindow.frame, - zeroCorner: prefs.zeroCorner - ).groupFrame - groupedRulerController = GroupedRulerController(frame: groupedFrame) - } - func showRulers() { createRulersIfNeeded() rulerManager.showAll() @@ -443,220 +361,49 @@ class AppDelegate: NSObject, NSApplicationDelegate { guard hasLegacyAutosave else { return nil } let settings = RulerSettings(defaults: prefs) - let horizontalWindow = LegacyRulerWindow( - ruler: Ruler(.horizontal, name: horizontalAutosaveName) + let horizontalFrame = legacyAutosavedFrame( + name: horizontalAutosaveName, + fallback: getDefaultContentRect(orientation: .horizontal, zeroCorner: settings.zeroCorner) ) - let verticalWindow = LegacyRulerWindow( - ruler: Ruler(.vertical, name: verticalAutosaveName) + let verticalFrame = legacyAutosavedFrame( + name: verticalAutosaveName, + fallback: getDefaultContentRect(orientation: .vertical, zeroCorner: settings.zeroCorner) ) - _ = horizontalWindow.setFrameUsingName(NSWindow.FrameAutosaveName(horizontalAutosaveName)) - _ = verticalWindow.setFrameUsingName(NSWindow.FrameAutosaveName(verticalAutosaveName)) return RulerInstanceState( settings: settings, layout: RulerLayoutState( - horizontalFrame: horizontalWindow.frame, - verticalFrame: verticalWindow.frame, + horizontalFrame: horizontalFrame, + verticalFrame: verticalFrame, zeroCorner: settings.zeroCorner ) ) } - func toggleRuler(orientation: Orientation) { - if !rulerManager.hasRulers && rulers.isEmpty { - createRulersIfNeeded() - } - - if rulerManager.hasRulers { - let controller = rulerManager.activeController ?? rulerManager.createRuler() - controller.toggleWing(orientation) - updateDisplay() - updateMouseTickTimer() - return - } - - guard canToggleRulerVisibility else { return } - guard rulerController(orientation: orientation) != nil else { return } - - if prefs.groupRulers { - syncGroupedRulerFramesToRulerWindows(persistAutosave: true) - } - - let shouldShowRulersIfNeeded = !hasVisibleRuler - rulerVisibility.toggle(orientation) - applyRulerWindowMode(showRulersIfNeeded: shouldShowRulersIfNeeded) - } - - private func detachRulerWindows() { - for ruler in rulers { - detachRulerWindow(ruler.rulerWindow) - } - } - - private func rulerController(orientation: Orientation) -> RulerController? { - createLegacyRulersIfNeeded() - return existingRulerController(orientation: orientation) - } - - private func existingRulerController(orientation: Orientation) -> RulerController? { - return rulers.first { $0.ruler.orientation == orientation } - } - - private func showRuler(_ ruler: RulerController, updateMode: Bool = true) { - ruler.showWindow(self) - ruler.rulerWindow.orderFrontRegardless() - if updateMode { - applyRulerWindowMode() - } - } - - private func detachRulerWindow(_ window: LegacyRulerWindow) { - for ruler in rulers { - guard ruler.rulerWindow != window else { continue } - - ruler.rulerWindow.removeChildWindow(window) - window.removeChildWindow(ruler.rulerWindow) - } - } - - private var rulerWindowMode: RulerWindowMode { - return prefs.groupRulers ? .grouped : .separate - } - - private func applyRulerWindowMode(showRulersIfNeeded: Bool = false) { - if rulerManager.hasRulers { - updateMouseTickTimer() - return - } - - createLegacyRulersIfNeeded() - detachRulerWindows() - - switch rulerWindowMode { - case .grouped: - showGroupedRulerWindow(showRulersIfNeeded: showRulersIfNeeded) - case .separate: - showSeparateRulerWindows() - } - - updateMouseTickTimer() - } - - private func showGroupedRulerWindow(showRulersIfNeeded: Bool) { - guard let groupedRulerController = groupedRulerController, - let horizontalRuler = existingRulerController(orientation: .horizontal), - let verticalRuler = existingRulerController(orientation: .vertical) else { - return - } - - guard rulerVisibility.hasVisibleRuler else { - groupedRulerController.hide() - horizontalRuler.rulerWindow.orderOut(self) - verticalRuler.rulerWindow.orderOut(self) - return - } - - let shouldShowGroupedRuler = showRulersIfNeeded - || groupedRulerController.isVisible - || horizontalRuler.rulerWindow.isVisible - || verticalRuler.rulerWindow.isVisible - - guard shouldShowGroupedRuler else { return } - - let horizontalFrame = groupedRulerController.isVisible - && groupedRulerController.groupedWindow.isRuleVisible(.horizontal) - ? groupedRulerController.groupedWindow.screenFrame(for: .horizontal) - : horizontalRuler.rulerWindow.frame - let verticalFrame = groupedRulerController.isVisible - && groupedRulerController.groupedWindow.isRuleVisible(.vertical) - ? groupedRulerController.groupedWindow.screenFrame(for: .vertical) - : verticalRuler.rulerWindow.frame - - groupedRulerController.show( - horizontalFrame: horizontalFrame, - verticalFrame: verticalFrame, - showsHorizontalRule: rulerVisibility.horizontal, - showsVerticalRule: rulerVisibility.vertical + private func legacyAutosavedFrame(name: String, fallback: NSRect) -> NSRect { + let window = NSWindow( + contentRect: fallback, + styleMask: [.borderless, .resizable, .fullSizeContentView], + backing: .buffered, + defer: false ) - horizontalRuler.rulerWindow.orderOut(self) - verticalRuler.rulerWindow.orderOut(self) + _ = window.setFrameUsingName(NSWindow.FrameAutosaveName(name)) + let frame = window.frame + window.close() + return frame } - private func showSeparateRulerWindows() { - guard let groupedRulerController = groupedRulerController else { - return - } - - if groupedRulerController.isVisible { - syncGroupedRulerFramesToRulerWindows(persistAutosave: true) - groupedRulerController.hide() - } - - for ruler in rulers { - if rulerVisibility.isVisible(ruler.ruler.orientation) { - showRuler(ruler, updateMode: false) - } else { - ruler.rulerWindow.orderOut(self) - } - } - - for ruler in rulers { - ruler.updateChildWindow() - } - } - - func syncGroupedRulerFramesToRulerWindows(persistAutosave: Bool = false) { - guard let groupedRulerController = groupedRulerController, - let horizontalRuler = existingRulerController(orientation: .horizontal), - let verticalRuler = existingRulerController(orientation: .vertical) else { - return - } - - groupedRulerController.syncFrames( - to: horizontalRuler.rulerWindow, - and: verticalRuler.rulerWindow, - persistAutosave: persistAutosave - ) - } - - private var isGroupedRulerVisible: Bool { - return groupedRulerController?.isVisible == true - } - - private var legacyGroupedRulerController: GroupedRulerController? { - guard let groupedRulerController = groupedRulerController, - !rulerManager.controllers.contains(where: { $0 === groupedRulerController }) else { - return nil - } - - return groupedRulerController - } - - private func isRulerVisible(_ ruler: RulerController?) -> Bool { - guard let ruler = ruler else { return false } - return rulerVisibility.isVisible(ruler.ruler.orientation) - } - - private var isRulerFrontmost: Bool { - if rulerManager.controllers.contains(where: { $0.groupedWindow.isKeyWindow }) { - return true - } - - if groupedRulerController?.groupedWindow.isKeyWindow == true { - return true - } + func toggleRuler(orientation: Orientation) { + createRulersIfNeeded() - return rulers.contains { $0.rulerWindow.isKeyWindow } + let controller = rulerManager.activeController ?? rulerManager.createRuler() + controller.toggleWing(orientation) + updateDisplay() + updateMouseTickTimer() } private var hasVisibleRuler: Bool { return rulerManager.hasVisibleRulers - || isGroupedRulerVisible - || rulers.contains { $0.rulerWindow.isVisible } - } - - private var canToggleRulerVisibility: Bool { - return isRulerFrontmost || !hasVisibleRuler } func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { @@ -669,13 +416,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { } func applicationDidBecomeActive(_ notification: Notification) { - for ruler in rulers { - ruler.foreground() - } for controller in rulerManager.controllers { controller.foreground() } - legacyGroupedRulerController?.foreground() mouseTickTimerPolicy.applicationDidBecomeActive() updateMouseTickTimer() @@ -684,13 +427,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { } func applicationDidResignActive(_ notification: Notification) { - for ruler in rulers { - ruler.background() - } for controller in rulerManager.controllers { controller.background() } - legacyGroupedRulerController?.background() mouseTickTimerPolicy.applicationDidResignActive() updateMouseTickTimer() @@ -814,20 +553,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { return } - if let groupedRulerController = groupedRulerController, - groupedRulerController.groupedWindow.isKeyWindow { - syncGroupedRulerFramesToRulerWindows(persistAutosave: true) - rulerVisibility.hideAll() - applyRulerWindowMode() - return - } - - if let ruler = rulers.first(where: { $0.rulerWindow.isKeyWindow }) { - rulerVisibility.set(ruler.ruler.orientation, isVisible: false) - applyRulerWindowMode() - return - } - NSApp.keyWindow?.performClose(sender) } @@ -836,46 +561,21 @@ class AppDelegate: NSObject, NSApplicationDelegate { mouseLoc.x = mouseLoc.x.rounded() mouseLoc.y = mouseLoc.y.rounded() + createRulersIfNeeded() + if let controller = rulerManager.activeController { controller.align(at: mouseLoc) - return - } - - if prefs.groupRulers, - let groupedRulerController = groupedRulerController, - groupedRulerController.isVisible { - groupedRulerController.align(at: mouseLoc) - syncGroupedRulerFramesToRulerWindows(persistAutosave: true) - return - } - - for ruler in rulers { - ruler.alignRuler(at: mouseLoc) } } @IBAction func resetRulerPositions(_ sender: Any) { + createRulersIfNeeded() + if let controller = rulerManager.activeController { controller.resetPosition() updateDisplay() updateMouseTickTimer() - return - } - - createLegacyRulersIfNeeded() - - prefs.zeroCorner = Prefs.defaultZeroCorner - - // ungroup rulers during reset operation - prefs.groupRulers = false - rulerVisibility.showAll() - for ruler in rulers { - ruler.resetPosition() - showRuler(ruler, updateMode: false) } - - prefs.groupRulers = Prefs.defaultGroupRulers - applyRulerWindowMode() } @IBAction func toggleHorizontalRuler(_ sender: Any) { @@ -897,63 +597,14 @@ class AppDelegate: NSObject, NSApplicationDelegate { } func flipRulers(along orientation: Orientation) { - if !rulerManager.hasRulers && rulers.isEmpty { - createRulersIfNeeded() - } + createRulersIfNeeded() if let controller = rulerManager.activeController { let flippedCorner = controller.state.settings.zeroCorner.flipped(along: orientation) controller.prepareForZeroCornerChange(to: flippedCorner) controller.redrawForPreferenceChange() updateDisplay() - return - } - - createLegacyRulersIfNeeded() - - let oldGeometry = ZeroCornerGeometry(zeroCorner: prefs.zeroCorner) - let flippedCorner = prefs.zeroCorner.flipped(along: orientation) - let flippedRuler = existingRulerController(orientation: orientation) - let otherOrientation: Orientation = orientation == .horizontal ? .vertical : .horizontal - let otherRuler = existingRulerController(orientation: otherOrientation) - let zeroPointOffset = zeroPointOffset( - from: flippedRuler?.rulerWindow, - to: otherRuler?.rulerWindow, - geometry: oldGeometry - ) - - if prefs.groupRulers, - let groupedRulerController = groupedRulerController, - groupedRulerController.isVisible { - groupedRulerController.prepareForZeroCornerChange(to: flippedCorner) - prefs.zeroCorner = flippedCorner - syncGroupedRulerFramesToRulerWindows(persistAutosave: true) - return } - - prefs.zeroCorner = flippedCorner - - guard prefs.groupRulers, - let flippedWindow = flippedRuler?.rulerWindow, - let otherWindow = otherRuler?.rulerWindow, - isRulerWindowShown(otherWindow), - let zeroPointOffset = zeroPointOffset else { return } - - let newGeometry = ZeroCornerGeometry(zeroCorner: flippedCorner) - let flippedZeroPoint = newGeometry.zeroPoint(in: flippedWindow.frame, for: orientation) - let targetOtherZeroPoint = NSPoint( - x: flippedZeroPoint.x + zeroPointOffset.width, - y: flippedZeroPoint.y + zeroPointOffset.height - ) - let otherFrame = newGeometry.frame( - for: otherOrientation, - zeroPoint: targetOtherZeroPoint, - size: otherWindow.frame.size - ) - - detachRulerWindows() - otherWindow.setFrame(otherFrame, display: true) - applyRulerWindowMode() } private func setUnit(_ unit: Unit) { @@ -966,41 +617,12 @@ class AppDelegate: NSObject, NSApplicationDelegate { prefs.unit = unit } - func isRulerWindowShown(_ window: LegacyRulerWindow) -> Bool { - return window.isVisible || window.parent != nil || rulers.contains { - $0.rulerWindow.childWindows?.contains(window) == true - } - } - - private func zeroPointOffset( - from sourceWindow: LegacyRulerWindow?, - to targetWindow: LegacyRulerWindow?, - geometry: ZeroCornerGeometry - ) -> NSSize? { - guard let sourceWindow = sourceWindow, - let targetWindow = targetWindow else { return nil } - - let sourceZeroPoint = geometry.zeroPoint( - in: sourceWindow.frame, - for: sourceWindow.ruler.orientation - ) - let targetZeroPoint = geometry.zeroPoint( - in: targetWindow.frame, - for: targetWindow.ruler.orientation - ) - - return NSSize( - width: targetZeroPoint.x - sourceZeroPoint.x, - height: targetZeroPoint.y - sourceZeroPoint.y - ) - } - func performRulerHotkey( keyCode: Int, modifierFlags: NSEvent.ModifierFlags, sender: Any ) -> Bool { - if let controller = sender as? GroupedRulerController { + if let controller = sender as? RulerController { rulerManager.markActive(controller) } @@ -1081,15 +703,11 @@ class AppDelegate: NSObject, NSApplicationDelegate { return rulerController.rulerWindow.screen } - if let groupedRulerController = sender as? GroupedRulerController { - return groupedRulerController.groupedWindow.screen - } - if let activeController = rulerManager.activeController { - return activeController.groupedWindow.screen + return activeController.rulerWindow.screen } - return rulers.first { $0.rulerWindow.isKeyWindow }?.rulerWindow.screen + return nil } private func unitLabel(_ unit: Unit) -> String { @@ -1139,11 +757,11 @@ extension AppDelegate: NSMenuItemValidation { return !isVisible || controller.state.isWingVisible(.vertical) } - let ruler = existingRulerController(orientation: .horizontal) - menuItem.title = isRulerVisible(ruler) - ? NSLocalizedString("Hide Horizontal Ruler", comment: "Menu item title to hide the horizontal ruler") - : NSLocalizedString("Show Horizontal Ruler", comment: "Menu item title to show the horizontal ruler") - return canToggleRulerVisibility + menuItem.title = NSLocalizedString( + "Show Horizontal Ruler", + comment: "Menu item title to show the horizontal ruler" + ) + return true case #selector(toggleVerticalRuler(_:)): if let controller = rulerManager.activeController { let isVisible = controller.state.isWingVisible(.vertical) @@ -1153,11 +771,11 @@ extension AppDelegate: NSMenuItemValidation { return !isVisible || controller.state.isWingVisible(.horizontal) } - let ruler = existingRulerController(orientation: .vertical) - menuItem.title = isRulerVisible(ruler) - ? NSLocalizedString("Hide Vertical Ruler", comment: "Menu item title to hide the vertical ruler") - : NSLocalizedString("Show Vertical Ruler", comment: "Menu item title to show the vertical ruler") - return canToggleRulerVisibility + menuItem.title = NSLocalizedString( + "Show Vertical Ruler", + comment: "Menu item title to show the vertical ruler" + ) + return true default: return true } @@ -1204,14 +822,9 @@ extension AppDelegate { } private func setMouseTickDrawingEnabled(_ isEnabled: Bool) { - for ruler in rulers { - ruler.rulerWindow.rule.showMouseTick = isEnabled - } - for controller in rulerManager.controllers { controller.setMouseTickDrawingEnabled(isEnabled) } - legacyGroupedRulerController?.setMouseTickDrawingEnabled(isEnabled) } private func updateMouseTickTimer() { @@ -1255,21 +868,8 @@ extension AppDelegate { mouseLoc.x = mouseLoc.x.rounded() mouseLoc.y = mouseLoc.y.rounded() - if rulerManager.hasRulers { - for controller in rulerManager.controllers where controller.isVisible { - controller.drawMouseTick(at: mouseLoc) - } - return - } - - if let groupedRulerController = groupedRulerController, - groupedRulerController.isVisible { - groupedRulerController.drawMouseTick(at: mouseLoc) - return - } - - for ruler in rulers { - ruler.rulerWindow.rule.drawMouseTick(at: mouseLoc) + for controller in rulerManager.controllers where controller.isVisible { + controller.drawMouseTick(at: mouseLoc) } } diff --git a/Free Ruler/AppStoreScreenshotPreview.swift b/Free Ruler/AppStoreScreenshotPreview.swift index 69596be..a5377da 100644 --- a/Free Ruler/AppStoreScreenshotPreview.swift +++ b/Free Ruler/AppStoreScreenshotPreview.swift @@ -1417,7 +1417,7 @@ private final class AppStoreScreenshotScenarioNSView: NSView { } } - private static func makeMeasureGroupedRulerPlacement() -> AppStoreViewPlacement { + private static func makeMeasureRulerWindowPlacement() -> AppStoreViewPlacement { let horizontalBoundsSize = NSSize( width: AppStoreMeasureScreenshotLayout.horizontalRulerLength / AppStoreMeasureScreenshotLayout.rulerScale, height: Ruler.thickness @@ -1436,7 +1436,7 @@ private final class AppStoreScreenshotScenarioNSView: NSView { frame: NSRect(origin: .zero, size: verticalBoundsSize) ) let color = RulerColors(customFill: Prefs.defaultRulerFillColor) - let groupedView = GroupedRulerContentView( + let rulerWindowView = RulerContentView( frame: NSRect(origin: .zero, size: boundsSize), horizontalRule: horizontalRule, verticalRule: verticalRule @@ -1446,19 +1446,19 @@ private final class AppStoreScreenshotScenarioNSView: NSView { verticalRule.color = color horizontalRule.showMouseTick = false verticalRule.showMouseTick = false - groupedView.color = color - groupedView.zeroCorner = .topLeft - groupedView.needsLayout = true - groupedView.layoutSubtreeIfNeeded() + rulerWindowView.color = color + rulerWindowView.zeroCorner = .topLeft + rulerWindowView.needsLayout = true + rulerWindowView.layoutSubtreeIfNeeded() return AppStoreViewPlacement( - view: groupedView, + view: rulerWindowView, frame: AppStoreMeasureScreenshotLayout.groupedRulerRect, boundsSize: boundsSize ) } - private static func makeGroupsGroupedRulerPlacement() -> AppStoreViewPlacement { + private static func makeGroupsRulerWindowPlacement() -> AppStoreViewPlacement { let horizontalBoundsSize = AppStoreGroupsScreenshotLayout.horizontalBoundsSize let verticalBoundsSize = AppStoreGroupsScreenshotLayout.verticalBoundsSize let boundsSize = AppStoreGroupsScreenshotLayout.groupedBoundsSize @@ -1471,7 +1471,7 @@ private final class AppStoreScreenshotScenarioNSView: NSView { frame: NSRect(origin: .zero, size: verticalBoundsSize) ) let color = RulerColors(customFill: AppStoreGroupsScreenshotLayout.rulerColor) - let groupedView = GroupedRulerContentView( + let rulerWindowView = RulerContentView( frame: NSRect(origin: .zero, size: boundsSize), horizontalRule: horizontalRule, verticalRule: verticalRule @@ -1481,20 +1481,20 @@ private final class AppStoreScreenshotScenarioNSView: NSView { verticalRule.color = color horizontalRule.showMouseTick = false verticalRule.showMouseTick = false - groupedView.color = color - groupedView.alphaValue = AppStoreGroupsScreenshotLayout.rulerOpacity - groupedView.zeroCorner = .topLeft - groupedView.needsLayout = true - groupedView.layoutSubtreeIfNeeded() + rulerWindowView.color = color + rulerWindowView.alphaValue = AppStoreGroupsScreenshotLayout.rulerOpacity + rulerWindowView.zeroCorner = .topLeft + rulerWindowView.needsLayout = true + rulerWindowView.layoutSubtreeIfNeeded() return AppStoreViewPlacement( - view: groupedView, + view: rulerWindowView, frame: AppStoreGroupsScreenshotLayout.groupedRulerRect, boundsSize: boundsSize ) } - private static func makeFlipGroupedRulerPlacements() -> [AppStoreViewPlacement] { + private static func makeFlipRulerWindowPlacements() -> [AppStoreViewPlacement] { AppStoreFlipScreenshotLayout.rulerSets.map { rulerSet in let horizontalBoundsSize = AppStoreFlipScreenshotLayout.horizontalBoundsSize(for: rulerSet) let verticalBoundsSize = AppStoreFlipScreenshotLayout.verticalBoundsSize(for: rulerSet) @@ -1510,7 +1510,7 @@ private final class AppStoreScreenshotScenarioNSView: NSView { zeroCorner: rulerSet.zeroCorner ) let color = RulerColors(customFill: rulerSet.fillColor) - let groupedView = GroupedRulerContentView( + let rulerWindowView = RulerContentView( frame: NSRect(origin: .zero, size: boundsSize), horizontalRule: horizontalRule, verticalRule: verticalRule @@ -1520,13 +1520,13 @@ private final class AppStoreScreenshotScenarioNSView: NSView { verticalRule.color = color horizontalRule.showMouseTick = false verticalRule.showMouseTick = false - groupedView.color = color - groupedView.zeroCorner = rulerSet.zeroCorner - groupedView.needsLayout = true - groupedView.layoutSubtreeIfNeeded() + rulerWindowView.color = color + rulerWindowView.zeroCorner = rulerSet.zeroCorner + rulerWindowView.needsLayout = true + rulerWindowView.layoutSubtreeIfNeeded() return AppStoreViewPlacement( - view: groupedView, + view: rulerWindowView, frame: rulerSet.groupedFrame(rulerScale: AppStoreFlipScreenshotLayout.rulerScale), boundsSize: boundsSize ) @@ -1547,11 +1547,11 @@ private final class AppStoreScreenshotScenarioNSView: NSView { ) -> [AppStoreViewPlacement] { switch screen.scenario { case .measure: - return [makeMeasureGroupedRulerPlacement()] + return [makeMeasureRulerWindowPlacement()] case .groups: - return [makeGroupsGroupedRulerPlacement()] + return [makeGroupsRulerWindowPlacement()] case .flipRulers: - return makeFlipGroupedRulerPlacements() + return makeFlipRulerWindowPlacements() case .units, .colors: return [] case .preferences: diff --git a/Free Ruler/LegacyRulerWindow.swift b/Free Ruler/LegacyRulerWindow.swift deleted file mode 100644 index f7a8386..0000000 --- a/Free Ruler/LegacyRulerWindow.swift +++ /dev/null @@ -1,160 +0,0 @@ -import Cocoa - -class LegacyRulerWindow: NSPanel { - - var ruler: Ruler - var rule: RuleView - - convenience init(_ ruler: Ruler) { - self.init(ruler: ruler) - } - - init(ruler: Ruler) { - self.ruler = ruler - self.rule = getRulerView(ruler: ruler) - - let styleMask: NSWindow.StyleMask = [ - .borderless, - .resizable, - .fullSizeContentView, - ] - - super.init( - contentRect: ruler.frame, - styleMask: styleMask, - backing: .buffered, - defer: false - ) - - self.alphaValue = windowAlphaValue(prefs.foregroundOpacity) - self.title = getTitle(for: ruler.orientation) - self.identifier = NSUserInterfaceItemIdentifier(getIdentifier(for: ruler.orientation)) - self.setAccessibilityIdentifier(getIdentifier(for: ruler.orientation)) - self.minSize = getMinSize(ruler: ruler) - self.maxSize = getMaxSize(ruler: ruler) - - self.isFloatingPanel = prefs.floatRulers - self.hidesOnDeactivate = false - self.isMovableByWindowBackground = true - self.hasShadow = prefs.rulerShadow - - rule.installWindowBorder() - rule.setAccessibilityElement(true) - rule.setAccessibilityIdentifier(getRuleIdentifier(for: ruler.orientation)) - - rule.nextResponder = self - self.contentView = rule - } - - override var canBecomeKey: Bool { - return true - } - - override var acceptsMouseMovedEvents: Bool { - get { return true } - set {} - } - - override func mouseDown(with event: NSEvent) { - nextResponder?.mouseDown(with: event) - super.mouseDown(with: event) - - if !leftMouseButtonIsPressed { - (nextResponder as? RulerController)?.finishMouseDrag(with: event) - } - } - - override func mouseUp(with event: NSEvent) { - nextResponder?.mouseUp(with: event) - super.mouseUp(with: event) - } - - private var leftMouseButtonIsPressed: Bool { - return NSEvent.pressedMouseButtons & 1 == 1 - } - -} - -extension LegacyRulerWindow: RulerContextMenuActivating { - func activateForRulerContextMenu() { - makeKey() - } -} - -private func getTitle(for orientation: Orientation) -> String { - switch orientation { - case .horizontal: - return NSLocalizedString( - "Horizontal Ruler", - comment: "Window title for the horizontal ruler" - ) - case .vertical: - return NSLocalizedString( - "Vertical Ruler", - comment: "Window title for the vertical ruler" - ) - } -} - -private func getIdentifier(for orientation: Orientation) -> String { - switch orientation { - case .horizontal: - return "horizontal-ruler-window" - case .vertical: - return "vertical-ruler-window" - } -} - -private func getRuleIdentifier(for orientation: Orientation) -> String { - switch orientation { - case .horizontal: - return "horizontal-ruler-view" - case .vertical: - return "vertical-ruler-view" - } -} - -extension LegacyRulerWindow { - private enum Distance: CGFloat { - case aLittle = 1 - case aLot = 10 - } - - func moveHorizontally(by pixels: CGFloat) { - var position = frame.origin - position.x = position.x + pixels - setFrameOrigin(position) - } - - func moveVertically(by pixels: CGFloat) { - var position = frame.origin - position.y = position.y + pixels - setFrameOrigin(position) - } - - private func distance(withShift: Bool) -> CGFloat { - let dist = withShift ? Distance.aLot : Distance.aLittle - return dist.rawValue - } - - func nudgeLeft(withShift shiftPressed: Bool) { - let dist = distance(withShift: shiftPressed) - moveHorizontally(by: dist * -1) - } - - func nudgeRight(withShift shiftPressed: Bool) { - let dist = distance(withShift: shiftPressed) - moveHorizontally(by: dist) - } - - func nudgeDown(withShift shiftPressed: Bool) { - let dist = distance(withShift: shiftPressed) - moveVertically(by: dist * -1) - } - - func nudgeUp(withShift shiftPressed: Bool) { - let dist = distance(withShift: shiftPressed) - moveVertically(by: dist) - } - -} diff --git a/Free Ruler/PreferencesController.swift b/Free Ruler/PreferencesController.swift index 153da90..9628afc 100644 --- a/Free Ruler/PreferencesController.swift +++ b/Free Ruler/PreferencesController.swift @@ -890,7 +890,7 @@ final class RulerSettingsWindow: NSPanel { final class RulerSettingsController: NSWindowController, NSWindowDelegate { - private weak var rulerController: GroupedRulerController? + private weak var rulerController: RulerController? private var colorPanelObserver: NSObjectProtocol? private var didConfigureWindow = false @@ -942,7 +942,7 @@ final class RulerSettingsController: NSWindowController, NSWindowDelegate { return settingsControlsView.rulerShadowCheckbox } - var currentRulerController: GroupedRulerController? { + var currentRulerController: RulerController? { return rulerController } @@ -950,7 +950,7 @@ final class RulerSettingsController: NSWindowController, NSWindowDelegate { return "RulerSettingsController" } - init(rulerController: GroupedRulerController) { + init(rulerController: RulerController) { self.rulerController = rulerController super.init(window: nil) loadWindow() @@ -1010,13 +1010,13 @@ final class RulerSettingsController: NSWindowController, NSWindowDelegate { window?.center() } - func show(attachedTo controller: GroupedRulerController, sender: Any?) { + func show(attachedTo controller: RulerController, sender: Any?) { updateRulerController(controller) guard let settingsWindow = window else { return } configureOpaqueColorPicking() - if settingsWindow.parent === controller.groupedWindow { + if settingsWindow.parent === controller.rulerWindow { position(settingsWindow, attachedTo: controller) settingsWindow.orderFront(sender) settingsWindow.makeKey() @@ -1026,7 +1026,7 @@ final class RulerSettingsController: NSWindowController, NSWindowDelegate { detachWindowIfNeeded() - guard controller.groupedWindow.isVisible else { + guard controller.rulerWindow.isVisible else { showWindow(sender) return } @@ -1036,7 +1036,7 @@ final class RulerSettingsController: NSWindowController, NSWindowDelegate { } position(settingsWindow, attachedTo: controller) - controller.groupedWindow.addChildWindow(settingsWindow, ordered: .above) + controller.rulerWindow.addChildWindow(settingsWindow, ordered: .above) settingsWindow.orderFront(sender) settingsWindow.makeKey() settingsWindow.makeFirstResponder(unitSegmentedControl) @@ -1056,7 +1056,7 @@ final class RulerSettingsController: NSWindowController, NSWindowDelegate { closeSheetColorControls() } - func updateRulerController(_ controller: GroupedRulerController) { + func updateRulerController(_ controller: RulerController) { rulerController = controller updateView() } @@ -1148,7 +1148,7 @@ final class RulerSettingsController: NSWindowController, NSWindowDelegate { unit: currentSettings?.unit ?? Prefs.defaultUnit, horizontalLength: rulerController?.state.layout.horizontalLength, verticalLength: rulerController?.state.layout.verticalLength, - dimensionScreen: rulerController?.groupedWindow.screen ?? window?.screen ?? NSScreen.main, + dimensionScreen: rulerController?.rulerWindow.screen ?? window?.screen ?? NSScreen.main, rulerColor: currentSettings?.rulerColor ?? Prefs.defaultRulerFillColor, foregroundOpacity: currentSettings?.foregroundOpacity ?? Prefs.defaultForegroundOpacity, backgroundOpacity: currentSettings?.backgroundOpacity ?? Prefs.defaultBackgroundOpacity, @@ -1269,7 +1269,7 @@ final class RulerSettingsController: NSWindowController, NSWindowDelegate { guard let controller = rulerController, let settingsWindow = window, settingsWindow.isVisible, - settingsWindow.parent === controller.groupedWindow else { return } + settingsWindow.parent === controller.rulerWindow else { return } position(settingsWindow, attachedTo: controller) @@ -1302,11 +1302,11 @@ final class RulerSettingsController: NSWindowController, NSWindowDelegate { applyRulerColor(colorPanel.color) } - private func position(_ settingsWindow: NSWindow, attachedTo controller: GroupedRulerController) { + private func position(_ settingsWindow: NSWindow, attachedTo controller: RulerController) { let settingsSize = settingsWindow.frame.size let frame = settingsFrame( size: settingsSize, - zeroPoint: controller.groupedWindow.zeroPoint(), + zeroPoint: controller.rulerWindow.zeroPoint(), zeroCorner: controller.state.settings.zeroCorner ) diff --git a/Free Ruler/Ruler.swift b/Free Ruler/Ruler.swift index 9a8ea47..ce20c69 100644 --- a/Free Ruler/Ruler.swift +++ b/Free Ruler/Ruler.swift @@ -1,5 +1,9 @@ import Cocoa +func windowAlphaValue(_ value: Int) -> CGFloat { + return CGFloat(value) / 100.0 +} + enum Orientation: String { case horizontal case vertical @@ -338,8 +342,8 @@ struct RulerLayoutState: Equatable, Codable { return (horizontalLength, verticalLength) } - func layout(zeroCorner: ZeroCorner) -> GroupedRulerLayout { - return GroupedRulerLayout.layout( + func layout(zeroCorner: ZeroCorner) -> RulerWindowLayout { + return RulerWindowLayout.layout( horizontalLength: horizontalLength, verticalLength: verticalLength, zeroPoint: zeroPoint, @@ -671,12 +675,3 @@ func getMaxSize(ruler: Ruler) -> NSSize { return NSSize(width: 40, height: 4000) } } - -func getRulerView(ruler: Ruler) -> RuleView { - switch ruler.orientation { - case .horizontal: - return HorizontalRule(frame: ruler.frame) - case .vertical: - return VerticalRule(frame: ruler.frame) - } -} diff --git a/Free Ruler/RulerController.swift b/Free Ruler/RulerController.swift deleted file mode 100644 index 0bfb1e5..0000000 --- a/Free Ruler/RulerController.swift +++ /dev/null @@ -1,290 +0,0 @@ -import Cocoa -import Carbon.HIToolbox // For key constants - - -class RulerController: NSWindowController, NSWindowDelegate, NotificationObserver { - - var observers: [NSKeyValueObservation] = [] - var notificationObservers: [NSObjectProtocol] = [] - - let ruler: Ruler - - let rulerWindow: LegacyRulerWindow - var otherWindow: LegacyRulerWindow? - - var keyListener: Any? - private var mouseInteraction: RulerMouseInteractionState! - var isLeftMouseButtonPressed = { - return NSEvent.pressedMouseButtons & 1 == 1 - } - - var preferencesWindowOpen = false { - didSet { - updateIsFloatingPanel() - // reset opacity to foreground in case they modified background opacity last - if !preferencesWindowOpen { - opacity = prefs.foregroundOpacity - } - } - } - - var opacity = prefs.foregroundOpacity { - didSet { - rulerWindow.alphaValue = windowAlphaValue(opacity) - } - } - - convenience init(_ ruler: Ruler) { - self.init(ruler: ruler) - } - - init(ruler: Ruler) { - self.ruler = ruler - self.rulerWindow = LegacyRulerWindow(ruler) - - super.init(window: self.rulerWindow) - - createObservers() - subscribeToPrefs() - - rulerWindow.delegate = self - rulerWindow.nextResponder = self - mouseInteraction = RulerMouseInteractionState(owner: self) { [weak self] event in - return self?.isMouseInsideRuler(with: event) ?? false - } - - if let windowFrameAutosaveName = ruler.name { - self.windowFrameAutosaveName = windowFrameAutosaveName - } - - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented. Use init(ruler: Ruler)") - } - - deinit { - mouseInteraction?.invalidate() - removeObservers(¬ificationObservers) - } - - func createObservers() { - notificationObservers = [ - addObserver(.preferencesWindowOpened) { [weak self] _ in - self?.preferencesWindowOpen = true - }, - addObserver(.preferencesWindowClosed) { [weak self] _ in - self?.preferencesWindowOpen = false - }, - ] - } - - func windowWillStartLiveResize(_ notification: Notification) { - mouseInteraction.windowWillStartLiveResize() - } - - func windowDidEndLiveResize(_ notification: Notification) { - mouseInteraction.windowDidEndLiveResize() - } - - func windowWillMove(_ notification: Notification) { - mouseInteraction.windowWillMove() - } - - func windowDidMove(_ notification: Notification) { - rulerWindow.invalidateShadow() - mouseInteraction.windowDidMove(isLeftMouseButtonPressed: isLeftMouseButtonPressed()) - } - - func windowDidBecomeKey(_ notification: Notification) { - updateChildWindow() - startKeyListener() - } - - func windowDidResignKey(_ notification: Notification) { - updateChildWindow() - stopKeyListener() - } - - override func mouseEntered(with event: NSEvent) { - mouseInteraction.mouseEntered(with: event) - } - - override func mouseExited(with event: NSEvent) { - mouseInteraction.mouseExited(with: event) - } - - override func mouseDown(with event: NSEvent) { - mouseInteraction.mouseDown(with: event) - } - - override func mouseUp(with event: NSEvent) { - finishMouseDrag(with: event) - } - - func finishMouseDrag(with event: NSEvent) { - mouseInteraction.finishMouseDrag(with: event) - } - - override func mouseMoved(with event: NSEvent) { - mouseInteraction.mouseMoved(with: event) - } - - func disableMouseTicks() { - mouseInteraction.disableMouseTicks() - } - - func enableMouseTicks() { - mouseInteraction.enableMouseTicks() - } - - private func isMouseInsideRuler(with event: NSEvent) -> Bool { - let location = rulerWindow.rule.convert(event.locationInWindow, from: nil) - return rulerWindow.rule.bounds.contains(location) - } - - func onChangeGrouped() { - updateChildWindow() - } - - func updateChildWindow() { - guard let otherWindow = otherWindow else { return } - - if prefs.groupRulers && rulerWindow.isKeyWindow { - rulerWindow.addChildWindow(otherWindow, ordered: .below) - } else { - rulerWindow.removeChildWindow(otherWindow) - } - } - - func updateIsFloatingPanel() { - // never float while preferences window is open - if preferencesWindowOpen { - rulerWindow.isFloatingPanel = false - } else { - rulerWindow.isFloatingPanel = prefs.floatRulers - } - } - - func foreground() { - opacity = prefs.foregroundOpacity - } - func background() { - opacity = prefs.backgroundOpacity - } - - func subscribeToPrefs() { - observers = [ - prefs.observe(\Prefs.foregroundOpacity, options: .new) { prefs, changed in - self.opacity = prefs.foregroundOpacity - }, - prefs.observe(\Prefs.backgroundOpacity, options: .new) { prefs, changed in - self.opacity = prefs.backgroundOpacity - }, - prefs.observe(\Prefs.floatRulers, options: .new) { prefs, changed in - self.updateIsFloatingPanel() - }, - prefs.observe(\Prefs.groupRulers, options: .new) { prefs, changed in - self.updateChildWindow() - }, - prefs.observe(\Prefs.rulerShadow, options: .new) { prefs, changed in - self.rulerWindow.hasShadow = prefs.rulerShadow - }, - ] - } - - func alignRuler(at point: NSPoint) { - // only key window controller should respond to this command - guard rulerWindow.isKeyWindow else { return } - - if prefs.groupRulers { - // if grouped, ungroup rulers, move both, regroup - prefs.groupRulers = false - alignRuler(window: rulerWindow, at: point) - alignRuler(window: otherWindow, at: point) - prefs.groupRulers = true - } else { - // if not groups, just move key window - alignRuler(window: rulerWindow, at: point) - } - } - - private func alignRuler(window: LegacyRulerWindow?, at point: NSPoint) { - guard let window = window else { return } - - let frame = window.frame - let rect = ZeroCornerGeometry(zeroCorner: prefs.zeroCorner).frame( - for: window.ruler.orientation, - zeroPoint: point, - size: frame.size - ) - - window.setFrame(rect, display: false) - } - - func resetPosition() { - let frame = getDefaultContentRect(orientation: ruler.orientation, zeroCorner: prefs.zeroCorner) - rulerWindow.setFrame(frame, display: true) - } - -} - -// MARK: KeyListener - -extension RulerController { - - func startKeyListener() { - self.keyListener = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] in - guard let self = self else { return $0 } - return self.onKeyDown(with: $0) - } - } - - func stopKeyListener() { - if let keyListener = self.keyListener { - NSEvent.removeMonitor(keyListener) - self.keyListener = nil - } - } - - // Return nil if the event was handled here. - func onKeyDown(with event: NSEvent) -> NSEvent? { - // print(ruler.orientation, "onKeyDown") - - let shift = event.modifierFlags.contains(.shift) - let keyboardModifiers = event.modifierFlags.intersection(.deviceIndependentFlagsMask) - - if rulerWindow.isKeyWindow, - let appDelegate = NSApp.delegate as? AppDelegate, - appDelegate.performRulerHotkey( - keyCode: Int(event.keyCode), - modifierFlags: keyboardModifiers, - sender: self - ) { - return nil - } - - switch Int(event.keyCode) { - case kVK_LeftArrow: - rulerWindow.nudgeLeft(withShift: shift) - return nil - case kVK_RightArrow: - rulerWindow.nudgeRight(withShift: shift) - return nil - case kVK_UpArrow: - rulerWindow.nudgeUp(withShift: shift) - return nil - case kVK_DownArrow: - rulerWindow.nudgeDown(withShift: shift) - return nil - default: - return event - } - } - -} - -// helper to convert opacity Int to window.alphaValue -func windowAlphaValue(_ value: Int) -> CGFloat { - return CGFloat(value) / 100.0 -} diff --git a/Free Ruler/RulerWindow.swift b/Free Ruler/RulerWindow.swift index 77e848a..f034402 100644 --- a/Free Ruler/RulerWindow.swift +++ b/Free Ruler/RulerWindow.swift @@ -1,7 +1,7 @@ import Cocoa import Carbon.HIToolbox -struct GroupedRulerLayout: Equatable { +struct RulerWindowLayout: Equatable { let groupFrame: NSRect let horizontalFrame: NSRect let verticalFrame: NSRect @@ -10,7 +10,7 @@ struct GroupedRulerLayout: Equatable { horizontalFrame: NSRect, verticalFrame: NSRect, zeroCorner: ZeroCorner - ) -> GroupedRulerLayout { + ) -> RulerWindowLayout { let zeroPoint = ZeroCornerGeometry(zeroCorner: zeroCorner) .zeroPoint(in: horizontalFrame, for: .horizontal) @@ -25,7 +25,7 @@ struct GroupedRulerLayout: Equatable { static func layout( groupFrame: NSRect, zeroCorner: ZeroCorner - ) -> GroupedRulerLayout { + ) -> RulerWindowLayout { let zeroPoint = zeroPoint(in: groupFrame, zeroCorner: zeroCorner) let horizontalLength = length( in: groupFrame, @@ -53,7 +53,7 @@ struct GroupedRulerLayout: Equatable { verticalLength: CGFloat, zeroPoint: NSPoint, zeroCorner: ZeroCorner - ) -> GroupedRulerLayout { + ) -> RulerWindowLayout { let geometry = ZeroCornerGeometry(zeroCorner: zeroCorner) let horizontalFrame = geometry.frame( for: .horizontal, @@ -66,7 +66,7 @@ struct GroupedRulerLayout: Equatable { size: NSSize(width: Ruler.thickness, height: verticalLength) ) - return GroupedRulerLayout( + return RulerWindowLayout( groupFrame: horizontalFrame.union(verticalFrame), horizontalFrame: horizontalFrame, verticalFrame: verticalFrame @@ -226,7 +226,7 @@ struct GroupedRulerLayout: Equatable { } } -private extension GroupedRulerLayout { +private extension RulerWindowLayout { func emptyCornerFrame(zeroCorner: ZeroCorner) -> NSRect { let geometry = ZeroCornerGeometry(zeroCorner: zeroCorner) let x: CGFloat @@ -266,18 +266,18 @@ final class RulerWindow: NSPanel { let horizontalRule: HorizontalRule let verticalRule: VerticalRule - private let groupedContentView: GroupedRulerContentView + private let rulerContentView: RulerContentView private(set) var settings: RulerSettings init(frame: NSRect, settings: RulerSettings = RulerSettings(defaults: prefs)) { self.settings = settings - horizontalRule = GroupedHorizontalRule( + horizontalRule = RulerWindowHorizontalRule( frame: NSRect(x: 0, y: 0, width: 300, height: Ruler.thickness) ) - verticalRule = GroupedVerticalRule( + verticalRule = RulerWindowVerticalRule( frame: NSRect(x: 0, y: 0, width: Ruler.thickness, height: 300) ) - groupedContentView = GroupedRulerContentView( + rulerContentView = RulerContentView( frame: NSRect(origin: .zero, size: frame.size), horizontalRule: horizontalRule, verticalRule: verticalRule @@ -301,10 +301,10 @@ final class RulerWindow: NSPanel { "Ruler", comment: "Window title for a ruler window" ) - identifier = NSUserInterfaceItemIdentifier("grouped-ruler-window") - setAccessibilityIdentifier("grouped-ruler-window") - minSize = GroupedRulerLayout.minSize(zeroCorner: settings.zeroCorner) - maxSize = GroupedRulerLayout.maxSize(zeroCorner: settings.zeroCorner) + identifier = NSUserInterfaceItemIdentifier("ruler-window") + setAccessibilityIdentifier("ruler-window") + minSize = RulerWindowLayout.minSize(zeroCorner: settings.zeroCorner) + maxSize = RulerWindowLayout.maxSize(zeroCorner: settings.zeroCorner) isOpaque = false backgroundColor = .clear @@ -319,9 +319,9 @@ final class RulerWindow: NSPanel { verticalRule.setAccessibilityIdentifier("vertical-ruler-view") horizontalRule.nextResponder = self verticalRule.nextResponder = self - groupedContentView.nextResponder = self + rulerContentView.nextResponder = self - contentView = groupedContentView + contentView = rulerContentView apply(settings: settings) updateLayoutForCurrentZeroCorner() } @@ -337,12 +337,12 @@ final class RulerWindow: NSPanel { override func setFrame(_ frameRect: NSRect, display flag: Bool) { super.setFrame(frameRect, display: flag) - updateGroupedContentFrame() + updateRulerContentFrame() } override func setContentSize(_ size: NSSize) { super.setContentSize(size) - updateGroupedContentFrame() + updateRulerContentFrame() } override func mouseDown(with event: NSEvent) { @@ -350,7 +350,7 @@ final class RulerWindow: NSPanel { super.mouseDown(with: event) if !leftMouseButtonIsPressed { - (nextResponder as? GroupedRulerController)?.finishMouseDrag(with: event) + (nextResponder as? RulerController)?.finishMouseDrag(with: event) } } @@ -373,11 +373,11 @@ final class RulerWindow: NSPanel { func updateLayoutForCurrentZeroCorner() { updateSizeConstraintsForVisibleRules() - updateGroupedContentFrame() - groupedContentView.zeroCorner = settings.zeroCorner - groupedContentView.needsLayout = true - groupedContentView.layoutSubtreeIfNeeded() - groupedContentView.needsDisplay = true + updateRulerContentFrame() + rulerContentView.zeroCorner = settings.zeroCorner + rulerContentView.needsLayout = true + rulerContentView.layoutSubtreeIfNeeded() + rulerContentView.needsDisplay = true } func apply(settings: RulerSettings) { @@ -387,7 +387,7 @@ final class RulerWindow: NSPanel { hasShadow = settings.rulerShadow horizontalRule.settingsOverride = settings verticalRule.settingsOverride = settings - groupedContentView.color = RulerColors(customFill: settings.rulerColor) + rulerContentView.color = RulerColors(customFill: settings.rulerColor) updateLayoutForCurrentZeroCorner() } @@ -398,36 +398,36 @@ final class RulerWindow: NSPanel { } func screenFrame(for orientation: Orientation) -> NSRect { - return convertToScreen(groupedContentView.localFrame(for: orientation)) + return convertToScreen(rulerContentView.localFrame(for: orientation)) } - func visibleFrame(in layout: GroupedRulerLayout) -> NSRect { + func visibleFrame(in layout: RulerWindowLayout) -> NSRect { return layout.visibleFrame( - showsHorizontalRule: groupedContentView.showsHorizontalRule, - showsVerticalRule: groupedContentView.showsVerticalRule + showsHorizontalRule: rulerContentView.showsHorizontalRule, + showsVerticalRule: rulerContentView.showsVerticalRule ) } func setVisibleRules(horizontal: Bool, vertical: Bool) { - groupedContentView.showsHorizontalRule = horizontal - groupedContentView.showsVerticalRule = vertical + rulerContentView.showsHorizontalRule = horizontal + rulerContentView.showsVerticalRule = vertical updateSizeConstraintsForVisibleRules() - groupedContentView.needsLayout = true - groupedContentView.layoutSubtreeIfNeeded() + rulerContentView.needsLayout = true + rulerContentView.layoutSubtreeIfNeeded() } func isRuleVisible(_ orientation: Orientation) -> Bool { switch orientation { case .horizontal: - return groupedContentView.showsHorizontalRule + return rulerContentView.showsHorizontalRule case .vertical: - return groupedContentView.showsVerticalRule + return rulerContentView.showsVerticalRule } } func isEmptyCorner(atWindowPoint windowPoint: NSPoint) -> Bool { - let contentPoint = groupedContentView.convert(windowPoint, from: nil) - return groupedContentView.containsEmptyCorner(contentPoint) + let contentPoint = rulerContentView.convert(windowPoint, from: nil) + return rulerContentView.containsEmptyCorner(contentPoint) } func zeroPoint() -> NSPoint { @@ -454,24 +454,24 @@ final class RulerWindow: NSPanel { return NSEvent.pressedMouseButtons & 1 == 1 } - private func updateGroupedContentFrame() { - guard contentView === groupedContentView else { return } + private func updateRulerContentFrame() { + guard contentView === rulerContentView else { return } - groupedContentView.frame = NSRect(origin: .zero, size: frame.size) - groupedContentView.needsLayout = true - groupedContentView.layoutSubtreeIfNeeded() + rulerContentView.frame = NSRect(origin: .zero, size: frame.size) + rulerContentView.needsLayout = true + rulerContentView.layoutSubtreeIfNeeded() } private func updateSizeConstraintsForVisibleRules() { - minSize = GroupedRulerLayout.minSize( + minSize = RulerWindowLayout.minSize( zeroCorner: settings.zeroCorner, - showsHorizontalRule: groupedContentView.showsHorizontalRule, - showsVerticalRule: groupedContentView.showsVerticalRule + showsHorizontalRule: rulerContentView.showsHorizontalRule, + showsVerticalRule: rulerContentView.showsVerticalRule ) - maxSize = GroupedRulerLayout.maxSize( + maxSize = RulerWindowLayout.maxSize( zeroCorner: settings.zeroCorner, - showsHorizontalRule: groupedContentView.showsHorizontalRule, - showsVerticalRule: groupedContentView.showsVerticalRule + showsHorizontalRule: rulerContentView.showsHorizontalRule, + showsVerticalRule: rulerContentView.showsVerticalRule ) } } @@ -479,17 +479,17 @@ final class RulerWindow: NSPanel { extension RulerWindow: RulerContextMenuActivating { func activateForRulerContextMenu() { makeKey() - (nextResponder as? GroupedRulerController)?.activateForRulerContextMenu() + (nextResponder as? RulerController)?.activateForRulerContextMenu() } } -private final class GroupedHorizontalRule: HorizontalRule { +private final class RulerWindowHorizontalRule: HorizontalRule { override func setFrameSize(_ newSize: NSSize) { super.setFrameSize(NSSize(width: newSize.width, height: Ruler.thickness)) } } -private final class GroupedVerticalRule: VerticalRule { +private final class RulerWindowVerticalRule: VerticalRule { override var rulerWidth: CGFloat { return Ruler.thickness } @@ -565,7 +565,7 @@ private final class RulerClipView: NSView { } } -private final class GroupedRulerBorderView: RulerBorderView { +private final class RulerWindowBorderView: RulerBorderView { var zeroCorner = prefs.zeroCorner { didSet { needsDisplay = true @@ -598,7 +598,7 @@ private final class GroupedRulerBorderView: RulerBorderView { } private func lShapedBorderPath() -> NSBezierPath { - return groupedRulerLShapedPath( + return rulerWindowLShapedPath( in: bounds, zeroCorner: zeroCorner, inset: Self.borderCenterInset @@ -613,7 +613,7 @@ private final class GroupedRulerBorderView: RulerBorderView { } } -private final class GroupedRulerZeroLabelsView: NSView { +private final class RulerWindowZeroLabelsView: NSView { private let horizontalRule: HorizontalRule private let verticalRule: VerticalRule private let zeroLabel = "0" @@ -763,7 +763,7 @@ private final class GroupedRulerZeroLabelsView: NSView { } } -final class GroupedRulerContentView: NSView { +final class RulerContentView: NSView { let horizontalRule: HorizontalRule let verticalRule: VerticalRule private let horizontalHost = RulerClipView(frame: .zero) @@ -772,8 +772,8 @@ final class GroupedRulerContentView: NSView { orientation: .horizontal, label: NSAttributedString(string: "") ) - private let zeroLabelsView: GroupedRulerZeroLabelsView - private let borderView = GroupedRulerBorderView(frame: .zero) + private let zeroLabelsView: RulerWindowZeroLabelsView + private let borderView = RulerWindowBorderView(frame: .zero) private var cornerTrackingArea: NSTrackingArea? var showsHorizontalRule = true { @@ -815,7 +815,7 @@ final class GroupedRulerContentView: NSView { ) { self.horizontalRule = horizontalRule self.verticalRule = verticalRule - self.zeroLabelsView = GroupedRulerZeroLabelsView( + self.zeroLabelsView = RulerWindowZeroLabelsView( horizontalRule: horizontalRule, verticalRule: verticalRule ) @@ -876,7 +876,7 @@ final class GroupedRulerContentView: NSView { override func layout() { super.layout() - let layout = GroupedRulerLayout.layout(groupFrame: bounds, zeroCorner: zeroCorner) + let layout = RulerWindowLayout.layout(groupFrame: bounds, zeroCorner: zeroCorner) let cornerFrame = layout.emptyCornerFrame(zeroCorner: zeroCorner) setFrame(ruleFrame(for: .horizontal, in: bounds, layout: layout), for: horizontalHost) setFrame(horizontalHost.bounds, for: horizontalRule) @@ -947,12 +947,12 @@ final class GroupedRulerContentView: NSView { } func localFrame(for orientation: Orientation) -> NSRect { - let layout = GroupedRulerLayout.layout(groupFrame: bounds, zeroCorner: zeroCorner) + let layout = RulerWindowLayout.layout(groupFrame: bounds, zeroCorner: zeroCorner) return ruleFrame(for: orientation, in: bounds, layout: layout) } private func cornerFrame() -> NSRect { - return GroupedRulerLayout + return RulerWindowLayout .layout(groupFrame: bounds, zeroCorner: zeroCorner) .emptyCornerFrame(zeroCorner: zeroCorner) } @@ -960,7 +960,7 @@ final class GroupedRulerContentView: NSView { private func ruleFrame( for orientation: Orientation, in bounds: NSRect, - layout: GroupedRulerLayout + layout: RulerWindowLayout ) -> NSRect { switch (showsHorizontalRule, showsVerticalRule) { case (true, true): @@ -1039,11 +1039,11 @@ final class GroupedRulerContentView: NSView { } private func rulerFillPath() -> NSBezierPath { - let layout = GroupedRulerLayout.layout(groupFrame: bounds, zeroCorner: zeroCorner) + let layout = RulerWindowLayout.layout(groupFrame: bounds, zeroCorner: zeroCorner) switch (showsHorizontalRule, showsVerticalRule) { case (true, true): - return groupedRulerLShapedPath(in: bounds, zeroCorner: zeroCorner, inset: 0) + return rulerWindowLShapedPath(in: bounds, zeroCorner: zeroCorner, inset: 0) case (true, false): return NSBezierPath(rect: ruleFrame(for: .horizontal, in: bounds, layout: layout)) case (false, true): @@ -1089,17 +1089,17 @@ final class GroupedRulerContentView: NSView { } #if !SNAPSHOT_GENERATOR -final class GroupedRulerController: NSWindowController, NSWindowDelegate, NotificationObserver { +final class RulerController: NSWindowController, NSWindowDelegate, NotificationObserver { var observers: [NSKeyValueObservation] = [] var notificationObservers: [NSObjectProtocol] = [] - let groupedWindow: RulerWindow + let rulerWindow: RulerWindow var state: RulerInstanceState - var onBecameActive: ((GroupedRulerController) -> Void)? - var onDragStarted: ((GroupedRulerController) -> Void)? - var onDragged: ((GroupedRulerController) -> Void)? - var onDragFinished: ((GroupedRulerController) -> Void)? - var onStateChanged: ((GroupedRulerController) -> Void)? + var onBecameActive: ((RulerController) -> Void)? + var onDragStarted: ((RulerController) -> Void)? + var onDragged: ((RulerController) -> Void)? + var onDragFinished: ((RulerController) -> Void)? + var onStateChanged: ((RulerController) -> Void)? private var keyListener: Any? private var mouseInteraction: RulerMouseInteractionState! @@ -1121,12 +1121,12 @@ final class GroupedRulerController: NSWindowController, NSWindowDelegate, Notifi var opacity = 0 { didSet { - groupedWindow.alphaValue = windowAlphaValue(opacity) + rulerWindow.alphaValue = windowAlphaValue(opacity) } } convenience init(frame: NSRect) { - let layout = GroupedRulerLayout.layout(groupFrame: frame, zeroCorner: prefs.zeroCorner) + let layout = RulerWindowLayout.layout(groupFrame: frame, zeroCorner: prefs.zeroCorner) let state = RulerInstanceState( settings: RulerSettings(defaults: prefs), layout: RulerLayoutState( @@ -1147,21 +1147,21 @@ final class GroupedRulerController: NSWindowController, NSWindowDelegate, Notifi self.state = state self.followsDefaultPreferences = followsDefaultPreferences let layout = state.layout.layout(zeroCorner: state.settings.zeroCorner) - groupedWindow = RulerWindow( + rulerWindow = RulerWindow( frame: layout.visibleFrame( showsHorizontalRule: state.visibility.showsHorizontal, showsVerticalRule: state.visibility.showsVertical ), settings: state.settings ) - super.init(window: groupedWindow) + super.init(window: rulerWindow) opacity = state.settings.foregroundOpacity createObservers() subscribeToPrefs() - groupedWindow.delegate = self - groupedWindow.nextResponder = self + rulerWindow.delegate = self + rulerWindow.nextResponder = self mouseInteraction = RulerMouseInteractionState(owner: self) { [weak self] event in return self?.mouseIsInsideRuler(with: event) ?? false } @@ -1179,13 +1179,13 @@ final class GroupedRulerController: NSWindowController, NSWindowDelegate, Notifi } var isVisible: Bool { - return groupedWindow.isVisible + return rulerWindow.isVisible } func show() { applyStateToWindow(display: false) showWindow(self) - groupedWindow.orderFrontRegardless() + rulerWindow.orderFrontRegardless() } func show( @@ -1208,7 +1208,7 @@ final class GroupedRulerController: NSWindowController, NSWindowDelegate, Notifi } func hide() { - groupedWindow.orderOut(self) + rulerWindow.orderOut(self) } @discardableResult @@ -1229,42 +1229,26 @@ final class GroupedRulerController: NSWindowController, NSWindowDelegate, Notifi return true } - func syncFrames( - to horizontalWindow: LegacyRulerWindow, - and verticalWindow: LegacyRulerWindow, - persistAutosave: Bool = false - ) { - guard isVisible else { return } - - let frames = syncedRulerFrames( - horizontalWindow: horizontalWindow, - verticalWindow: verticalWindow - ) - - syncFrame(frames.horizontal, to: horizontalWindow, persistAutosave: persistAutosave) - syncFrame(frames.vertical, to: verticalWindow, persistAutosave: persistAutosave) - } - func align(at point: NSPoint) { - let horizontalLength = groupedWindow.screenFrame(for: .horizontal).width - let verticalLength = groupedWindow.screenFrame(for: .vertical).height - let layout = GroupedRulerLayout.layout( + let horizontalLength = rulerWindow.screenFrame(for: .horizontal).width + let verticalLength = rulerWindow.screenFrame(for: .vertical).height + let layout = RulerWindowLayout.layout( horizontalLength: horizontalLength, verticalLength: verticalLength, zeroPoint: point, zeroCorner: state.settings.zeroCorner ) - groupedWindow.setFrame(groupedWindow.visibleFrame(in: layout), display: true) - groupedWindow.updateLayoutForCurrentZeroCorner() + rulerWindow.setFrame(rulerWindow.visibleFrame(in: layout), display: true) + rulerWindow.updateLayoutForCurrentZeroCorner() captureStateFromWindow() } func prepareForZeroCornerChange(to zeroCorner: ZeroCorner) { - let zeroPoint = groupedWindow.zeroPoint() - let horizontalLength = groupedWindow.screenFrame(for: .horizontal).width - let verticalLength = groupedWindow.screenFrame(for: .vertical).height - let layout = GroupedRulerLayout.layout( + let zeroPoint = rulerWindow.zeroPoint() + let horizontalLength = rulerWindow.screenFrame(for: .horizontal).width + let verticalLength = rulerWindow.screenFrame(for: .vertical).height + let layout = RulerWindowLayout.layout( horizontalLength: horizontalLength, verticalLength: verticalLength, zeroPoint: zeroPoint, @@ -1272,11 +1256,11 @@ final class GroupedRulerController: NSWindowController, NSWindowDelegate, Notifi ) state.settings.zeroCorner = zeroCorner - groupedWindow.apply(settings: state.settings) - groupedWindow.alphaValue = windowAlphaValue(opacity) + rulerWindow.apply(settings: state.settings) + rulerWindow.alphaValue = windowAlphaValue(opacity) updateIsFloatingPanel() updateHasShadow() - groupedWindow.setFrame(groupedWindow.visibleFrame(in: layout), display: true) + rulerWindow.setFrame(rulerWindow.visibleFrame(in: layout), display: true) captureStateFromWindow() notifyStateChanged() } @@ -1290,15 +1274,15 @@ final class GroupedRulerController: NSWindowController, NSWindowDelegate, Notifi } func updateIsFloatingPanel() { - groupedWindow.isFloatingPanel = preferencesWindowOpen ? false : state.settings.floatRulers + rulerWindow.isFloatingPanel = preferencesWindowOpen ? false : state.settings.floatRulers } func updateHasShadow() { - groupedWindow.hasShadow = state.settings.rulerShadow + rulerWindow.hasShadow = state.settings.rulerShadow } func redrawForPreferenceChange() { - groupedWindow.redrawForPreferenceChange() + rulerWindow.redrawForPreferenceChange() } func updateSettings(_ update: (inout RulerSettings) -> Void) { @@ -1314,7 +1298,7 @@ final class GroupedRulerController: NSWindowController, NSWindowDelegate, Notifi let maxVerticalLength = getMaxSize(ruler: Ruler(.vertical)).height state.layout = RulerLayoutState( - zeroPoint: groupedWindow.zeroPoint(), + zeroPoint: rulerWindow.zeroPoint(), horizontalLength: min(max(horizontalLength, minHorizontalLength), maxHorizontalLength), verticalLength: min(max(verticalLength, minVerticalLength), maxVerticalLength) ) @@ -1323,7 +1307,7 @@ final class GroupedRulerController: NSWindowController, NSWindowDelegate, Notifi } func move(to frame: NSRect) { - groupedWindow.setFrame(frame, display: false) + rulerWindow.setFrame(frame, display: false) captureStateFromWindow() } @@ -1338,11 +1322,11 @@ final class GroupedRulerController: NSWindowController, NSWindowDelegate, Notifi } func drawMouseTick(at mouseLoc: NSPoint) { - if groupedWindow.isRuleVisible(.horizontal) { - groupedWindow.horizontalRule.drawMouseTick(at: mouseLoc) + if rulerWindow.isRuleVisible(.horizontal) { + rulerWindow.horizontalRule.drawMouseTick(at: mouseLoc) } - if groupedWindow.isRuleVisible(.vertical) { - groupedWindow.verticalRule.drawMouseTick(at: mouseLoc) + if rulerWindow.isRuleVisible(.vertical) { + rulerWindow.verticalRule.drawMouseTick(at: mouseLoc) } } @@ -1352,53 +1336,53 @@ final class GroupedRulerController: NSWindowController, NSWindowDelegate, Notifi } private func updateMouseTickDrawingVisibility() { - groupedWindow.horizontalRule.showMouseTick = isMouseTickDrawingEnabled - && groupedWindow.isRuleVisible(.horizontal) - groupedWindow.verticalRule.showMouseTick = isMouseTickDrawingEnabled - && groupedWindow.isRuleVisible(.vertical) + rulerWindow.horizontalRule.showMouseTick = isMouseTickDrawingEnabled + && rulerWindow.isRuleVisible(.horizontal) + rulerWindow.verticalRule.showMouseTick = isMouseTickDrawingEnabled + && rulerWindow.isRuleVisible(.vertical) } private func applyStateToWindow(display: Bool) { let zeroCorner = state.settings.zeroCorner let layout = state.layout.layout(zeroCorner: zeroCorner) - groupedWindow.apply(settings: state.settings) - groupedWindow.alphaValue = windowAlphaValue(opacity) + rulerWindow.apply(settings: state.settings) + rulerWindow.alphaValue = windowAlphaValue(opacity) updateIsFloatingPanel() updateHasShadow() - groupedWindow.setVisibleRules( + rulerWindow.setVisibleRules( horizontal: state.visibility.showsHorizontal, vertical: state.visibility.showsVertical ) updateMouseTickDrawingVisibility() - groupedWindow.setFrame( + rulerWindow.setFrame( layout.visibleFrame( showsHorizontalRule: state.visibility.showsHorizontal, showsVerticalRule: state.visibility.showsVertical ), display: display ) - groupedWindow.updateLayoutForCurrentZeroCorner() + rulerWindow.updateLayoutForCurrentZeroCorner() } private func captureStateFromWindow() { var horizontalLength = state.layout.horizontalLength var verticalLength = state.layout.verticalLength - if groupedWindow.isRuleVisible(.horizontal) { - horizontalLength = groupedWindow.screenFrame(for: .horizontal).width + if rulerWindow.isRuleVisible(.horizontal) { + horizontalLength = rulerWindow.screenFrame(for: .horizontal).width } - if groupedWindow.isRuleVisible(.vertical) { - verticalLength = groupedWindow.screenFrame(for: .vertical).height + if rulerWindow.isRuleVisible(.vertical) { + verticalLength = rulerWindow.screenFrame(for: .vertical).height } state.layout = RulerLayoutState( - zeroPoint: groupedWindow.zeroPoint(), + zeroPoint: rulerWindow.zeroPoint(), horizontalLength: horizontalLength, verticalLength: verticalLength ) state.visibility = RulerWingVisibility( - horizontal: groupedWindow.isRuleVisible(.horizontal), - vertical: groupedWindow.isRuleVisible(.vertical) + horizontal: rulerWindow.isRuleVisible(.horizontal), + vertical: rulerWindow.isRuleVisible(.vertical) ) notifyStateChanged() } @@ -1407,80 +1391,11 @@ final class GroupedRulerController: NSWindowController, NSWindowDelegate, Notifi onStateChanged?(self) } - private func syncedRulerFrames( - horizontalWindow: LegacyRulerWindow, - verticalWindow: LegacyRulerWindow - ) -> (horizontal: NSRect, vertical: NSRect) { - let showsHorizontalRule = groupedWindow.isRuleVisible(.horizontal) - let showsVerticalRule = groupedWindow.isRuleVisible(.vertical) - - switch (showsHorizontalRule, showsVerticalRule) { - case (true, true): - return ( - groupedWindow.screenFrame(for: .horizontal), - groupedWindow.screenFrame(for: .vertical) - ) - case (true, false): - let horizontalFrame = groupedWindow.screenFrame(for: .horizontal) - let zeroPoint = ZeroCornerGeometry(zeroCorner: state.settings.zeroCorner) - .zeroPoint(in: horizontalFrame, for: .horizontal) - return ( - horizontalFrame, - hiddenRuleFrame( - orientation: .vertical, - zeroPoint: zeroPoint, - size: verticalWindow.frame.size - ) - ) - case (false, true): - let verticalFrame = groupedWindow.screenFrame(for: .vertical) - let zeroPoint = ZeroCornerGeometry(zeroCorner: state.settings.zeroCorner) - .zeroPoint(in: verticalFrame, for: .vertical) - return ( - hiddenRuleFrame( - orientation: .horizontal, - zeroPoint: zeroPoint, - size: horizontalWindow.frame.size - ), - verticalFrame - ) - case (false, false): - return (horizontalWindow.frame, verticalWindow.frame) - } - } - - private func hiddenRuleFrame( - orientation: Orientation, - zeroPoint: NSPoint, - size: NSSize - ) -> NSRect { - return ZeroCornerGeometry(zeroCorner: state.settings.zeroCorner).frame( - for: orientation, - zeroPoint: zeroPoint, - size: size - ) - } - - private func syncFrame( - _ frame: NSRect, - to window: LegacyRulerWindow, - persistAutosave: Bool - ) { - window.setFrame(frame, display: false) - - guard persistAutosave else { return } - - if let frameAutosaveName = window.ruler.name { - window.saveFrame(usingName: NSWindow.FrameAutosaveName(frameAutosaveName)) - } - } - func windowWillStartLiveResize(_ notification: Notification) { mouseInteraction.windowWillStartLiveResize() } func windowDidEndLiveResize(_ notification: Notification) { - syncRulerWindowFrames(persistAutosave: true) captureStateFromWindow() mouseInteraction.windowDidEndLiveResize() } @@ -1490,12 +1405,7 @@ final class GroupedRulerController: NSWindowController, NSWindowDelegate, Notifi } func windowDidMove(_ notification: Notification) { - groupedWindow.invalidateShadow() - syncRulerWindowFrames( - persistAutosave: mouseInteraction.shouldPersistFrameAutosaveOnWindowMove( - isLeftMouseButtonPressed: isLeftMouseButtonPressed() - ) - ) + rulerWindow.invalidateShadow() captureStateFromWindow() onDragged?(self) mouseInteraction.windowDidMove(isLeftMouseButtonPressed: isLeftMouseButtonPressed()) @@ -1529,7 +1439,6 @@ final class GroupedRulerController: NSWindowController, NSWindowDelegate, Notifi func finishMouseDrag(with event: NSEvent) { if mouseInteraction.finishMouseDrag(with: event) { - syncRulerWindowFrames(persistAutosave: true) captureStateFromWindow() onDragFinished?(self) } @@ -1543,30 +1452,22 @@ final class GroupedRulerController: NSWindowController, NSWindowDelegate, Notifi mouseInteraction.mouseMoved(with: event) } - private var appDelegate: AppDelegate? { - return NSApp.delegate as? AppDelegate - } - - private func syncRulerWindowFrames(persistAutosave: Bool = false) { - appDelegate?.syncGroupedRulerFramesToRulerWindows(persistAutosave: persistAutosave) - } - private func mouseIsInsideRuler(with event: NSEvent) -> Bool { return orientation(at: event) != nil - || groupedWindow.isEmptyCorner(atWindowPoint: event.locationInWindow) + || rulerWindow.isEmptyCorner(atWindowPoint: event.locationInWindow) } private func orientation(at event: NSEvent) -> Orientation? { - let horizontalLocation = groupedWindow.horizontalRule.convert(event.locationInWindow, from: nil) - let verticalLocation = groupedWindow.verticalRule.convert(event.locationInWindow, from: nil) + let horizontalLocation = rulerWindow.horizontalRule.convert(event.locationInWindow, from: nil) + let verticalLocation = rulerWindow.verticalRule.convert(event.locationInWindow, from: nil) - if groupedWindow.isRuleVisible(.horizontal), - groupedWindow.horizontalRule.bounds.contains(horizontalLocation) { + if rulerWindow.isRuleVisible(.horizontal), + rulerWindow.horizontalRule.bounds.contains(horizontalLocation) { return .horizontal } - if groupedWindow.isRuleVisible(.vertical), - groupedWindow.verticalRule.bounds.contains(verticalLocation) { + if rulerWindow.isRuleVisible(.vertical), + rulerWindow.verticalRule.bounds.contains(verticalLocation) { return .vertical } @@ -1609,7 +1510,7 @@ final class GroupedRulerController: NSWindowController, NSWindowDelegate, Notifi // MARK: KeyListener -extension GroupedRulerController { +extension RulerController { func startKeyListener() { keyListener = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] in guard let self = self else { return $0 } @@ -1628,7 +1529,7 @@ extension GroupedRulerController { let shift = event.modifierFlags.contains(.shift) let keyboardModifiers = event.modifierFlags.intersection(.deviceIndependentFlagsMask) - if groupedWindow.isKeyWindow, + if rulerWindow.isKeyWindow, let appDelegate = NSApp.delegate as? AppDelegate, appDelegate.performRulerHotkey( keyCode: Int(event.keyCode), @@ -1640,16 +1541,16 @@ extension GroupedRulerController { switch Int(event.keyCode) { case kVK_LeftArrow: - groupedWindow.nudgeLeft(withShift: shift) + rulerWindow.nudgeLeft(withShift: shift) return nil case kVK_RightArrow: - groupedWindow.nudgeRight(withShift: shift) + rulerWindow.nudgeRight(withShift: shift) return nil case kVK_UpArrow: - groupedWindow.nudgeUp(withShift: shift) + rulerWindow.nudgeUp(withShift: shift) return nil case kVK_DownArrow: - groupedWindow.nudgeDown(withShift: shift) + rulerWindow.nudgeDown(withShift: shift) return nil default: return event @@ -1658,7 +1559,7 @@ extension GroupedRulerController { } final class RulerManager { - typealias ControllerFactory = (RulerInstanceState) -> GroupedRulerController + typealias ControllerFactory = (RulerInstanceState) -> RulerController private struct GroupedDragState { let draggedRulerID: UUID @@ -1666,16 +1567,16 @@ final class RulerManager { } private let controllerFactory: ControllerFactory - private(set) var controllers: [GroupedRulerController] = [] + private(set) var controllers: [RulerController] = [] private(set) var activeRulerID: UUID? - var onActiveControllerChanged: ((GroupedRulerController?) -> Void)? + var onActiveControllerChanged: ((RulerController?) -> Void)? var onStateChanged: ((RulerManager) -> Void)? private var groupedDragState: GroupedDragState? private var isApplyingGroupedDrag = false init( initialStates: [RulerInstanceState] = [], - controllerFactory: @escaping ControllerFactory = { GroupedRulerController(state: $0) } + controllerFactory: @escaping ControllerFactory = { RulerController(state: $0) } ) { self.controllerFactory = controllerFactory restore(initialStates) @@ -1689,13 +1590,13 @@ final class RulerManager { return controllers.contains { $0.isVisible } } - var activeController: GroupedRulerController? { + var activeController: RulerController? { if let activeRulerID = activeRulerID, let controller = controllers.first(where: { $0.state.id == activeRulerID }) { return controller } - if let keyController = controllers.first(where: { $0.groupedWindow.isKeyWindow }) { + if let keyController = controllers.first(where: { $0.rulerWindow.isKeyWindow }) { return keyController } @@ -1710,7 +1611,7 @@ final class RulerManager { func createRuler( defaults: RulerSettings = RulerSettings(defaults: prefs), screenFrame: NSRect = defaultRulerScreenFrame() - ) -> GroupedRulerController { + ) -> RulerController { let defaultState = RulerInstanceState.createFromDefaults( defaults: defaults, screenFrame: screenFrame @@ -1721,7 +1622,7 @@ final class RulerManager { } @discardableResult - func addRuler(state: RulerInstanceState) -> GroupedRulerController { + func addRuler(state: RulerInstanceState) -> RulerController { let controller = controllerFactory(state) configure(controller) controllers.append(controller) @@ -1756,12 +1657,12 @@ final class RulerManager { } if let activeController = activeController { - activeController.groupedWindow.makeKey() + activeController.rulerWindow.makeKey() } } @discardableResult - func cycleActiveRuler() -> GroupedRulerController? { + func cycleActiveRuler() -> RulerController? { let visibleControllers = controllers.filter(\.isVisible) guard !visibleControllers.isEmpty else { return nil } @@ -1773,8 +1674,8 @@ final class RulerManager { let nextController = visibleControllers[nextIndex] markActive(nextController) - nextController.groupedWindow.orderFrontRegardless() - nextController.groupedWindow.makeKey() + nextController.rulerWindow.orderFrontRegardless() + nextController.rulerWindow.makeKey() return nextController } @@ -1786,7 +1687,7 @@ final class RulerManager { return true } - func close(_ controller: GroupedRulerController) { + func close(_ controller: RulerController) { controller.hide() controllers.removeAll { $0 === controller } @@ -1798,7 +1699,7 @@ final class RulerManager { notifyStateChanged() } - func markActive(_ controller: GroupedRulerController) { + func markActive(_ controller: RulerController) { guard controllers.contains(where: { $0 === controller }) else { return } activeRulerID = controller.state.id @@ -1806,7 +1707,7 @@ final class RulerManager { notifyStateChanged() } - func beginGroupedDrag(from controller: GroupedRulerController) { + func beginGroupedDrag(from controller: RulerController) { guard prefs.groupRulers, controllers.contains(where: { $0 === controller }) else { groupedDragState = nil @@ -1818,12 +1719,12 @@ final class RulerManager { framesByRulerID: Dictionary( uniqueKeysWithValues: controllers .filter(\.isVisible) - .map { ($0.state.id, $0.groupedWindow.frame) } + .map { ($0.state.id, $0.rulerWindow.frame) } ) ) } - func syncGroupedDrag(from controller: GroupedRulerController) { + func syncGroupedDrag(from controller: RulerController) { guard prefs.groupRulers, !isApplyingGroupedDrag, let groupedDragState = groupedDragState, @@ -1833,8 +1734,8 @@ final class RulerManager { } let offset = NSSize( - width: controller.groupedWindow.frame.minX - originalDraggedFrame.minX, - height: controller.groupedWindow.frame.minY - originalDraggedFrame.minY + width: controller.rulerWindow.frame.minX - originalDraggedFrame.minX, + height: controller.rulerWindow.frame.minY - originalDraggedFrame.minY ) guard offset.width != 0 || offset.height != 0 else { return } @@ -1854,22 +1755,22 @@ final class RulerManager { notifyStateChanged() } - func finishGroupedDrag(from controller: GroupedRulerController) { + func finishGroupedDrag(from controller: RulerController) { syncGroupedDrag(from: controller) groupedDragState = nil } - func controller(containing window: NSWindow?) -> GroupedRulerController? { + func controller(containing window: NSWindow?) -> RulerController? { guard let window = window else { return nil } - return controllers.first { $0.groupedWindow === window } + return controllers.first { $0.rulerWindow === window } } - func controller(id: UUID) -> GroupedRulerController? { + func controller(id: UUID) -> RulerController? { return controllers.first { $0.state.id == id } } - private func configure(_ controller: GroupedRulerController) { + private func configure(_ controller: RulerController) { controller.onBecameActive = { [weak self, weak controller] _ in guard let controller = controller else { return } self?.markActive(controller) @@ -1913,12 +1814,12 @@ final class RulerManager { } #endif -private func groupedRulerLShapedPath( +private func rulerWindowLShapedPath( in bounds: NSRect, zeroCorner: ZeroCorner, inset: CGFloat ) -> NSBezierPath { - let layout = GroupedRulerLayout.layout(groupFrame: bounds, zeroCorner: zeroCorner) + let layout = RulerWindowLayout.layout(groupFrame: bounds, zeroCorner: zeroCorner) let horizontalFrame = layout.localFrame(for: .horizontal) let verticalFrame = layout.localFrame(for: .vertical) let minX = bounds.minX + inset diff --git a/FreeRulerTests/RulerCoreTests.swift b/FreeRulerTests/RulerCoreTests.swift index b3b531a..49710df 100644 --- a/FreeRulerTests/RulerCoreTests.swift +++ b/FreeRulerTests/RulerCoreTests.swift @@ -212,9 +212,9 @@ final class RulerCoreTests: XCTestCase { first.show() second.show() - let firstFrame = first.groupedWindow.frame - let secondFrame = second.groupedWindow.frame - let hiddenFrame = hidden.groupedWindow.frame + let firstFrame = first.rulerWindow.frame + let secondFrame = second.rulerWindow.frame + let hiddenFrame = hidden.rulerWindow.frame let dragOffset = NSSize(width: 37, height: -24) var movedFirstFrame = firstFrame movedFirstFrame.origin.x += dragOffset.width @@ -225,14 +225,14 @@ final class RulerCoreTests: XCTestCase { appDelegate.rulerManager.syncGroupedDrag(from: first) appDelegate.rulerManager.finishGroupedDrag(from: first) - XCTAssertEqual(first.groupedWindow.frame, movedFirstFrame) - XCTAssertEqual(second.groupedWindow.frame.minX, secondFrame.minX + dragOffset.width) - XCTAssertEqual(second.groupedWindow.frame.minY, secondFrame.minY + dragOffset.height) - XCTAssertEqual(hidden.groupedWindow.frame, hiddenFrame) + XCTAssertEqual(first.rulerWindow.frame, movedFirstFrame) + XCTAssertEqual(second.rulerWindow.frame.minX, secondFrame.minX + dragOffset.width) + XCTAssertEqual(second.rulerWindow.frame.minY, secondFrame.minY + dragOffset.height) + XCTAssertEqual(hidden.rulerWindow.frame, hiddenFrame) XCTAssertEqual( second.state.layout.zeroPoint, ZeroCornerGeometry(zeroCorner: second.state.settings.zeroCorner).zeroPoint( - in: second.groupedWindow.screenFrame(for: .horizontal), + in: second.rulerWindow.screenFrame(for: .horizontal), for: .horizontal ) ) @@ -281,10 +281,10 @@ final class RulerCoreTests: XCTestCase { ) manager.markActive(second) - let menu = first.groupedWindow.horizontalRule.menu(for: mouseEvent( + let menu = first.rulerWindow.horizontalRule.menu(for: mouseEvent( type: .rightMouseDown, location: .zero, - windowNumber: first.groupedWindow.windowNumber, + windowNumber: first.rulerWindow.windowNumber, timestamp: 0 )) @@ -337,13 +337,13 @@ final class RulerCoreTests: XCTestCase { manager.showAll() XCTAssertTrue(manager.hasVisibleRulers) - XCTAssertTrue(manager.controllers[0].groupedWindow.isRuleVisible(.horizontal)) - XCTAssertFalse(manager.controllers[0].groupedWindow.isRuleVisible(.vertical)) - XCTAssertFalse(manager.controllers[1].groupedWindow.isRuleVisible(.horizontal)) - XCTAssertTrue(manager.controllers[1].groupedWindow.isRuleVisible(.vertical)) + XCTAssertTrue(manager.controllers[0].rulerWindow.isRuleVisible(.horizontal)) + XCTAssertFalse(manager.controllers[0].rulerWindow.isRuleVisible(.vertical)) + XCTAssertFalse(manager.controllers[1].rulerWindow.isRuleVisible(.horizontal)) + XCTAssertTrue(manager.controllers[1].rulerWindow.isRuleVisible(.vertical)) } - func testManagedGroupedRulerAppliesPerRulerSettingsToWindowAndRules() { + func testRulerControllerAppliesPerRulerSettingsToWindowAndRules() { let color = NSColor(deviceRed: 0.1, green: 0.4, blue: 0.8, alpha: 1) let settings = RulerSettings( unit: .inches, @@ -354,7 +354,7 @@ final class RulerCoreTests: XCTestCase { rulerShadow: true, zeroCorner: .bottomRight ) - let controller = GroupedRulerController( + let controller = RulerController( state: RulerInstanceState( settings: settings, layout: RulerLayoutState( @@ -368,25 +368,25 @@ final class RulerCoreTests: XCTestCase { controller.hide() } - XCTAssertEqual(controller.groupedWindow.horizontalRule.unit, .inches) - XCTAssertEqual(controller.groupedWindow.verticalRule.unit, .inches) - XCTAssertEqual(controller.groupedWindow.horizontalRule.zeroCorner, .bottomRight) - XCTAssertEqual(controller.groupedWindow.verticalRule.zeroCorner, .bottomRight) - assertColor(controller.groupedWindow.horizontalRule.color.fill, equals: color) - assertColor(controller.groupedWindow.verticalRule.color.fill, equals: color) - XCTAssertEqual(controller.groupedWindow.alphaValue, 0.73, accuracy: 0.0001) - XCTAssertFalse(controller.groupedWindow.isFloatingPanel) - XCTAssertTrue(controller.groupedWindow.hasShadow) + XCTAssertEqual(controller.rulerWindow.horizontalRule.unit, .inches) + XCTAssertEqual(controller.rulerWindow.verticalRule.unit, .inches) + XCTAssertEqual(controller.rulerWindow.horizontalRule.zeroCorner, .bottomRight) + XCTAssertEqual(controller.rulerWindow.verticalRule.zeroCorner, .bottomRight) + assertColor(controller.rulerWindow.horizontalRule.color.fill, equals: color) + assertColor(controller.rulerWindow.verticalRule.color.fill, equals: color) + XCTAssertEqual(controller.rulerWindow.alphaValue, 0.73, accuracy: 0.0001) + XCTAssertFalse(controller.rulerWindow.isFloatingPanel) + XCTAssertTrue(controller.rulerWindow.hasShadow) controller.background() - XCTAssertEqual(controller.groupedWindow.alphaValue, 0.31, accuracy: 0.0001) + XCTAssertEqual(controller.rulerWindow.alphaValue, 0.31, accuracy: 0.0001) } - func testManagedGroupedRulerIgnoresDefaultPreferenceChanges() { + func testRulerControllerIgnoresDefaultPreferenceChanges() { withRestoredRulerPreferences { let color = NSColor(deviceRed: 0.2, green: 0.3, blue: 0.7, alpha: 1) - let controller = GroupedRulerController( + let controller = RulerController( state: RulerInstanceState( settings: RulerSettings( unit: .inches, @@ -417,12 +417,12 @@ final class RulerCoreTests: XCTestCase { prefs.zeroCorner = .topRight XCTAssertEqual(controller.state.settings.unit, .inches) - XCTAssertEqual(controller.groupedWindow.horizontalRule.unit, .inches) - XCTAssertEqual(controller.groupedWindow.horizontalRule.zeroCorner, .bottomLeft) - assertColor(controller.groupedWindow.horizontalRule.color.fill, equals: color) - XCTAssertEqual(controller.groupedWindow.alphaValue, 0.64, accuracy: 0.0001) - XCTAssertFalse(controller.groupedWindow.isFloatingPanel) - XCTAssertFalse(controller.groupedWindow.hasShadow) + XCTAssertEqual(controller.rulerWindow.horizontalRule.unit, .inches) + XCTAssertEqual(controller.rulerWindow.horizontalRule.zeroCorner, .bottomLeft) + assertColor(controller.rulerWindow.horizontalRule.color.fill, equals: color) + XCTAssertEqual(controller.rulerWindow.alphaValue, 0.64, accuracy: 0.0001) + XCTAssertFalse(controller.rulerWindow.isFloatingPanel) + XCTAssertFalse(controller.rulerWindow.hasShadow) } } @@ -438,7 +438,7 @@ final class RulerCoreTests: XCTestCase { prefs.defaultHorizontalLength = 640 prefs.defaultVerticalLength = 280 - let controller = GroupedRulerController( + let controller = RulerController( state: RulerInstanceState( settings: RulerSettings( unit: .inches, @@ -465,14 +465,14 @@ final class RulerCoreTests: XCTestCase { settingsController.setUnit(settingsController.unitSegmentedControl) XCTAssertEqual(controller.state.settings.unit, .millimeters) - XCTAssertEqual(controller.groupedWindow.horizontalRule.unit, .millimeters) - XCTAssertEqual(controller.groupedWindow.verticalRule.unit, .millimeters) + XCTAssertEqual(controller.rulerWindow.horizontalRule.unit, .millimeters) + XCTAssertEqual(controller.rulerWindow.verticalRule.unit, .millimeters) XCTAssertEqual(settingsController.unitSegmentedControl.selectedSegment, Unit.millimeters.rawValue) XCTAssertEqual(prefs.unit, .pixels) let enteredWidthMillimeters: CGFloat = 100 let enteredHeightMillimeters: CGFloat = 80 - let zeroPointBeforeDimensionChange = controller.groupedWindow.zeroPoint() + let zeroPointBeforeDimensionChange = controller.rulerWindow.zeroPoint() settingsController.dimensionWidthField.stringValue = "\(enteredWidthMillimeters)" settingsController.dimensionHeightField.stringValue = "\(enteredHeightMillimeters)" let expectedHorizontalLength = settingsController.settingsControlsView.selectedHorizontalLength @@ -481,10 +481,10 @@ final class RulerCoreTests: XCTestCase { XCTAssertEqual(controller.state.layout.horizontalLength, expectedHorizontalLength, accuracy: 0.0001) XCTAssertEqual(controller.state.layout.verticalLength, expectedVerticalLength, accuracy: 0.0001) - XCTAssertEqual(controller.groupedWindow.screenFrame(for: .horizontal).width, expectedHorizontalLength, accuracy: 0.0001) - XCTAssertEqual(controller.groupedWindow.screenFrame(for: .vertical).height, expectedVerticalLength, accuracy: 0.0001) - XCTAssertEqual(controller.groupedWindow.zeroPoint().x, zeroPointBeforeDimensionChange.x, accuracy: 0.0001) - XCTAssertEqual(controller.groupedWindow.zeroPoint().y, zeroPointBeforeDimensionChange.y, accuracy: 0.0001) + XCTAssertEqual(controller.rulerWindow.screenFrame(for: .horizontal).width, expectedHorizontalLength, accuracy: 0.0001) + XCTAssertEqual(controller.rulerWindow.screenFrame(for: .vertical).height, expectedVerticalLength, accuracy: 0.0001) + XCTAssertEqual(controller.rulerWindow.zeroPoint().x, zeroPointBeforeDimensionChange.x, accuracy: 0.0001) + XCTAssertEqual(controller.rulerWindow.zeroPoint().y, zeroPointBeforeDimensionChange.y, accuracy: 0.0001) XCTAssertEqual(settingsController.dimensionWidthField.doubleValue, Double(enteredWidthMillimeters), accuracy: 0.15) XCTAssertEqual(settingsController.dimensionHeightField.doubleValue, Double(enteredHeightMillimeters), accuracy: 0.15) XCTAssertEqual(prefs.defaultHorizontalLength, 640) @@ -500,8 +500,8 @@ final class RulerCoreTests: XCTestCase { let normalizedColor = NSColor(deviceRed: 0.8, green: 0.2, blue: 0.4, alpha: 1) assertColor(controller.state.settings.rulerColor, equals: normalizedColor) - assertColor(controller.groupedWindow.horizontalRule.color.fill, equals: normalizedColor) - assertColor(controller.groupedWindow.verticalRule.color.fill, equals: normalizedColor) + assertColor(controller.rulerWindow.horizontalRule.color.fill, equals: normalizedColor) + assertColor(controller.rulerWindow.verticalRule.color.fill, equals: normalizedColor) assertColor(settingsController.rulerColorWell.color, equals: normalizedColor) assertColor(prefs.rulerColor, equals: defaultColor) XCTAssertFalse(settingsController.resetRulerColorButton.isHidden) @@ -510,7 +510,7 @@ final class RulerCoreTests: XCTestCase { settingsController.setForegroundOpacity(settingsController.foregroundOpacitySlider) XCTAssertEqual(controller.state.settings.foregroundOpacity, 65) - XCTAssertEqual(controller.groupedWindow.alphaValue, 0.65, accuracy: 0.0001) + XCTAssertEqual(controller.rulerWindow.alphaValue, 0.65, accuracy: 0.0001) XCTAssertEqual(settingsController.foregroundOpacityLabel.stringValue, "65%") XCTAssertEqual(prefs.foregroundOpacity, 90) @@ -518,7 +518,7 @@ final class RulerCoreTests: XCTestCase { settingsController.setBackgroundOpacity(settingsController.backgroundOpacitySlider) XCTAssertEqual(controller.state.settings.backgroundOpacity, 35) - XCTAssertEqual(controller.groupedWindow.alphaValue, 0.35, accuracy: 0.0001) + XCTAssertEqual(controller.rulerWindow.alphaValue, 0.35, accuracy: 0.0001) XCTAssertEqual(settingsController.backgroundOpacityLabel.stringValue, "35%") XCTAssertEqual(prefs.backgroundOpacity, 50) @@ -526,7 +526,7 @@ final class RulerCoreTests: XCTestCase { settingsController.setFloatRulers(settingsController.floatRulersCheckbox) XCTAssertTrue(controller.state.settings.floatRulers) - XCTAssertTrue(controller.groupedWindow.isFloatingPanel) + XCTAssertTrue(controller.rulerWindow.isFloatingPanel) XCTAssertTrue(settingsController.floatRulersCheckbox.state == .on) XCTAssertTrue(prefs.floatRulers) @@ -534,14 +534,14 @@ final class RulerCoreTests: XCTestCase { settingsController.setRulerShadow(settingsController.rulerShadowCheckbox) XCTAssertTrue(controller.state.settings.rulerShadow) - XCTAssertTrue(controller.groupedWindow.hasShadow) + XCTAssertTrue(controller.rulerWindow.hasShadow) XCTAssertTrue(settingsController.rulerShadowCheckbox.state == .on) XCTAssertFalse(prefs.rulerShadow) settingsController.resetRulerColor(settingsController.resetRulerColorButton) assertColor(controller.state.settings.rulerColor, equals: Prefs.defaultRulerFillColor) - assertColor(controller.groupedWindow.horizontalRule.color.fill, equals: Prefs.defaultRulerFillColor) + assertColor(controller.rulerWindow.horizontalRule.color.fill, equals: Prefs.defaultRulerFillColor) assertColor(prefs.rulerColor, equals: defaultColor) XCTAssertTrue(settingsController.resetRulerColorButton.isHidden) } @@ -560,7 +560,7 @@ final class RulerCoreTests: XCTestCase { prefs.defaultVerticalLength = 400 let rulerColor = NSColor(deviceRed: 0.72, green: 0.24, blue: 0.44, alpha: 1) - let controller = GroupedRulerController( + let controller = RulerController( state: RulerInstanceState( settings: RulerSettings( unit: .inches, @@ -611,7 +611,7 @@ final class RulerCoreTests: XCTestCase { prefs.defaultHorizontalLength = 320 prefs.defaultVerticalLength = 220 - let controller = GroupedRulerController( + let controller = RulerController( state: RulerInstanceState( settings: RulerSettings( unit: .inches, @@ -651,13 +651,13 @@ final class RulerCoreTests: XCTestCase { XCTAssertEqual(settingsController.foregroundOpacityLabel.stringValue, "88%") XCTAssertEqual(settingsController.backgroundOpacityLabel.stringValue, "44%") XCTAssertEqual(controller.opacity, 88) - XCTAssertEqual(controller.groupedWindow.alphaValue, windowAlphaValue(88), accuracy: 0.0001) + XCTAssertEqual(controller.rulerWindow.alphaValue, windowAlphaValue(88), accuracy: 0.0001) XCTAssertEqual(prefs.foregroundOpacity, 88) } } func testRulerSettingsControllerAppliesColorPanelChangesToActiveRuler() { - let controller = GroupedRulerController( + let controller = RulerController( state: RulerInstanceState( settings: RulerSettings( rulerColor: NSColor(deviceRed: 0.2, green: 0.3, blue: 0.4, alpha: 1) @@ -682,12 +682,12 @@ final class RulerCoreTests: XCTestCase { let normalizedColor = NSColor(deviceRed: 0.7, green: 0.1, blue: 0.5, alpha: 1) assertColor(controller.state.settings.rulerColor, equals: normalizedColor) - assertColor(controller.groupedWindow.horizontalRule.color.fill, equals: normalizedColor) + assertColor(controller.rulerWindow.horizontalRule.color.fill, equals: normalizedColor) assertColor(settingsController.rulerColorWell.color, equals: normalizedColor) } func testRulerSettingsControllerCheckboxKeyEquivalentsToggleFloatAndShadow() { - let controller = GroupedRulerController( + let controller = RulerController( state: RulerInstanceState( settings: RulerSettings(floatRulers: false, rulerShadow: false), layout: RulerLayoutState( @@ -956,7 +956,7 @@ final class RulerCoreTests: XCTestCase { } func testRulerSettingsControllerPresentsAsAttachedSheetOnRulerWindow() { - let controller = GroupedRulerController( + let controller = RulerController( state: RulerInstanceState( settings: RulerSettings(), layout: RulerLayoutState( @@ -979,7 +979,7 @@ final class RulerCoreTests: XCTestCase { XCTFail("Expected settings window") return } - XCTAssertTrue(controller.groupedWindow.childWindows?.contains(settingsWindow) ?? false) + XCTAssertTrue(controller.rulerWindow.childWindows?.contains(settingsWindow) ?? false) XCTAssertNil(settingsWindow.sheetParent) } @@ -988,7 +988,7 @@ final class RulerCoreTests: XCTestCase { let zeroPoint = NSPoint(x: visibleFrame.midX, y: visibleFrame.midY) for zeroCorner in [ZeroCorner.topLeft, .topRight, .bottomLeft, .bottomRight] { - let controller = GroupedRulerController( + let controller = RulerController( state: RulerInstanceState( settings: RulerSettings(zeroCorner: zeroCorner), layout: RulerLayoutState( @@ -1012,7 +1012,7 @@ final class RulerCoreTests: XCTestCase { return } - let rulerZeroPoint = controller.groupedWindow.zeroPoint() + let rulerZeroPoint = controller.rulerWindow.zeroPoint() switch zeroCorner { case .topLeft: XCTAssertEqual(settingsWindow.frame.minX, rulerZeroPoint.x, accuracy: 1) @@ -1031,7 +1031,7 @@ final class RulerCoreTests: XCTestCase { } func testRulerSettingsControllerUsesFloatingUtilityPanelStyle() { - let controller = GroupedRulerController( + let controller = RulerController( state: RulerInstanceState( settings: RulerSettings(), layout: RulerLayoutState( @@ -1088,7 +1088,7 @@ final class RulerCoreTests: XCTestCase { } func testRulerSettingsControllerRestoresForegroundOpacityWhenClosingSheet() { - let controller = GroupedRulerController( + let controller = RulerController( state: RulerInstanceState( settings: RulerSettings( foregroundOpacity: 80, @@ -1112,15 +1112,15 @@ final class RulerCoreTests: XCTestCase { settingsController.backgroundOpacitySlider.integerValue = 35 settingsController.setBackgroundOpacity(settingsController.backgroundOpacitySlider) - XCTAssertEqual(controller.groupedWindow.alphaValue, 0.35, accuracy: 0.0001) + XCTAssertEqual(controller.rulerWindow.alphaValue, 0.35, accuracy: 0.0001) settingsController.close() - XCTAssertEqual(controller.groupedWindow.alphaValue, 0.8, accuracy: 0.0001) + XCTAssertEqual(controller.rulerWindow.alphaValue, 0.8, accuracy: 0.0001) } func testRulerSettingsControllerTitlebarCloseClosesAttachedSheet() { - let controller = GroupedRulerController( + let controller = RulerController( state: RulerInstanceState( settings: RulerSettings(), layout: RulerLayoutState( @@ -1147,7 +1147,7 @@ final class RulerCoreTests: XCTestCase { settingsWindow.performClose(self) - XCTAssertFalse(controller.groupedWindow.childWindows?.contains(settingsWindow) ?? false) + XCTAssertFalse(controller.rulerWindow.childWindows?.contains(settingsWindow) ?? false) XCTAssertFalse(settingsWindow.isVisible) } @@ -1180,13 +1180,13 @@ final class RulerCoreTests: XCTestCase { return } - let initialZeroPoint = controller.groupedWindow.zeroPoint() + let initialZeroPoint = controller.rulerWindow.zeroPoint() XCTAssertEqual(settingsWindow.frame.minX, initialZeroPoint.x, accuracy: 1) XCTAssertEqual(settingsWindow.frame.maxY, initialZeroPoint.y, accuracy: 1) appDelegate.flipRulers(along: .horizontal) - let flippedZeroPoint = controller.groupedWindow.zeroPoint() + let flippedZeroPoint = controller.rulerWindow.zeroPoint() XCTAssertEqual(controller.state.settings.zeroCorner, .topRight) XCTAssertEqual(settingsWindow.frame.maxX, flippedZeroPoint.x, accuracy: 1) XCTAssertEqual(settingsWindow.frame.maxY, flippedZeroPoint.y, accuracy: 1) @@ -1224,19 +1224,19 @@ final class RulerCoreTests: XCTestCase { XCTAssertEqual(existing.state.settings.unit, .pixels) XCTAssertEqual(existing.state.layout.horizontalLength, 260) XCTAssertEqual(existing.state.layout.verticalLength, 180) - XCTAssertEqual(existing.groupedWindow.horizontalRule.unit, .pixels) - XCTAssertEqual(existing.groupedWindow.horizontalRule.zeroCorner, .topLeft) + XCTAssertEqual(existing.rulerWindow.horizontalRule.unit, .pixels) + XCTAssertEqual(existing.rulerWindow.horizontalRule.zeroCorner, .topLeft) assertColor( - existing.groupedWindow.horizontalRule.color.fill, + existing.rulerWindow.horizontalRule.color.fill, equals: NSColor(deviceRed: 0.1, green: 0.2, blue: 0.3, alpha: 1) ) XCTAssertEqual(createdAfterDefaultsChange.state.settings.unit, .millimeters) XCTAssertEqual(createdAfterDefaultsChange.state.layout.horizontalLength, 320) XCTAssertEqual(createdAfterDefaultsChange.state.layout.verticalLength, 240) - XCTAssertEqual(createdAfterDefaultsChange.groupedWindow.horizontalRule.unit, .millimeters) - XCTAssertEqual(createdAfterDefaultsChange.groupedWindow.horizontalRule.zeroCorner, .topRight) + XCTAssertEqual(createdAfterDefaultsChange.rulerWindow.horizontalRule.unit, .millimeters) + XCTAssertEqual(createdAfterDefaultsChange.rulerWindow.horizontalRule.zeroCorner, .topRight) assertColor( - createdAfterDefaultsChange.groupedWindow.horizontalRule.color.fill, + createdAfterDefaultsChange.rulerWindow.horizontalRule.color.fill, equals: NSColor(deviceRed: 0.8, green: 0.7, blue: 0.2, alpha: 1) ) } @@ -1630,7 +1630,7 @@ final class RulerCoreTests: XCTestCase { } } - func testGroupedRulerLayoutJoinsSeparateRulersWithoutChangingRuleFrames() { + func testRulerWindowLayoutJoinsSeparateRulersWithoutChangingRuleFrames() { let zeroPoint = NSPoint(x: 200, y: 300) let horizontalSize = NSSize(width: 120, height: Ruler.thickness) let verticalSize = NSSize(width: Ruler.thickness, height: 160) @@ -1648,12 +1648,12 @@ final class RulerCoreTests: XCTestCase { size: verticalSize ) - let layout = GroupedRulerLayout.joined( + let layout = RulerWindowLayout.joined( horizontalFrame: horizontalFrame, verticalFrame: verticalFrame, zeroCorner: zeroCorner ) - let roundTrippedLayout = GroupedRulerLayout.layout( + let roundTrippedLayout = RulerWindowLayout.layout( groupFrame: layout.groupFrame, zeroCorner: zeroCorner ) @@ -1675,13 +1675,13 @@ final class RulerCoreTests: XCTestCase { } } - func testGroupedRulerContentViewLaysOutLegsAndHitTestsCorner() { + func testRulerContentViewLaysOutLegsAndHitTestsCorner() { let contentSize = NSSize(width: 260, height: 220) for zeroCorner in [ZeroCorner.topLeft, .topRight, .bottomLeft, .bottomRight] { - let view = groupedContentView(size: contentSize, zeroCorner: zeroCorner) - let layout = GroupedRulerLayout.layout(groupFrame: view.bounds, zeroCorner: zeroCorner) - let emptyCornerPoint = pointInsideEmptyGroupedCorner( + let view = rulerContentView(size: contentSize, zeroCorner: zeroCorner) + let layout = RulerWindowLayout.layout(groupFrame: view.bounds, zeroCorner: zeroCorner) + let emptyCornerPoint = pointInsideEmptyRulerWindowCorner( horizontalFrame: layout.localFrame(for: Orientation.horizontal), verticalFrame: layout.localFrame(for: Orientation.vertical), bounds: view.bounds @@ -1708,8 +1708,8 @@ final class RulerCoreTests: XCTestCase { } } - func testGroupedRulerContentViewRestoresStandaloneLabelsWhenOnlyOneLegIsVisible() { - let view = groupedContentView(size: NSSize(width: 260, height: 220), zeroCorner: .topLeft) + func testRulerContentViewRestoresStandaloneLabelsWhenOnlyOneLegIsVisible() { + let view = rulerContentView(size: NSSize(width: 260, height: 220), zeroCorner: .topLeft) XCTAssertFalse(view.horizontalRule.showsUnitLabel) XCTAssertFalse(view.verticalRule.showsUnitLabel) @@ -1734,39 +1734,39 @@ final class RulerCoreTests: XCTestCase { XCTAssertTrue(view.verticalRule.showsZeroTick) } - func testGroupedRulerControllerEnablesMouseTicksOnlyForVisibleLegs() { + func testRulerControllerEnablesMouseTicksOnlyForVisibleLegs() { withRestoredZeroCornerPreference { prefs.zeroCorner = .topLeft - let controller = GroupedRulerController( + let controller = RulerController( frame: NSRect(x: 100, y: 100, width: 260, height: 220) ) - controller.groupedWindow.setVisibleRules(horizontal: true, vertical: false) + controller.rulerWindow.setVisibleRules(horizontal: true, vertical: false) controller.setMouseTickDrawingEnabled(true) - XCTAssertTrue(controller.groupedWindow.horizontalRule.showMouseTick) - XCTAssertFalse(controller.groupedWindow.verticalRule.showMouseTick) + XCTAssertTrue(controller.rulerWindow.horizontalRule.showMouseTick) + XCTAssertFalse(controller.rulerWindow.verticalRule.showMouseTick) - controller.groupedWindow.setVisibleRules(horizontal: false, vertical: true) + controller.rulerWindow.setVisibleRules(horizontal: false, vertical: true) controller.setMouseTickDrawingEnabled(true) - XCTAssertFalse(controller.groupedWindow.horizontalRule.showMouseTick) - XCTAssertTrue(controller.groupedWindow.verticalRule.showMouseTick) + XCTAssertFalse(controller.rulerWindow.horizontalRule.showMouseTick) + XCTAssertTrue(controller.rulerWindow.verticalRule.showMouseTick) controller.setMouseTickDrawingEnabled(false) - XCTAssertFalse(controller.groupedWindow.horizontalRule.showMouseTick) - XCTAssertFalse(controller.groupedWindow.verticalRule.showMouseTick) + XCTAssertFalse(controller.rulerWindow.horizontalRule.showMouseTick) + XCTAssertFalse(controller.rulerWindow.verticalRule.showMouseTick) } } - func testGroupedRulerControllerRestoresMouseTicksWhenHiddenLegReappears() { + func testRulerControllerRestoresMouseTicksWhenHiddenLegReappears() { withRestoredZeroCornerPreference { prefs.zeroCorner = .topLeft let horizontalFrame = NSRect(x: 200, y: 299, width: 320, height: Ruler.thickness) let verticalFrame = NSRect(x: 161, y: 120, width: Ruler.thickness, height: 180) - let controller = GroupedRulerController( - frame: GroupedRulerLayout.joined( + let controller = RulerController( + frame: RulerWindowLayout.joined( horizontalFrame: horizontalFrame, verticalFrame: verticalFrame, zeroCorner: .topLeft @@ -1782,8 +1782,8 @@ final class RulerCoreTests: XCTestCase { controller.setMouseTickDrawingEnabled(false) controller.setMouseTickDrawingEnabled(true) - XCTAssertTrue(controller.groupedWindow.horizontalRule.showMouseTick) - XCTAssertFalse(controller.groupedWindow.verticalRule.showMouseTick) + XCTAssertTrue(controller.rulerWindow.horizontalRule.showMouseTick) + XCTAssertFalse(controller.rulerWindow.verticalRule.showMouseTick) controller.show( horizontalFrame: horizontalFrame, @@ -1792,19 +1792,19 @@ final class RulerCoreTests: XCTestCase { showsVerticalRule: true ) - XCTAssertTrue(controller.groupedWindow.horizontalRule.showMouseTick) - XCTAssertTrue(controller.groupedWindow.verticalRule.showMouseTick) - controller.groupedWindow.orderOut(self) + XCTAssertTrue(controller.rulerWindow.horizontalRule.showMouseTick) + XCTAssertTrue(controller.rulerWindow.verticalRule.showMouseTick) + controller.rulerWindow.orderOut(self) } } - func testGroupedRulerControllerShrinksWindowToOnlyVisibleLeg() { + func testRulerControllerShrinksWindowToOnlyVisibleLeg() { withRestoredZeroCornerPreference { prefs.zeroCorner = .topLeft let horizontalFrame = NSRect(x: 200, y: 299, width: 320, height: Ruler.thickness) let verticalFrame = NSRect(x: 161, y: 120, width: Ruler.thickness, height: 180) - let controller = GroupedRulerController( - frame: GroupedRulerLayout.joined( + let controller = RulerController( + frame: RulerWindowLayout.joined( horizontalFrame: horizontalFrame, verticalFrame: verticalFrame, zeroCorner: .topLeft @@ -1818,12 +1818,12 @@ final class RulerCoreTests: XCTestCase { showsVerticalRule: false ) - XCTAssertEqual(controller.groupedWindow.frame, horizontalFrame) + XCTAssertEqual(controller.rulerWindow.frame, horizontalFrame) XCTAssertEqual( - controller.groupedWindow.screenFrame(for: .horizontal), + controller.rulerWindow.screenFrame(for: .horizontal), horizontalFrame ) - XCTAssertFalse(controller.groupedWindow.isRuleVisible(.vertical)) + XCTAssertFalse(controller.rulerWindow.isRuleVisible(.vertical)) controller.show( horizontalFrame: horizontalFrame, @@ -1832,61 +1832,23 @@ final class RulerCoreTests: XCTestCase { showsVerticalRule: true ) - XCTAssertEqual(controller.groupedWindow.frame, verticalFrame) + XCTAssertEqual(controller.rulerWindow.frame, verticalFrame) XCTAssertEqual( - controller.groupedWindow.screenFrame(for: .vertical), + controller.rulerWindow.screenFrame(for: .vertical), verticalFrame ) - XCTAssertFalse(controller.groupedWindow.isRuleVisible(.horizontal)) - controller.groupedWindow.orderOut(self) - } - } - - func testGroupedRulerControllerSyncsHiddenLegToVisibleZeroPoint() { - withRestoredZeroCornerPreference { - prefs.zeroCorner = .topLeft - let horizontalWindow = LegacyRulerWindow( - Ruler(.horizontal, frame: NSRect(x: 200, y: 299, width: 320, height: Ruler.thickness)) - ) - let verticalWindow = LegacyRulerWindow( - Ruler(.vertical, frame: NSRect(x: 161, y: 120, width: Ruler.thickness, height: 180)) - ) - let controller = GroupedRulerController( - frame: GroupedRulerLayout.joined( - horizontalFrame: horizontalWindow.frame, - verticalFrame: verticalWindow.frame, - zeroCorner: .topLeft - ).groupFrame - ) - - controller.show( - horizontalFrame: horizontalWindow.frame, - verticalFrame: verticalWindow.frame, - showsHorizontalRule: false, - showsVerticalRule: true - ) - controller.groupedWindow.setFrameOrigin(NSPoint(x: 300, y: 200)) - - controller.syncFrames(to: horizontalWindow, and: verticalWindow) - - let geometry = ZeroCornerGeometry(zeroCorner: .topLeft) - XCTAssertEqual(verticalWindow.frame, controller.groupedWindow.frame) - XCTAssertEqual(horizontalWindow.frame.size, NSSize(width: 320, height: Ruler.thickness)) - XCTAssertEqual( - geometry.zeroPoint(in: horizontalWindow.frame, for: .horizontal), - geometry.zeroPoint(in: verticalWindow.frame, for: .vertical) - ) - controller.groupedWindow.orderOut(self) + XCTAssertFalse(controller.rulerWindow.isRuleVisible(.horizontal)) + controller.rulerWindow.orderOut(self) } } - func testGroupedRulerControllerAlignsOnlyVisibleLegWithoutExpandingWindow() { + func testRulerControllerAlignsOnlyVisibleLegWithoutExpandingWindow() { withRestoredZeroCornerPreference { prefs.zeroCorner = .topLeft let horizontalFrame = NSRect(x: 200, y: 299, width: 320, height: Ruler.thickness) let verticalFrame = NSRect(x: 161, y: 120, width: Ruler.thickness, height: 180) - let controller = GroupedRulerController( - frame: GroupedRulerLayout.joined( + let controller = RulerController( + frame: RulerWindowLayout.joined( horizontalFrame: horizontalFrame, verticalFrame: verticalFrame, zeroCorner: .topLeft @@ -1903,32 +1865,32 @@ final class RulerCoreTests: XCTestCase { controller.align(at: targetZeroPoint) - let expectedFrame = GroupedRulerLayout.layout( + let expectedFrame = RulerWindowLayout.layout( horizontalLength: 0, verticalLength: verticalFrame.height, zeroPoint: targetZeroPoint, zeroCorner: .topLeft ).visibleFrame(showsHorizontalRule: false, showsVerticalRule: true) - XCTAssertEqual(controller.groupedWindow.frame, expectedFrame) - XCTAssertEqual(controller.groupedWindow.frame.size, verticalFrame.size) + XCTAssertEqual(controller.rulerWindow.frame, expectedFrame) + XCTAssertEqual(controller.rulerWindow.frame.size, verticalFrame.size) XCTAssertEqual( ZeroCornerGeometry(zeroCorner: .topLeft).zeroPoint( - in: controller.groupedWindow.screenFrame(for: .vertical), + in: controller.rulerWindow.screenFrame(for: .vertical), for: .vertical ), targetZeroPoint ) - controller.groupedWindow.orderOut(self) + controller.rulerWindow.orderOut(self) } } - func testGroupedRulerControllerFlipsOnlyVisibleLegWithoutExpandingWindow() { + func testRulerControllerFlipsOnlyVisibleLegWithoutExpandingWindow() { withRestoredZeroCornerPreference { prefs.zeroCorner = .topLeft let horizontalFrame = NSRect(x: 200, y: 299, width: 320, height: Ruler.thickness) let verticalFrame = NSRect(x: 161, y: 120, width: Ruler.thickness, height: 180) - let controller = GroupedRulerController( - frame: GroupedRulerLayout.joined( + let controller = RulerController( + frame: RulerWindowLayout.joined( horizontalFrame: horizontalFrame, verticalFrame: verticalFrame, zeroCorner: .topLeft @@ -1946,22 +1908,22 @@ final class RulerCoreTests: XCTestCase { controller.prepareForZeroCornerChange(to: .topRight) - let expectedFrame = GroupedRulerLayout.layout( + let expectedFrame = RulerWindowLayout.layout( horizontalLength: 0, verticalLength: verticalFrame.height, zeroPoint: oldZeroPoint, zeroCorner: .topRight ).visibleFrame(showsHorizontalRule: false, showsVerticalRule: true) - XCTAssertEqual(controller.groupedWindow.frame, expectedFrame) - XCTAssertEqual(controller.groupedWindow.frame.size, verticalFrame.size) + XCTAssertEqual(controller.rulerWindow.frame, expectedFrame) + XCTAssertEqual(controller.rulerWindow.frame.size, verticalFrame.size) XCTAssertEqual( ZeroCornerGeometry(zeroCorner: .topRight).zeroPoint( - in: controller.groupedWindow.frame, + in: controller.rulerWindow.frame, for: .vertical ), oldZeroPoint ) - controller.groupedWindow.orderOut(self) + controller.rulerWindow.orderOut(self) } } @@ -3115,9 +3077,11 @@ final class RulerCoreTests: XCTestCase { } func testResizeHandleDisablesWindowBackgroundDraggingDuringResizeDrag() { - let ruler = Ruler(.horizontal, frame: NSRect(x: 0, y: 0, width: 300, height: Ruler.thickness)) - let window = LegacyRulerWindow(ruler) - guard let resizeHandle = window.rule.subviews.first(where: { $0 is ResizeHandleView }) as? ResizeHandleView else { + let window = oneWingRulerWindow( + orientation: .horizontal, + frame: NSRect(x: 0, y: 0, width: 300, height: Ruler.thickness) + ) + guard let resizeHandle = resizeHandle(in: window.horizontalRule) else { return XCTFail("Expected horizontal ruler to install a resize handle") } @@ -3153,15 +3117,17 @@ final class RulerCoreTests: XCTestCase { XCTAssertTrue(window.isMovableByWindowBackground) } - func testResizeHandleDetachesChildWindowsAttachedWhileBecomingKey() { - let childWindow = LegacyRulerWindow( - Ruler(.vertical, frame: NSRect(x: 0, y: 0, width: Ruler.thickness, height: 300)) + func testResizeHandleDetachesChildWindowsDuringResizeDrag() { + let childWindow = oneWingRulerWindow( + orientation: .vertical, + frame: NSRect(x: 0, y: 0, width: Ruler.thickness, height: 300) ) - let window = ChildAttachingRulerWindow( - ruler: Ruler(.horizontal, frame: NSRect(x: 0, y: 0, width: 300, height: Ruler.thickness)), - childWindow: childWindow + let window = oneWingRulerWindow( + orientation: .horizontal, + frame: NSRect(x: 0, y: 0, width: 300, height: Ruler.thickness) ) - guard let resizeHandle = window.rule.subviews.first(where: { $0 is ResizeHandleView }) as? ResizeHandleView else { + window.addChildWindow(childWindow, ordered: .below) + guard let resizeHandle = resizeHandle(in: window.horizontalRule) else { return XCTFail("Expected horizontal ruler to install a resize handle") } let location = resizeHandle.convert(NSPoint(x: 1, y: 1), to: nil) @@ -3190,9 +3156,8 @@ final class RulerCoreTests: XCTestCase { prefs.zeroCorner = .topLeft let initialFrame = NSRect(x: 100, y: 200, width: 300, height: Ruler.thickness) - let ruler = Ruler(.horizontal, frame: initialFrame) - let window = LegacyRulerWindow(ruler) - guard let resizeHandle = window.rule.subviews.first(where: { $0 is ResizeHandleView }) as? ResizeHandleView else { + let window = oneWingRulerWindow(orientation: .horizontal, frame: initialFrame) + guard let resizeHandle = resizeHandle(in: window.horizontalRule) else { return XCTFail("Expected horizontal ruler to install a resize handle") } @@ -3245,11 +3210,13 @@ final class RulerCoreTests: XCTestCase { prefs.zeroCorner = .topLeft let horizontalInitialFrame = NSRect(x: 100, y: 200, width: 300, height: Ruler.thickness) - let horizontalWindow = LegacyRulerWindow(Ruler(.horizontal, frame: horizontalInitialFrame)) + let horizontalWindow = oneWingRulerWindow( + orientation: .horizontal, + frame: horizontalInitialFrame, + settings: RulerSettings(zeroCorner: .topRight) + ) defer { horizontalWindow.close() } - horizontalWindow.rule.settingsOverride = RulerSettings(zeroCorner: .topRight) - guard let horizontalResizeHandle = horizontalWindow.rule.subviews - .first(where: { $0 is ResizeHandleView }) as? ResizeHandleView else { + guard let horizontalResizeHandle = resizeHandle(in: horizontalWindow.horizontalRule) else { return XCTFail("Expected horizontal ruler to install a resize handle") } @@ -3282,11 +3249,13 @@ final class RulerCoreTests: XCTestCase { )) let verticalInitialFrame = NSRect(x: 300, y: 200, width: Ruler.thickness, height: 300) - let verticalWindow = LegacyRulerWindow(Ruler(.vertical, frame: verticalInitialFrame)) + let verticalWindow = oneWingRulerWindow( + orientation: .vertical, + frame: verticalInitialFrame, + settings: RulerSettings(zeroCorner: .bottomLeft) + ) defer { verticalWindow.close() } - verticalWindow.rule.settingsOverride = RulerSettings(zeroCorner: .bottomLeft) - guard let verticalResizeHandle = verticalWindow.rule.subviews - .first(where: { $0 is ResizeHandleView }) as? ResizeHandleView else { + guard let verticalResizeHandle = resizeHandle(in: verticalWindow.verticalRule) else { return XCTFail("Expected vertical ruler to install a resize handle") } @@ -3320,135 +3289,7 @@ final class RulerCoreTests: XCTestCase { } } - func testRulerControllerKeepsMouseTicksHiddenWhileDragging() { - withInstalledAppDelegate { appDelegate in - let ruler = Ruler(.horizontal, frame: NSRect(x: 0, y: 0, width: 300, height: Ruler.thickness)) - let controller = RulerController(ruler: ruler) - appDelegate.rulers = [controller] - let mouseDownEvent = NSEvent.mouseEvent( - with: .leftMouseDown, - location: NSPoint(x: 10, y: 10), - modifierFlags: [], - timestamp: 0, - windowNumber: controller.rulerWindow.windowNumber, - context: nil, - eventNumber: 0, - clickCount: 1, - pressure: 1 - )! - let mouseUpEvent = NSEvent.mouseEvent( - with: .leftMouseUp, - location: NSPoint(x: 10, y: 10), - modifierFlags: [], - timestamp: 0.1, - windowNumber: controller.rulerWindow.windowNumber, - context: nil, - eventNumber: 1, - clickCount: 1, - pressure: 0 - )! - - controller.mouseDown(with: mouseDownEvent) - controller.windowDidMove(Notification(name: NSWindow.didMoveNotification, object: controller.rulerWindow)) - RunLoop.current.run(until: Date().addingTimeInterval(0.2)) - - XCTAssertFalse(controller.rulerWindow.rule.showMouseTick) - - controller.mouseUp(with: mouseUpEvent) - RunLoop.current.run(until: Date().addingTimeInterval(0.2)) - - XCTAssertFalse(controller.rulerWindow.rule.showMouseTick) - - controller.mouseExited(with: mouseUpEvent) - - XCTAssertTrue(controller.rulerWindow.rule.showMouseTick) - } - } - - func testRulerControllerResumesMouseTicksWhenWindowDragLoopEnds() { - withInstalledAppDelegate { appDelegate in - let controller = RulerController( - ruler: Ruler(.horizontal, frame: NSRect(x: 0, y: 0, width: 300, height: Ruler.thickness)) - ) - let otherController = RulerController( - ruler: Ruler(.vertical, frame: NSRect(x: 0, y: 0, width: Ruler.thickness, height: 300)) - ) - controller.otherWindow = otherController.rulerWindow - appDelegate.rulers = [controller, otherController] - let mouseDownEvent = NSEvent.mouseEvent( - with: .leftMouseDown, - location: NSPoint(x: 10, y: 10), - modifierFlags: [], - timestamp: 0, - windowNumber: controller.rulerWindow.windowNumber, - context: nil, - eventNumber: 0, - clickCount: 1, - pressure: 1 - )! - let mouseUpOutsideEvent = NSEvent.mouseEvent( - with: .leftMouseUp, - location: NSPoint(x: -10, y: -10), - modifierFlags: [], - timestamp: 0.1, - windowNumber: controller.rulerWindow.windowNumber, - context: nil, - eventNumber: 1, - clickCount: 1, - pressure: 0 - )! - - controller.mouseDown(with: mouseDownEvent) - controller.windowDidMove(Notification(name: NSWindow.didMoveNotification, object: controller.rulerWindow)) - RunLoop.current.run(until: Date().addingTimeInterval(0.2)) - - XCTAssertFalse(controller.rulerWindow.rule.showMouseTick) - XCTAssertFalse(otherController.rulerWindow.rule.showMouseTick) - - controller.finishMouseDrag(with: mouseUpOutsideEvent) - RunLoop.current.run(until: Date().addingTimeInterval(0.2)) - - XCTAssertTrue(controller.rulerWindow.rule.showMouseTick) - XCTAssertTrue(otherController.rulerWindow.rule.showMouseTick) - } - } - - func testGroupedChildMoveDoesNotResumeMouseTicksDuringDrag() { - withInstalledAppDelegate { appDelegate in - let draggedController = RulerController( - ruler: Ruler(.horizontal, frame: NSRect(x: 0, y: 0, width: 300, height: Ruler.thickness)) - ) - let groupedChildController = RulerController( - ruler: Ruler(.vertical, frame: NSRect(x: 0, y: 0, width: Ruler.thickness, height: 300)) - ) - appDelegate.rulers = [draggedController, groupedChildController] - draggedController.otherWindow = groupedChildController.rulerWindow - groupedChildController.otherWindow = draggedController.rulerWindow - groupedChildController.isLeftMouseButtonPressed = { true } - let mouseDownEvent = NSEvent.mouseEvent( - with: .leftMouseDown, - location: NSPoint(x: 10, y: 10), - modifierFlags: [], - timestamp: 0, - windowNumber: draggedController.rulerWindow.windowNumber, - context: nil, - eventNumber: 0, - clickCount: 1, - pressure: 1 - )! - - draggedController.mouseDown(with: mouseDownEvent) - groupedChildController.windowDidMove( - Notification(name: NSWindow.didMoveNotification, object: groupedChildController.rulerWindow) - ) - RunLoop.current.run(until: Date().addingTimeInterval(0.2)) - - XCTAssertFalse(draggedController.rulerWindow.rule.showMouseTick) - XCTAssertFalse(groupedChildController.rulerWindow.rule.showMouseTick) - } - } - - func testPrimaryGroupedRulerHotkeysToggleLegVisibilityWithoutLegacyWindows() { + func testPrimaryRulerHotkeysToggleWingVisibilityWithoutLegacyWindows() { withRestoredZeroCornerPreference { let previousGroupRulers = prefs.groupRulers defer { prefs.groupRulers = previousGroupRulers } @@ -3465,12 +3306,11 @@ final class RulerCoreTests: XCTestCase { ) ) - let groupedWindow = appDelegate.groupedRulerController?.groupedWindow + let rulerWindow = appDelegate.rulerManager.activeController?.rulerWindow XCTAssertTrue(prefs.groupRulers) - XCTAssertFalse(groupedWindow?.isRuleVisible(.horizontal) ?? true) - XCTAssertTrue(groupedWindow?.isRuleVisible(.vertical) ?? false) - XCTAssertTrue(appDelegate.rulers.isEmpty) - groupedWindow?.orderOut(self) + XCTAssertFalse(rulerWindow?.isRuleVisible(.horizontal) ?? true) + XCTAssertTrue(rulerWindow?.isRuleVisible(.vertical) ?? false) + rulerWindow?.orderOut(self) } } @@ -3545,10 +3385,10 @@ final class RulerCoreTests: XCTestCase { ) ) - XCTAssertFalse(first.groupedWindow.isRuleVisible(.horizontal)) - XCTAssertTrue(first.groupedWindow.isRuleVisible(.vertical)) - XCTAssertTrue(second.groupedWindow.isRuleVisible(.horizontal)) - XCTAssertTrue(second.groupedWindow.isRuleVisible(.vertical)) + XCTAssertFalse(first.rulerWindow.isRuleVisible(.horizontal)) + XCTAssertTrue(first.rulerWindow.isRuleVisible(.vertical)) + XCTAssertTrue(second.rulerWindow.isRuleVisible(.horizontal)) + XCTAssertTrue(second.rulerWindow.isRuleVisible(.vertical)) } func testManagedCommandsApplySettingsToActiveRulerOnly() { @@ -3576,9 +3416,9 @@ final class RulerCoreTests: XCTestCase { XCTAssertEqual(first.state.settings.unit, .inches) XCTAssertFalse(first.state.settings.floatRulers) XCTAssertTrue(first.state.settings.rulerShadow) - XCTAssertEqual(first.groupedWindow.horizontalRule.unit, .inches) - XCTAssertFalse(first.groupedWindow.isFloatingPanel) - XCTAssertTrue(first.groupedWindow.hasShadow) + XCTAssertEqual(first.rulerWindow.horizontalRule.unit, .inches) + XCTAssertFalse(first.rulerWindow.isFloatingPanel) + XCTAssertTrue(first.rulerWindow.hasShadow) XCTAssertEqual(second.state.settings.unit, .millimeters) XCTAssertTrue(second.state.settings.floatRulers) XCTAssertFalse(second.state.settings.rulerShadow) @@ -3609,7 +3449,7 @@ final class RulerCoreTests: XCTestCase { appDelegate.flipRulers(along: .horizontal) XCTAssertEqual(second.state.settings.zeroCorner, .topRight) - XCTAssertEqual(second.groupedWindow.horizontalRule.zeroCorner, .topRight) + XCTAssertEqual(second.rulerWindow.horizontalRule.zeroCorner, .topRight) XCTAssertEqual(first.state.settings.zeroCorner, .bottomLeft) XCTAssertEqual(prefs.zeroCorner, .topRight) @@ -3682,166 +3522,51 @@ final class RulerCoreTests: XCTestCase { XCTAssertTrue(appDelegate.validateMenuItem(groupItem)) } - func testUngroupedHorizontalFlipDoesNotMoveRulerWindows() { - withRestoredZeroCornerPreference { - let previousGroupRulers = prefs.groupRulers - defer { prefs.groupRulers = previousGroupRulers } - - prefs.zeroCorner = .topLeft - prefs.groupRulers = false - let appDelegate = AppDelegate() - let horizontalController = RulerController( - ruler: Ruler(.horizontal, frame: NSRect(x: 100, y: 299, width: 120, height: Ruler.thickness)) - ) - let verticalController = RulerController( - ruler: Ruler(.vertical, frame: NSRect(x: 51, y: 150, width: Ruler.thickness, height: 160)) - ) - appDelegate.rulers = [verticalController, horizontalController] - - appDelegate.flipRulers(along: .horizontal) - - XCTAssertEqual(prefs.zeroCorner, .topRight) - XCTAssertEqual(horizontalController.rulerWindow.frame, NSRect(x: 100, y: 299, width: 120, height: Ruler.thickness)) - XCTAssertEqual(verticalController.rulerWindow.frame, NSRect(x: 51, y: 150, width: Ruler.thickness, height: 160)) - } - } - - func testGroupedHorizontalFlipMovesVerticalRulerToPreserveZeroPointOffset() { - withRestoredZeroCornerPreference { - let previousGroupRulers = prefs.groupRulers - defer { prefs.groupRulers = previousGroupRulers } - - prefs.zeroCorner = .topLeft - prefs.groupRulers = true - let appDelegate = TestableFlipAppDelegate() - let horizontalController = RulerController( - ruler: Ruler(.horizontal, frame: NSRect(x: 100, y: 299, width: 120, height: Ruler.thickness)) - ) - let verticalController = RulerController( - ruler: Ruler(.vertical, frame: NSRect(x: 51, y: 150, width: Ruler.thickness, height: 160)) - ) - appDelegate.rulers = [verticalController, horizontalController] - - appDelegate.flipRulers(along: .horizontal) - - XCTAssertEqual(prefs.zeroCorner, .topRight) - XCTAssertEqual(horizontalController.rulerWindow.frame, NSRect(x: 100, y: 299, width: 120, height: Ruler.thickness)) - XCTAssertEqual(verticalController.rulerWindow.frame, NSRect(x: 210, y: 150, width: Ruler.thickness, height: 160)) - } - } - - func testGroupedVerticalFlipMovesHorizontalRulerToPreserveZeroPointOffset() { - withRestoredZeroCornerPreference { - let previousGroupRulers = prefs.groupRulers - defer { prefs.groupRulers = previousGroupRulers } - - prefs.zeroCorner = .topLeft - prefs.groupRulers = true - let appDelegate = TestableFlipAppDelegate() - let horizontalController = RulerController( - ruler: Ruler(.horizontal, frame: NSRect(x: 100, y: 299, width: 120, height: Ruler.thickness)) - ) - let verticalController = RulerController( - ruler: Ruler(.vertical, frame: NSRect(x: 61, y: 140, width: Ruler.thickness, height: 160)) - ) - appDelegate.rulers = [verticalController, horizontalController] - - appDelegate.flipRulers(along: .vertical) - - XCTAssertEqual(prefs.zeroCorner, .bottomLeft) - XCTAssertEqual(verticalController.rulerWindow.frame, NSRect(x: 61, y: 140, width: Ruler.thickness, height: 160)) - XCTAssertEqual(horizontalController.rulerWindow.frame, NSRect(x: 100, y: 101, width: 120, height: Ruler.thickness)) - } - } - - func testGroupedFlipDoesNotShowHiddenRulerWindows() { - withRestoredZeroCornerPreference { - let previousGroupRulers = prefs.groupRulers - defer { prefs.groupRulers = previousGroupRulers } - - prefs.zeroCorner = .topLeft - prefs.groupRulers = true - let appDelegate = AppDelegate() - let horizontalController = RulerController( - ruler: Ruler(.horizontal, frame: NSRect(x: 100, y: 299, width: 120, height: Ruler.thickness)) - ) - let verticalController = RulerController( - ruler: Ruler(.vertical, frame: NSRect(x: 61, y: 140, width: Ruler.thickness, height: 160)) - ) - appDelegate.rulers = [verticalController, horizontalController] - let horizontalFrame = horizontalController.rulerWindow.frame - let verticalFrame = verticalController.rulerWindow.frame - - XCTAssertFalse(horizontalController.rulerWindow.isVisible) - XCTAssertFalse(verticalController.rulerWindow.isVisible) - - appDelegate.flipRulers(along: .horizontal) - - XCTAssertFalse(horizontalController.rulerWindow.isVisible) - XCTAssertFalse(verticalController.rulerWindow.isVisible) - XCTAssertEqual(horizontalController.rulerWindow.frame, horizontalFrame) - XCTAssertEqual(verticalController.rulerWindow.frame, verticalFrame) + func testShiftHotkeysFlipActiveRulerOrigin() { + let appDelegate = AppDelegate() + let controller = appDelegate.rulerManager.createRuler( + defaults: RulerSettings(zeroCorner: .topLeft) + ) + defer { + controller.hide() } - } - func testShiftHotkeysFlipRulerOrigins() { - withRestoredZeroCornerPreference { - let previousGroupRulers = prefs.groupRulers - defer { prefs.groupRulers = previousGroupRulers } - - prefs.zeroCorner = .topLeft - prefs.groupRulers = false - let appDelegate = AppDelegate() - let horizontalController = RulerController( - ruler: Ruler(.horizontal, frame: NSRect(x: 100, y: 299, width: 120, height: Ruler.thickness)) - ) - let verticalController = RulerController( - ruler: Ruler(.vertical, frame: NSRect(x: 61, y: 140, width: Ruler.thickness, height: 160)) - ) - appDelegate.rulers = [verticalController, horizontalController] - - XCTAssertTrue( - appDelegate.performRulerHotkey( - keyCode: kVK_ANSI_H, - modifierFlags: .shift, - sender: horizontalController - ) + XCTAssertTrue( + appDelegate.performRulerHotkey( + keyCode: kVK_ANSI_H, + modifierFlags: .shift, + sender: controller ) - XCTAssertEqual(prefs.zeroCorner, .topRight) + ) + XCTAssertEqual(controller.state.settings.zeroCorner, .topRight) - XCTAssertTrue( - appDelegate.performRulerHotkey( - keyCode: kVK_ANSI_V, - modifierFlags: .shift, - sender: verticalController - ) + XCTAssertTrue( + appDelegate.performRulerHotkey( + keyCode: kVK_ANSI_V, + modifierFlags: .shift, + sender: controller ) - XCTAssertEqual(prefs.zeroCorner, .bottomRight) - } + ) + XCTAssertEqual(controller.state.settings.zeroCorner, .bottomRight) } func testShiftHotkeysIgnoreCapsLock() { - withRestoredZeroCornerPreference { - let previousGroupRulers = prefs.groupRulers - defer { prefs.groupRulers = previousGroupRulers } - - prefs.zeroCorner = .topLeft - prefs.groupRulers = false - let appDelegate = AppDelegate() - let horizontalController = RulerController( - ruler: Ruler(.horizontal, frame: NSRect(x: 100, y: 299, width: 120, height: Ruler.thickness)) - ) - appDelegate.rulers = [horizontalController] + let appDelegate = AppDelegate() + let controller = appDelegate.rulerManager.createRuler( + defaults: RulerSettings(zeroCorner: .topLeft) + ) + defer { + controller.hide() + } - XCTAssertTrue( - appDelegate.performRulerHotkey( - keyCode: kVK_ANSI_H, - modifierFlags: [.shift, .capsLock], - sender: horizontalController - ) + XCTAssertTrue( + appDelegate.performRulerHotkey( + keyCode: kVK_ANSI_H, + modifierFlags: [.shift, .capsLock], + sender: controller ) - XCTAssertEqual(prefs.zeroCorner, .topRight) - } + ) + XCTAssertEqual(controller.state.settings.zeroCorner, .topRight) } func testNonShiftModifiedRulerHotkeysAreIgnored() { @@ -3856,52 +3581,6 @@ final class RulerCoreTests: XCTestCase { ) } - func testResetPositionUsesCurrentZeroCorner() { - withRestoredZeroCornerPreference { - prefs.zeroCorner = .bottomRight - let horizontalController = RulerController( - ruler: Ruler(.horizontal, frame: NSRect(x: 10, y: 20, width: 300, height: Ruler.thickness)) - ) - let verticalController = RulerController( - ruler: Ruler(.vertical, frame: NSRect(x: 10, y: 20, width: Ruler.thickness, height: 300)) - ) - - horizontalController.resetPosition() - verticalController.resetPosition() - - XCTAssertEqual( - horizontalController.rulerWindow.frame, - getDefaultContentRect(orientation: .horizontal, zeroCorner: .bottomRight) - ) - XCTAssertEqual( - verticalController.rulerWindow.frame, - getDefaultContentRect(orientation: .vertical, zeroCorner: .bottomRight) - ) - XCTAssertEqual(prefs.zeroCorner, .bottomRight) - } - } - - func testResetPositionKeepsFlippedDefaultRulersOnSharedZeroPoint() { - withRestoredZeroCornerPreference { - prefs.zeroCorner = .topRight - let horizontalController = RulerController( - ruler: Ruler(.horizontal, frame: NSRect(x: 10, y: 20, width: 300, height: Ruler.thickness)) - ) - let verticalController = RulerController( - ruler: Ruler(.vertical, frame: NSRect(x: 10, y: 20, width: Ruler.thickness, height: 300)) - ) - - horizontalController.resetPosition() - verticalController.resetPosition() - - let geometry = ZeroCornerGeometry(zeroCorner: .topRight) - XCTAssertEqual( - geometry.zeroPoint(in: horizontalController.rulerWindow.frame, for: .horizontal), - geometry.zeroPoint(in: verticalController.rulerWindow.frame, for: .vertical) - ) - } - } - private func mouseEvent( type: NSEvent.EventType, location: NSPoint, @@ -4029,7 +3708,7 @@ final class RulerCoreTests: XCTestCase { file: StaticString = #filePath, line: UInt = #line ) { - let controller = GroupedRulerController( + let controller = RulerController( state: RulerInstanceState( settings: RulerSettings(zeroCorner: zeroCorner), layout: RulerLayoutState( @@ -4165,26 +3844,6 @@ final class RulerCoreTests: XCTestCase { } } -private final class ChildAttachingRulerWindow: LegacyRulerWindow { - private let childWindowToAttach: NSWindow - - init(ruler: Ruler, childWindow: NSWindow) { - self.childWindowToAttach = childWindow - super.init(ruler: ruler) - } - - override func makeKey() { - super.makeKey() - addChildWindow(childWindowToAttach, ordered: .below) - } -} - -private final class TestableFlipAppDelegate: AppDelegate { - override func isRulerWindowShown(_ window: LegacyRulerWindow) -> Bool { - return true - } -} - private final class TestableZeroCornerHorizontalRule: HorizontalRule { var testZeroCorner: ZeroCorner = .topLeft @@ -4193,14 +3852,14 @@ private final class TestableZeroCornerHorizontalRule: HorizontalRule { } } -private func groupedContentView(size: NSSize, zeroCorner: ZeroCorner) -> GroupedRulerContentView { +private func rulerContentView(size: NSSize, zeroCorner: ZeroCorner) -> RulerContentView { let horizontalRule = HorizontalRule( frame: NSRect(x: 0, y: 0, width: 120, height: Ruler.thickness) ) let verticalRule = VerticalRule( frame: NSRect(x: 0, y: 0, width: Ruler.thickness, height: 160) ) - let view = GroupedRulerContentView( + let view = RulerContentView( frame: NSRect(origin: .zero, size: size), horizontalRule: horizontalRule, verticalRule: verticalRule @@ -4211,7 +3870,26 @@ private func groupedContentView(size: NSSize, zeroCorner: ZeroCorner) -> Grouped return view } -private func pointInsideEmptyGroupedCorner( +private func oneWingRulerWindow( + orientation: Orientation, + frame: NSRect, + settings: RulerSettings = RulerSettings() +) -> RulerWindow { + let window = RulerWindow(frame: frame, settings: settings) + window.setVisibleRules( + horizontal: orientation == .horizontal, + vertical: orientation == .vertical + ) + window.setFrame(frame, display: false) + window.updateLayoutForCurrentZeroCorner() + return window +} + +private func resizeHandle(in rule: RuleView) -> ResizeHandleView? { + return rule.subviews.first { $0 is ResizeHandleView } as? ResizeHandleView +} + +private func pointInsideEmptyRulerWindowCorner( horizontalFrame: NSRect, verticalFrame: NSRect, bounds: NSRect diff --git a/FreeRulerTests/RulerSnapshotTests.swift b/FreeRulerTests/RulerSnapshotTests.swift index b554dc1..b6d9a1d 100644 --- a/FreeRulerTests/RulerSnapshotTests.swift +++ b/FreeRulerTests/RulerSnapshotTests.swift @@ -24,10 +24,10 @@ final class RulerSnapshotTests: XCTestCase { ) } - func testGroupedWindowLayoutRenderingMatchesSnapshot() throws { + func testRulerWindowLayoutRenderingMatchesSnapshot() throws { try assertSnapshot( named: "grouped-ruler-window-layout", - view: RulerSnapshotFactory.groupedWindowLayoutSnapshotView() + view: RulerSnapshotFactory.rulerWindowLayoutSnapshotView() ) } @@ -222,15 +222,15 @@ enum RulerSnapshotFactory { return canvas } - static func groupedWindowLayoutSnapshotView() -> NSView { + static func rulerWindowLayoutSnapshotView() -> NSView { let margin: CGFloat = 24 - let groupedSize = NSSize(width: 620, height: 360) + let rulerWindowSize = NSSize(width: 620, height: 360) let canvas = SnapshotCanvasView( frame: NSRect( x: 0, y: 0, - width: groupedSize.width + (margin * 2), - height: groupedSize.height + (margin * 2) + width: rulerWindowSize.width + (margin * 2), + height: rulerWindowSize.height + (margin * 2) ) ) canvas.backgroundColor = NSColor(deviceWhite: 0.18, alpha: 1) @@ -249,17 +249,17 @@ enum RulerSnapshotFactory { configure(horizontalRule, fill: SnapshotColors.lightRuler, showsMouseTick: false) configure(verticalRule, fill: SnapshotColors.lightRuler, showsMouseTick: false) - let groupedView = GroupedRulerContentView( - frame: NSRect(origin: NSPoint(x: margin, y: margin), size: groupedSize), + let rulerWindowView = RulerContentView( + frame: NSRect(origin: NSPoint(x: margin, y: margin), size: rulerWindowSize), horizontalRule: horizontalRule, verticalRule: verticalRule ) - groupedView.zeroCorner = zeroCorner - groupedView.color = RulerColors(customFill: SnapshotColors.lightRuler) - groupedView.layoutSubtreeIfNeeded() - groupedView.needsDisplay = true + rulerWindowView.zeroCorner = zeroCorner + rulerWindowView.color = RulerColors(customFill: SnapshotColors.lightRuler) + rulerWindowView.layoutSubtreeIfNeeded() + rulerWindowView.needsDisplay = true - canvas.addSubview(groupedView) + canvas.addSubview(rulerWindowView) return canvas } @@ -269,7 +269,7 @@ enum RulerSnapshotFactory { let snapshots: [(name: String, view: NSView)] = [ ("ruler-zero-corners", zeroCornerSnapshotView()), ("ruler-mouse-tick-labels", mouseTickLabelSnapshotView()), - ("grouped-ruler-window-layout", groupedWindowLayoutSnapshotView()), + ("grouped-ruler-window-layout", rulerWindowLayoutSnapshotView()), ] for snapshot in snapshots { From 9e34ae9cd709169365ef4b4b22bba14d9f74fbc5 Mon Sep 17 00:00:00 2001 From: Pascal Date: Fri, 19 Jun 2026 16:13:10 -0400 Subject: [PATCH 2/2] Add preference state writing and accessibility identifier for preferences window * Implemented writing of active settings state in UITestSupport for better UI testing. * Added accessibility identifier to the preferences window for improved accessibility. * Updated UITests to reflect changes in ruler window references and ensure consistent behavior across tests. --- Free Ruler/AppDelegate.swift | 3 + Free Ruler/PreferencesController.swift | 1 + Free Ruler/UITestSupport+App.swift | 6 +- FreeRulerUITests/FreeRulerUITests.swift | 171 ++++++++++++------------ 4 files changed, 95 insertions(+), 86 deletions(-) diff --git a/Free Ruler/AppDelegate.swift b/Free Ruler/AppDelegate.swift index fce4836..0767702 100644 --- a/Free Ruler/AppDelegate.swift +++ b/Free Ruler/AppDelegate.swift @@ -316,6 +316,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { controller.updateSettings(update) updateDisplay() + uiTestSupport?.writePreferencesState(activeSettings: controller.state.settings) return true } @@ -468,6 +469,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { settings.floatRulers = shouldFloat } updateFloatRulersMenuItem() + uiTestSupport?.writePreferencesState(activeSettings: controller.state.settings) showHotkeyBezel( shouldFloat ? .rulersFloated : .rulersUnfloated, on: bezelScreen(for: sender) @@ -493,6 +495,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { settings.rulerShadow = shouldShowShadow } updateRulerShadowMenuItem() + uiTestSupport?.writePreferencesState(activeSettings: controller.state.settings) showHotkeyBezel( shouldShowShadow ? .shadowEnabled : .shadowDisabled, on: bezelScreen(for: sender) diff --git a/Free Ruler/PreferencesController.swift b/Free Ruler/PreferencesController.swift index 9628afc..83ed714 100644 --- a/Free Ruler/PreferencesController.swift +++ b/Free Ruler/PreferencesController.swift @@ -663,6 +663,7 @@ class PreferencesController: NSWindowController, NSWindowDelegate, NotificationP window?.delegate = self window?.identifier = NSUserInterfaceItemIdentifier("preferences-window") + window?.setAccessibilityIdentifier("preferences-window") window?.isMovableByWindowBackground = true configureOpaqueColorPicking() settingsControlsView.delegate = self diff --git a/Free Ruler/UITestSupport+App.swift b/Free Ruler/UITestSupport+App.swift index dede0be..5d2765b 100644 --- a/Free Ruler/UITestSupport+App.swift +++ b/Free Ruler/UITestSupport+App.swift @@ -28,8 +28,12 @@ extension UITestSupport { prefs.zeroCorner = Prefs.defaultZeroCorner } - func writePreferencesState() { + func writePreferencesState(activeSettings: RulerSettings? = nil) { + let activeSettings = activeSettings ?? RulerSettings(defaults: prefs) let state = [ + "activeFloatRulers": boolStateValue(activeSettings.floatRulers), + "activeRulerShadow": boolStateValue(activeSettings.rulerShadow), + "activeUnit": unitStateValue(activeSettings.unit), "floatRulers": boolStateValue(prefs.floatRulers), "groupRulers": boolStateValue(prefs.groupRulers), "rulerShadow": boolStateValue(prefs.rulerShadow), diff --git a/FreeRulerUITests/FreeRulerUITests.swift b/FreeRulerUITests/FreeRulerUITests.swift index 13257b6..66345c9 100644 --- a/FreeRulerUITests/FreeRulerUITests.swift +++ b/FreeRulerUITests/FreeRulerUITests.swift @@ -27,7 +27,7 @@ final class FreeRulerUITests: XCTestCase { } func testRulerVisibilityKeyboardCommands() { - XCTAssertTrue(groupedRuler.waitForExistence(timeout: 3)) + XCTAssertTrue(rulerWindow.waitForExistence(timeout: 3)) XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 3)) XCTAssertTrue(verticalRuler.waitForExistence(timeout: 3)) @@ -35,7 +35,7 @@ final class FreeRulerUITests: XCTestCase { app.typeKey("h", modifierFlags: []) XCTAssertTrue(horizontalRuler.waitForNonExistence(timeout: 2)) XCTAssertTrue(verticalRuler.exists) - XCTAssertTrue(groupedRuler.exists) + XCTAssertTrue(rulerWindow.exists) app.typeKey("h", modifierFlags: []) XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 2)) @@ -44,14 +44,14 @@ final class FreeRulerUITests: XCTestCase { app.typeKey("v", modifierFlags: []) XCTAssertTrue(verticalRuler.waitForNonExistence(timeout: 2)) XCTAssertTrue(horizontalRuler.exists) - XCTAssertTrue(groupedRuler.exists) + XCTAssertTrue(rulerWindow.exists) app.typeKey("v", modifierFlags: []) XCTAssertTrue(verticalRuler.waitForExistence(timeout: 2)) } - func testGroupedRulerToggleHidesRequestedLegWithoutUngrouping() { - XCTAssertTrue(groupedRuler.waitForExistence(timeout: 3)) + func testRulerWindowToggleHidesRequestedWingWithoutChangingGroupedDragging() { + XCTAssertTrue(rulerWindow.waitForExistence(timeout: 3)) XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 3)) XCTAssertTrue(verticalRuler.waitForExistence(timeout: 3)) XCTAssertTrue(waitForPreference("groupRulers", equals: true)) @@ -61,11 +61,11 @@ final class FreeRulerUITests: XCTestCase { XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 2)) XCTAssertTrue(verticalRuler.waitForNonExistence(timeout: 2)) - XCTAssertTrue(groupedRuler.exists) + XCTAssertTrue(rulerWindow.exists) assertFrame( - groupedRuler.frame, + rulerWindow.frame, matches: horizontalRuler.frame, - message: "Grouped window should shrink to the visible horizontal ruler frame" + message: "Ruler window should shrink to the visible horizontal ruler frame" ) XCTAssertTrue(waitForPreference("groupRulers", equals: true)) @@ -77,50 +77,56 @@ final class FreeRulerUITests: XCTestCase { XCTAssertTrue(verticalRuler.waitForExistence(timeout: 2)) XCTAssertTrue(horizontalRuler.waitForNonExistence(timeout: 2)) - XCTAssertTrue(groupedRuler.exists) + XCTAssertTrue(rulerWindow.exists) assertFrame( - groupedRuler.frame, + rulerWindow.frame, matches: verticalRuler.frame, - message: "Grouped window should shrink to the visible vertical ruler frame" + message: "Ruler window should shrink to the visible vertical ruler frame" ) XCTAssertTrue(waitForPreference("groupRulers", equals: true)) } - func testGroupRulersKeyboardCommandUngroupsOnFirstAttempt() { - XCTAssertTrue(groupedRuler.waitForExistence(timeout: 3)) + func testGroupRulersKeyboardCommandTogglesGroupedDraggingWithoutChangingRulerWindow() { + XCTAssertTrue(rulerWindow.waitForExistence(timeout: 3)) XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 3)) XCTAssertTrue(verticalRuler.waitForExistence(timeout: 3)) XCTAssertTrue(waitForPreference("groupRulers", equals: true)) + let originalFrame = rulerWindow.frame + horizontalRuler.click() app.typeKey("g", modifierFlags: []) XCTAssertTrue(waitForPreference("groupRulers", equals: false)) - XCTAssertTrue(groupedRuler.waitForNonExistence(timeout: 2)) - XCTAssertTrue(horizontalRulerWindow.waitForExistence(timeout: 2)) - XCTAssertTrue(verticalRulerWindow.waitForExistence(timeout: 2)) + XCTAssertTrue(rulerWindow.waitForExistence(timeout: 2)) + XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 2)) + XCTAssertTrue(verticalRuler.waitForExistence(timeout: 2)) + assertFrame( + rulerWindow.frame, + matches: originalFrame, + message: "Toggling grouped dragging should not replace or resize the ruler window" + ) app.typeKey("g", modifierFlags: []) XCTAssertTrue(waitForPreference("groupRulers", equals: true)) - XCTAssertTrue(groupedRuler.waitForExistence(timeout: 2)) + XCTAssertTrue(rulerWindow.waitForExistence(timeout: 2)) XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 2)) XCTAssertTrue(verticalRuler.waitForExistence(timeout: 2)) verticalRuler.click() app.typeKey("g", modifierFlags: []) XCTAssertTrue(waitForPreference("groupRulers", equals: false)) - XCTAssertTrue(groupedRuler.waitForNonExistence(timeout: 2)) - XCTAssertTrue(horizontalRulerWindow.waitForExistence(timeout: 2)) - XCTAssertTrue(verticalRulerWindow.waitForExistence(timeout: 2)) + XCTAssertTrue(rulerWindow.waitForExistence(timeout: 2)) + XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 2)) + XCTAssertTrue(verticalRuler.waitForExistence(timeout: 2)) } func testPreferencesCloseWithCommandW() { - app.typeKey(",", modifierFlags: .command) + openPreferencesShortcut() - let preferences = app.windows["Free Ruler Preferences"] - XCTAssertTrue(preferences.waitForExistence(timeout: 3)) + XCTAssertTrue(preferencesWindow.waitForExistence(timeout: 3)) app.typeKey("w", modifierFlags: .command) - XCTAssertTrue(preferences.waitForNonExistence(timeout: 2)) + XCTAssertTrue(preferencesWindow.waitForNonExistence(timeout: 2)) } func testRulerColorPanelHidesOpacityControl() { @@ -153,26 +159,26 @@ final class FreeRulerUITests: XCTestCase { app.launch() app.activate() - XCTAssertTrue(groupedRuler.waitForExistence(timeout: 3)) + XCTAssertTrue(rulerWindow.waitForExistence(timeout: 3)) XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 3)) XCTAssertTrue(colorPanel.waitForNonExistence(timeout: 2)) } func testRulerCloseWithCommandW() { - XCTAssertTrue(groupedRuler.waitForExistence(timeout: 3)) + XCTAssertTrue(rulerWindow.waitForExistence(timeout: 3)) XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 3)) XCTAssertTrue(verticalRuler.waitForExistence(timeout: 3)) - groupedRuler.click() + rulerWindow.click() app.typeKey("w", modifierFlags: .command) - XCTAssertTrue(groupedRuler.waitForNonExistence(timeout: 2)) + XCTAssertTrue(rulerWindow.waitForNonExistence(timeout: 2)) XCTAssertTrue(horizontalRuler.waitForNonExistence(timeout: 2)) XCTAssertTrue(verticalRuler.waitForNonExistence(timeout: 2)) } - func testHiddenRulersCanBeRestoredAndResetRestoresVisibility() { - XCTAssertTrue(groupedRuler.waitForExistence(timeout: 3)) + func testLastVisibleWingStaysVisibleAndResetRestoresBothWings() { + XCTAssertTrue(rulerWindow.waitForExistence(timeout: 3)) XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 3)) XCTAssertTrue(verticalRuler.waitForExistence(timeout: 3)) @@ -182,12 +188,11 @@ final class FreeRulerUITests: XCTestCase { verticalRuler.click() app.typeKey("v", modifierFlags: []) - XCTAssertTrue(verticalRuler.waitForNonExistence(timeout: 2)) - XCTAssertTrue(groupedRuler.waitForNonExistence(timeout: 2)) + XCTAssertTrue(verticalRuler.waitForExistence(timeout: 2)) + XCTAssertTrue(rulerWindow.waitForExistence(timeout: 2)) app.typeKey("h", modifierFlags: []) - app.typeKey("v", modifierFlags: []) - XCTAssertTrue(groupedRuler.waitForExistence(timeout: 2)) + XCTAssertTrue(rulerWindow.waitForExistence(timeout: 2)) XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 2)) XCTAssertTrue(verticalRuler.waitForExistence(timeout: 2)) @@ -196,45 +201,48 @@ final class FreeRulerUITests: XCTestCase { XCTAssertTrue(horizontalRuler.waitForNonExistence(timeout: 2)) app.typeKey("r", modifierFlags: .command) - XCTAssertTrue(groupedRuler.waitForExistence(timeout: 2)) + XCTAssertTrue(rulerWindow.waitForExistence(timeout: 2)) XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 2)) XCTAssertTrue(verticalRuler.waitForExistence(timeout: 2)) } func testFloatShadowAndUnitKeyboardCommands() { - XCTAssertTrue(groupedRuler.waitForExistence(timeout: 3)) + XCTAssertTrue(rulerWindow.waitForExistence(timeout: 3)) XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 3)) XCTAssertTrue(verticalRuler.waitForExistence(timeout: 3)) - XCTAssertTrue(waitForPreference("floatRulers", equals: true)) - XCTAssertTrue(waitForPreference("rulerShadow", equals: false)) + XCTAssertTrue(waitForPreference("activeFloatRulers", equals: true)) + XCTAssertTrue(waitForPreference("activeRulerShadow", equals: false)) horizontalRuler.click() app.typeKey("f", modifierFlags: []) - XCTAssertTrue(waitForPreference("floatRulers", equals: false)) + XCTAssertTrue(waitForPreference("activeFloatRulers", equals: false)) app.typeKey("f", modifierFlags: []) - XCTAssertTrue(waitForPreference("floatRulers", equals: true)) + XCTAssertTrue(waitForPreference("activeFloatRulers", equals: true)) app.typeKey("s", modifierFlags: []) - XCTAssertTrue(waitForPreference("rulerShadow", equals: true)) + XCTAssertTrue(waitForPreference("activeRulerShadow", equals: true)) app.typeKey("s", modifierFlags: []) - XCTAssertTrue(waitForPreference("rulerShadow", equals: false)) + XCTAssertTrue(waitForPreference("activeRulerShadow", equals: false)) XCTAssertEqual(horizontalRulerView.value as? String, "px") app.typeKey("u", modifierFlags: []) + XCTAssertTrue(waitForPreference("activeUnit", equals: "mm")) XCTAssertEqual(horizontalRulerView.value as? String, "mm") app.typeKey("u", modifierFlags: []) + XCTAssertTrue(waitForPreference("activeUnit", equals: "in")) XCTAssertEqual(horizontalRulerView.value as? String, "in") app.typeKey("u", modifierFlags: []) + XCTAssertTrue(waitForPreference("activeUnit", equals: "px")) XCTAssertEqual(horizontalRulerView.value as? String, "px") } func testOptionHotkeysShowStatusBezel() { - XCTAssertTrue(groupedRuler.waitForExistence(timeout: 3)) + XCTAssertTrue(rulerWindow.waitForExistence(timeout: 3)) XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 3)) XCTAssertTrue(verticalRuler.waitForExistence(timeout: 3)) @@ -268,7 +276,7 @@ final class FreeRulerUITests: XCTestCase { } func testAlignRulersAtMouseLocationKeyboardCommand() { - XCTAssertTrue(groupedRuler.waitForExistence(timeout: 3)) + XCTAssertTrue(rulerWindow.waitForExistence(timeout: 3)) XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 3)) XCTAssertTrue(verticalRuler.waitForExistence(timeout: 3)) @@ -282,44 +290,39 @@ final class FreeRulerUITests: XCTestCase { XCTAssertTrue(verticalRuler.waitForFrameChange(from: originalVerticalFrame, timeout: 2)) } - func testRulerCursorsForGroupedAndUngroupedScenarios() { - XCTAssertTrue(groupedRuler.waitForExistence(timeout: 3)) + func testRulerCursorsForVisibleWings() { + XCTAssertTrue(rulerWindow.waitForExistence(timeout: 3)) XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 3)) XCTAssertTrue(verticalRuler.waitForExistence(timeout: 3)) - XCTContext.runActivity(named: "ungrouped horizontal ruler cursor") { _ in + XCTContext.runActivity(named: "horizontal-only ruler cursor") { _ in resetRulerCursorScenario() - isolateHorizontalRulerByUngroupingWithVerticalToggle() + isolateHorizontalWing() - assertCursorSequence(on: horizontalRuler, label: "ungrouped horizontal ruler") + assertCursorSequence(on: horizontalRuler, label: "horizontal-only ruler") } - XCTContext.runActivity(named: "ungrouped vertical ruler cursor") { _ in + XCTContext.runActivity(named: "vertical-only ruler cursor") { _ in resetRulerCursorScenario() - isolateHorizontalRulerByUngroupingWithVerticalToggle() - - app.typeKey("h", modifierFlags: []) - XCTAssertTrue(horizontalRuler.waitForNonExistence(timeout: 2)) - app.typeKey("v", modifierFlags: []) - XCTAssertTrue(verticalRuler.waitForExistence(timeout: 2)) + isolateVerticalWing() - assertCursorSequence(on: verticalRuler, label: "ungrouped vertical ruler") + assertCursorSequence(on: verticalRuler, label: "vertical-only ruler") } - XCTContext.runActivity(named: "grouped cursor with horizontal key ruler") { _ in + XCTContext.runActivity(named: "both wings visible with horizontal key ruler") { _ in resetRulerCursorScenario() horizontalRuler.click() - assertCursorSequence(on: horizontalRulerView, label: "grouped key horizontal ruler") - assertCursorSequence(on: verticalRulerView, label: "grouped child vertical ruler") + assertCursorSequence(on: horizontalRulerView, label: "key horizontal ruler") + assertCursorSequence(on: verticalRulerView, label: "vertical ruler") } - XCTContext.runActivity(named: "grouped cursor with vertical key ruler") { _ in + XCTContext.runActivity(named: "both wings visible with vertical key ruler") { _ in resetRulerCursorScenario() verticalRuler.click() - assertCursorSequence(on: verticalRulerView, label: "grouped key vertical ruler") - assertCursorSequence(on: horizontalRulerView, label: "grouped child horizontal ruler") + assertCursorSequence(on: verticalRulerView, label: "key vertical ruler") + assertCursorSequence(on: horizontalRulerView, label: "horizontal ruler") } } @@ -331,16 +334,8 @@ final class FreeRulerUITests: XCTestCase { verticalRulerView } - private var groupedRuler: XCUIElement { - app.dialogs["grouped-ruler-window"] - } - - private var horizontalRulerWindow: XCUIElement { - app.dialogs["horizontal-ruler-window"] - } - - private var verticalRulerWindow: XCUIElement { - app.dialogs["vertical-ruler-window"] + private var rulerWindow: XCUIElement { + app.dialogs["ruler-window"] } private var horizontalRulerView: XCUIElement { @@ -352,7 +347,7 @@ final class FreeRulerUITests: XCTestCase { } private var preferencesWindow: XCUIElement { - app.windows["Free Ruler Preferences"] + app.windows["preferences-window"] } private var rulerColorWell: XCUIElement { @@ -373,11 +368,15 @@ final class FreeRulerUITests: XCTestCase { private func openPreferences() { if !preferencesWindow.exists { - app.typeKey(",", modifierFlags: .command) + openPreferencesShortcut() XCTAssertTrue(preferencesWindow.waitForExistence(timeout: 3)) } } + private func openPreferencesShortcut() { + app.typeKey(",", modifierFlags: [.command, .option]) + } + private func openRulerColorPanel() { openPreferences() @@ -391,26 +390,28 @@ final class FreeRulerUITests: XCTestCase { XCTAssertTrue(colorPanel.waitForExistence(timeout: 3)) } - private func isolateHorizontalRulerByUngroupingWithVerticalToggle() { + private func isolateHorizontalWing() { horizontalRuler.click() - app.typeKey("g", modifierFlags: []) - - XCTAssertTrue(waitForPreference("groupRulers", equals: false)) - XCTAssertTrue(groupedRuler.waitForNonExistence(timeout: 2)) - XCTAssertTrue(horizontalRulerWindow.waitForExistence(timeout: 2)) - XCTAssertTrue(verticalRulerWindow.waitForExistence(timeout: 2)) - app.typeKey("v", modifierFlags: []) XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 2)) XCTAssertTrue(verticalRuler.waitForNonExistence(timeout: 2)) - XCTAssertTrue(waitForPreference("groupRulers", equals: false)) + XCTAssertTrue(waitForPreference("groupRulers", equals: true)) + } + + private func isolateVerticalWing() { + verticalRuler.click() + app.typeKey("h", modifierFlags: []) + + XCTAssertTrue(verticalRuler.waitForExistence(timeout: 2)) + XCTAssertTrue(horizontalRuler.waitForNonExistence(timeout: 2)) + XCTAssertTrue(waitForPreference("groupRulers", equals: true)) } private func resetRulerCursorScenario() { app.typeKey("r", modifierFlags: .command) - XCTAssertTrue(groupedRuler.waitForVisibleFrame(timeout: 1)) + XCTAssertTrue(rulerWindow.waitForVisibleFrame(timeout: 1)) XCTAssertTrue(horizontalRuler.waitForVisibleFrame(timeout: 1)) XCTAssertTrue(verticalRuler.waitForVisibleFrame(timeout: 1)) XCTAssertTrue(waitForPreference("groupRulers", equals: true))