diff --git a/src/app.rs b/src/app.rs index 5dbd75b..ff0459d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -202,17 +202,11 @@ impl eframe::App for UsageApp { impl UsageApp { fn gauges_row(&self, ui: &mut egui::Ui, th: &Theme) { - let avail = ui.available_width(); - let cols = if avail >= 720.0 { - 4 - } else if avail >= 400.0 { - 2 - } else { - 1 - }; + // Compact fixed-size cards — don't stretch to fill the window width. + const GAUGE_SIZE: f32 = 148.0; + let pad = th.spacing.md * 2.0; + let card_w = GAUGE_SIZE + pad; let gap = th.spacing.md; - let cell_w = ((avail - gap * (cols as f32 - 1.0)) / cols as f32).max(140.0); - let gauge_size = (cell_w - th.spacing.md * 2.0).clamp(120.0, 180.0); let disk_r = self.snap.disk_total.read_bps; let disk_w = self.snap.disk_total.write_bps; @@ -224,7 +218,6 @@ impl UsageApp { let nr_max = self.net_rx_scale.current(); let nw_max = self.net_tx_scale.current(); - // Gauges only — anomaly tables are full-width below (too narrow in-card). let cards: [(&str, f64, f64, &str); 4] = [ ("Disk read", disk_r, dr_max, "Disk Read"), ("Disk write", disk_w, dw_max, "Disk Write"), @@ -232,8 +225,10 @@ impl UsageApp { ("Network up", net_w, nw_max, "Net Up"), ]; + // Explicit sized slots avoid horizontal_wrapped overlap / stretch bugs. ui.horizontal_wrapped(|ui| { ui.spacing_mut().item_spacing = egui::vec2(gap, gap); + ui.style_mut().spacing.item_spacing = egui::vec2(gap, gap); for (caption, value, max, label) in cards { let frac = if max > 0.0 { (value / max) as f32 @@ -241,51 +236,61 @@ impl UsageApp { 0.0 }; let color = heat_color(th, frac); - th.card_frame().show(ui, |ui| { - ui.with_layout(Layout::top_down(Align::Center), |ui| { - ui.set_width(cell_w - 1.0); - ui.set_min_height(gauge_size + 72.0); + // Reserve a fixed cell so frames cannot paint over neighbors. + let cell = egui::vec2(card_w, GAUGE_SIZE + 64.0); + ui.allocate_ui_with_layout(cell, Layout::top_down(Align::Center), |ui| { + ui.set_min_size(cell); + ui.set_max_size(cell); + th.card_frame().show(ui, |ui| { + ui.set_min_width(card_w - 4.0); + ui.set_max_width(card_w - 4.0); ui.spacing_mut().item_spacing.y = th.spacing.xs.max(2.0); - - dim_label(ui, th, caption); - arc_gauge( - ui, - th, - gauge_size, - frac, - &format_bps(value), - label, - color, - ); - dim_label(ui, th, &format!("scale {}", format_bps(max))); + ui.vertical_centered(|ui| { + dim_label(ui, th, caption); + arc_gauge( + ui, + th, + GAUGE_SIZE, + frac, + &format_bps(value), + label, + color, + ); + dim_label(ui, th, &format!("scale {}", format_bps(max))); + }); }); }); } }); } - /// Full-width Process | Time | Usage tables for abnormal disk I/O. + /// Compact Process | Time | Usage tables for abnormal disk I/O. fn anomaly_section(&self, ui: &mut egui::Ui, th: &Theme) { let read = &self.disk_read_anomalies; let write = &self.disk_write_anomalies; - let both = !read.is_empty() && !write.is_empty(); + // Fixed card width — no need to span the whole window. + const CARD_W: f32 = 340.0; + let gap = th.spacing.md; - if both && ui.available_width() >= 560.0 { - ui.columns(2, |cols| { - anomaly_card(&mut cols[0], th, "Abnormal disk read", read, "anomaly_read"); - anomaly_card(&mut cols[1], th, "Abnormal disk write", write, "anomaly_write"); - }); - } else { + ui.horizontal_wrapped(|ui| { + ui.spacing_mut().item_spacing = egui::vec2(gap, gap); if !read.is_empty() { - anomaly_card(ui, th, "Abnormal disk read", read, "anomaly_read"); + th.card_frame().show(ui, |ui| { + ui.set_width(CARD_W); + title_2(ui, th, "Abnormal disk read"); + ui.add_space(th.spacing.xs.max(2.0)); + anomaly_table(ui, th, read, "anomaly_read"); + }); } if !write.is_empty() { - if !read.is_empty() { - ui.add_space(th.spacing.sm); - } - anomaly_card(ui, th, "Abnormal disk write", write, "anomaly_write"); + th.card_frame().show(ui, |ui| { + ui.set_width(CARD_W); + title_2(ui, th, "Abnormal disk write"); + ui.add_space(th.spacing.xs.max(2.0)); + anomaly_table(ui, th, write, "anomaly_write"); + }); } - } + }); } fn breakdown(&self, ui: &mut egui::Ui, th: &Theme) { @@ -401,20 +406,13 @@ impl UsageApp { } } -fn anomaly_card(ui: &mut egui::Ui, th: &Theme, title: &str, hits: &[AnomalyHit], id: &str) { - th.card_frame().show(ui, |ui| { - title_2(ui, th, title); - ui.add_space(th.spacing.xs.max(2.0)); - anomaly_table(ui, th, hits, id); - }); -} - -/// Process | Time | Usage — single-line cells, fixed time/usage widths. +/// Process | Time | Usage — single-line cells, compact fixed columns. fn anomaly_table(ui: &mut egui::Ui, th: &Theme, hits: &[AnomalyHit], id: &str) { - const TIME_W: f32 = 104.0; - const USAGE_W: f32 = 88.0; + const NAME_W: f32 = 120.0; + const TIME_W: f32 = 100.0; + const USAGE_W: f32 = 80.0; let row_h = th.type_scale.caption + 10.0; - let name_w = (ui.available_width() - TIME_W - USAGE_W - th.spacing.md * 2.0).max(96.0); + let name_w = NAME_W; // Header {