Compare commits

..
Author SHA1 Message Date
Ultradesu 4e235cbac6 Keep device data paths on one line
The tray used a fixed sample for the data-path column, so longer relay names wrapped and made device rows uneven. Measure each rendered path and retain the widest value, then render path labels without wrapping. Replace direct/via words with embedded Phosphor route icons while preserving transport, relay name, hop count and explanatory tooltips. The app already ships the font; keep its setup in one function and verify registration alongside a long-relay-width regression test. No wire or stored-state changes. Checks: cargo fmt --all -- --check; cargo clippy --locked --workspace --all-targets -- -D warnings; cargo test --locked --workspace --all-targets.
2026-10-14 13:50:05 +01:00
2 changed files with 105 additions and 11 deletions

No files matched your search

+24 -3
View File
@@ -15,6 +15,12 @@ use crate::devices;
use crate::stats::Traffic;
use crate::ui::{self, UiState};
fn app_fonts() -> egui::FontDefinitions {
let mut fonts = egui::FontDefinitions::default();
egui_phosphor::add_to_fonts(&mut fonts, egui_phosphor::Variant::Regular);
fonts
}
/// The window's state.
pub(crate) struct App {
agent: AgentClient,
@@ -34,9 +40,7 @@ impl App {
let ctx = cc.egui_ctx.clone();
// Add the Phosphor icon glyphs to the font set so the icon buttons
// render instead of showing as tofu.
let mut fonts = egui::FontDefinitions::default();
egui_phosphor::add_to_fonts(&mut fonts, egui_phosphor::Variant::Regular);
ctx.set_fonts(fonts);
ctx.set_fonts(app_fonts());
let agent = AgentClient::spawn(&runtime, ctx);
Ok(Self {
@@ -143,3 +147,20 @@ impl App {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn route_icons_have_an_embedded_font_in_the_app() {
let fonts = app_fonts();
assert!(fonts.font_data.contains_key("phosphor"));
assert!(
fonts
.families
.get(&egui::FontFamily::Proportional)
.is_some_and(|family| family.iter().any(|name| name == "phosphor"))
);
}
}
+81 -8
View File
@@ -8,6 +8,7 @@
use std::collections::HashMap;
use eframe::egui;
use egui_phosphor::regular as icon;
use tsunagi::ipc::{DataPathReport, NetworkReport, OverlayPeerReport};
@@ -469,7 +470,7 @@ const COLUMN_SAMPLES: [&str; 10] = [
"end-to-end",
"999d / 9999 ms",
"255.255.255.255",
"direct over tcp-tls",
"data path",
"99999/99999",
"1023.9 MB / 1023.9 MB",
"1023.9 kB/s",
@@ -602,6 +603,14 @@ fn device_table(
.x
});
widths[0] = widths[0].max(natural + 24.0);
let (path, _) = data_path_text(device.path, device.via_name.as_deref(), device.online);
let path_width = ui.fonts(|fonts| {
fonts
.layout_no_wrap(path, font.clone(), egui::Color32::WHITE)
.size()
.x
});
widths[5] = widths[5].max(path_width);
}
ui.memory_mut(|m| m.data.insert_temp(widths_id, widths));
let widths = stretch_columns(widths, ui.available_width());
@@ -812,7 +821,8 @@ fn tunnel_cell(ui: &mut egui::Ui, device: &Device<'_>) {
ui.label(protocol).on_hover_text(tunnel_hint(protocol));
}
/// Current route text and whether that route is the preferred direct form.
/// Compact route label and whether that route is the preferred direct form.
/// The icon font is embedded by egui-phosphor and installed in `App::new`.
fn data_path_text(
path: Option<&DataPathReport>,
via_name: Option<&str>,
@@ -822,9 +832,13 @@ fn data_path_text(
return ("offline".into(), false);
}
match path {
Some(DataPathReport::Direct { transport }) => (format!("direct over {transport}"), true),
Some(DataPathReport::Direct { transport }) => (format!("{} {transport}", icon::LINK), true),
Some(DataPathReport::Relayed { hops, .. }) => (
format!("via {} · {hops} hops", via_name.unwrap_or("another device")),
format!(
"{} {} · {hops} hops",
icon::SHARE_NETWORK,
via_name.unwrap_or("another device")
),
false,
),
Some(DataPathReport::None) | None => ("no data path yet".into(), false),
@@ -838,9 +852,12 @@ fn data_path_text(
fn data_path_cell(ui: &mut egui::Ui, device: &Device<'_>) {
let (text, good) = data_path_text(device.path, device.via_name.as_deref(), device.online);
let response = if good {
ui.label(text)
ui.add(egui::Label::new(text).wrap_mode(egui::TextWrapMode::Extend))
} else {
ui.label(egui::RichText::new(text).color(AMBER))
ui.add(
egui::Label::new(egui::RichText::new(text).color(AMBER))
.wrap_mode(egui::TextWrapMode::Extend),
)
};
response.on_hover_text(match device.path {
Some(DataPathReport::Direct { .. }) => "Direct transport; no mesh relay.",
@@ -940,7 +957,7 @@ mod tests {
None,
true,
),
("direct over tcp-tls".into(), true)
(format!("{} tcp-tls", icon::LINK), true)
);
assert_eq!(
tunnel_hint("tcp-tls"),
@@ -960,7 +977,7 @@ mod tests {
Some("iris"),
true,
),
("via iris · 2 hops".into(), false)
(format!("{} iris · 2 hops", icon::SHARE_NETWORK), false)
);
assert_eq!(
data_path_text(Some(&DataPathReport::None), None, true),
@@ -1017,6 +1034,62 @@ mod tests {
);
}
#[test]
fn a_long_relay_name_gets_its_full_column_width() {
let mut relay = member("near", true, DataPathReport::None);
relay.hostname = Some("a-relay-with-a-long-device-name".into());
let network = NetworkReport {
network_id: "network".into(),
members: vec![
relay,
member(
"far",
true,
DataPathReport::Relayed {
hops: 2,
via: "near".into(),
},
),
],
..Default::default()
};
let rows = devices(&network, "own");
let far = rows.iter().find(|device| device.id == "far").unwrap();
let (label, _) = data_path_text(far.path, far.via_name.as_deref(), far.online);
let context = egui::Context::default();
let mut fonts = egui::FontDefinitions::default();
egui_phosphor::add_to_fonts(&mut fonts, egui_phosphor::Variant::Regular);
context.set_fonts(fonts);
let _ = context.run(egui::RawInput::default(), |ctx| {
egui::CentralPanel::default().show(ctx, |ui| {
device_table(
ui,
Unit::Bytes,
&network,
&rows,
&Traffic::default(),
&mut None,
&mut None,
);
let stored: [f32; 10] = ui
.memory(|memory| {
memory
.data
.get_temp(egui::Id::new(("devices column widths", "network")))
})
.unwrap();
let font = egui::TextStyle::Body.resolve(ui.style());
let needed = ui.fonts(|fonts| {
fonts
.layout_no_wrap(label.clone(), font, egui::Color32::WHITE)
.size()
.x
});
assert!(stored[5] >= needed);
});
});
}
#[test]
fn rendered_table_uses_window_width_without_an_expanding_device_cell() {
let context = egui::Context::default();