summary.rs (4428B)
1 use crate::channels::Channels; 2 use crate::event::LoadingState; 3 use crate::ui; 4 5 #[derive(Clone, Default)] 6 pub struct Summary { 7 pub total_msat: i64, 8 pub avail_out_msat: i64, 9 pub avail_in_msat: i64, 10 pub channel_count: usize, 11 pub largest_msat: i64, 12 pub outbound_pct: f32, // fraction of total capacity 13 } 14 15 pub fn compute_summary(ch: &Channels) -> Summary { 16 let total_msat: i64 = ch.channels.iter().map(|c| c.original.total_msat).sum(); 17 let largest_msat: i64 = ch 18 .channels 19 .iter() 20 .map(|c| c.original.total_msat) 21 .max() 22 .unwrap_or(0); 23 let outbound_pct = if total_msat > 0 { 24 ch.avail_out as f32 / total_msat as f32 25 } else { 26 0.0 27 }; 28 29 Summary { 30 total_msat, 31 avail_out_msat: ch.avail_out, 32 avail_in_msat: ch.avail_in, 33 channel_count: ch.channels.len(), 34 largest_msat, 35 outbound_pct, 36 } 37 } 38 39 pub fn summary_ui( 40 ui: &mut egui::Ui, 41 last_summary: Option<&Summary>, 42 summary: &LoadingState<Summary, lnsocket::Error>, 43 ) { 44 match summary { 45 LoadingState::Loading => { 46 ui.label("loading summary"); 47 } 48 LoadingState::Failed(err) => { 49 ui.label(format!("Failed to get summary: {err}")); 50 } 51 LoadingState::Loaded(summary) => { 52 summary_cards_ui(ui, summary, last_summary); 53 ui.add_space(8.0); 54 } 55 } 56 } 57 58 pub fn summary_cards_ui(ui: &mut egui::Ui, s: &Summary, prev: Option<&Summary>) { 59 let old = prev.cloned().unwrap_or_default(); 60 let items: [(&str, String, Option<String>); 6] = [ 61 ( 62 "Total capacity", 63 ui::human_sat(s.total_msat), 64 prev.map(|_| ui::delta_str(s.total_msat, old.total_msat)), 65 ), 66 ( 67 "Avail out", 68 ui::human_sat(s.avail_out_msat), 69 prev.map(|_| ui::delta_str(s.avail_out_msat, old.avail_out_msat)), 70 ), 71 ( 72 "Avail in", 73 ui::human_sat(s.avail_in_msat), 74 prev.map(|_| ui::delta_str(s.avail_in_msat, old.avail_in_msat)), 75 ), 76 ("# Channels", s.channel_count.to_string(), None), 77 ("Largest", ui::human_sat(s.largest_msat), None), 78 ( 79 "Outbound %", 80 format!("{:.0}%", s.outbound_pct * 100.0), 81 None, 82 ), 83 ]; 84 85 // --- responsive columns --- 86 let min_card = 160.0; 87 let cols = ((ui.available_width() / min_card).floor() as usize).max(1); 88 89 egui::Grid::new("summary_grid") 90 .num_columns(cols) 91 .min_col_width(min_card) 92 .spacing(egui::vec2(8.0, 8.0)) 93 .show(ui, |ui| { 94 let items_len = items.len(); 95 for (i, (t, v, d)) in items.into_iter().enumerate() { 96 card_cell(ui, t, v, d, min_card); 97 98 // End the row when we filled a row worth of cells 99 if (i + 1) % cols == 0 { 100 ui.end_row(); 101 } 102 } 103 104 // If the last row wasn't full, close it anyway 105 if !items_len.is_multiple_of(cols) { 106 ui.end_row(); 107 } 108 }); 109 } 110 111 fn card_cell(ui: &mut egui::Ui, title: &str, value: String, delta: Option<String>, min_card: f32) { 112 let weak = ui.visuals().weak_text_color(); 113 egui::Frame::group(ui.style()) 114 .fill(ui.visuals().extreme_bg_color) 115 .corner_radius(egui::CornerRadius::same(10)) 116 .inner_margin(egui::Margin::same(10)) 117 .stroke(ui.visuals().widgets.noninteractive.bg_stroke) 118 .show(ui, |ui| { 119 ui.set_min_width(min_card); 120 ui.vertical(|ui| { 121 ui.add( 122 egui::Label::new(egui::RichText::new(title).small().color(weak)) 123 .wrap_mode(egui::TextWrapMode::Wrap), 124 ); 125 ui.add_space(4.0); 126 ui.add( 127 egui::Label::new(egui::RichText::new(value).strong().size(18.0)) 128 .wrap_mode(egui::TextWrapMode::Wrap), 129 ); 130 if let Some(d) = delta { 131 ui.add_space(2.0); 132 ui.add( 133 egui::Label::new(egui::RichText::new(d).small().color(weak)) 134 .wrap_mode(egui::TextWrapMode::Wrap), 135 ); 136 } 137 }); 138 ui.set_min_height(20.0); 139 }); 140 }