use crate::geometry::Size;
use crate::overlay::{OverlayConfig, OverlayPosition};
use crate::segment::Segment;
use crate::style::Style;
use crate::text::{string_display_width, truncate_to_display_width};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ToastPosition {
TopLeft,
#[default]
TopRight,
BottomLeft,
BottomRight,
}
#[derive(Clone, Debug)]
pub struct Toast {
message: String,
position: ToastPosition,
style: Style,
width: u16,
}
impl Toast {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
position: ToastPosition::TopRight,
style: Style::default(),
width: 30,
}
}
#[must_use]
pub fn with_position(mut self, pos: ToastPosition) -> Self {
self.position = pos;
self
}
#[must_use]
pub fn with_style(mut self, style: Style) -> Self {
self.style = style;
self
}
#[must_use]
pub fn with_width(mut self, width: u16) -> Self {
self.width = width;
self
}
pub fn render_to_lines(&self) -> Vec<Vec<Segment>> {
let w = self.width as usize;
let truncated = truncate_to_display_width(&self.message, w);
let display_w = string_display_width(truncated) as usize;
let mut padded = String::with_capacity(w);
padded.push_str(truncated);
for _ in display_w..w {
padded.push(' ');
}
vec![vec![Segment::styled(padded, self.style.clone())]]
}
pub fn to_overlay_config(&self, screen: Size) -> OverlayConfig {
let height = 1u16;
let pos = match self.position {
ToastPosition::TopLeft => crate::geometry::Position::new(0, 0),
ToastPosition::TopRight => {
let x = screen.width.saturating_sub(self.width);
crate::geometry::Position::new(x, 0)
}
ToastPosition::BottomLeft => {
let y = screen.height.saturating_sub(height);
crate::geometry::Position::new(0, y)
}
ToastPosition::BottomRight => {
let x = screen.width.saturating_sub(self.width);
let y = screen.height.saturating_sub(height);
crate::geometry::Position::new(x, y)
}
};
OverlayConfig {
position: OverlayPosition::At(pos),
size: Size::new(self.width, height),
z_offset: 0,
dim_background: false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_toast_defaults() {
let t = Toast::new("Hello");
assert!(t.message == "Hello");
assert!(t.position == ToastPosition::TopRight);
assert!(t.width == 30);
}
#[test]
fn render_to_lines_produces_content() {
let t = Toast::new("Test message");
let lines = t.render_to_lines();
assert!(lines.len() == 1);
let text: String = lines[0].iter().map(|s| &*s.text).collect();
assert!(text.contains("Test message"));
}
#[test]
fn top_right_position() {
let t = Toast::new("Hi").with_width(10);
let config = t.to_overlay_config(Size::new(80, 24));
match &config.position {
OverlayPosition::At(p) => {
assert!(p.x == 70); assert!(p.y == 0);
}
_ => unreachable!(),
}
}
#[test]
fn bottom_left_position() {
let t = Toast::new("Hi")
.with_position(ToastPosition::BottomLeft)
.with_width(10);
let config = t.to_overlay_config(Size::new(80, 24));
match &config.position {
OverlayPosition::At(p) => {
assert!(p.x == 0);
assert!(p.y == 23); }
_ => unreachable!(),
}
}
#[test]
fn top_left_position() {
let t = Toast::new("Hi")
.with_position(ToastPosition::TopLeft)
.with_width(10);
let config = t.to_overlay_config(Size::new(80, 24));
match &config.position {
OverlayPosition::At(p) => {
assert!(p.x == 0);
assert!(p.y == 0);
}
_ => unreachable!(),
}
}
#[test]
fn bottom_right_position() {
let t = Toast::new("Hi")
.with_position(ToastPosition::BottomRight)
.with_width(10);
let config = t.to_overlay_config(Size::new(80, 24));
match &config.position {
OverlayPosition::At(p) => {
assert!(p.x == 70); assert!(p.y == 23); }
_ => unreachable!(),
}
}
#[test]
fn toast_style_applied() {
let style = Style::new().bold(true);
let t = Toast::new("Styled").with_style(style);
let lines = t.render_to_lines();
assert!(lines[0][0].style.bold);
}
#[test]
fn custom_width_respected() {
let t = Toast::new("AB").with_width(5);
let lines = t.render_to_lines();
let text: String = lines[0].iter().map(|s| &*s.text).collect();
assert!(text.len() == 5);
assert!(text.starts_with("AB"));
}
#[test]
fn no_dim_background() {
let t = Toast::new("msg");
let config = t.to_overlay_config(Size::new(80, 24));
assert!(!config.dim_background);
}
}