diff --git a/ComfyNotch/Handlers/PanelProximityHandler.swift b/ComfyNotch/Handlers/PanelProximityHandler.swift index a21c7cac..baf83d0d 100644 --- a/ComfyNotch/Handlers/PanelProximityHandler.swift +++ b/ComfyNotch/Handlers/PanelProximityHandler.swift @@ -11,6 +11,11 @@ class PanelProximityHandler: NSObject { private var padding: CGFloat = 15 private var distanceThreshold: CGFloat = 300 + /// Throttling properties + private var lastProcessedTime: TimeInterval = 0 + /// ~30fps (0.016 for 60) + private let throttleInterval: TimeInterval = 0.033 + private let uiManager = UIManager.shared override init() { @@ -74,7 +79,15 @@ class PanelProximityHandler: NSObject { } private func handleMouseMoved(_ event: NSEvent) { - guard let panel = panel else { return } + // Throttle the event processing + let currentTime = CACurrentMediaTime() + guard currentTime - lastProcessedTime > throttleInterval else { return } + lastProcessedTime = currentTime + + // Early exit checks before expensive operations + guard let panel = panel, + UIManager.shared.panelState == .open, + !SettingsModel.shared.isSettingsWindowOpen else { return } let mouseLocation = NSEvent.mouseLocation let panelFrame = panel.frame @@ -88,11 +101,10 @@ class PanelProximityHandler: NSObject { ) /// Don't open the panel with proximity, only allow closing - if (UIManager.shared.panelState == .open - && !paddedFrame.contains(mouseLocation)) { + if !paddedFrame.contains(mouseLocation) { let distance = distanceFromPanel(to: mouseLocation, panelFrame: panelFrame) - if distance > distanceThreshold && !SettingsModel.shared.isSettingsWindowOpen { + if distance > distanceThreshold { withAnimation(.easeInOut(duration: 0.1)) { diff --git a/ComfyNotch/Main/ComfyNotchView.swift b/ComfyNotch/Main/ComfyNotchView.swift index ec6c8ae5..0a04f5e7 100644 --- a/ComfyNotch/Main/ComfyNotchView.swift +++ b/ComfyNotch/Main/ComfyNotchView.swift @@ -45,6 +45,8 @@ struct ComfyNotchView: View { @ObservedObject private var uiManager = UIManager.shared @ObservedObject private var settings = SettingsModel.shared + @State private var lastPanelState: PanelState = .closed + init() { } @@ -57,6 +59,10 @@ struct ComfyNotchView: View { /// This manager was added in to make sure that the popInPresentation is playing /// when we open it, it doesnt bug out .onChange(of: uiManager.panelState) { _, newState in + + guard newState != lastPanelState else { return } + lastPanelState = newState + if newState == .open { if animationState.currentPanelState == .popInPresentation { animationState.currentPanelState = .home @@ -125,8 +131,6 @@ struct ComfyNotchView: View { if restrictedStates.contains(animationState.currentPanelState) { return } - -// print("translation \(translation)") switch phase { case .ended: if translation > settings.notchScrollThreshold { @@ -167,14 +171,8 @@ struct ComfyNotchView: View { if animationState.isExpanded || animationState.currentPanelState == .popInPresentation { /// see QuickAccessWidget.swift file to see how it works - // if settings.isFirstLaunch { - // Onboarding() - // .padding(.horizontal, 4) - // } else { expandedView .padding(.horizontal, 4) - // } - } Spacer() diff --git a/ComfyNotch/Managers/UIManager.swift b/ComfyNotch/Managers/UIManager.swift index 7c36c2e0..390e9bbe 100644 --- a/ComfyNotch/Managers/UIManager.swift +++ b/ComfyNotch/Managers/UIManager.swift @@ -197,7 +197,6 @@ class UIManager: ObservableObject { public func applyCompactWidgetLayout() { /// When the notch is closed we wanna show the compact album on the left, and dots on the right and hide - /// The Settings Widget DispatchQueue.main.async { withAnimation(Anim.spring) { self.compactWidgetStore.hideWidget(named: "QuickAccessWidget") diff --git a/ComfyNotch/Models/SettingsModel.swift b/ComfyNotch/Models/SettingsModel.swift index d08b4495..f31beca5 100644 --- a/ComfyNotch/Models/SettingsModel.swift +++ b/ComfyNotch/Models/SettingsModel.swift @@ -108,19 +108,19 @@ class SettingsModel: ObservableObject { private init() { loadSettings() - $isSettingsWindowOpen - .receive(on: RunLoop.main) - .sink { isOpen in - if isOpen { - NSApp.setActivationPolicy(.regular) - NSApp.activate(ignoringOtherApps: true) - } else { - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - NSApp.setActivationPolicy(.accessory) - } - } - } - .store(in: &cancellables) +// $isSettingsWindowOpen +// .receive(on: RunLoop.main) +// .sink { isOpen in +// if isOpen { +// NSApp.setActivationPolicy(.regular) +// NSApp.activate(ignoringOtherApps: true) +// } else { +// DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { +// NSApp.setActivationPolicy(.accessory) +// } +// } +// } +// .store(in: &cancellables) } func checkForUpdates() { diff --git a/ComfyNotch/Widgets/CompactWidgets/CompactAlbumWidget.swift b/ComfyNotch/Widgets/CompactWidgets/CompactAlbumWidget.swift index 85a1f2cd..7afa5b5d 100644 --- a/ComfyNotch/Widgets/CompactWidgets/CompactAlbumWidget.swift +++ b/ComfyNotch/Widgets/CompactWidgets/CompactAlbumWidget.swift @@ -2,27 +2,23 @@ import AppKit import SwiftUI import Combine +struct WidgetSizeConfig { + let width: CGFloat + let height: CGFloat +} + struct CompactAlbumWidget: View, Widget { var alignment: WidgetAlignment? = .left var name: String = "AlbumWidget" + + var swiftUIView: AnyView { + AnyView(self) + } @ObservedObject var model: MusicPlayerWidgetModel = .shared @ObservedObject var panelAnimationState: PanelAnimationState = .shared var scrollManager = ScrollHandler.shared - private let smallSizeWidth: CGFloat = 25 - private let smallSizeHeight: CGFloat = 22 - private let bigSizeWidth: CGFloat = 28 - private let bigSizeHeight: CGFloat = 25 - - private var width: CGFloat { - panelAnimationState.hoverHandler.scaleHoverOverLeftItems ? bigSizeWidth : smallSizeWidth - } - - private var height: CGFloat { - panelAnimationState.hoverHandler.scaleHoverOverLeftItems ? bigSizeHeight : smallSizeHeight - } - private var animationStiffness: CGFloat = 300 private var animationDamping: CGFloat = 15 @@ -33,7 +29,8 @@ struct CompactAlbumWidget: View, Widget { panelAnimationState.hoverHandler.scaleHoverOverLeftItems ? 1 : 0 } - + @State private var sizeConfig: WidgetSizeConfig = .init(width: 0, height: 0) + var body: some View { panelButton { Group { @@ -41,7 +38,7 @@ struct CompactAlbumWidget: View, Widget { Image(nsImage: artwork) .resizable() .scaledToFit() - .frame(width: width, height: height) + .frame(width: sizeConfig.width, height: sizeConfig.height) .cornerRadius(4) .padding(.top, 2) } else { @@ -62,8 +59,12 @@ struct CompactAlbumWidget: View, Widget { .interpolatingSpring(stiffness: animationStiffness, damping: animationDamping), value: panelAnimationState.hoverHandler.scaleHoverOverLeftItems ) + .onAppear { sizeConfig = widgetSize() } + .onChange(of: panelAnimationState.hoverHandler.scaleHoverOverLeftItems) { + sizeConfig = widgetSize() + } } - + private func panelButton(@ViewBuilder label: () -> Label) -> some View { Button(action: { withAnimation(Anim.spring) { @@ -80,8 +81,25 @@ struct CompactAlbumWidget: View, Widget { } .buttonStyle(.plain) } - - var swiftUIView: AnyView { - AnyView(self) + + func widgetSize() -> WidgetSizeConfig { + guard let screen = DisplayManager.shared.selectedScreen else { + return .init(width: 22, height: 25) + } + + let scale = screen.backingScaleFactor + let resolution = CGSize(width: screen.frame.width * scale, + height: screen.frame.height * scale) + + let w = resolution.width + let isExpanded = panelAnimationState.hoverHandler.scaleHoverOverLeftItems + + if w < 2800 { + return isExpanded ? .init(width: 20, height: 20) : .init(width: 15, height: 14) + } else if w <= 3500 { + return isExpanded ? .init(width: 26, height: 23) : .init(width: 17, height: 17) + } else { + return isExpanded ? .init(width: 28, height: 25) : .init(width: 22, height: 23) + } } } diff --git a/Scripts/stats_longest_file.sh b/Scripts/stats_longest_file.sh index ab78eb6d..d34c9a42 100755 --- a/Scripts/stats_longest_file.sh +++ b/Scripts/stats_longest_file.sh @@ -11,7 +11,7 @@ RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' -NC='\033[0m' # No Color +NC='\033[0m' # Function to display usage show_usage() { @@ -20,12 +20,6 @@ show_usage() { echo " SHOW_EMPTY=true - Include files with 0 lines" echo " MIN_LINES=n - Only show files with at least n lines" echo " EXCLUDE_TESTS=true - Exclude test files" - echo "" - echo "Examples:" - echo " $0 # Analyze current directory" - echo " $0 ./Sources # Analyze specific directory" - echo " MIN_LINES=50 $0 # Only show files with 50+ lines" - echo " EXCLUDE_TESTS=true $0 # Exclude test files" } # Check if directory exists @@ -35,27 +29,19 @@ if [[ ! -d "$TARGET_DIR" ]]; then exit 1 fi -# Build find command with filters +# Build find command find_cmd="find \"$TARGET_DIR\" -name \"*.swift\" -type f" - -# Exclude test files if requested if [[ "$EXCLUDE_TESTS" == "true" ]]; then find_cmd="$find_cmd ! -path \"*/Tests/*\" ! -path \"*/*Test*\" ! -name \"*Test*.swift\" ! -name \"*Tests.swift\"" fi - -# Find all .swift files swift_files=$(eval $find_cmd) -# Check if any Swift files were found if [[ -z "$swift_files" ]]; then echo -e "${YELLOW}No Swift files found in '$TARGET_DIR'${NC}" exit 0 fi -# Count total files file_count=$(echo "$swift_files" | wc -l) - -# Calculate totals total_lines=0 total_non_empty=0 total_comments=0 @@ -64,17 +50,17 @@ total_blank=0 echo -e "${BLUE}🔍 Analyzing Swift files in: $TARGET_DIR${NC}" echo -e "${BLUE}📁 Files found: $file_count${NC}" -# First pass: calculate totals and detailed metrics -declare -A file_stats +stats_temp=$(mktemp) + while IFS= read -r file; do if [[ -f "$file" ]]; then lines=$(wc -l < "$file" 2>/dev/null || echo 0) non_empty=$(grep -c -v '^[[:space:]]*$' "$file" 2>/dev/null || echo 0) comments=$(grep -c '^\s*\/\/' "$file" 2>/dev/null || echo 0) blank=$((lines - non_empty)) - - file_stats["$file"]="$lines:$non_empty:$comments:$blank" - + + echo "$file:$lines:$non_empty:$comments:$blank" >> "$stats_temp" + total_lines=$((total_lines + lines)) total_non_empty=$((total_non_empty + non_empty)) total_comments=$((total_comments + comments)) @@ -82,31 +68,30 @@ while IFS= read -r file; do fi done <<< "$swift_files" -# Display summary +# Summary echo "" echo -e "${GREEN}📊 Summary Statistics:${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" printf "%-20s %10s %10s %10s %10s\n" "Metric" "Total" "Avg/File" "Min" "Max" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -# Calculate min/max min_lines=999999 max_lines=0 -for file in $swift_files; do - if [[ -f "$file" ]]; then - lines=$(echo "${file_stats[$file]}" | cut -d: -f1) - [[ $lines -lt $min_lines ]] && min_lines=$lines - [[ $lines -gt $max_lines ]] && max_lines=$lines - fi -done -avg_lines=$((total_lines / file_count)) -avg_non_empty=$((total_non_empty / file_count)) +while IFS=: read -r file lines non_empty comments blank; do + [[ $lines -lt $min_lines ]] && min_lines=$lines + [[ $lines -gt $max_lines ]] && max_lines=$lines +done < "$stats_temp" + +avg_lines=$((file_count > 0 ? total_lines / file_count : 0)) +avg_non_empty=$((file_count > 0 ? total_non_empty / file_count : 0)) +avg_comments=$((file_count > 0 ? total_comments / file_count : 0)) +avg_blank=$((file_count > 0 ? total_blank / file_count : 0)) printf "%-20s %10s %10s %10s %10s\n" "Total Lines" "$total_lines" "$avg_lines" "$min_lines" "$max_lines" printf "%-20s %10s %10s %10s %10s\n" "Non-empty Lines" "$total_non_empty" "$avg_non_empty" "-" "-" -printf "%-20s %10s %10s %10s %10s\n" "Comment Lines" "$total_comments" "$((total_comments / file_count))" "-" "-" -printf "%-20s %10s %10s %10s %10s\n" "Blank Lines" "$total_blank" "$((total_blank / file_count))" "-" "-" +printf "%-20s %10s %10s %10s %10s\n" "Comment Lines" "$total_comments" "$avg_comments" "-" "-" +printf "%-20s %10s %10s %10s %10s\n" "Blank Lines" "$total_blank" "$avg_blank" "-" "-" echo "" echo -e "${GREEN}📋 Detailed File Analysis:${NC}" @@ -114,71 +99,45 @@ echo "━━━━━━━━━━━━━━━━━━━━━━━━ printf "%6s %7s %6s %6s %s\n" "Lines" "Percent" "Code" "Blank" "File" echo "──────────────────────────────────────────────────────────────────────────" -# Create temporary file for sorting -temp_file=$(mktemp) +output_temp=$(mktemp) +while IFS=: read -r file lines non_empty comments blank; do + if [[ "$SHOW_EMPTY" == "false" && $lines -eq 0 ]]; then + continue + fi + if [[ $lines -lt $MIN_LINES ]]; then + continue + fi + + percent=$(awk "BEGIN { printf \"%.2f\", ($lines/$total_lines)*100 }") -# Second pass: output file details -for file in $swift_files; do - if [[ -f "$file" ]]; then - IFS=':' read -r lines non_empty comments blank <<< "${file_stats[$file]}" - - # Apply filters - if [[ "$SHOW_EMPTY" == "false" && $lines -eq 0 ]]; then - continue - fi - - if [[ $lines -lt $MIN_LINES ]]; then - continue - fi - - if [[ $total_lines -gt 0 ]]; then - percent=$(awk "BEGIN { printf \"%.2f\", ($lines/$total_lines)*100 }") - else - percent="0.00" - fi - - # Color code based on file size - color="" - if [[ $lines -gt 500 ]]; then - color="$RED" - elif [[ $lines -gt 200 ]]; then - color="$YELLOW" - else - color="$GREEN" - fi - - # Clean up file path for display - display_file=$(echo "$file" | sed "s|^$TARGET_DIR/||" | sed 's|^\./||') - - printf "%s%6s %6s%% %6s %6s %s%s\n" "$color" "$lines" "$percent" "$non_empty" "$blank" "$display_file" "$NC" >> "$temp_file" + if [[ $lines -gt 500 ]]; then + color="$RED" + elif [[ $lines -gt 200 ]]; then + color="$YELLOW" + else + color="$GREEN" fi -done -# Sort by line count (descending) and display -sort -rn "$temp_file" -rm "$temp_file" + display_file=$(echo "$file" | sed "s|^$TARGET_DIR/||" | sed 's|^\./||') + printf "%s%6s %6s%% %6s %6s %s%s\n" "$color" "$lines" "$percent" "$non_empty" "$blank" "$display_file" "$NC" >> "$output_temp" +done < "$stats_temp" + +sort -rn "$output_temp" +rm "$output_temp" echo "" echo -e "${BLUE}💡 Tips:${NC}" echo "• Files with >500 lines (${RED}red${NC}) might benefit from refactoring" echo "• Files with >200 lines (${YELLOW}yellow${NC}) should be monitored" -echo "• Use MIN_LINES=50 to focus on larger files" -echo "• Use EXCLUDE_TESTS=true to exclude test files" -# Show largest files echo "" echo -e "${YELLOW}🔝 Top 5 Largest Files:${NC}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -largest_files=$(for file in $swift_files; do - if [[ -f "$file" ]]; then - lines=$(echo "${file_stats[$file]}" | cut -d: -f1) - echo "$lines:$file" - fi -done | sort -rn | head -5) - -counter=1 -while IFS=':' read -r lines file; do +sort -t: -k2 -nr "$stats_temp" | head -n 5 | nl | while read -r num line; do + file=$(echo "$line" | cut -d: -f1) + lines=$(echo "$line" | cut -d: -f2) display_file=$(echo "$file" | sed "s|^$TARGET_DIR/||" | sed 's|^\./||') - printf "%d. %s lines - %s\n" "$counter" "$lines" "$display_file" - counter=$((counter + 1)) -done <<< "$largest_files" + echo "$num. $lines lines - $display_file" +done + +rm "$stats_temp"