notedeck

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

channels.rs (4000B)


      1 use crate::event::LoadingState;
      2 use crate::ui;
      3 use egui::Color32;
      4 use serde::{Deserialize, Serialize};
      5 
      6 #[derive(Deserialize, Serialize)]
      7 pub struct ListPeerChannel {
      8     pub short_channel_id: String,
      9     pub our_reserve_msat: i64,
     10     pub to_us_msat: i64,
     11     pub total_msat: i64,
     12     pub their_reserve_msat: i64,
     13 }
     14 
     15 pub struct Channel {
     16     pub to_us: i64,
     17     pub to_them: i64,
     18     pub original: ListPeerChannel,
     19 }
     20 
     21 pub struct Channels {
     22     pub max_total_msat: i64,
     23     pub avail_in: i64,
     24     pub avail_out: i64,
     25     pub channels: Vec<Channel>,
     26 }
     27 
     28 pub fn channels_ui(ui: &mut egui::Ui, channels: &LoadingState<Channels, lnsocket::Error>) {
     29     match channels {
     30         LoadingState::Loaded(channels) => {
     31             if channels.channels.is_empty() {
     32                 ui.label("no channels yet...");
     33                 return;
     34             }
     35 
     36             for channel in &channels.channels {
     37                 channel_ui(ui, channel, channels.max_total_msat);
     38             }
     39 
     40             ui.label(format!(
     41                 "available out {}",
     42                 ui::human_sat(channels.avail_out)
     43             ));
     44             ui.label(format!("available in {}", ui::human_sat(channels.avail_in)));
     45         }
     46         LoadingState::Failed(err) => {
     47             ui.label(format!("error fetching channels: {err}"));
     48         }
     49         LoadingState::Loading => {
     50             ui.label("fetching channels...");
     51         }
     52     }
     53 }
     54 
     55 pub fn channel_ui(ui: &mut egui::Ui, c: &Channel, max_total_msat: i64) {
     56     // ---------- numbers ----------
     57     let short_channel_id = &c.original.short_channel_id;
     58 
     59     let cap_ratio = (c.original.total_msat as f32 / max_total_msat.max(1) as f32).clamp(0.0, 1.0);
     60     // Feel free to switch to log scaling if you have whales:
     61     //let cap_ratio = ((c.original.total_msat as f32 + 1.0).log10() / (max_total_msat as f32 + 1.0).log10()).clamp(0.0, 1.0);
     62 
     63     // ---------- colors & style ----------
     64     let out_color = Color32::from_rgb(84, 69, 201); // blue
     65     let in_color = Color32::from_rgb(158, 56, 180); // purple
     66 
     67     // Thickness scales with capacity, but keeps a nice minimum
     68     let thickness = 10.0 + cap_ratio * 22.0; // 10 → 32 px
     69     let row_h = thickness + 14.0;
     70 
     71     // ---------- layout ----------
     72     let (rect, response) = ui.allocate_exact_size(
     73         egui::vec2(ui.available_width(), row_h),
     74         egui::Sense::hover(),
     75     );
     76     let painter = ui.painter_at(rect);
     77 
     78     let bar_rect = egui::Rect::from_min_max(
     79         egui::pos2(rect.left(), rect.center().y - thickness * 0.5),
     80         egui::pos2(rect.right(), rect.center().y + thickness * 0.5),
     81     );
     82     let corner_radius = (thickness * 0.5) as u8;
     83     let out_radius = egui::CornerRadius {
     84         ne: 0,
     85         nw: corner_radius,
     86         sw: corner_radius,
     87         se: 0,
     88     };
     89     let in_radius = egui::CornerRadius {
     90         ne: corner_radius,
     91         nw: 0,
     92         sw: 0,
     93         se: corner_radius,
     94     };
     95     /*
     96     painter.rect_filled(bar_rect, rounding, track_color);
     97     painter.rect_stroke(bar_rect, rounding, track_stroke, egui::StrokeKind::Middle);
     98     */
     99 
    100     // Split widths
    101     let usable = (c.to_us + c.to_them).max(1) as f32;
    102     let out_w = (bar_rect.width() * (c.to_us as f32 / usable)).round();
    103     let split_x = bar_rect.left() + out_w;
    104 
    105     // Outbound fill (left)
    106     let out_rect = egui::Rect::from_min_max(bar_rect.min, egui::pos2(split_x, bar_rect.max.y));
    107     if out_rect.width() > 0.5 {
    108         painter.rect_filled(out_rect, out_radius, out_color);
    109     }
    110 
    111     // Inbound fill (right)
    112     let in_rect = egui::Rect::from_min_max(egui::pos2(split_x, bar_rect.min.y), bar_rect.max);
    113     if in_rect.width() > 0.5 {
    114         painter.rect_filled(in_rect, in_radius, in_color);
    115     }
    116 
    117     // Tooltip
    118     response.on_hover_text_at_pointer(format!(
    119         "Channel ID {short_channel_id}\nOutbound (ours): {} sats\nInbound (theirs): {} sats\nCapacity: {} sats",
    120         ui::human_sat(c.to_us),
    121         ui::human_sat(c.to_them),
    122         ui::human_sat(c.original.total_msat),
    123     ));
    124 }