1212import Cocoa
1313import Kit
1414
15+ private let DiskActivityExpandedProcessLimit = 50
16+
1517internal class Preview : PreviewWrapper {
1618 private var mainID : String ? = nil
1719
@@ -73,6 +75,7 @@ internal class Preview: PreviewWrapper {
7375
7476 private var activityPeriod : DiskActivityPeriod = . hour1
7577 private var activitySort : DiskActivityProcessSort = . total
78+ private var activityProcessesExpanded : Bool = false
7679 private var periodReadValueField : ValueField ?
7780 private var periodWriteValueField : ValueField ?
7881 private var periodTotalValueField : ValueField ?
@@ -82,6 +85,7 @@ internal class Preview: PreviewWrapper {
8285 private var periodCoverageHintField : NSTextField ?
8386 private var periodCoverageProgress : NSProgressIndicator ?
8487 private var periodProcessTitleField : NSTextField ?
88+ private var periodProcessToggleButton : NSButton ?
8589 private var periodTopSourceValueField : ValueField ?
8690 private var periodTimelineChart : DiskActivityTimelineChart ?
8791 private var periodProcessTable : DiskActivityProcessTable ?
@@ -106,7 +110,6 @@ internal class Preview: PreviewWrapper {
106110 self . activitySort = DiskActivityProcessSort (
107111 rawValue: Store . shared. string ( key: " \( self . module. stringValue) _activitySort " , defaultValue: self . activitySort. rawValue)
108112 ) ?? self . activitySort
109-
110113 self . addArrangedSubview ( PreferencesSection ( [ self . usageView ( ) ] ) )
111114
112115 let allDisks = PreferencesSection ( title: localizedString ( " All disks " ) , subtitle: " " , [ self . disks] )
@@ -479,8 +482,31 @@ internal class Preview: PreviewWrapper {
479482 summary. addArrangedSubview ( totals)
480483 summary. addArrangedSubview ( peaks)
481484
485+ let processHeader = NSStackView ( )
486+ processHeader. orientation = . horizontal
487+ processHeader. alignment = . centerY
488+ processHeader. spacing = Constants . Settings. margin/ 1.5
489+ processHeader. translatesAutoresizingMaskIntoConstraints = false
490+ processHeader. setContentCompressionResistancePriority ( . defaultLow, for: . horizontal)
491+
482492 let processTitle = LabelField ( localizedString ( " Disk activity processes " ) )
483493 processTitle. font = . systemFont( ofSize: 11 , weight: . semibold)
494+ processTitle. lineBreakMode = . byTruncatingTail
495+ processTitle. setContentCompressionResistancePriority ( . defaultLow, for: . horizontal)
496+
497+ let processToggleButton = NSButton ( title: localizedString ( " Show more processes " ) , target: self , action: #selector( self . toggleActivityProcessesExpanded) )
498+ processToggleButton. bezelStyle = . inline
499+ processToggleButton. isBordered = false
500+ processToggleButton. font = . systemFont( ofSize: 11 , weight: . medium)
501+ processToggleButton. contentTintColor = . controlAccentColor
502+ processToggleButton. setContentHuggingPriority ( . required, for: . horizontal)
503+ processToggleButton. setContentCompressionResistancePriority ( . required, for: . horizontal)
504+ processToggleButton. isHidden = true
505+
506+ processHeader. addArrangedSubview ( processTitle)
507+ processHeader. addArrangedSubview ( NSView ( ) )
508+ processHeader. addArrangedSubview ( processToggleButton)
509+
484510 let processHint = LabelField ( localizedString ( " Disk activity process counters, not exact files " ) )
485511 processHint. font = . systemFont( ofSize: 10 , weight: . regular)
486512 processHint. textColor = . tertiaryLabelColor
@@ -490,13 +516,14 @@ internal class Preview: PreviewWrapper {
490516 table. setContentCompressionResistancePriority ( . defaultLow, for: . horizontal)
491517 self . periodProcessTable = table
492518 self . periodProcessTitleField = processTitle
519+ self . periodProcessToggleButton = processToggleButton
493520
494521 view. addArrangedSubview ( coverageStatus)
495522 view. addArrangedSubview ( summary)
496- view. addArrangedSubview ( processTitle )
523+ view. addArrangedSubview ( processHeader )
497524 view. addArrangedSubview ( processHint)
498525 view. addArrangedSubview ( table)
499- [ coverageStatus, summary, processTitle , processHint, table] . forEach { item in
526+ [ coverageStatus, summary, processHeader , processHint, table] . forEach { item in
500527 item. translatesAutoresizingMaskIntoConstraints = false
501528 item. widthAnchor. constraint ( equalTo: view. widthAnchor) . isActive = true
502529 }
@@ -758,12 +785,17 @@ internal class Preview: PreviewWrapper {
758785 } )
759786 }
760787
788+ @objc private func toggleActivityProcessesExpanded( ) {
789+ self . activityProcessesExpanded. toggle ( )
790+ self . refreshPeriodActivity ( )
791+ }
792+
761793 private func refreshPeriodActivity( ) {
762794 let summary = DiskActivityHistoryStore . shared. summary (
763795 diskID: self . mainID,
764796 period: self . activityPeriod,
765797 sort: self . activitySort,
766- limit: self . processLimit
798+ limit: self . processDisplayLimit
767799 )
768800
769801 self . periodReadValueField? . stringValue = Units ( bytes: summary. read) . getReadableMemory ( )
@@ -783,9 +815,19 @@ internal class Preview: PreviewWrapper {
783815 }
784816 let displayRows = self . periodActivityDisplayRows ( from: summary)
785817 self . periodProcessTitleField? . stringValue = self . periodProcessTitle ( for: summary, displayRows: displayRows)
818+ self . updateProcessToggle ( for: summary)
786819 self . periodProcessTable? . setRows ( displayRows)
787820 }
788821
822+ private var processDisplayLimit : Int {
823+ let compactLimit = self . processLimit
824+ guard compactLimit > 0 else { return 0 }
825+ if self . activityProcessesExpanded {
826+ return max ( compactLimit, DiskActivityExpandedProcessLimit)
827+ }
828+ return compactLimit
829+ }
830+
789831 private func periodActivityDisplayRows( from summary: DiskActivitySummary ) -> [ DiskActivityProcessDisplayRow ] {
790832 var rows = summary. processes. map { DiskActivityProcessDisplayRow ( process: $0) }
791833 let shareDenominator = max ( summary. total, summary. capturedProcessTotal + summary. unattributedTotal)
@@ -828,6 +870,15 @@ internal class Preview: PreviewWrapper {
828870 return " \( base) · \( visibleProcessCount) / \( summary. totalProcessCount) \( localizedString ( " shown " ) ) · \( displayRows. count) \( localizedString ( " rows " ) ) "
829871 }
830872
873+ private func updateProcessToggle( for summary: DiskActivitySummary ) {
874+ guard let button = self . periodProcessToggleButton else { return }
875+ let compactLimit = self . processLimit
876+ let canExpand = compactLimit > 0 && summary. totalProcessCount > compactLimit
877+ button. isHidden = !canExpand
878+ button. title = localizedString ( self . activityProcessesExpanded ? " Show fewer processes " : " Show more processes " )
879+ button. toolTip = localizedString ( self . activityProcessesExpanded ? " Fewer process rows are shown " : " More captured processes are available " )
880+ }
881+
831882 private func updatePeriodCoverage( _ coverage: DiskActivityCoverage ) {
832883 let percent = Int ( ( coverage. coverageRatio * 100 ) . rounded ( ) )
833884 let title = self . periodCoverageTitle ( for: coverage. state)
@@ -1184,7 +1235,7 @@ private struct DiskActivityProcessDisplayRow {
11841235}
11851236
11861237private class DiskActivityProcessTable : NSStackView {
1187- private let maxRows : Int = ( NumbersOfProcesses . max ( ) ?? 15 ) + 2
1238+ private let maxRows : Int = DiskActivityExpandedProcessLimit + 2
11881239 private var rowViews : [ DiskActivityProcessRow ] = [ ]
11891240
11901241 init ( ) {
0 commit comments