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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions Free Ruler/Base.lproj/PreferencesController.xib
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<window title="Free Ruler Settings" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" frameAutosaveName="preferencesWindow" animationBehavior="default" id="F0z-JX-Cv5">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
<rect key="contentRect" x="602" y="400" width="360" height="415"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<rect key="screenRect" x="0.0" y="0.0" width="1512" height="944"/>
<view key="contentView" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="360" height="415"/>
<autoresizingMask key="autoresizingMask"/>
Expand All @@ -31,15 +31,15 @@
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<box borderType="line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="PREF-defaults-box">
<box ambiguous="YES" borderType="line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="PREF-defaults-box">
<rect key="frame" x="9" y="58" width="342" height="319"/>
<view key="contentView" id="PREF-defaults-box-content">
<view key="contentView" ambiguous="YES" id="PREF-defaults-box-content">
<rect key="frame" x="4" y="5" width="334" height="311"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<customView id="PREF-controls" customClass="RulerSettingsControlsView" customModule="Free_Ruler" customModuleProvider="target">
<customView ambiguous="YES" id="PREF-controls" customClass="RulerSettingsControlsView" customModule="Free_Ruler" customModuleProvider="target">
<rect key="frame" x="-4" y="4" width="340" height="309"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
</customView>
</subviews>
</view>
Expand All @@ -59,7 +59,6 @@
<constraint firstItem="PREF-defaults-box" firstAttribute="top" secondItem="PREF-defaults-header" secondAttribute="bottom" constant="12" id="PREF-box-below-header"/>
<constraint firstItem="PREF-defaults-box" firstAttribute="leading" secondItem="se5-gp-TjO" secondAttribute="leading" constant="12" id="PREF-box-leading"/>
<constraint firstAttribute="trailing" secondItem="PREF-defaults-box" secondAttribute="trailing" constant="12" id="PREF-box-trailing"/>
<constraint firstItem="PREF-factory-button" firstAttribute="top" secondItem="PREF-defaults-box" secondAttribute="bottom" constant="24" id="PREF-button-below-box"/>
<constraint firstAttribute="bottom" secondItem="PREF-factory-button" secondAttribute="bottom" constant="18" id="PREF-button-bottom"/>
<constraint firstItem="PREF-factory-button" firstAttribute="leading" secondItem="se5-gp-TjO" secondAttribute="leading" constant="18" id="PREF-button-leading"/>
<constraint firstItem="PREF-defaults-header" firstAttribute="leading" secondItem="se5-gp-TjO" secondAttribute="leading" constant="24" id="PREF-header-leading"/>
Expand Down
2 changes: 1 addition & 1 deletion Free Ruler/Prefs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ extension Prefs {
}

static var defaultGroupRulers: Bool {
return true
return false
}

func applyDefaults(from settings: RulerSettings, layout: RulerLayoutState? = nil) {
Expand Down
100 changes: 96 additions & 4 deletions Free Ruler/RulerWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1352,6 +1352,10 @@ final class RulerController: NSWindowController, NSWindowDelegate, NotificationO
captureStateFromWindow()
}

func captureCurrentState() {
captureStateFromWindow()
}

func resetPosition() {
state.settings.zeroCorner = Prefs.defaultZeroCorner
state.layout = RulerLayoutState.defaults(
Expand Down Expand Up @@ -1605,6 +1609,7 @@ final class RulerManager {
private struct GroupedDragState {
let draggedRulerID: UUID
let framesByRulerID: [UUID: NSRect]
let attachedRulerIDs: Set<UUID>
}

private let controllerFactory: ControllerFactory
Expand Down Expand Up @@ -1758,18 +1763,26 @@ final class RulerManager {
}

func beginGroupedDrag(from controller: RulerController) {
if let groupedDragState = groupedDragState {
detachGroupedDragFollowers(groupedDragState)
}

guard prefs.groupRulers,
controllers.contains(where: { $0 === controller }) else {
groupedDragState = nil
return
}

let visibleControllers = controllers.filter(\.isVisible)
groupedDragState = GroupedDragState(
draggedRulerID: controller.state.id,
framesByRulerID: Dictionary(
uniqueKeysWithValues: controllers
.filter(\.isVisible)
uniqueKeysWithValues: visibleControllers
.map { ($0.state.id, $0.rulerWindow.frame) }
),
attachedRulerIDs: attachGroupedDragFollowers(
to: controller,
visibleControllers: visibleControllers
)
)
}
Expand All @@ -1794,20 +1807,41 @@ final class RulerManager {
isApplyingGroupedDrag = false
}

var movedFollower = false
for otherController in controllers where otherController !== controller && otherController.isVisible {
guard var frame = groupedDragState.framesByRulerID[otherController.state.id] else { continue }
guard !rulerMovesWithGroupedDragParent(
otherController,
draggedController: controller,
groupedDragState: groupedDragState
) else {
continue
}

frame.origin.x += offset.width
frame.origin.y += offset.height
otherController.move(to: frame)
movedFollower = true
}

notifyStateChanged()
if movedFollower {
notifyStateChanged()
}
}

func finishGroupedDrag(from controller: RulerController) {
guard let groupedDragState = groupedDragState else { return }
guard groupedDragState.draggedRulerID == controller.state.id else {
detachGroupedDragFollowers(groupedDragState)
self.groupedDragState = nil
return
}

syncGroupedDrag(from: controller)
groupedDragState = nil
detachGroupedDragFollowers(from: controller, groupedDragState: groupedDragState)
captureGroupedDragFollowerStates(excluding: controller, groupedDragState: groupedDragState)
self.groupedDragState = nil
notifyStateChanged()
}

func controller(containing window: NSWindow?) -> RulerController? {
Expand Down Expand Up @@ -1846,6 +1880,64 @@ final class RulerManager {
}
}

private func attachGroupedDragFollowers(
to draggedController: RulerController,
visibleControllers: [RulerController]
) -> Set<UUID> {
let draggedWindow = draggedController.rulerWindow
var attachedRulerIDs = Set<UUID>()

for followerController in visibleControllers where followerController !== draggedController {
let followerWindow = followerController.rulerWindow
guard followerWindow.parent == nil else { continue }

draggedWindow.addChildWindow(followerWindow, ordered: .below)
attachedRulerIDs.insert(followerController.state.id)
}

return attachedRulerIDs
}

private func detachGroupedDragFollowers(_ groupedDragState: GroupedDragState) {
guard let draggedController = controller(id: groupedDragState.draggedRulerID) else { return }

detachGroupedDragFollowers(from: draggedController, groupedDragState: groupedDragState)
}

private func detachGroupedDragFollowers(
from draggedController: RulerController,
groupedDragState: GroupedDragState
) {
let draggedWindow = draggedController.rulerWindow

for rulerID in groupedDragState.attachedRulerIDs {
guard let followerController = controller(id: rulerID),
followerController.rulerWindow.parent === draggedWindow else { continue }

draggedWindow.removeChildWindow(followerController.rulerWindow)
}
}

private func rulerMovesWithGroupedDragParent(
_ followerController: RulerController,
draggedController: RulerController,
groupedDragState: GroupedDragState
) -> Bool {
return groupedDragState.attachedRulerIDs.contains(followerController.state.id)
|| followerController.rulerWindow.parent === draggedController.rulerWindow
}

private func captureGroupedDragFollowerStates(
excluding draggedController: RulerController,
groupedDragState: GroupedDragState
) {
for followerController in controllers where followerController !== draggedController {
guard groupedDragState.framesByRulerID[followerController.state.id] != nil else { continue }

followerController.captureCurrentState()
}
}

private func staggeredState(from defaultState: RulerInstanceState) -> RulerInstanceState {
var state = defaultState
let offset = Ruler.thickness / 2
Expand Down
2 changes: 1 addition & 1 deletion Free Ruler/UITestSupport+App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ extension UITestSupport {
"NSWindow Frame preferencesWindow",
].forEach(defaults.removeObject(forKey:))

prefs.groupRulers = true
prefs.groupRulers = Prefs.defaultGroupRulers
prefs.floatRulers = true
prefs.rulerShadow = false
prefs.foregroundOpacity = 90
Expand Down
20 changes: 19 additions & 1 deletion FreeRulerTests/RulerCoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,10 @@ final class RulerCoreTests: XCTestCase {
movedFirstFrame.origin.y += dragOffset.height

appDelegate.rulerManager.beginGroupedDrag(from: first)
XCTAssertTrue(first.rulerWindow.childWindows?.contains(second.rulerWindow) ?? false)
XCTAssertTrue(second.rulerWindow.parent === first.rulerWindow)
XCTAssertFalse(first.rulerWindow.childWindows?.contains(hidden.rulerWindow) ?? false)

first.move(to: movedFirstFrame)
appDelegate.rulerManager.syncGroupedDrag(from: first)
appDelegate.rulerManager.finishGroupedDrag(from: first)
Expand All @@ -266,6 +270,8 @@ final class RulerCoreTests: XCTestCase {
for: .horizontal
)
)
XCTAssertFalse(first.rulerWindow.childWindows?.contains(second.rulerWindow) ?? false)
XCTAssertNil(second.rulerWindow.parent)
}
}
}
Expand Down Expand Up @@ -756,7 +762,7 @@ final class RulerCoreTests: XCTestCase {
prefs.foregroundOpacity = 42
prefs.backgroundOpacity = 21
prefs.floatRulers = false
prefs.groupRulers = false
prefs.groupRulers = true
prefs.rulerShadow = true
prefs.zeroCorner = .bottomRight
prefs.defaultHorizontalLength = 333
Expand Down Expand Up @@ -1452,6 +1458,18 @@ final class RulerCoreTests: XCTestCase {
}
}

func testGroupRulersDefaultsOffAndPersistsToUserDefaults() {
withRestoredRulerPreferences {
XCTAssertFalse(Prefs.defaultGroupRulers)

prefs.groupRulers = true
XCTAssertTrue(UserDefaults.standard.bool(forKey: "groupRulers"))

prefs.groupRulers = false
XCTAssertFalse(UserDefaults.standard.bool(forKey: "groupRulers"))
}
}

func testZeroCornerGeometryDerivesOrientationTraits() {
let cases: [
(
Expand Down
20 changes: 10 additions & 10 deletions FreeRulerUITests/FreeRulerUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ final class FreeRulerUITests: XCTestCase {
XCTAssertTrue(rulerWindow.waitForExistence(timeout: 3))
XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 3))
XCTAssertTrue(verticalRuler.waitForExistence(timeout: 3))
XCTAssertTrue(waitForPreference("groupRulers", equals: true))
XCTAssertTrue(waitForPreference("groupRulers", equals: false))

horizontalRuler.click()
app.typeKey("v", modifierFlags: [])
Expand All @@ -67,7 +67,7 @@ final class FreeRulerUITests: XCTestCase {
matches: horizontalRuler.frame,
message: "Ruler window should shrink to the visible horizontal ruler frame"
)
XCTAssertTrue(waitForPreference("groupRulers", equals: true))
XCTAssertTrue(waitForPreference("groupRulers", equals: false))

app.typeKey("v", modifierFlags: [])
XCTAssertTrue(verticalRuler.waitForExistence(timeout: 2))
Expand All @@ -83,20 +83,20 @@ final class FreeRulerUITests: XCTestCase {
matches: verticalRuler.frame,
message: "Ruler window should shrink to the visible vertical ruler frame"
)
XCTAssertTrue(waitForPreference("groupRulers", equals: true))
XCTAssertTrue(waitForPreference("groupRulers", equals: false))
}

func testGroupRulersKeyboardCommandTogglesGroupedDraggingWithoutChangingRulerWindow() {
XCTAssertTrue(rulerWindow.waitForExistence(timeout: 3))
XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 3))
XCTAssertTrue(verticalRuler.waitForExistence(timeout: 3))
XCTAssertTrue(waitForPreference("groupRulers", equals: true))
XCTAssertTrue(waitForPreference("groupRulers", equals: false))

let originalFrame = rulerWindow.frame

horizontalRuler.click()
app.typeKey("g", modifierFlags: [])
XCTAssertTrue(waitForPreference("groupRulers", equals: false))
XCTAssertTrue(waitForPreference("groupRulers", equals: true))
XCTAssertTrue(rulerWindow.waitForExistence(timeout: 2))
XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 2))
XCTAssertTrue(verticalRuler.waitForExistence(timeout: 2))
Expand All @@ -107,14 +107,14 @@ final class FreeRulerUITests: XCTestCase {
)

app.typeKey("g", modifierFlags: [])
XCTAssertTrue(waitForPreference("groupRulers", equals: true))
XCTAssertTrue(waitForPreference("groupRulers", equals: false))
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(waitForPreference("groupRulers", equals: true))
XCTAssertTrue(rulerWindow.waitForExistence(timeout: 2))
XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 2))
XCTAssertTrue(verticalRuler.waitForExistence(timeout: 2))
Expand Down Expand Up @@ -396,7 +396,7 @@ final class FreeRulerUITests: XCTestCase {

XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 2))
XCTAssertTrue(verticalRuler.waitForNonExistence(timeout: 2))
XCTAssertTrue(waitForPreference("groupRulers", equals: true))
XCTAssertTrue(waitForPreference("groupRulers", equals: false))
}

private func isolateVerticalWing() {
Expand All @@ -405,7 +405,7 @@ final class FreeRulerUITests: XCTestCase {

XCTAssertTrue(verticalRuler.waitForExistence(timeout: 2))
XCTAssertTrue(horizontalRuler.waitForNonExistence(timeout: 2))
XCTAssertTrue(waitForPreference("groupRulers", equals: true))
XCTAssertTrue(waitForPreference("groupRulers", equals: false))
}

private func resetRulerCursorScenario() {
Expand All @@ -414,7 +414,7 @@ final class FreeRulerUITests: XCTestCase {
XCTAssertTrue(rulerWindow.waitForVisibleFrame(timeout: 1))
XCTAssertTrue(horizontalRuler.waitForVisibleFrame(timeout: 1))
XCTAssertTrue(verticalRuler.waitForVisibleFrame(timeout: 1))
XCTAssertTrue(waitForPreference("groupRulers", equals: true))
XCTAssertTrue(waitForPreference("groupRulers", equals: false))
}

private func assertCursorSequence(on ruler: XCUIElement, label: String) {
Expand Down