use serde::{Deserialize, Serialize};
use std::cmp::min;
use crate::importer::SongFile;
use crate::song::Song;
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct PresentationChapter {
pub slides: Vec<Slide>,
pub linked_entity: LinkedEntity,
}
impl PresentationChapter {
pub fn new(slides: Vec<Slide>, linked_entity: LinkedEntity) -> Self {
PresentationChapter {
slides,
linked_entity,
}
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub enum LinkedEntity {
Song(Song),
Title(String),
SongFile(SongFile),
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub enum SlideContent {
SingleLanguageMainContent(SingleLanguageMainContentSlide),
Title(TitleSlide),
MultiLanguageMainContent(MultiLanguageMainContentSlide),
SimplePicture(SimplePictureSlide),
Empty(EmptySlide),
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct Slide {
pub slide_content: SlideContent,
pub linked_file: Option<SongFile>,
}
impl Slide {
pub fn new_empty_slide(black_background: bool) -> Self {
Slide {
slide_content: SlideContent::Empty(EmptySlide { black_background }),
linked_file: None,
}
}
pub fn new_content_slide(
main_text: String,
spoiler_text: Option<String>,
meta_text: Option<String>,
) -> Self {
Slide {
slide_content: SlideContent::SingleLanguageMainContent(
SingleLanguageMainContentSlide::new(
main_text.trim().to_string(),
match spoiler_text {
Some(string) => Some(string.trim().to_string()),
None => None,
},
match meta_text {
Some(string) => Some(string.trim().to_string()),
None => None,
},
),
),
linked_file: None,
}
}
pub fn new_title_slide(title_text: String, meta_text: Option<String>) -> Self {
Slide {
slide_content: SlideContent::Title(TitleSlide {
title_text: title_text.trim().to_string(),
meta_text: match meta_text {
Some(string) => Some(string.trim().to_string()),
None => None,
},
}),
linked_file: None,
}
}
pub fn with_song_file(self, linked_file: SongFile) -> Self {
let mut cloned_self = self.clone();
cloned_self.linked_file = Some(linked_file);
cloned_self
}
pub fn has_spoiler(&self) -> bool {
match &self.slide_content {
SlideContent::SingleLanguageMainContent(single_language_main_content_slide) => {
single_language_main_content_slide.spoiler_text.is_some()
}
SlideContent::Title(_) => false,
SlideContent::MultiLanguageMainContent(multi_language_main_content_slide) => {
!multi_language_main_content_slide
.spoiler_text_vector
.is_empty()
}
SlideContent::SimplePicture(_) => false,
SlideContent::Empty(_) => false,
}
}
pub fn has_meta_text(&self) -> bool {
match &self.slide_content {
SlideContent::SingleLanguageMainContent(single_language_main_content_slide) => {
single_language_main_content_slide.meta_text.is_some()
}
SlideContent::Title(title_slide) => title_slide.meta_text.is_some(),
SlideContent::MultiLanguageMainContent(multi_language_main_content_slide) => {
multi_language_main_content_slide.meta_text.is_some()
}
SlideContent::SimplePicture(_) => false,
SlideContent::Empty(_) => false,
}
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct SingleLanguageMainContentSlide {
main_text: String,
spoiler_text: Option<String>,
meta_text: Option<String>,
}
impl SingleLanguageMainContentSlide {
fn new(main_text: String, spoiler_text: Option<String>, meta_text: Option<String>) -> Self {
let parsed_spoiler_text: Option<String> = match spoiler_text {
Some(str) => match str.trim() {
"" => None,
_ => Some(str),
},
None => None,
};
let parsed_meta_text: Option<String> = match meta_text {
Some(str) => match str.trim() {
"" => None,
_ => Some(str),
},
None => None,
};
SingleLanguageMainContentSlide {
main_text,
spoiler_text: parsed_spoiler_text,
meta_text: parsed_meta_text,
}
}
pub fn spoiler_text(self) -> Option<String> {
self.spoiler_text
}
pub fn main_text(self) -> String {
self.main_text
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct MultiLanguageMainContentSlide {
pub main_text_list: Vec<String>,
pub spoiler_text_vector: Vec<String>,
pub meta_text: Option<String>,
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct EmptySlide {
pub black_background: bool,
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct TitleSlide {
pub title_text: String,
pub meta_text: Option<String>,
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct SimplePictureSlide {
picture_path: String,
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub struct SlideSettings {
pub title_slide: bool,
pub show_spoiler: bool,
pub show_meta_information: ShowMetaInformation,
pub meta_syntax: String,
pub empty_last_slide: bool,
pub max_lines: Option<usize>,
}
impl Default for SlideSettings {
fn default() -> Self {
SlideSettings {
title_slide: true,
meta_syntax: "".to_string(),
show_meta_information: ShowMetaInformation::FirstSlideAndLastSlide,
empty_last_slide: true,
show_spoiler: true,
max_lines: None,
}
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
pub enum ShowMetaInformation {
None,
FirstSlide,
LastSlide,
FirstSlideAndLastSlide,
}
impl ShowMetaInformation {
pub fn on_first_slide(&self) -> bool {
match self {
ShowMetaInformation::FirstSlide | ShowMetaInformation::FirstSlideAndLastSlide => true,
_ => false,
}
}
pub fn on_last_slide(&self) -> bool {
match self {
ShowMetaInformation::LastSlide | ShowMetaInformation::FirstSlideAndLastSlide => true,
_ => false,
}
}
}
pub fn wrap_blocks(
blocks: &Vec<Vec<Vec<String>>>,
maximum_lines: usize,
persistence: bool,
) -> Vec<Vec<Vec<String>>> {
if blocks.is_empty() {
return blocks.clone();
}
let first_block_length = blocks[0].len();
if blocks.len() > 1 {
for i in 1..blocks.len() {
if blocks[i].len() != first_block_length {
panic!("The length of every block has to be equal.")
}
}
}
let mut wrapped_blocks = blocks.clone();
let mut block_index: usize = 0;
let mut skip_next: bool = false;
while block_index < wrapped_blocks[0].len() {
#[cfg(test)]
{
eprintln!("DBG idx={}, lens={:?}", block_index, wrapped_blocks.iter().map(|b| b.len()).collect::<Vec<_>>());
}
if skip_next {
skip_next = false;
block_index += 1;
continue;
}
if wrapped_blocks[0][block_index].len() > maximum_lines {
let total_lines = wrapped_blocks[0][block_index].len();
let target_first_len = if maximum_lines == 1 {
1
} else if total_lines % 2 == 0 {
min(maximum_lines, total_lines / 2)
} else {
min(maximum_lines, (total_lines + 1) / 2)
};
let has_next = wrapped_blocks[0].get(block_index + 1).is_some();
let insert_new_block = !has_next || persistence || (!persistence && has_next);
if insert_new_block {
wrapped_blocks
.iter_mut()
.for_each(|block| block.insert(block_index + 1, vec![]));
}
let merging_into_existing_next = !persistence && has_next;
let destination_index = block_index + 1;
let mut moved_line_count = 0;
while wrapped_blocks[0][block_index].len() > target_first_len {
let primary_line = wrapped_blocks[0][block_index].remove(target_first_len);
wrapped_blocks[0][destination_index].insert(moved_line_count, primary_line);
for block in wrapped_blocks.iter_mut().skip(1) {
if target_first_len < block[block_index].len() {
let primary_line = block[block_index].remove(target_first_len);
block[destination_index].insert(moved_line_count, primary_line);
}
}
moved_line_count += 1;
}
if !persistence && has_next {
for block in wrapped_blocks.iter_mut() {
if block.len() > block_index + 2 {
let original_next_content = std::mem::take(&mut block[block_index + 2]);
block[destination_index].extend(original_next_content);
}
}
return wrapped_blocks;
}
}
block_index += 1;
}
wrapped_blocks
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_empty_slide() {
let slide = Slide::new_empty_slide(false);
assert!(matches!(slide.slide_content, SlideContent::Empty(_)));
}
#[test]
fn check_has_spoiler_function() {
let slide_1 = Slide::new_content_slide("Test".to_string(), Some("Hallo".to_string()), None);
assert!(slide_1.has_spoiler());
let slide_2 = Slide::new_content_slide(
"Test".to_string(),
Some("".to_string()),
Some("".to_string()),
);
assert!(!slide_2.has_spoiler());
}
#[test]
fn test_wrap_blocks_function() {
let example_blocks = vec![
vec![
vec![
"A1".to_string(),
"A2".to_string(),
"A3".to_string(),
"A4".to_string(),
"A5".to_string(),
],
vec![
"A6".to_string(),
"A7".to_string(),
"A8".to_string(),
"A9".to_string(),
"A10".to_string(),
],
],
vec![
vec![
"B1".to_string(),
"B2".to_string(),
"B3".to_string(),
"B4".to_string(),
],
vec![
"B5".to_string(),
"B6".to_string(),
"B7".to_string(),
"B8".to_string(),
"B9".to_string(),
],
],
];
let wrapped_blocks = wrap_blocks(&example_blocks, 3, true);
dbg!(&wrapped_blocks);
}
#[test]
fn test_wrap_blocks_with_odd_lines() {
let blocks_with_odd_lines = vec![
vec![
vec![
"L1".to_string(),
"L2".to_string(),
"L3".to_string(),
"L4".to_string(),
"L5".to_string(),
],
],
];
let wrapped_blocks = wrap_blocks(&blocks_with_odd_lines, 3, true);
assert_eq!(wrapped_blocks[0][0].len(), 3);
assert_eq!(wrapped_blocks[0][1].len(), 2);
assert_eq!(wrapped_blocks[0][0], vec!["L1".to_string(), "L2".to_string(), "L3".to_string()]);
assert_eq!(wrapped_blocks[0][1], vec!["L4".to_string(), "L5".to_string()]);
}
#[test]
fn test_wrap_blocks_empty() {
let empty_blocks: Vec<Vec<Vec<String>>> = vec![];
let wrapped_empty = wrap_blocks(&empty_blocks, 3, true);
assert_eq!(wrapped_empty.len(), 0);
let blocks_with_empty = vec![vec![vec![]]];
let wrapped_with_empty = wrap_blocks(&blocks_with_empty, 3, true);
assert_eq!(wrapped_with_empty, blocks_with_empty);
}
#[test]
fn test_wrap_blocks_exact_maximum() {
let blocks_exact = vec![
vec![
vec![
"A1".to_string(),
"A2".to_string(),
"A3".to_string(),
],
],
];
let wrapped_exact = wrap_blocks(&blocks_exact, 3, true);
assert_eq!(wrapped_exact, blocks_exact);
assert_eq!(wrapped_exact[0][0].len(), 3);
assert_eq!(wrapped_exact[0].len(), 1); }
#[test]
fn test_wrap_blocks_persistence() {
let test_blocks = vec![
vec![
vec![
"A1".to_string(),
"A2".to_string(),
"A3".to_string(),
"A4".to_string(),
],
vec![
"B1".to_string(),
"B2".to_string(),
],
],
];
let wrapped_persistent = wrap_blocks(&test_blocks, 2, true);
assert_eq!(wrapped_persistent[0].len(), 3);
assert_eq!(wrapped_persistent[0][0], vec!["A1".to_string(), "A2".to_string()]);
assert_eq!(wrapped_persistent[0][1], vec!["A3".to_string(), "A4".to_string()]);
assert_eq!(wrapped_persistent[0][2], vec!["B1".to_string(), "B2".to_string()]);
let test_blocks_with_next = vec![
vec![
vec![
"A1".to_string(),
"A2".to_string(),
"A3".to_string(),
"A4".to_string(),
],
vec![
"B1".to_string(),
"B2".to_string(),
],
],
];
let wrapped_non_persistent = wrap_blocks(&test_blocks_with_next, 2, false);
assert_eq!(wrapped_non_persistent[0].len(), 3);
assert_eq!(wrapped_non_persistent[0][0], vec!["A1".to_string(), "A2".to_string()]);
assert_eq!(wrapped_non_persistent[0][1], vec!["A3".to_string(), "A4".to_string(), "B1".to_string(), "B2".to_string()]);
}
#[test]
fn test_wrap_blocks_multiple_blocks() {
let multiple_blocks = vec![
vec![
vec![
"A1".to_string(),
"A2".to_string(),
"A3".to_string(),
"A4".to_string(),
],
],
vec![
vec![
"B1".to_string(),
"B2".to_string(),
"B3".to_string(),
"B4".to_string(),
],
],
];
let wrapped_multiple = wrap_blocks(&multiple_blocks, 2, true);
assert_eq!(wrapped_multiple.len(), 2);
assert_eq!(wrapped_multiple[0].len(), 2);
assert_eq!(wrapped_multiple[1].len(), 2);
assert_eq!(wrapped_multiple[0][0], vec!["A1".to_string(), "A2".to_string()]);
assert_eq!(wrapped_multiple[0][1], vec!["A3".to_string(), "A4".to_string()]);
assert_eq!(wrapped_multiple[1][0], vec!["B1".to_string(), "B2".to_string()]);
assert_eq!(wrapped_multiple[1][1], vec!["B3".to_string(), "B4".to_string()]);
}
#[test]
fn test_wrap_blocks_edge_cases() {
let blocks_for_extreme = vec![
vec![
vec![
"A1".to_string(),
"A2".to_string(),
"A3".to_string(),
],
],
];
let wrapped_extreme = wrap_blocks(&blocks_for_extreme, 1, true);
assert_eq!(wrapped_extreme[0].len(), 3);
assert_eq!(wrapped_extreme[0][0], vec!["A1".to_string()]);
assert_eq!(wrapped_extreme[0][1], vec!["A2".to_string()]);
assert_eq!(wrapped_extreme[0][2], vec!["A3".to_string()]);
}
}