use embedded_graphics::{prelude::*, primitives::Rectangle};
use heapless::{String, Vec};
use crate::FsTheme;
const MAX_MESSAGE_LINES: usize = 8;
const MAX_LINE_CHARS: usize = 96;
const LINE_HEIGHT: u32 = 18;
const TITLE_HEIGHT: u32 = 40;
const MESSAGE_TOP: i32 = 72;
const BUTTON_HEIGHT: u32 = 40;
const H_PADDING: u32 = 24;
const V_PADDING: u32 = 22;
pub(crate) type WrappedLines = Vec<String<MAX_LINE_CHARS>, MAX_MESSAGE_LINES>;
pub(crate) struct AlertLayout<const N: usize> {
pub panel: Rectangle,
pub message_lines: WrappedLines,
}
pub(crate) fn alert_layout<const N: usize>(
bounds: Rectangle,
theme: &FsTheme,
message: &str,
) -> AlertLayout<N> {
let max_width = bounds
.size
.width
.saturating_sub(theme.modal_margin.saturating_mul(2));
let width = ((bounds.size.width.saturating_mul(13)) / 20)
.max(360)
.min(max_width);
let message_width = width.saturating_sub(H_PADDING * 2);
let max_chars = (message_width / 7).max(12) as usize;
let message_lines = wrap_text(message, max_chars);
let text_height = (message_lines.len().max(1) as u32) * LINE_HEIGHT;
let height = V_PADDING * 2 + TITLE_HEIGHT + text_height + 24 + BUTTON_HEIGHT;
let x = bounds.top_left.x + ((bounds.size.width.saturating_sub(width)) / 2) as i32;
let y = bounds.top_left.y + ((bounds.size.height.saturating_sub(height)) / 2) as i32;
alert_layout_in_panel(
Rectangle::new(Point::new(x, y), Size::new(width, height)),
message,
)
}
pub(crate) fn alert_layout_in_panel<const N: usize>(
panel: Rectangle,
message: &str,
) -> AlertLayout<N> {
let message_width = panel.size.width.saturating_sub(H_PADDING * 2);
let max_chars = (message_width / 7).max(12) as usize;
AlertLayout {
panel,
message_lines: wrap_text(message, max_chars),
}
}
pub(crate) fn button_frames<const N: usize>(panel: Rectangle) -> [Rectangle; N] {
let width = panel.size.width.saturating_sub(36);
let button_width = width / N as u32;
core::array::from_fn(|index| {
let x = panel.top_left.x + 18 + (index as i32 * button_width as i32);
let width = if index + 1 == N {
panel
.size
.width
.saturating_sub(36 + button_width * index as u32)
} else {
button_width.saturating_sub(8)
};
Rectangle::new(
Point::new(
x,
panel.top_left.y + panel.size.height as i32
- V_PADDING as i32
- BUTTON_HEIGHT as i32,
),
Size::new(width, BUTTON_HEIGHT),
)
})
}
pub(crate) fn message_top(panel: Rectangle) -> i32 {
panel.top_left.y + MESSAGE_TOP
}
fn wrap_text(text: &str, max_chars: usize) -> WrappedLines {
let mut lines = WrappedLines::new();
let mut current = String::<MAX_LINE_CHARS>::new();
for segment in text.split('\n') {
push_segment(&mut lines, &mut current, segment, max_chars);
if !current.is_empty() {
let _ = lines.push(current.clone());
current.clear();
}
}
if lines.is_empty() {
let mut line = String::<MAX_LINE_CHARS>::new();
let _ = line.push_str(text);
let _ = lines.push(line);
}
lines
}
fn push_segment(
lines: &mut WrappedLines,
current: &mut String<MAX_LINE_CHARS>,
segment: &str,
max_chars: usize,
) {
for word in segment.split_whitespace() {
if current.is_empty() {
push_word(lines, current, word, max_chars);
continue;
}
if current.len() + 1 + word.len() <= max_chars {
let _ = current.push(' ');
let _ = current.push_str(word);
continue;
}
let _ = lines.push(current.clone());
current.clear();
push_word(lines, current, word, max_chars);
}
}
fn push_word(
lines: &mut WrappedLines,
current: &mut String<MAX_LINE_CHARS>,
word: &str,
max_chars: usize,
) {
if word.len() <= max_chars {
let _ = current.push_str(word);
return;
}
for ch in word.chars() {
if current.len() >= max_chars {
let _ = lines.push(current.clone());
current.clear();
}
let _ = current.push(ch);
}
}