mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Somewhat bearable
This commit is contained in:
parent
cc53e73cd1
commit
bf9ecef9c5
2 changed files with 119 additions and 38 deletions
|
|
@ -30,25 +30,34 @@ struct ContentView: View {
|
||||||
@Default(.showNetworkGraph) var showNetworkGraph
|
@Default(.showNetworkGraph) var showNetworkGraph
|
||||||
@Default(.showDiskGraph) var showDiskGraph
|
@Default(.showDiskGraph) var showDiskGraph
|
||||||
|
|
||||||
|
// Dynamic sizing based on view type and graph count with smooth transitions
|
||||||
var dynamicNotchSize: CGSize {
|
var dynamicNotchSize: CGSize {
|
||||||
// Only apply dynamic sizing when on stats tab and stats are enabled
|
// Always return the target size for the current view to ensure smooth transitions
|
||||||
guard coordinator.currentView == .stats && enableStatsFeature else {
|
let targetSize: CGSize
|
||||||
return openNotchSize
|
|
||||||
|
if coordinator.currentView == .stats {
|
||||||
|
// Count enabled graphs using the same logic as NotchStatsView
|
||||||
|
var enabledGraphsCount = 0
|
||||||
|
if showCpuGraph { enabledGraphsCount += 1 }
|
||||||
|
if showMemoryGraph { enabledGraphsCount += 1 }
|
||||||
|
if showGpuGraph { enabledGraphsCount += 1 }
|
||||||
|
if showNetworkGraph { enabledGraphsCount += 1 }
|
||||||
|
if showDiskGraph { enabledGraphsCount += 1 }
|
||||||
|
|
||||||
|
if enabledGraphsCount > 3 {
|
||||||
|
// Expand height for 4+ graphs
|
||||||
|
targetSize = CGSize(
|
||||||
|
width: openNotchSize.width,
|
||||||
|
height: openNotchSize.height + 65 // Add extra height for second row
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
targetSize = openNotchSize
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
targetSize = openNotchSize
|
||||||
}
|
}
|
||||||
|
|
||||||
let enabledGraphsCount = [showCpuGraph, showMemoryGraph, showGpuGraph, showNetworkGraph, showDiskGraph].filter { $0 }.count
|
return targetSize
|
||||||
|
|
||||||
// Calculate height based on layout: 1-3 graphs = single row, 4+ graphs = two rows
|
|
||||||
var requiredHeight = openNotchSize.height
|
|
||||||
|
|
||||||
if enabledGraphsCount >= 4 {
|
|
||||||
// Two rows needed - add height for second row plus spacing
|
|
||||||
let extraHeight: CGFloat = 120 + 12 // Graph height + spacing
|
|
||||||
requiredHeight = openNotchSize.height + extraHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
// Width stays constant - no horizontal expansion
|
|
||||||
return CGSize(width: openNotchSize.width, height: requiredHeight)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -57,6 +66,8 @@ struct ContentView: View {
|
||||||
@State private var debounceWorkItem: DispatchWorkItem?
|
@State private var debounceWorkItem: DispatchWorkItem?
|
||||||
|
|
||||||
@State private var isHoverStateChanging: Bool = false
|
@State private var isHoverStateChanging: Bool = false
|
||||||
|
@State private var isStatsTransitioning: Bool = false
|
||||||
|
@State private var lastHapticTime: Date = Date()
|
||||||
|
|
||||||
@State private var gestureProgress: CGFloat = .zero
|
@State private var gestureProgress: CGFloat = .zero
|
||||||
|
|
||||||
|
|
@ -68,7 +79,7 @@ struct ContentView: View {
|
||||||
|
|
||||||
@Default(.showNotHumanFace) var showNotHumanFace
|
@Default(.showNotHumanFace) var showNotHumanFace
|
||||||
@Default(.useModernCloseAnimation) var useModernCloseAnimation
|
@Default(.useModernCloseAnimation) var useModernCloseAnimation
|
||||||
|
|
||||||
private let extendedHoverPadding: CGFloat = 30
|
private let extendedHoverPadding: CGFloat = 30
|
||||||
private let zeroHeightHoverPadding: CGFloat = 10
|
private let zeroHeightHoverPadding: CGFloat = 10
|
||||||
|
|
||||||
|
|
@ -98,18 +109,22 @@ struct ContentView: View {
|
||||||
.conditionalModifier(!useModernCloseAnimation) { view in
|
.conditionalModifier(!useModernCloseAnimation) { view in
|
||||||
let hoverAnimationAnimation = Animation.bouncy.speed(1.2)
|
let hoverAnimationAnimation = Animation.bouncy.speed(1.2)
|
||||||
let notchStateAnimation = Animation.spring.speed(1.2)
|
let notchStateAnimation = Animation.spring.speed(1.2)
|
||||||
|
let viewTransitionAnimation = Animation.easeInOut(duration: 0.4)
|
||||||
return view
|
return view
|
||||||
.animation(hoverAnimationAnimation, value: isHovering)
|
.animation(hoverAnimationAnimation, value: isHovering)
|
||||||
.animation(notchStateAnimation, value: vm.notchState)
|
.animation(notchStateAnimation, value: vm.notchState)
|
||||||
|
.animation(viewTransitionAnimation, value: coordinator.currentView)
|
||||||
.animation(.smooth, value: gestureProgress)
|
.animation(.smooth, value: gestureProgress)
|
||||||
.transition(.blurReplace.animation(.interactiveSpring(dampingFraction: 1.2)))
|
.transition(.blurReplace.animation(.interactiveSpring(dampingFraction: 1.2)))
|
||||||
}
|
}
|
||||||
.conditionalModifier(useModernCloseAnimation) { view in
|
.conditionalModifier(useModernCloseAnimation) { view in
|
||||||
let hoverAnimationAnimation = Animation.bouncy.speed(1.2)
|
let hoverAnimationAnimation = Animation.bouncy.speed(1.2)
|
||||||
let notchStateAnimation = Animation.spring.speed(1.2)
|
let notchStateAnimation = Animation.spring.speed(1.2)
|
||||||
|
let viewTransitionAnimation = Animation.easeInOut(duration: 0.4)
|
||||||
return view
|
return view
|
||||||
.animation(hoverAnimationAnimation, value: isHovering)
|
.animation(hoverAnimationAnimation, value: isHovering)
|
||||||
.animation(notchStateAnimation, value: vm.notchState)
|
.animation(notchStateAnimation, value: vm.notchState)
|
||||||
|
.animation(viewTransitionAnimation, value: coordinator.currentView)
|
||||||
}
|
}
|
||||||
.conditionalModifier(Defaults[.openNotchOnHover]) { view in
|
.conditionalModifier(Defaults[.openNotchOnHover]) { view in
|
||||||
view.onHover { hovering in
|
view.onHover { hovering in
|
||||||
|
|
@ -119,26 +134,11 @@ struct ContentView: View {
|
||||||
.conditionalModifier(!Defaults[.openNotchOnHover]) { view in
|
.conditionalModifier(!Defaults[.openNotchOnHover]) { view in
|
||||||
view
|
view
|
||||||
.onHover { hovering in
|
.onHover { hovering in
|
||||||
withAnimation(vm.animation) {
|
handleSimpleHover(hovering)
|
||||||
isHovering = hovering
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only close if mouse leaves and the notch is open and no popovers are active
|
|
||||||
if !hovering && vm.notchState == .open && !vm.isBatteryPopoverActive && !vm.isClipboardPopoverActive && !vm.isColorPickerPopoverActive && !vm.isStatsPopoverActive {
|
|
||||||
// Add slight delay for stats tab to prevent accidental closure during layout changes
|
|
||||||
let delay: Double = (coordinator.currentView == .stats) ? 0.2 : 0.0
|
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
|
||||||
// Double-check conditions after delay
|
|
||||||
if !isHovering && vm.notchState == .open && !vm.isBatteryPopoverActive && !vm.isClipboardPopoverActive && !vm.isColorPickerPopoverActive && !vm.isStatsPopoverActive {
|
|
||||||
vm.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if (vm.notchState == .closed) && Defaults[.enableHaptics] {
|
if (vm.notchState == .closed) && Defaults[.enableHaptics] {
|
||||||
haptics.toggle()
|
triggerHapticIfAllowed()
|
||||||
}
|
}
|
||||||
doOpen()
|
doOpen()
|
||||||
}
|
}
|
||||||
|
|
@ -210,6 +210,19 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onChange(of: coordinator.currentView) { _, newView in
|
||||||
|
// Cancel any pending hover actions during view transitions
|
||||||
|
hoverWorkItem?.cancel()
|
||||||
|
debounceWorkItem?.cancel()
|
||||||
|
|
||||||
|
// Set transition state briefly to prevent immediate closure
|
||||||
|
isStatsTransitioning = true
|
||||||
|
let delay = newView == .stats ? 1.0 : 0.5
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
||||||
|
isStatsTransitioning = false
|
||||||
|
}
|
||||||
|
}
|
||||||
.sensoryFeedback(.alignment, trigger: haptics)
|
.sensoryFeedback(.alignment, trigger: haptics)
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
SettingsLink(label: {
|
SettingsLink(label: {
|
||||||
|
|
@ -521,7 +534,7 @@ struct ContentView: View {
|
||||||
|
|
||||||
// Only provide haptic feedback if notch is closed
|
// Only provide haptic feedback if notch is closed
|
||||||
if vm.notchState == .closed && Defaults[.enableHaptics] {
|
if vm.notchState == .closed && Defaults[.enableHaptics] {
|
||||||
haptics.toggle()
|
triggerHapticIfAllowed()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay opening the notch
|
// Delay opening the notch
|
||||||
|
|
@ -560,7 +573,9 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the notch if it's open and no popovers are active
|
// Close the notch if it's open and no popovers are active
|
||||||
if vm.notchState == .open && !vm.isBatteryPopoverActive && !vm.isClipboardPopoverActive && !vm.isColorPickerPopoverActive {
|
let hasActivePopovers = vm.isBatteryPopoverActive || vm.isClipboardPopoverActive || vm.isColorPickerPopoverActive || vm.isStatsPopoverActive
|
||||||
|
|
||||||
|
if vm.notchState == .open && !hasActivePopovers {
|
||||||
vm.close()
|
vm.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -571,6 +586,67 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Simple hover handler for non-openNotchOnHover mode
|
||||||
|
private func handleSimpleHover(_ hovering: Bool) {
|
||||||
|
// Cancel any existing work items first
|
||||||
|
hoverWorkItem?.cancel()
|
||||||
|
debounceWorkItem?.cancel()
|
||||||
|
|
||||||
|
if hovering {
|
||||||
|
// Mouse entered
|
||||||
|
withAnimation(vm.animation) {
|
||||||
|
isHovering = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mouse exited - use debounce to prevent rapid firing
|
||||||
|
let debounce = DispatchWorkItem {
|
||||||
|
withAnimation(vm.animation) {
|
||||||
|
isHovering = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we should close after a delay
|
||||||
|
let hasPopovers = hasAnyActivePopovers()
|
||||||
|
|
||||||
|
if vm.notchState == .open && !hasPopovers {
|
||||||
|
let isStatsTab = coordinator.currentView == .stats
|
||||||
|
let delay: Double = isStatsTab ? 0.8 : 0.2
|
||||||
|
|
||||||
|
let closeTask = DispatchWorkItem {
|
||||||
|
if !isHovering && vm.notchState == .open && !hasAnyActivePopovers() {
|
||||||
|
vm.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hoverWorkItem = closeTask
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: closeTask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debounceWorkItem = debounce
|
||||||
|
// Longer debounce for stats tab to prevent rapid hover events
|
||||||
|
let debounceDelay = coordinator.currentView == .stats ? 0.2 : 0.05
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + debounceDelay, execute: debounce)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to check if any popovers are active
|
||||||
|
private func hasAnyActivePopovers() -> Bool {
|
||||||
|
return vm.isBatteryPopoverActive ||
|
||||||
|
vm.isClipboardPopoverActive ||
|
||||||
|
vm.isColorPickerPopoverActive ||
|
||||||
|
vm.isStatsPopoverActive
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to prevent rapid haptic feedback
|
||||||
|
private func triggerHapticIfAllowed() {
|
||||||
|
let now = Date()
|
||||||
|
if now.timeIntervalSince(lastHapticTime) > 0.3 { // Minimum 300ms between haptics
|
||||||
|
haptics.toggle()
|
||||||
|
lastHapticTime = now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Gesture Handling
|
// MARK: - Gesture Handling
|
||||||
|
|
||||||
private func handleDownGesture(translation: CGFloat, phase: NSEvent.Phase) {
|
private func handleDownGesture(translation: CGFloat, phase: NSEvent.Phase) {
|
||||||
|
|
@ -588,7 +664,7 @@ struct ContentView: View {
|
||||||
|
|
||||||
if translation > Defaults[.gestureSensitivity] {
|
if translation > Defaults[.gestureSensitivity] {
|
||||||
if Defaults[.enableHaptics] {
|
if Defaults[.enableHaptics] {
|
||||||
haptics.toggle()
|
triggerHapticIfAllowed()
|
||||||
}
|
}
|
||||||
withAnimation(.smooth) {
|
withAnimation(.smooth) {
|
||||||
gestureProgress = .zero
|
gestureProgress = .zero
|
||||||
|
|
@ -617,7 +693,7 @@ struct ContentView: View {
|
||||||
vm.close()
|
vm.close()
|
||||||
|
|
||||||
if Defaults[.enableHaptics] {
|
if Defaults[.enableHaptics] {
|
||||||
haptics.toggle()
|
triggerHapticIfAllowed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -340,6 +340,11 @@ struct NotchStatsView: View {
|
||||||
if enableStatsFeature && Defaults[.autoStartStatsMonitoring] && !statsManager.isMonitoring {
|
if enableStatsFeature && Defaults[.autoStartStatsMonitoring] && !statsManager.isMonitoring {
|
||||||
statsManager.startMonitoring()
|
statsManager.startMonitoring()
|
||||||
}
|
}
|
||||||
|
// Protect against hover interference during view transition
|
||||||
|
isResizingForStats = true
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
|
||||||
|
isResizingForStats = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
// Keep monitoring running when tab is not visible
|
// Keep monitoring running when tab is not visible
|
||||||
|
|
@ -349,7 +354,7 @@ struct NotchStatsView: View {
|
||||||
.onChange(of: availableGraphs.count) { _, newCount in
|
.onChange(of: availableGraphs.count) { _, newCount in
|
||||||
// Protect against hover interference during dynamic sizing
|
// Protect against hover interference during dynamic sizing
|
||||||
isResizingForStats = true
|
isResizingForStats = true
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
|
||||||
isResizingForStats = false
|
isResizingForStats = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue