1use crate::ui::alert::AlertResult::{Negative, Positive};
2use crate::ui::prelude::*;
3use crate::ui::styles::AlertStyle;
4use buffer_graphics_lib::prelude::Positioning::Center;
5use buffer_graphics_lib::prelude::*;
6
7const BUTTON_Y: isize = 28;
8const ALERT_SIZE: (usize, usize) = (200, 50);
9const ACK_OFFSET: Coord = Coord::new(90, BUTTON_Y);
10const NEGATIVE_OFFSET: Coord = Coord::new(6, BUTTON_Y);
11const TEXT_POS: Coord = Coord::new(ALERT_SIZE.0 as isize / 2, 10);
12
13#[derive(Debug, Copy, Clone, Eq, PartialEq)]
14pub enum AlertResult {
15 Positive,
16 Negative,
17}
18
19#[derive(Debug)]
20pub struct Alert {
21 negative: Option<Button>,
22 positive: Button,
23 message: Vec<Text>,
24 background: ShapeCollection,
25 bounds: Rect,
26 style: AlertStyle,
27}
28
29impl Alert {
30 pub fn new_question(
35 message: &[&str],
36 negative_text: &str,
37 positive_text: &str,
38 width: usize,
39 height: usize,
40 style: &AlertStyle,
41 ) -> Self {
42 let mut message = message;
43 let pos = Coord::from((width / 2, height / 2)) - Coord::from(ALERT_SIZE) / 2;
44 let (bounds, background) = Self::background(style, pos);
45 let min = Button::calc_bounds(Coord::default(), positive_text, None, style.button.font)
46 .width()
47 .max(
48 Button::calc_bounds(Coord::default(), negative_text, None, style.button.font)
49 .width(),
50 );
51 let positive = Button::new(
52 pos + (ALERT_SIZE.0 - min - 6, BUTTON_Y as usize),
53 positive_text,
54 Some(min),
55 &style.button,
56 );
57 let negative = Button::new(
58 pos + NEGATIVE_OFFSET,
59 negative_text,
60 Some(min),
61 &style.button,
62 );
63 if message.is_empty() {
64 message = &[""];
65 }
66 Self {
67 negative: Some(negative),
68 positive,
69 message: Self::text(message, pos, style.text, style.font),
70 background,
71 bounds,
72 style: style.clone(),
73 }
74 }
75
76 pub fn new_warning(message: &[&str], width: usize, height: usize, style: &AlertStyle) -> Self {
81 let pos = Coord::from((width / 2, height / 2)) - Coord::from(ALERT_SIZE) / 2;
82 let (bounds, background) = Self::background(style, pos);
83 let positive = Button::new(pos + ACK_OFFSET, "OK", Some(20), &style.button);
84 Self {
85 negative: None,
86 positive,
87 message: Self::text(message, pos, style.warning_text, style.font),
88 background,
89 bounds,
90 style: style.clone(),
91 }
92 }
93
94 fn text(lines: &[&str], pos: Coord, color: Color, font: PixelFont) -> Vec<Text> {
95 let mut output = vec![];
96 for (i, line) in lines.iter().enumerate() {
97 output.push(Text::new(
98 line,
99 TextPos::px(pos + TEXT_POS + (0, i * (font.size().1 + font.spacing() * 2))),
100 (color, font, WrappingStrategy::Cutoff(30), Center),
101 ));
102 }
103 output
104 }
105
106 fn background(style: &AlertStyle, start: Coord) -> (Rect, ShapeCollection) {
107 let rect = Rect::new_with_size(start, ALERT_SIZE.0, ALERT_SIZE.1);
108 let mut back = ShapeCollection::default();
109 if let Some(color) = style.background {
110 InsertShape::insert_above(&mut back, rect.clone(), fill(color));
111 }
112 if let Some(color) = style.shadow {
113 InsertShape::insert_above(&mut back, rect.translate_by(coord!(1, 1)), stroke(color));
114 }
115 if let Some(color) = style.border {
116 InsertShape::insert_above(&mut back, rect.clone(), stroke(color));
117 }
118
119 (rect, back)
120 }
121}
122
123impl Alert {
124 pub fn change_text(&mut self, text: &[&str]) {
125 let pos = self.bounds.top_left();
126 let color = self.message[0].formatting().color();
127 let font = self.message[0].formatting().font();
128
129 let mut output = vec![];
130 for (i, line) in text.iter().enumerate() {
131 output.push(Text::new(
132 line,
133 TextPos::px(pos + TEXT_POS + (0, i * (font.size().1 + font.spacing() * 2))),
134 (color, font, WrappingStrategy::Cutoff(30), Center),
135 ));
136 }
137 self.message = output
138 }
139
140 #[must_use]
141 pub fn on_mouse_click(&mut self, down: Coord, up: Coord) -> Option<AlertResult> {
142 if self.positive.on_mouse_click(down, up) {
143 return Some(Positive);
144 }
145 if let Some(neg) = &mut self.negative {
146 if neg.on_mouse_click(down, up) {
147 return Some(Negative);
148 }
149 }
150 None
151 }
152
153 pub fn render(&self, graphics: &mut Graphics, mouse: &MouseData) {
154 if let Some(color) = self.style.shade {
155 graphics.draw_rect(
156 Rect::new_with_size((0, 0), graphics.width(), graphics.height()),
157 fill(color),
158 );
159 }
160 self.background.render(graphics);
161 self.positive.render(graphics, mouse);
162 if let Some(neg) = &self.negative {
163 neg.render(graphics, mouse);
164 }
165 for line in &self.message {
166 line.render(graphics);
167 }
168 }
169}