notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

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 }