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 }