gardesk/gartop / 358d0c1

Browse files

Eliminate tab switching lag and improve column spacing

- ProcessList now renders from reference instead of owning copies
- Tab switching sorts in-place without cloning 100 ProcessInfo structs
- Adjusted column spacing for network/disk tabs (Sock/Listen/Estab/User)
- Column positions: 240/310/380/450 for better readability
Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
358d0c11c858b5ec4fe2a7c57ecd6fa0718734e1
Parents
9daf2bb
Tree
f0869c4

2 changed files

StatusFile+-
M gartop/src/gui/app.rs 6 7
M gartop/src/gui/process_list.rs 26 28
gartop/src/gui/app.rsmodified
@@ -254,7 +254,7 @@ impl App {
254254
         }) {
255255
             if resp.success {
256256
                 self.processes = resp.data.and_then(|d| serde_json::from_value(d).ok()).unwrap_or_default();
257
-                self.process_list.set_processes(self.processes.clone());
257
+                self.process_list.set_process_count(self.processes.len());
258258
                 self.process_list.set_sort(sort_field);
259259
             }
260260
         }
@@ -572,8 +572,8 @@ impl App {
572572
             }
573573
         }
574574
 
575
-        // Render process list
576
-        self.process_list.render(&self.renderer, &self.theme)?;
575
+        // Render process list (pass reference, no clone)
576
+        self.process_list.render(&self.renderer, &self.theme, &self.processes)?;
577577
 
578578
         Ok(())
579579
     }
@@ -620,7 +620,7 @@ impl App {
620620
         self.header = HeaderBar::new(Rect::new(0, 0, width, HEADER_HEIGHT));
621621
         self.tab_bar.set_bounds(Rect::new(0, HEADER_HEIGHT as i32, width, TAB_BAR_HEIGHT));
622622
         self.process_list = Self::create_process_list(width, height);
623
-        self.process_list.set_processes(self.processes.clone());
623
+        self.process_list.set_process_count(self.processes.len());
624624
 
625625
         Ok(())
626626
     }
@@ -644,14 +644,14 @@ impl App {
644644
         }
645645
 
646646
         // Check process list
647
-        if self.process_list.on_click(pos).is_some() {
647
+        if self.process_list.on_click(pos, &self.processes).is_some() {
648648
             return true;
649649
         }
650650
 
651651
         false
652652
     }
653653
 
654
-    /// Sort cached processes by the given field and update process list.
654
+    /// Sort processes in place by the given field (no cloning).
655655
     fn sort_processes(&mut self, sort_field: SortField) {
656656
         match sort_field {
657657
             SortField::Cpu => self.processes.sort_by(|a, b| {
@@ -685,7 +685,6 @@ impl App {
685685
             SortField::Pid => self.processes.sort_by_key(|p| p.pid),
686686
             SortField::Name => self.processes.sort_by(|a, b| a.name.cmp(&b.name)),
687687
         }
688
-        self.process_list.set_processes(self.processes.clone());
689688
         self.process_list.set_sort(sort_field);
690689
     }
691690
 
gartop/src/gui/process_list.rsmodified
@@ -30,14 +30,14 @@ const ROW_HEIGHT: u32 = 22;
3030
 /// Header row height.
3131
 const HEADER_HEIGHT: u32 = 24;
3232
 
33
-/// Process list component.
33
+/// Process list component - renders process data without owning it.
3434
 pub struct ProcessList {
3535
     bounds: Rect,
36
-    processes: Vec<ProcessInfo>,
3736
     scroll_offset: usize,
3837
     selected: Option<usize>,
3938
     sort_field: SortField,
4039
     visible_rows: usize,
40
+    process_count: usize,
4141
 }
4242
 
4343
 impl ProcessList {
@@ -46,11 +46,11 @@ impl ProcessList {
4646
         let visible_rows = ((bounds.height.saturating_sub(HEADER_HEIGHT)) / ROW_HEIGHT) as usize;
4747
         Self {
4848
             bounds,
49
-            processes: Vec::new(),
5049
             scroll_offset: 0,
5150
             selected: None,
5251
             sort_field: SortField::Cpu,
5352
             visible_rows,
53
+            process_count: 0,
5454
         }
5555
     }
5656
 
@@ -60,9 +60,9 @@ impl ProcessList {
6060
         self.visible_rows = ((bounds.height.saturating_sub(HEADER_HEIGHT)) / ROW_HEIGHT) as usize;
6161
     }
6262
 
63
-    /// Set processes to display.
64
-    pub fn set_processes(&mut self, processes: Vec<ProcessInfo>) {
65
-        self.processes = processes;
63
+    /// Update process count (for scroll calculations).
64
+    pub fn set_process_count(&mut self, count: usize) {
65
+        self.process_count = count;
6666
         // Clamp scroll offset
6767
         if self.scroll_offset > self.max_scroll() {
6868
             self.scroll_offset = self.max_scroll();
@@ -81,7 +81,7 @@ impl ProcessList {
8181
 
8282
     /// Maximum scroll offset.
8383
     fn max_scroll(&self) -> usize {
84
-        self.processes.len().saturating_sub(self.visible_rows)
84
+        self.process_count.saturating_sub(self.visible_rows)
8585
     }
8686
 
8787
     /// Handle scroll (delta is number of rows to scroll, positive = down).
@@ -94,7 +94,7 @@ impl ProcessList {
9494
     }
9595
 
9696
     /// Handle click, returns selected process PID if any.
97
-    pub fn on_click(&mut self, pos: Point) -> Option<i32> {
97
+    pub fn on_click(&mut self, pos: Point, processes: &[ProcessInfo]) -> Option<i32> {
9898
         if !self.bounds.contains_point(pos) {
9999
             return None;
100100
         }
@@ -110,17 +110,17 @@ impl ProcessList {
110110
         let row = ((local_y - HEADER_HEIGHT as i32) / ROW_HEIGHT as i32) as usize;
111111
         let process_idx = self.scroll_offset + row;
112112
 
113
-        if process_idx < self.processes.len() {
113
+        if process_idx < processes.len() {
114114
             self.selected = Some(process_idx);
115
-            return Some(self.processes[process_idx].pid);
115
+            return Some(processes[process_idx].pid);
116116
         }
117117
 
118118
         None
119119
     }
120120
 
121121
     /// Get selected process PID.
122
-    pub fn selected_pid(&self) -> Option<i32> {
123
-        self.selected.and_then(|idx| self.processes.get(idx).map(|p| p.pid))
122
+    pub fn selected_pid(&self, processes: &[ProcessInfo]) -> Option<i32> {
123
+        self.selected.and_then(|idx| processes.get(idx).map(|p| p.pid))
124124
     }
125125
 
126126
     /// Clear selection.
@@ -129,7 +129,7 @@ impl ProcessList {
129129
     }
130130
 
131131
     /// Render the process list.
132
-    pub fn render(&self, renderer: &Renderer, theme: &Theme) -> anyhow::Result<()> {
132
+    pub fn render(&self, renderer: &Renderer, theme: &Theme, processes: &[ProcessInfo]) -> anyhow::Result<()> {
133133
         // Background
134134
         renderer.fill_rect(self.bounds, theme.panel_bg)?;
135135
 
@@ -152,13 +152,18 @@ impl ProcessList {
152152
             ..text_style.clone()
153153
         };
154154
 
155
-        // Column positions (PID can be 7 digits, ~56px at 11px monospace)
155
+        // Column positions - adjusted for better spacing
156156
         let x = self.bounds.x as f64;
157157
         let col_pid = x + 8.0;
158158
         let col_name = x + 80.0;
159
-        let col_cpu = x + 230.0;
160
-        let col_mem = x + 300.0;
161
-        let col_user = x + 390.0;
159
+        let col_cpu = x + 240.0;   // CPU%/Read/Sock
160
+        let col_mem = x + 310.0;   // Mem%/Write/Listen
161
+        let col_extra = x + 380.0; // Estab (network only)
162
+        let col_user = x + 450.0;  // User
163
+
164
+        // Show different columns based on sort field
165
+        let is_disk_sort = matches!(self.sort_field, SortField::DiskRead | SortField::DiskWrite | SortField::DiskTotal);
166
+        let is_net_sort = matches!(self.sort_field, SortField::NetConnections | SortField::NetTcp | SortField::NetBandwidth);
162167
 
163168
         // Header - position text near top (Pango uses top-left positioning)
164169
         let header_y = self.bounds.y as f64 + 4.0;
@@ -177,13 +182,6 @@ impl ProcessList {
177182
             ..header_style.clone()
178183
         };
179184
 
180
-        // Show different columns based on sort field
181
-        let is_disk_sort = matches!(self.sort_field, SortField::DiskRead | SortField::DiskWrite | SortField::DiskTotal);
182
-        let is_net_sort = matches!(self.sort_field, SortField::NetConnections | SortField::NetTcp | SortField::NetBandwidth);
183
-
184
-        // Extra column position for network mode
185
-        let col_extra = x + 350.0;
186
-
187185
         if is_disk_sort {
188186
             renderer.text("Read/s", col_cpu, header_y, &sort_style)?;
189187
             renderer.text("Write/s", col_mem, header_y, &sort_style)?;
@@ -216,7 +214,7 @@ impl ProcessList {
216214
 
217215
         // Process rows
218216
         let start_y = self.bounds.y + HEADER_HEIGHT as i32;
219
-        for (i, process) in self.processes.iter()
217
+        for (i, process) in processes.iter()
220218
             .skip(self.scroll_offset)
221219
             .take(self.visible_rows)
222220
             .enumerate()
@@ -316,12 +314,12 @@ impl ProcessList {
316314
         }
317315
 
318316
         // Scroll indicator if needed
319
-        if self.processes.len() > self.visible_rows {
317
+        if processes.len() > self.visible_rows {
320318
             let scroll_info = format!(
321319
                 "{}-{} of {}",
322320
                 self.scroll_offset + 1,
323
-                (self.scroll_offset + self.visible_rows).min(self.processes.len()),
324
-                self.processes.len()
321
+                (self.scroll_offset + self.visible_rows).min(processes.len()),
322
+                processes.len()
325323
             );
326324
             let info_style = TextStyle {
327325
                 font_size: 9.0,