breakwater_egui_overlay/
lib.rs1pub use eframe;
2pub use egui;
3use egui::Margin;
4
5use crate::colors::{COLOR_BACKGROUND, COLOR_HIGHLIGHT, COLOR_PRIMARY};
6
7const RUSTC_VERSION: *const std::ffi::c_char = concat!(
13 include_str!(concat!(env!("OUT_DIR"), "/RUSTC_VERSION.txt")),
14 "\0"
15)
16.as_ptr()
17.cast();
18
19const BREAKWATER_VERSION: *const std::ffi::c_char =
20 concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr().cast();
21
22#[repr(C)]
23#[derive(Clone)]
24pub struct Versions {
25 pub rustc: *const std::ffi::c_char,
26 pub breakwater: *const std::ffi::c_char,
27}
28
29impl std::fmt::Debug for Versions {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 use std::ffi::CStr;
32
33 #[derive(Debug)]
34 #[allow(dead_code)]
35 struct Versions<'a> {
36 rustc: &'a CStr,
37 breakwater: &'a CStr,
38 }
39
40 unsafe {
41 Versions {
42 rustc: CStr::from_ptr(self.rustc),
43 breakwater: CStr::from_ptr(self.breakwater),
44 }
45 .fmt(f)
46 }
47 }
48}
49
50pub const VERSIONS: Versions = Versions {
51 rustc: RUSTC_VERSION,
52 breakwater: BREAKWATER_VERSION,
53};
54
55impl PartialEq for Versions {
56 fn eq(&self, other: &Self) -> bool {
57 use std::ffi::CStr;
58
59 unsafe {
60 CStr::from_ptr(self.rustc).eq(CStr::from_ptr(other.rustc))
61 && CStr::from_ptr(self.breakwater).eq(CStr::from_ptr(other.breakwater))
62 }
63 }
64}
65
66pub type New = extern "C" fn(*mut std::ffi::c_void);
67pub type Drop = extern "C" fn(*mut std::ffi::c_void);
68#[allow(improper_ctypes_definitions)] pub type DrawUi = extern "C" fn(
70 data: *mut std::ffi::c_void,
71 viewport_idx: u32,
72 ctx: &egui::Context,
73 advertised_endpoints: &[String],
74 connections: u32,
75 ips_v6: u32,
76 ips_v4: u32,
77 bytes_per_s: u64,
78);
79
80#[repr(C)]
81pub struct DynamicOverlay {
82 pub data: *mut std::ffi::c_void,
83 pub new: *const New,
84 pub draw_ui: DrawUi,
85 pub drop: *const Drop,
86}
87
88#[allow(dead_code)]
90mod colors {
91 use egui::Color32;
92
93 pub const COLOR_PRIMARY: Color32 = Color32::from_rgb(0xFF, 0x50, 0x53);
94 pub const COLOR_HIGHLIGHT: Color32 = Color32::from_rgb(0xFE, 0xF2, 0xFF);
95 pub const COLOR_ACCENT_A: Color32 = Color32::from_rgb(0xB2, 0xAA, 0xFF);
96 pub const COLOR_ACCENT_B: Color32 = Color32::from_rgb(0x6A, 0x5F, 0xDB);
97 pub const COLOR_ACCENT_C: Color32 = Color32::from_rgb(0x29, 0x11, 0x4C);
98 pub const COLOR_ACCENT_D: Color32 = Color32::from_rgb(0x26, 0x1A, 0x66);
99 pub const COLOR_ACCENT_E: Color32 = Color32::from_rgb(0x19, 0x0B, 0x2F);
100 pub const COLOR_BACKGROUND: Color32 = Color32::from_rgb(0x0F, 0x00, 0x0A);
101}
102
103pub const DEFAULT_OVERLAY: DynamicOverlay = DynamicOverlay {
104 data: std::ptr::null_mut(),
105 new: std::ptr::null(),
106 draw_ui: draw_ui as _,
107 drop: std::ptr::null(),
108};
109
110#[allow(improper_ctypes_definitions)]
111extern "C" fn draw_ui(
112 _: *mut std::ffi::c_void,
113 viewport_idx: u32,
114 ctx: &egui::Context,
115 advertised_endpoints: &[String],
116 connections: u32,
117 ips_v6: u32,
118 ips_v4: u32,
119 bytes_per_s: u64,
120) {
121 if viewport_idx > 0 {
123 return;
124 }
125
126 let stats_frame = egui::Frame {
127 fill: COLOR_BACKGROUND.gamma_multiply(0.7),
128 stroke: egui::Stroke::new(1.0_f32, COLOR_PRIMARY),
129 corner_radius: egui::CornerRadius::same(10),
130 shadow: eframe::epaint::Shadow::default(),
131 inner_margin: Margin::same(12),
132 outer_margin: Margin::same(12),
133 };
134
135 egui::Area::new(egui::Id::new("overlay_area"))
136 .movable(true)
137 .default_pos(egui::pos2(20.0, 20.0))
138 .show(ctx, |ui| {
139 stats_frame.show(ui, |ui| {
140 ui.label(
141 egui::RichText::new("breakwater | Pixelflut")
142 .size(48.0)
143 .color(COLOR_HIGHLIGHT),
144 );
145 ui.separator();
146
147 egui::Grid::new(egui::Id::new("stats_header_grid")).show(ui, |ui| {
148 for ep in advertised_endpoints {
149 ui.label(
150 egui::RichText::new(ep)
151 .color(COLOR_HIGHLIGHT)
152 .size(32.0)
153 .strong(),
154 );
155 ui.end_row();
156 }
157 });
158
159 ui.separator();
160 egui::Grid::new(egui::Id::new("stats_metrics_grid")).show(ui, |ui| {
161 ui.label(
162 egui::RichText::new("Connections: ")
163 .color(COLOR_HIGHLIGHT)
164 .size(24.0),
165 );
166 ui.label(
167 egui::RichText::new(format!("{connections}"))
168 .color(COLOR_HIGHLIGHT)
169 .size(24.0)
170 .strong(),
171 );
172 ui.label(
173 egui::RichText::new("IPv6: ")
174 .color(COLOR_HIGHLIGHT)
175 .size(24.0),
176 );
177 ui.label(
178 egui::RichText::new(format!("{ips_v6}"))
179 .color(COLOR_HIGHLIGHT)
180 .size(24.0)
181 .strong(),
182 );
183 ui.end_row();
184 ui.label(
185 egui::RichText::new("RX: ")
186 .color(COLOR_HIGHLIGHT)
187 .size(24.0),
188 );
189 ui.label(
190 egui::RichText::new(format!(
191 "{:.2} GBit/s ",
192 (bytes_per_s * 8) as f32 / 1024.0 / 1024.0 / 1024.0
193 ))
194 .color(COLOR_HIGHLIGHT)
195 .size(24.0)
196 .strong(),
197 );
198 ui.label(
199 egui::RichText::new("IPv4: ")
200 .color(COLOR_HIGHLIGHT)
201 .size(24.0),
202 );
203 ui.label(
204 egui::RichText::new(format!("{ips_v4}"))
205 .color(COLOR_HIGHLIGHT)
206 .size(24.0)
207 .strong(),
208 );
209 ui.end_row();
210 });
211 });
212 });
213}