pixels_graphics_lib/ui/
alert.rs

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    ///
31    /// # Parameters
32    /// * `width` - Screen width
33    /// * `height` - Screen height
34    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    ///
77    /// # Parameters
78    /// * `width` - Screen width
79    /// * `height` - Screen height
80    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}