use egui::{Id, Rect, Response, Sense, Ui, WidgetInfo, WidgetType};
#[derive(Clone, Debug, PartialEq)]
pub struct Semantics {
pub typ: WidgetType,
pub label: String,
pub value: Option<f64>,
pub selected: Option<bool>,
pub enabled: bool,
pub severity: crate::Severity,
pub code: Option<&'static str>,
}
impl Semantics {
pub fn new(typ: WidgetType, label: impl Into<String>) -> Self {
Self {
typ,
label: label.into(),
value: None,
selected: None,
enabled: true,
severity: crate::Severity::Info,
code: None,
}
}
pub fn error_code(mut self, code: &'static str) -> Self {
self.code = Some(code);
self
}
pub fn button(label: impl Into<String>) -> Self {
Self::new(WidgetType::Button, label)
}
pub fn image(label: impl Into<String>) -> Self {
Self::new(WidgetType::Image, label)
}
pub fn list_item(label: impl Into<String>, selected: bool) -> Self {
Self { selected: Some(selected), ..Self::new(WidgetType::SelectableLabel, label) }
}
pub fn value(mut self, v: f64) -> Self {
self.value = Some(v);
self
}
pub fn selected(mut self, sel: bool) -> Self {
self.selected = Some(sel);
self
}
pub fn enabled(mut self, en: bool) -> Self {
self.enabled = en;
self
}
pub fn severity(mut self, sev: crate::Severity) -> Self {
self.severity = sev;
self
}
pub fn error(label: impl Into<String>) -> Self {
Self::new(WidgetType::Label, label).severity(crate::Severity::Error)
}
pub fn warning(label: impl Into<String>) -> Self {
Self::new(WidgetType::Label, label).severity(crate::Severity::Warning)
}
pub fn widget_info(&self) -> WidgetInfo {
let mut info = match self.selected {
Some(sel) => WidgetInfo::selected(self.typ, self.enabled, sel, self.label.clone()),
None => WidgetInfo::labeled(self.typ, self.enabled, self.label.clone()),
};
info.value = self.value;
info
}
}
pub fn stable_id(base: Id, key: impl std::hash::Hash + std::fmt::Debug) -> Id {
base.with(key)
}
pub fn node(
ui: &mut Ui,
base: Id,
key: impl std::hash::Hash + std::fmt::Debug,
sense: Sense,
rect: Rect,
sem: Semantics,
) -> Response {
let id = stable_id(base, key);
let response = ui.interact(rect, id, sense);
response.widget_info(|| sem.widget_info());
response
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn semantics_button_carries_label_and_value() {
let s = Semantics::button("worker · running").value(42.0);
let info = s.widget_info();
assert_eq!(info.typ, WidgetType::Button);
assert_eq!(info.label.as_deref(), Some("worker · running"));
assert_eq!(info.value, Some(42.0));
assert_eq!(info.selected, None);
}
#[test]
fn semantics_list_item_carries_selection() {
let s = Semantics::list_item("file.txt", true);
let info = s.widget_info();
assert_eq!(info.typ, WidgetType::SelectableLabel);
assert_eq!(info.selected, Some(true));
assert_eq!(info.label.as_deref(), Some("file.txt"));
}
#[test]
fn semantics_carry_structural_severity() {
use crate::Severity;
assert_eq!(Semantics::button("ok").severity, Severity::Info);
assert_eq!(Semantics::error("scan failed").severity, Severity::Error);
assert_eq!(Semantics::warning("partial load").severity, Severity::Warning);
assert_eq!(
Semantics::image("map").severity(Severity::Error).severity,
Severity::Error
);
let atoms = [
Semantics::button("run"),
Semantics::error("open warehouse failed"),
Semantics::list_item("row", false),
];
assert_eq!(
crate::worst_severity(atoms.iter().map(|s| s.severity)),
Severity::Error,
"one error atom makes the pane red"
);
}
#[test]
fn stable_id_is_key_derived_not_index() {
let base = Id::new("comp");
assert_eq!(stable_id(base, "node-a"), stable_id(base, "node-a"));
assert_ne!(stable_id(base, "node-a"), stable_id(base, "node-b"));
}
}