blockworx/tools/
notices.rs1use crate::grid::GRID_SIZE;
15use crate::tools::chrome::Panel;
16
17pub enum Notice {
18 Failure(String),
21 Standing(String),
23}
24
25pub struct Acknowledged(pub usize);
27
28const MAX_WIDTH: f32 = 0.4;
30
31pub fn draw(
35 ui: &mut egui::Ui,
36 viewport: egui::Rect,
37 above: egui::Rect,
38 notices: &[Notice],
39) -> Option<Acknowledged> {
40 if notices.is_empty() {
41 return None;
42 }
43 let top = if above.is_positive() {
44 above.bottom() + GRID_SIZE
45 } else {
46 viewport.top() + GRID_SIZE
47 };
48 let strip = egui::Rect::from_min_max(
49 egui::pos2(viewport.left() + GRID_SIZE, top),
50 viewport.right_bottom(),
51 );
52 let mut child = ui.new_child(
53 Panel::DocumentNotices
54 .ui_builder()
55 .max_rect(strip)
56 .layout(egui::Layout::top_down(egui::Align::Min)),
57 );
58 let mut acknowledged = None;
59 egui::Frame::popup(child.style()).show(&mut child, |ui| {
60 ui.set_max_width(viewport.width() * MAX_WIDTH);
61 let mut failures = 0;
62 for notice in notices {
63 match notice {
64 Notice::Standing(text) => {
65 ui.label(egui::RichText::new(text).small());
66 }
67 Notice::Failure(text) => {
68 let failure = failures;
69 failures += 1;
70 ui.horizontal(|ui| {
71 ui.add(egui::Label::new(egui::RichText::new(text).small()).wrap());
75 if ui
76 .small_button(DISMISS)
77 .on_hover_text("Done with this message")
78 .clicked()
79 {
80 acknowledged = Some(Acknowledged(failure));
81 }
82 });
83 }
84 }
85 }
86 });
87 acknowledged
88}
89
90pub(crate) const DISMISS: &str = "Dismiss";
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use crate::canvas::convert::{IntoEgui as _, IntoGeom as _};
98 use crate::tools::painted::Chrome;
99 use egui::{Rect, pos2, vec2};
100
101 fn viewport() -> Rect {
102 Rect::from_min_size(pos2(0.0, 0.0), vec2(1000.0, 800.0))
103 }
104
105 fn toolbar() -> Rect {
108 Rect::from_min_size(pos2(380.0, 8.0), vec2(240.0, 40.0))
109 }
110
111 #[test]
114 fn the_strip_never_covers_the_chrome_above_it() {
115 let long = "Read-only \u{2014} the log does not verify at record 41 of 128, whose \
116 stamp names a fold this build does not produce";
117 let mut chrome = Chrome::new(viewport().geom());
118 chrome.settle(|ui| {
119 let notices = [
120 Notice::Standing(long.to_owned()),
121 Notice::Failure(long.to_owned()),
122 ];
123 let _ = draw(ui, viewport(), toolbar(), ¬ices);
124 });
125 assert!(
126 chrome.shows(DISMISS),
127 "the failure carries no acknowledgement: {:?}",
128 chrome.texts(),
129 );
130 let drawn = chrome
131 .rect(long)
132 .expect("the notices drew their text")
133 .union(chrome.rect(DISMISS).expect("and the dismiss button"));
134 assert!(
135 drawn.top() > toolbar().bottom(),
136 "the notices at {drawn:?} sit over the toolbar at {:?}",
137 toolbar(),
138 );
139 assert!(
140 viewport().contains_rect(drawn.egui()),
141 "the notices at {drawn:?} left the canvas",
142 );
143 }
144
145 #[test]
148 fn dismissing_names_the_failure_it_dismissed() {
149 let mut chrome = Chrome::new(viewport().geom());
150 let mut acknowledged = None;
151 let notices = || {
152 [
153 Notice::Failure("first".to_owned()),
154 Notice::Standing("Read-only \u{2014} another blockworx has it".to_owned()),
155 Notice::Failure("second".to_owned()),
156 ]
157 };
158 chrome.settle(|ui| {
159 let _ = draw(ui, viewport(), toolbar(), ¬ices());
160 });
161 let second = chrome.rect("second").expect("the second failure drew");
164 let dismiss = chrome
165 .rects(DISMISS)
166 .into_iter()
167 .find(|rect| (rect.center().y - second.center().y).abs() < 4.0)
168 .expect("the second failure carries a dismiss button");
169 assert_eq!(chrome.rects(DISMISS).len(), 2, "a standing notice grew one");
170 chrome.click_at(dismiss.center(), |ui| {
171 if let Some(hit) = draw(ui, viewport(), toolbar(), ¬ices()) {
172 acknowledged = Some(hit.0);
173 }
174 });
175 assert_eq!(acknowledged, Some(1));
176 }
177}