simple_editor\ui/
import_ui.rs1use crate::engine::import::{ImportReport, Level};
7use crate::theme::Palette;
8use eframe::egui;
9
10#[derive(Default)]
11pub struct ImportUi {
12 pub open: bool,
13 pub report: Option<ImportReport>,
14}
15
16pub fn show(ctx: &egui::Context, state: &mut ImportUi, palette: &Palette) -> bool {
18 if !state.open {
19 return false;
20 }
21 let mut accepted = false;
22 let mut open = state.open;
23 let mut close = false;
24 egui::Window::new("Import Report").open(&mut open).default_width(520.0).default_height(360.0).show(ctx, |ui| {
25 let Some(r) = &state.report else {
26 ui.weak("Nothing imported.");
27 return;
28 };
29 ui.horizontal(|ui| {
30 ui.strong(&r.project.name);
31 ui.weak(format!("{}×{} @ {:.3} fps", r.project.width, r.project.height, r.project.fps));
32 });
33 ui.label(format!("{} clips on {} tracks", r.clips, r.tracks));
34 ui.horizontal(|ui| {
35 if r.missing_media > 0 {
36 ui.colored_label(ui.visuals().warn_fg_color, format!("{} missing files", r.missing_media));
37 }
38 ui.weak(format!("{} problems, {} notes", r.problems(), r.ok()));
39 });
40 ui.separator();
41 egui::ScrollArea::vertical().auto_shrink([false, false]).max_height(220.0).show(ui, |ui| {
42 egui::Grid::new("import_issues").num_columns(3).striped(true).spacing([10.0, 2.0]).show(ui, |ui| {
43 for level in [Level::Skipped, Level::Warning, Level::Ok] {
45 for i in r.issues.iter().filter(|i| i.level == level) {
46 ui.colored_label(level_color(level, ui, palette), level_name(level));
47 ui.label(&i.subject);
48 ui.label(&i.detail);
49 ui.end_row();
50 }
51 }
52 if r.issues.is_empty() {
53 ui.weak("Everything mapped cleanly.");
54 ui.end_row();
55 }
56 });
57 });
58 ui.separator();
59 ui.horizontal(|ui| {
60 if ui.add_enabled(r.clips > 0, egui::Button::new("Use this project")).clicked() {
61 accepted = true;
62 close = true;
63 }
64 if ui.button("Copy report").clicked() {
65 ui.ctx().copy_text(r.to_markdown());
66 }
67 if ui.button("Close").clicked() {
68 close = true;
69 }
70 });
71 });
72 state.open = open && !close;
73 if !state.open && !accepted {
74 state.report = None;
75 }
76 accepted
77}
78
79fn level_name(level: Level) -> &'static str {
80 match level {
81 Level::Ok => "Ok",
82 Level::Warning => "Warning",
83 Level::Skipped => "Skipped",
84 }
85}
86
87fn level_color(level: Level, ui: &egui::Ui, palette: &Palette) -> egui::Color32 {
88 match level {
89 Level::Ok => palette.text_dim,
90 Level::Warning => ui.visuals().warn_fg_color,
91 Level::Skipped => ui.visuals().error_fg_color,
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use crate::engine::import::Issue;
99 use crate::model::Project;
100
101 fn report(clips: usize) -> ImportReport {
102 ImportReport {
103 project: Project::new(),
104 issues: vec![
105 Issue { level: Level::Warning, subject: "clip 'a.mp4'".into(), detail: "media not found".into() },
106 Issue { level: Level::Ok, subject: "sequence".into(), detail: "1920×1080".into() },
107 ],
108 clips,
109 tracks: 2,
110 missing_media: 1,
111 }
112 }
113
114 #[test]
116 fn show_headless() {
117 let ctx = egui::Context::default();
118 let palette = Palette::new(true, egui::Color32::BLUE);
119 let mut state = ImportUi { open: true, report: Some(report(3)) };
120 for _ in 0..2 {
121 let _ = ctx.run(egui::RawInput::default(), |ctx| assert!(!show(ctx, &mut state, &palette)));
122 }
123 assert!(state.open && state.report.is_some());
124 state.open = false;
126 let _ = ctx.run(egui::RawInput::default(), |ctx| assert!(!show(ctx, &mut state, &palette)));
127 assert!(state.report.is_some(), "a closed window is not asked to clean up");
128 let mut empty = ImportUi { open: true, report: None };
129 let _ = ctx.run(egui::RawInput::default(), |ctx| assert!(!show(ctx, &mut empty, &palette)));
130 }
131}