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..0767702 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() { @@ -377,6 +316,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { controller.updateSettings(update) updateDisplay() + uiTestSupport?.writePreferencesState(activeSettings: controller.state.settings) return true } @@ -386,27 +326,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 +362,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 +417,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 +428,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() @@ -729,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) @@ -754,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) @@ -814,20 +556,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 +564,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 +600,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 +620,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 +706,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 +760,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 +774,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 +825,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 +871,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..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 @@ -890,7 +891,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 +943,7 @@ final class RulerSettingsController: NSWindowController, NSWindowDelegate { return settingsControlsView.rulerShadowCheckbox } - var currentRulerController: GroupedRulerController? { + var currentRulerController: RulerController? { return rulerController } @@ -950,7 +951,7 @@ final class RulerSettingsController: NSWindowController, NSWindowDelegate { return "RulerSettingsController" } - init(rulerController: GroupedRulerController) { + init(rulerController: RulerController) { self.rulerController = rulerController super.init(window: nil) loadWindow() @@ -1010,13 +1011,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 +1027,7 @@ final class RulerSettingsController: NSWindowController, NSWindowDelegate { detachWindowIfNeeded() - guard controller.groupedWindow.isVisible else { + guard controller.rulerWindow.isVisible else { showWindow(sender) return } @@ -1036,7 +1037,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 +1057,7 @@ final class RulerSettingsController: NSWindowController, NSWindowDelegate { closeSheetColorControls() } - func updateRulerController(_ controller: GroupedRulerController) { + func updateRulerController(_ controller: RulerController) { rulerController = controller updateView() } @@ -1148,7 +1149,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 +1270,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 +1303,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/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/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 { 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))