use crate::core::{AlignItems, BorderStyle, Color, Element, FlexDirection, JustifyContent};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum ModalAlign {
#[default]
Center,
Top,
Bottom,
}
#[derive(Default)]
pub struct Modal {
title: Option<String>,
children: Vec<Element>,
border_style: BorderStyle,
width: Option<u16>,
height: Option<u16>,
padding: u16,
align: ModalAlign,
background: Option<Color>,
border_color: Option<Color>,
title_color: Option<Color>,
backdrop: bool,
backdrop_char: char,
}
impl Modal {
pub fn new() -> Self {
Self {
title: None,
children: Vec::new(),
border_style: BorderStyle::Single,
width: None,
height: None,
padding: 1,
align: ModalAlign::Center,
background: None,
border_color: None,
title_color: None,
backdrop: false,
backdrop_char: ' ',
}
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn child(mut self, child: Element) -> Self {
self.children.push(child);
self
}
pub fn children(mut self, children: impl IntoIterator<Item = Element>) -> Self {
self.children.extend(children);
self
}
pub fn border_style(mut self, style: BorderStyle) -> Self {
self.border_style = style;
self
}
pub fn width(mut self, width: u16) -> Self {
self.width = Some(width);
self
}
pub fn height(mut self, height: u16) -> Self {
self.height = Some(height);
self
}
pub fn padding(mut self, padding: u16) -> Self {
self.padding = padding;
self
}
pub fn align(mut self, align: ModalAlign) -> Self {
self.align = align;
self
}
pub fn background(mut self, color: Color) -> Self {
self.background = Some(color);
self
}
pub fn border_color(mut self, color: Color) -> Self {
self.border_color = Some(color);
self
}
pub fn title_color(mut self, color: Color) -> Self {
self.title_color = Some(color);
self
}
pub fn backdrop(mut self, enabled: bool) -> Self {
self.backdrop = enabled;
self
}
pub fn backdrop_char(mut self, ch: char) -> Self {
self.backdrop_char = ch;
self
}
pub fn into_element(self) -> Element {
use crate::components::Box;
use crate::components::Text;
let mut content_box = Box::new()
.flex_direction(FlexDirection::Column)
.border_style(self.border_style)
.padding(self.padding);
if let Some(w) = self.width {
content_box = content_box.width(w);
}
if let Some(h) = self.height {
content_box = content_box.height(h);
}
if let Some(bg) = self.background {
content_box = content_box.background(bg);
}
if let Some(bc) = self.border_color {
content_box = content_box.border_color(bc);
}
if let Some(title) = &self.title {
let mut title_text = Text::new(title).bold();
if let Some(tc) = self.title_color {
title_text = title_text.color(tc);
}
content_box = content_box.child(title_text.into_element());
content_box = content_box.child(Text::new("").into_element());
}
for child in self.children {
content_box = content_box.child(child);
}
let justify = match self.align {
ModalAlign::Center => JustifyContent::Center,
ModalAlign::Top => JustifyContent::FlexStart,
ModalAlign::Bottom => JustifyContent::FlexEnd,
};
let mut wrapper = Box::new()
.flex_direction(FlexDirection::Column)
.justify_content(justify)
.align_items(AlignItems::Center)
.flex_grow(1.0);
if self.backdrop {
wrapper = wrapper.background(Color::Black);
}
wrapper.child(content_box.into_element()).into_element()
}
}
#[derive(Default)]
pub struct Dialog {
title: Option<String>,
message: Option<String>,
content: Vec<Element>,
confirm_label: String,
cancel_label: String,
focused_button: usize,
border_style: BorderStyle,
width: Option<u16>,
confirm_color: Option<Color>,
cancel_color: Option<Color>,
focus_color: Option<Color>,
}
impl Dialog {
pub fn new() -> Self {
Self {
title: None,
message: None,
content: Vec::new(),
confirm_label: "OK".to_string(),
cancel_label: "Cancel".to_string(),
focused_button: 0,
border_style: BorderStyle::Round,
width: Some(50),
confirm_color: Some(Color::Green),
cancel_color: Some(Color::Red),
focus_color: Some(Color::Cyan),
}
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn message(mut self, message: impl Into<String>) -> Self {
self.message = Some(message.into());
self
}
pub fn content(mut self, element: Element) -> Self {
self.content.push(element);
self
}
pub fn confirm_label(mut self, label: impl Into<String>) -> Self {
self.confirm_label = label.into();
self
}
pub fn cancel_label(mut self, label: impl Into<String>) -> Self {
self.cancel_label = label.into();
self
}
pub fn focused_button(mut self, index: usize) -> Self {
self.focused_button = index.min(1);
self
}
pub fn border_style(mut self, style: BorderStyle) -> Self {
self.border_style = style;
self
}
pub fn width(mut self, width: u16) -> Self {
self.width = Some(width);
self
}
pub fn confirm_color(mut self, color: Color) -> Self {
self.confirm_color = Some(color);
self
}
pub fn cancel_color(mut self, color: Color) -> Self {
self.cancel_color = Some(color);
self
}
pub fn focus_color(mut self, color: Color) -> Self {
self.focus_color = Some(color);
self
}
pub fn into_element(self) -> Element {
use crate::components::Box;
use crate::components::Text;
let mut modal = Modal::new().border_style(self.border_style);
if let Some(w) = self.width {
modal = modal.width(w);
}
if let Some(title) = &self.title {
modal = modal.title(title);
}
if let Some(msg) = &self.message {
modal = modal.child(Text::new(msg).into_element());
}
for element in self.content {
modal = modal.child(element);
}
modal = modal.child(Text::new("").into_element());
let confirm_focused = self.focused_button == 0;
let cancel_focused = self.focused_button == 1;
let mut confirm_text = Text::new(format!("[ {} ]", self.confirm_label));
if let Some(color) = self.confirm_color {
confirm_text = confirm_text.color(color);
}
if confirm_focused {
confirm_text = confirm_text.bold();
if let Some(fc) = self.focus_color {
confirm_text = confirm_text.color(fc);
}
}
let mut cancel_text = Text::new(format!("[ {} ]", self.cancel_label));
if let Some(color) = self.cancel_color {
cancel_text = cancel_text.color(color);
}
if cancel_focused {
cancel_text = cancel_text.bold();
if let Some(fc) = self.focus_color {
cancel_text = cancel_text.color(fc);
}
}
let button_row = Box::new()
.flex_direction(FlexDirection::Row)
.justify_content(JustifyContent::Center)
.gap(2.0)
.child(confirm_text.into_element())
.child(cancel_text.into_element())
.into_element();
modal = modal.child(button_row);
modal.into_element()
}
}
#[derive(Debug, Clone, Default)]
pub struct DialogState {
pub visible: bool,
pub focused_button: usize,
}
impl DialogState {
pub fn new() -> Self {
Self {
visible: false,
focused_button: 0,
}
}
pub fn show(&mut self) {
self.visible = true;
self.focused_button = 0;
}
pub fn hide(&mut self) {
self.visible = false;
}
pub fn toggle(&mut self) {
self.visible = !self.visible;
}
pub fn focus_next(&mut self) {
self.focused_button = (self.focused_button + 1) % 2;
}
pub fn focus_previous(&mut self) {
self.focused_button = if self.focused_button == 0 { 1 } else { 0 };
}
pub fn is_confirm_focused(&self) -> bool {
self.focused_button == 0
}
pub fn is_cancel_focused(&self) -> bool {
self.focused_button == 1
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_modal_creation() {
let modal = Modal::new()
.title("Test Modal")
.width(40)
.padding(2)
.border_style(BorderStyle::Round);
let _element = modal.into_element();
}
#[test]
fn test_modal_with_children() {
use crate::components::Text;
let modal = Modal::new()
.title("Test")
.child(Text::new("Line 1").into_element())
.child(Text::new("Line 2").into_element())
.into_element();
assert!(!modal.children.is_empty());
}
#[test]
fn test_modal_alignment() {
let top = Modal::new().align(ModalAlign::Top).into_element();
let center = Modal::new().align(ModalAlign::Center).into_element();
let bottom = Modal::new().align(ModalAlign::Bottom).into_element();
assert!(!top.children.is_empty());
assert!(!center.children.is_empty());
assert!(!bottom.children.is_empty());
}
#[test]
fn test_dialog_creation() {
let dialog = Dialog::new()
.title("Confirm")
.message("Are you sure?")
.confirm_label("Yes")
.cancel_label("No");
let _element = dialog.into_element();
}
#[test]
fn test_dialog_state() {
let mut state = DialogState::new();
assert!(!state.visible);
assert!(state.is_confirm_focused());
state.show();
assert!(state.visible);
state.focus_next();
assert!(state.is_cancel_focused());
state.focus_previous();
assert!(state.is_confirm_focused());
state.hide();
assert!(!state.visible);
}
#[test]
fn test_dialog_focused_button() {
let dialog = Dialog::new()
.title("Test")
.message("Test message")
.focused_button(1);
let _element = dialog.into_element();
}
}