hgame 0.26.4

CG production management structs, e.g. of assets, personnels, progress, etc.
Documentation
mod composer;
mod note;
mod reply;
mod sequence;

use super::*;
pub use composer::*;
pub use note::*;
pub use reply::*;
pub use sequence::*;

#[cfg(feature = "gui")]
use crate::link::*;

#[allow(unused_imports)]
use crossbeam_channel::Sender;
#[cfg(feature = "gui")]
use mkutil::tempfile::NamedTempFile;

#[cfg(all(feature = "gui", not(feature = "image_processing")))]
const IMAGE_LABEL: &str = "img .";

#[derive(Debug, Clone, PartialEq)]
pub enum ImageUploaded {
    Success(u8),
    NoTask,
}

// use mkutil::dialog::Report;
// impl Into<Report> for ImageUploaded {
//     fn into(self) -> Report {
//         match self {
//             Self::NoTask => Report::new("Zero Image Uploaded", "No images queued for uploading"),
//             Self::Success(count) => {
//                 Report::into_new("Completed", format!("{} images uploaded", count))
//             }
//         }
//     }
// }

// -------------------------------------------------------------------------------
#[derive(Debug)]
struct UserImage<T>
where
    T: AsRef<Path>,
{
    /// An either existing image or got saved from clipboard, but can be unselected later by user.
    /// Selected image will be uploaded to the path at `Self::uploaded`.
    selecting: Option<T>,

    /// Upload result of `Self::selecting`.
    uploaded: Option<AnyResult<PathBuf>>,
}

impl<T> UserImage<T>
where
    T: AsRef<Path>,
{
    /// User selecting some image.
    fn selecting(img: T) -> Self {
        Self {
            selecting: Some(img),
            uploaded: None,
        }
    }

    fn is_selected(&self) -> bool {
        self.selecting.is_some()
    }

    #[cfg(feature = "gui")]
    fn unselect(&mut self) {
        self.selecting.take();
    }

    fn selecting_as_ref_unwrap(&self) -> &T {
        self.selecting.as_ref().unwrap()
    }

    fn uploaded_mut(&mut self, result: AnyResult<PathBuf>, count: &mut u8) {
        if let Ok(_) = &result {
            *count = *count + 1;
        };
        self.uploaded = Some(result);
    }

    #[cfg(debug_assertions)]
    /// SAFETY: caller is responsible for ensuring `Self::choice` `is_some`.
    fn log_uploaded_unwrap(&self) {
        match &self.uploaded {
            Some(Ok(path)) => {
                debug!("Uploaded image into {}", path.display());
            }
            Some(Err(e)) => {
                error!(
                    "Failed to upload image {}: {}",
                    self.selecting_as_ref_unwrap().as_ref().display(),
                    e
                );
            }
            None => {
                // the image was unselected by user
                unreachable!()
            }
        }
    }

    #[cfg(feature = "gui")]
    fn hint_selected_ui(&mut self, ui: &mut egui::Ui) {
        if let Some(img) = &self.selecting {
            ui.label(format!(
                "{:?}",
                img.as_ref()
                    .file_name()
                    .expect("Failed to extract file name")
            ));

            if ui.button("⊗").clicked() {
                // unpick the file
                self.unselect();
            };
        };
    }
}

#[cfg(feature = "gui")]
impl UserImage<NamedTempFile> {
    fn hint_saved_ui(&mut self, ui: &mut egui::Ui, num: usize) {
        if let Some(_) = self.selecting {
            ui.label(format!("clipboard img #{}", num + 1));

            if ui.button("🗑").clicked() {
                self.unselect_and_close();
            };
        };
    }

    /// Drops the temp file.
    fn unselect_and_close(&mut self) {
        if let Some(temp_file) = self.selecting.take() {
            match temp_file.close() {
                Ok(_) => {}
                Err(e) => {
                    warn!("{}", e);
                }
            }
        };
    }
}

// ----------------------------------------------------------------------------
#[derive(Debug, Clone, Default, PartialEq, Eq, strum::AsRefStr)]
/// `strum::AsRefStr`s are used as prefix for `egui::Slider`
pub enum SequenceType {
    #[default]
    #[strum(serialize = "snapshot ")]
    /// Sequence of quick screenshots.
    Snapshot,

    #[strum(serialize = "frame ")]
    /// Sequence of "proper" images, e.g. generating full-frame from a software.
    Image,
}

// ----------------------------------------------------------------------------
#[derive(Debug, Clone, Default)]
pub struct ImageArray {
    img_paths: Option<Vec<PathBuf>>,

    #[cfg(all(feature = "image_processing", feature = "gui"))]
    img_specs: Option<Vec<Option<ImageSpec>>>,
}

impl PartialEq for ImageArray {
    fn eq(&self, other: &Self) -> bool {
        self.img_paths == other.img_paths
    }
}

impl ImageArray {
    fn with_img_paths(mut self, img_paths: Option<Vec<PathBuf>>) -> Self {
        self.img_paths = img_paths;
        self
    }

    #[cfg(all(feature = "image_processing", feature = "gui"))]
    fn img_specs_mut(&mut self) {
        self.img_specs = self.img_paths.as_ref().map(|paths| {
            paths
                .iter()
                .map(|i| ImageSpec::from_retained_image(i))
                .collect()
        });
    }

    #[cfg(feature = "gui")]
    fn ui(&mut self, ui: &mut egui::Ui, _thumb_width: f32) {
        #[cfg(feature = "image_processing")]
        if let Some(specs) = self.img_specs.as_mut() {
            // SAFETY: `Self::img_paths` should already `is_some`,
            // since otherwise `Self::img_specs` would be `is_none`.
            for (path, spec) in self
                .img_paths
                .as_ref()
                .unwrap()
                .iter()
                .zip(specs.iter())
                .filter(|(_, spec)| spec.is_some())
            {
                ui.add(ImageLink::with_retained_image_actual_height(
                    ui.ctx(),
                    spec.as_ref().unwrap().inner(),
                    Some(path),
                    _thumb_width,
                ));
            }
        };

        #[cfg(not(feature = "image_processing"))]
        // using simpler `hgame::FinderLink`
        if let Some(paths) = self.img_paths.as_ref() {
            for (i, path) in paths.iter().enumerate() {
                ui.add(FinderLink::with_url(
                    Some(&format!("{}{}", IMAGE_LABEL, i + 1)),
                    &path,
                ));
            }
        };
    }
}

// ----------------------------------------------------------------------------
#[derive(Debug, Clone, Default, PartialEq, strum::AsRefStr, strum::EnumIter)]
#[cfg_attr(feature = "persistence", derive(Serialize, Deserialize))]
pub enum EmbedLayoutPref {
    #[strum(serialize = "Array")]
    RowOrColumn,

    #[default]
    Carousel,
}

// ----------------------------------------------------------------------------
#[derive(Debug, Clone, PartialEq, strum::AsRefStr)]
pub enum ImageEmbedLayout {
    /// "Traditional", simplistic layout, i.e. images will be
    /// laid out in a row/column and not as a sequence with a slider.
    RowOrColumn(ImageArray),

    /// Newer layout where images will be displayed as a series with a slider.
    Carousel(InPlaceSeqDisplay),
}

impl Default for ImageEmbedLayout {
    fn default() -> Self {
        Self::RowOrColumn(Default::default())
    }
}

impl ImageEmbedLayout {
    pub(super) fn with_img_paths(self, img_paths: Option<Vec<PathBuf>>) -> Self {
        match self {
            Self::RowOrColumn(seq) => Self::RowOrColumn(seq.with_img_paths(img_paths)),
            Self::Carousel(seq) => Self::Carousel(seq.with_img_paths(img_paths)),
        }
    }

    pub(super) fn img_paths_as_ref(&self) -> Option<&Vec<PathBuf>> {
        match self {
            Self::RowOrColumn(seq) => seq.img_paths.as_ref(),
            Self::Carousel(seq) => seq.inner().img_paths.as_ref(),
        }
    }

    #[cfg(debug_assertions)]
    pub(super) fn img_paths_as_mut(&mut self) -> Option<&mut Vec<PathBuf>> {
        match self {
            Self::RowOrColumn(seq) => seq.img_paths.as_mut(),
            Self::Carousel(seq) => seq.inner_mut().img_paths.as_mut(),
        }
    }
}

#[cfg(all(feature = "image_processing", feature = "gui"))]
impl ImageEmbedLayout {
    pub(super) fn img_specs(&self) -> Option<&Vec<Option<ImageSpec>>> {
        match self {
            Self::RowOrColumn(seq) => seq.img_specs.as_ref(),
            Self::Carousel(seq) => seq.inner().scrub.img_specs.as_ref(),
        }
    }

    pub(super) fn img_specs_mut(&mut self) {
        match self {
            Self::RowOrColumn(seq) => {
                seq.img_specs_mut();
            }
            Self::Carousel(seq) => {
                seq.inner_mut().img_specs_mut();
            }
        }
    }
}