use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mark {
Good,
Bad,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Prompt {
pub(super) text: String,
pub(super) choices: Vec<String>,
pub(super) marks: Option<Vec<Vec<(usize, Mark)>>>,
pub(super) dismiss: String,
pub(super) auto_dismiss: Option<Duration>,
}
impl Prompt {
pub fn new(
text: impl Into<String>,
choices: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
Self {
text: text.into(),
choices: choices.into_iter().map(Into::into).collect(),
marks: None,
dismiss: "Dismiss".into(),
auto_dismiss: None,
}
}
#[must_use]
pub fn marks(mut self, marks: impl Fn(usize) -> Vec<(usize, Mark)>) -> Self {
let count = self.choices.len();
self.marks = Some(
(0..count)
.map(|clicked| {
marks(clicked)
.into_iter()
.filter(|&(index, _)| index < count)
.collect()
})
.collect(),
);
self
}
#[must_use]
pub fn dismiss_label(mut self, label: impl Into<String>) -> Self {
self.dismiss = label.into();
self
}
#[must_use]
pub fn auto_dismiss(mut self, after: Option<Duration>) -> Self {
self.auto_dismiss = after;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PopupStyle {
pub width: f32,
pub text_size: f32,
pub button_text_size: f32,
pub fade: Duration,
pub hold: Duration,
}
impl Default for PopupStyle {
fn default() -> Self {
Self {
width: 360.0,
text_size: 16.0,
button_text_size: 14.0,
fade: Duration::from_millis(300),
hold: Duration::from_millis(1500),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prompt_has_quiet_defaults() {
let p = Prompt::new("Q?", ["a", "b"]);
assert_eq!(p.choices, ["a", "b"]);
assert_eq!(p.marks, None);
assert_eq!(p.dismiss, "Dismiss");
assert_eq!(p.auto_dismiss, None);
}
#[test]
fn prompt_builders_set_each_part() {
let p = Prompt::new("Q?", ["a", "b"])
.marks(|clicked| vec![(clicked, Mark::Good)])
.dismiss_label("Skip")
.auto_dismiss(Some(Duration::from_secs(30)));
assert_eq!(
p.marks,
Some(vec![vec![(0, Mark::Good)], vec![(1, Mark::Good)]])
);
assert_eq!(p.dismiss, "Skip");
assert_eq!(p.auto_dismiss, Some(Duration::from_secs(30)));
}
#[test]
fn prompt_takes_owned_strings() {
let choices = vec![String::from("a"), String::from("b")];
let p = Prompt::new(String::from("Q?"), choices).dismiss_label(String::from("Skip"));
assert_eq!((p.text.as_str(), p.dismiss.as_str()), ("Q?", "Skip"));
assert_eq!(p.choices, ["a", "b"]);
}
#[test]
fn marks_out_of_range_are_dropped() {
let p = Prompt::new("Q?", ["a", "b"]).marks(|_| vec![(0, Mark::Good), (5, Mark::Bad)]);
assert_eq!(
p.marks,
Some(vec![vec![(0, Mark::Good)], vec![(0, Mark::Good)]])
);
}
#[test]
fn default_style_keeps_the_old_look() {
let style = PopupStyle::default();
assert_eq!(style.width, 360.0);
assert_eq!((style.text_size, style.button_text_size), (16.0, 14.0));
assert_eq!(style.fade, Duration::from_millis(300));
assert_eq!(style.hold, Duration::from_millis(1500));
}
}