diff --git a/DynamicIsland/ContentView.swift b/DynamicIsland/ContentView.swift index 4828c08..206c6a2 100644 --- a/DynamicIsland/ContentView.swift +++ b/DynamicIsland/ContentView.swift @@ -232,7 +232,7 @@ struct ContentView: View { } } } - .conditionalModifier(Defaults[.closeGestureEnabled] && Defaults[.enableGestures] && interactionsEnabled) { view in + .conditionalModifier((Defaults[.closeGestureEnabled] || Defaults[.reverseScrollGestures]) && Defaults[.enableGestures] && interactionsEnabled) { view in view .panGesture(direction: .up) { translation, phase in handleUpGesture(translation: translation, phase: phase) @@ -883,18 +883,38 @@ struct ContentView: View { // MARK: - Gesture Handling private func handleDownGesture(translation: CGFloat, phase: NSEvent.Phase) { + handleScrollGesture(isDownward: true, translation: translation, phase: phase) + } + + private func handleUpGesture(translation: CGFloat, phase: NSEvent.Phase) { + handleScrollGesture(isDownward: false, translation: translation, phase: phase) + } + + private func handleScrollGesture(isDownward: Bool, translation: CGFloat, phase: NSEvent.Phase) { + let reverse = Defaults[.reverseScrollGestures] + let shouldOpen = isDownward ? !reverse : reverse + + if shouldOpen { + handleOpenScrollGesture(translation: translation, phase: phase) + } else { + guard Defaults[.closeGestureEnabled] else { return } + handleCloseScrollGesture(translation: translation, phase: phase) + } + } + + private func handleOpenScrollGesture(translation: CGFloat, phase: NSEvent.Phase) { guard vm.notchState == .closed else { return } - + withAnimation(.smooth) { gestureProgress = (translation / Defaults[.gestureSensitivity]) * 20 } - + if phase == .ended { withAnimation(.smooth) { gestureProgress = .zero } } - + if translation > Defaults[.gestureSensitivity] { if Defaults[.enableHaptics] { triggerHapticIfAllowed() @@ -905,29 +925,29 @@ struct ContentView: View { openNotch() } } - - private func handleUpGesture(translation: CGFloat, phase: NSEvent.Phase) { - if vm.notchState == .open && !vm.isHoveringCalendar && !vm.isScrollGestureActive { + + private func handleCloseScrollGesture(translation: CGFloat, phase: NSEvent.Phase) { + guard vm.notchState == .open, !vm.isHoveringCalendar, !vm.isScrollGestureActive else { return } + + withAnimation(.smooth) { + gestureProgress = (translation / Defaults[.gestureSensitivity]) * -20 + } + + if phase == .ended { withAnimation(.smooth) { - gestureProgress = (translation / Defaults[.gestureSensitivity]) * -20 + gestureProgress = .zero } - - if phase == .ended { - withAnimation(.smooth) { - gestureProgress = .zero - } + } + + if translation > Defaults[.gestureSensitivity] { + withAnimation(.smooth) { + gestureProgress = .zero + isHovering = false } - - if translation > Defaults[.gestureSensitivity] { - withAnimation(.smooth) { - gestureProgress = .zero - isHovering = false - } - vm.close() - - if Defaults[.enableHaptics] { - triggerHapticIfAllowed() - } + vm.close() + + if Defaults[.enableHaptics] { + triggerHapticIfAllowed() } } } @@ -944,13 +964,19 @@ struct ContentView: View { } if skipGestureActiveDirection == nil && translation > Defaults[.gestureSensitivity] { - skipGestureActiveDirection = direction + let effectiveDirection: MusicManager.SkipDirection + if Defaults[.reverseSwipeGestures] { + effectiveDirection = direction == .forward ? .backward : .forward + } else { + effectiveDirection = direction + } + skipGestureActiveDirection = effectiveDirection if Defaults[.enableHaptics] { triggerHapticIfAllowed() } - musicManager.handleSkipGesture(direction: direction) + musicManager.handleSkipGesture(direction: effectiveDirection) } } diff --git a/DynamicIsland/components/Settings/SettingsView.swift b/DynamicIsland/components/Settings/SettingsView.swift index de3ffa0..1fc6926 100644 --- a/DynamicIsland/components/Settings/SettingsView.swift +++ b/DynamicIsland/components/Settings/SettingsView.swift @@ -577,6 +577,8 @@ struct SettingsView: View { SettingsSearchEntry(tab: .general, title: "Hide Dynamic Island during screenshots & recordings", keywords: ["privacy", "screenshot", "recording"], highlightID: SettingsTab.general.highlightID(for: "Hide Dynamic Island during screenshots & recordings")), SettingsSearchEntry(tab: .general, title: "Enable gestures", keywords: ["gestures", "trackpad"], highlightID: SettingsTab.general.highlightID(for: "Enable gestures")), SettingsSearchEntry(tab: .general, title: "Close gesture", keywords: ["pinch", "swipe"], highlightID: SettingsTab.general.highlightID(for: "Close gesture")), + SettingsSearchEntry(tab: .general, title: "Reverse swipe gestures", keywords: ["reverse", "swipe", "media"], highlightID: SettingsTab.general.highlightID(for: "Reverse swipe gestures")), + SettingsSearchEntry(tab: .general, title: "Reverse scroll gestures", keywords: ["reverse", "scroll", "open", "close"], highlightID: SettingsTab.general.highlightID(for: "Reverse scroll gestures")), SettingsSearchEntry(tab: .general, title: "Extend hover area", keywords: ["hover", "cursor"], highlightID: SettingsTab.general.highlightID(for: "Extend hover area")), SettingsSearchEntry(tab: .general, title: "Enable haptics", keywords: ["haptic", "feedback"], highlightID: SettingsTab.general.highlightID(for: "Enable haptics")), SettingsSearchEntry(tab: .general, title: "Open notch on hover", keywords: ["hover to open", "auto open"], highlightID: SettingsTab.general.highlightID(for: "Open notch on hover")), @@ -845,6 +847,8 @@ struct GeneralSettings: View { @Default(.enableMinimalisticUI) var enableMinimalisticUI @Default(.enableHorizontalMusicGestures) var enableHorizontalMusicGestures @Default(.musicGestureBehavior) var musicGestureBehavior + @Default(.reverseSwipeGestures) var reverseSwipeGestures + @Default(.reverseScrollGestures) var reverseScrollGestures private func highlightID(_ title: String) -> String { SettingsTab.general.highlightID(for: title) @@ -1003,6 +1007,9 @@ struct GeneralSettings: View { Text(musicGestureBehavior.description) .font(.caption) .foregroundStyle(.secondary) + + Defaults.Toggle("Reverse swipe gestures", key: .reverseSwipeGestures) + .settingsHighlight(id: highlightID("Reverse swipe gestures")) } Defaults.Toggle("Close gesture", key: .closeGestureEnabled) @@ -1015,6 +1022,9 @@ struct GeneralSettings: View { .foregroundStyle(.secondary) } } + + Defaults.Toggle("Reverse open/close scroll gestures", key: .reverseScrollGestures) + .settingsHighlight(id: highlightID("Reverse scroll gestures")) } } header: { HStack { diff --git a/DynamicIsland/models/Constants.swift b/DynamicIsland/models/Constants.swift index d05c7f5..4f00e16 100644 --- a/DynamicIsland/models/Constants.swift +++ b/DynamicIsland/models/Constants.swift @@ -520,6 +520,8 @@ extension Defaults.Keys { static let gestureSensitivity = Key("gestureSensitivity", default: 200.0) static let enableHorizontalMusicGestures = Key("enableHorizontalMusicGestures", default: true) static let musicGestureBehavior = Key("musicGestureBehavior", default: .track) + static let reverseSwipeGestures = Key("reverseSwipeGestures", default: false) + static let reverseScrollGestures = Key("reverseScrollGestures", default: false) // MARK: Media playback static let coloredSpectrogram = Key("coloredSpectrogram", default: true)