diff --git a/src/app.rs b/src/app.rs --- a/src/app.rs +++ b/src/app.rs @@ -4,9 +4,8 @@ use eframe::egui::{self, Align, Layout, RichText}; use vidya::{ - apply, body, compact_card, data_table, dim_label, lead_trail, metric_bps, metric_cell_px, - metric_rate, pack, page_body, status_dot, table_metric, table_text, title, title_2, Col, - ColKind, Mode, Theme, METRIC_BPS_CHARS, METRIC_RATE_CHARS, + apply, body, compact_card, dim_label, grid_cols, lead_trail, pack, page_body, status_dot, + title, title_2, ColSpec, Mode, Theme, }; use crate::gauge::{arc_gauge, heat_color, mini_bar, PeakScale}; @@ -329,75 +328,65 @@ active.into_iter().take(40).collect() }; - let write_w = metric_cell_px(th, METRIC_BPS_CHARS); - let freq_w = metric_cell_px(th, METRIC_RATE_CHARS); - let cols = [ - Col { - header: "Name", - kind: ColKind::Flex, + // Grid DSL: columns + rows (no hand-rolled egui::Grid). + grid_cols( + ui, + th, + "process_io_table", + &[ + ColSpec::Flex, + ColSpec::Flex, + ColSpec::MetricBps, + ColSpec::MetricRate, + ], + |g| { + g.row(|r| { + r.heading("Name"); + r.heading("Path"); + r.heading("Write"); + r.heading("Write freq"); + }); + for p in &rows { + g.row(|r| { + r.text(&truncate_chars(&p.name, 28)); + let path_show = if p.path.is_empty() { + "—".to_string() + } else { + truncate_middle(&p.path, 64) + }; + r.dim(&path_show); + r.metric_bps(p.write_bps); + r.metric_rate(p.write_freq); + }); + } }, - Col { - header: "Path", - kind: ColKind::Flex, - }, - Col { - header: "Write", - kind: ColKind::Metric { width: write_w }, - }, - Col { - header: "Write freq", - kind: ColKind::Metric { width: freq_w }, - }, - ]; - let n = rows.len(); - data_table(ui, th, "process_io_table", &cols, |ui, i| { - let p = rows[i]; - table_text(ui, th, &truncate_chars(&p.name, 28), true); - let path_show = if p.path.is_empty() { - "—".to_string() - } else { - truncate_middle(&p.path, 64) - }; - table_text(ui, th, &path_show, false); - table_metric(ui, th, write_w, &metric_bps(p.write_bps), false); - table_metric(ui, th, freq_w, &metric_rate(p.write_freq), true); - }, n); + ); }); } } -/// Abnormal I/O list via vidya [`data_table`] (fixed metric column, no stretch). +/// Abnormal I/O list via vidya grid DSL. fn anomaly_table(ui: &mut egui::Ui, th: &Theme, hits: &[AnomalyHit], id: &str) { - let usage_w = metric_cell_px(th, METRIC_BPS_CHARS); - let cols = [ - Col { - header: "Process", - kind: ColKind::Flex, + grid_cols( + ui, + th, + id, + &[ColSpec::Flex, ColSpec::Flex, ColSpec::MetricBps], + |g| { + g.row(|r| { + r.heading("Process"); + r.heading("Time"); + r.heading("Usage"); + }); + for hit in hits.iter().rev() { + g.row(|r| { + r.warn(&truncate_chars(&hit.proc_name, 28)); + r.dim(&hit.stamp); + r.metric_bps(hit.rate_bps); + }); + } }, - Col { - header: "Time", - kind: ColKind::Flex, - }, - Col { - header: "Usage", - kind: ColKind::Metric { width: usage_w }, - }, - ]; - // Newest first for display. - let view: Vec<&AnomalyHit> = hits.iter().rev().collect(); - let n = view.len(); - data_table(ui, th, id, &cols, |ui, i| { - let hit = view[i]; - // Warning-colored process name. - ui.label( - RichText::new(truncate_chars(&hit.proc_name, 28)) - .size(th.type_scale.caption) - .strong() - .color(th.palette.warning), - ); - table_text(ui, th, &hit.stamp, false); - table_metric(ui, th, usage_w, &metric_bps(hit.rate_bps), true); - }, n); + ); } fn truncate_chars(s: &str, max_chars: usize) -> String { @@ -518,7 +507,7 @@ }); } -/// Consumer uses Vidya layout DSL for process/anomaly tables. +/// Consumer uses Vidya grid layout DSL for process/anomaly tables. #[cfg(test)] mod layout_dsl_consumer { fn app_src() -> &'static str { @@ -526,43 +515,42 @@ } #[test] - fn process_table_uses_vidya_data_table_and_metrics() { + fn process_table_uses_vidya_grid_cols() { let src = app_src(); let start = src.find("fn proc_table(").expect("proc_table"); let body = &src[start..]; let end = body.find("\nfn anomaly_table(").unwrap_or(body.len()); let body = &body[..end]; - assert!(body.contains("data_table"), "must use vidya::data_table"); + assert!(body.contains("grid_cols"), "must use vidya::grid_cols"); + assert!(body.contains("ColSpec::MetricBps") && body.contains("ColSpec::MetricRate")); assert!(body.contains("metric_bps") && body.contains("metric_rate")); - assert!(body.contains("table_metric") && body.contains("table_text")); assert!(!body.contains("max_write") && !body.contains("bar_w")); assert!(body.contains("\"Name\"") && body.contains("\"Write\"")); } #[test] - fn anomaly_section_uses_compact_card_and_pack() { + fn anomaly_section_uses_compact_card_pack_and_grid() { let src = app_src(); - let start = src.find("fn anomaly_section(").expect("anomaly_section"); + assert!(src.contains("compact_card") && src.contains("pack")); + let start = src.find("fn anomaly_table(").expect("anomaly_table"); let body = &src[start..]; - let end = body.find("\n fn breakdown(").unwrap_or(body.len()); + let end = body.find("\nfn truncate_chars(").unwrap_or(body.len()); let body = &body[..end]; - assert!(body.contains("compact_card")); - assert!(body.contains("pack")); - assert!(!body.contains("set_width(CARD_W)") && !body.contains("card_frame().show")); + assert!(body.contains("grid_cols")); + assert!(body.contains("r.warn") || body.contains(".warn(")); } #[test] - fn shipped_vidya_metric_widths_are_stable() { - // Drive real Vidya formatters (path dep), not local copies. - for bps in [0.0_f64, 1024.0, 2.0 * 1024.0 * 1024.0] { - let s = vidya::metric_bps(bps); - assert_eq!(s.chars().count(), vidya::METRIC_BPS_CHARS); - } - for r in [0.0_f64, 134.0, 2600.0] { - let s = vidya::metric_rate(r); - assert_eq!(s.chars().count(), vidya::METRIC_RATE_CHARS); - } + fn shipped_vidya_grid_policy() { assert!(vidya::side_by_side(800.0, 160.0, 12.0)); assert!(!vidya::side_by_side(200.0, 160.0, 12.0)); + let th = vidya::Theme::dark(); + assert!(vidya::ColSpec::MetricBps.px(&th).unwrap() > 40.0); + for bps in [0.0_f64, 1024.0, 2.0 * 1024.0 * 1024.0] { + assert_eq!( + vidya::metric_bps(bps).chars().count(), + vidya::METRIC_BPS_CHARS + ); + } } }