use crate::{ExportModel, ExportRunModel, MainWindow, RenderJobsModel, TiltVideoExportModel};
use slint::ComponentHandle;
pub(crate) const BUSY_MESSAGE: &str = "An export is running -- wait for it or cancel it.";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ExportActivity {
pub still: bool,
pub video: bool,
pub queue_job: bool,
}
impl ExportActivity {
pub(crate) fn read(ui: &MainWindow) -> Self {
Self {
still: ui.global::<ExportModel>().get_is_exporting(),
video: ui.global::<TiltVideoExportModel>().get_is_exporting(),
queue_job: ui.global::<RenderJobsModel>().get_job_running(),
}
}
pub(crate) const fn running(self) -> bool {
self.still || self.video || self.queue_job
}
pub(crate) const fn direct(self) -> bool {
self.still || self.video
}
}
pub(crate) fn busy_refusal(ui: &MainWindow) -> Option<&'static str> {
ExportActivity::read(ui).running().then_some(BUSY_MESSAGE)
}
pub(crate) fn video_indicator_text(done: i32, total: i32, eta: &str) -> String {
let mut text = if total > 0 {
let shown = (done.max(0) + 1).min(total);
format!("Exporting video -- frame {shown} of {total}")
} else {
"Exporting video".to_string()
};
push_eta(&mut text, eta);
text
}
pub(crate) fn still_indicator_text(
progress: f32,
preset_index: i32,
preset_total: i32,
eta: &str,
) -> String {
let percent = (progress.clamp(0.0, 1.0) * 100.0).round() as i32;
let mut text = if preset_total > 1 {
let shown = (preset_index.max(0) + 1).min(preset_total);
format!("Exporting image {shown} of {preset_total} -- {percent}%")
} else {
format!("Exporting image -- {percent}%")
};
push_eta(&mut text, eta);
text
}
fn push_eta(text: &mut String, eta: &str) {
if !eta.is_empty() {
text.push_str(" \u{b7} ");
text.push_str(eta);
}
}
pub(crate) fn work_at_risk_text(activity: ExportActivity) -> Option<String> {
let mut parts = Vec::new();
if activity.still {
parts.push(
"An image export is running. Closing now stops it, and the pictures it has not \
written yet are lost.",
);
}
if activity.video {
parts.push(
"A tilt video export is running. Closing now stops it; the frames it has written \
stay in its folder.",
);
}
(!parts.is_empty()).then(|| parts.join(" "))
}
pub(crate) fn work_at_risk(ui: &MainWindow) -> Option<String> {
work_at_risk_text(ExportActivity::read(ui))
}
pub(super) fn setup_export_run_callbacks(ui: &MainWindow) {
let model = ui.global::<ExportRunModel>();
model.on_video_text(|done, total, eta| video_indicator_text(done, total, &eta).into());
model.on_still_text(|progress, index, total, eta| {
still_indicator_text(progress, index, total, &eta).into()
});
}
#[cfg(test)]
mod tests {
use super::*;
const fn activity(still: bool, video: bool, queue_job: bool) -> ExportActivity {
ExportActivity {
still,
video,
queue_job,
}
}
#[test]
fn an_export_is_running_when_any_of_the_three_is() {
assert!(!activity(false, false, false).running());
assert!(activity(true, false, false).running());
assert!(activity(false, true, false).running());
assert!(activity(false, false, true).running());
assert!(activity(true, true, true).running());
}
#[test]
fn only_a_dialog_export_is_direct() {
assert!(!activity(false, false, true).direct());
assert!(activity(true, false, false).direct());
assert!(activity(false, true, true).direct());
assert!(!activity(false, false, false).direct());
}
#[test]
fn the_video_text_names_the_frame_and_the_estimate() {
assert_eq!(
video_indicator_text(36, 181, "about 2 min left"),
"Exporting video -- frame 37 of 181 \u{b7} about 2 min left"
);
assert_eq!(
video_indicator_text(0, 181, ""),
"Exporting video -- frame 1 of 181"
);
assert_eq!(
video_indicator_text(181, 181, ""),
"Exporting video -- frame 181 of 181"
);
assert_eq!(video_indicator_text(0, 0, ""), "Exporting video");
}
#[test]
fn the_still_text_gives_the_percentage_and_the_picture_of_a_fan_out() {
assert_eq!(
still_indicator_text(0.424, 0, 1, ""),
"Exporting image -- 42%"
);
assert_eq!(
still_indicator_text(0.5, 1, 5, "about 40 s left"),
"Exporting image 2 of 5 -- 50% \u{b7} about 40 s left"
);
assert_eq!(
still_indicator_text(7.0, 0, 1, ""),
"Exporting image -- 100%"
);
assert_eq!(
still_indicator_text(-1.0, 0, 1, ""),
"Exporting image -- 0%"
);
}
#[test]
fn the_close_guard_names_the_export_that_would_stop() {
assert_eq!(work_at_risk_text(activity(false, false, false)), None);
assert_eq!(work_at_risk_text(activity(false, false, true)), None);
let image = work_at_risk_text(activity(true, false, false)).unwrap();
assert!(image.starts_with("An image export is running."));
let video = work_at_risk_text(activity(false, true, false)).unwrap();
assert!(video.starts_with("A tilt video export is running."));
}
}