simple_editor\ui/
paste_ui.rs1use crate::model::AttrSet;
7use eframe::egui;
8
9#[derive(Default)]
10pub struct PasteUi {
11 pub open: bool,
12 pub set: AttrSet,
13}
14
15#[cfg(test)]
17fn mark(ui: &egui::Ui, name: &str, r: &egui::Response) {
18 ui.ctx().data_mut(|d| d.insert_temp(egui::Id::new(("paste_btn", name)), r.rect));
19}
20#[cfg(not(test))]
21fn mark(_ui: &egui::Ui, _name: &str, _r: &egui::Response) {}
22
23pub fn show(ctx: &egui::Context, state: &mut PasteUi, source_name: &str, targets: usize) -> Option<AttrSet> {
24 if !state.open {
25 return None;
26 }
27 let mut out = None;
28 let mut close = false;
29 let mut open = true;
30 egui::Window::new("Paste Attributes").open(&mut open).resizable(false).show(ctx, |ui| {
31 if source_name.is_empty() {
32 ui.weak("Nothing copied — use Copy Attributes (Ctrl+Alt+C) first");
33 } else {
34 ui.weak(format!("from '{source_name}'"));
35 }
36 ui.horizontal(|ui| {
37 if ui.small_button("All").clicked() {
38 for (_, v) in state.set.fields() {
39 *v = true;
40 }
41 }
42 if ui.small_button("None").clicked() {
43 state.set = AttrSet::NONE;
44 }
45 });
46 ui.separator();
47 for (label, v) in state.set.fields() {
48 ui.checkbox(v, label);
49 }
50 ui.separator();
51 ui.weak(match targets {
52 1 => "1 clip will change".to_string(),
53 n => format!("{n} clips will change"),
54 });
55 let ready = targets > 0 && state.set.any() && !source_name.is_empty();
56 ui.horizontal(|ui| {
57 let r = ui.add_enabled(ready, egui::Button::new("Paste"));
58 mark(ui, "paste", &r);
59 if r.clicked() {
60 out = Some(state.set);
61 }
62 if ui.button("Close").clicked() {
63 close = true;
64 }
65 });
66 });
67 state.open = open && !close && out.is_none();
69 out
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 struct H {
77 ctx: egui::Context,
78 state: PasteUi,
79 time: f64,
80 }
81
82 impl H {
83 fn new() -> Self {
84 Self { ctx: egui::Context::default(), state: PasteUi { open: true, ..Default::default() }, time: 0.0 }
85 }
86 fn frame(&mut self, events: Vec<egui::Event>) -> Option<AttrSet> {
87 self.time += 0.05;
88 let input = egui::RawInput { time: Some(self.time), events, ..Default::default() };
89 let mut out = None;
90 let (state, _) = (&mut self.state, ());
91 let _ = self.ctx.run(input, |ctx| out = show(ctx, state, "clip.mp4", 3));
92 out
93 }
94 fn rect(&self, name: &str) -> egui::Rect {
95 self.ctx
96 .data(|d| d.get_temp::<egui::Rect>(egui::Id::new(("paste_btn", name))))
97 .expect("button rect recorded")
98 }
99 fn click(&mut self, pos: egui::Pos2) -> Option<AttrSet> {
100 self.frame(vec![egui::Event::PointerMoved(pos)]);
101 let press = egui::Event::PointerButton {
102 pos,
103 button: egui::PointerButton::Primary,
104 pressed: true,
105 modifiers: egui::Modifiers::NONE,
106 };
107 self.frame(vec![press]);
108 let release = egui::Event::PointerButton {
109 pos,
110 button: egui::PointerButton::Primary,
111 pressed: false,
112 modifiers: egui::Modifiers::NONE,
113 };
114 self.frame(vec![release])
115 }
116 }
117
118 #[test]
119 fn closed_window_returns_nothing() {
120 let ctx = egui::Context::default();
121 let mut st = PasteUi::default();
122 let _ = ctx.run(egui::RawInput::default(), |ctx| {
123 assert!(show(ctx, &mut st, "a.mp4", 1).is_none());
124 });
125 }
126
127 #[test]
128 fn paste_button_returns_the_set_and_closes() {
129 let mut h = H::new();
130 h.frame(vec![]);
131 h.frame(vec![]);
132 let r = h.rect("paste");
133 let got = h.click(r.center()).expect("Paste returns the attribute set");
134 assert_eq!(got, AttrSet::default());
135 assert!(!h.state.open, "confirming closes the window");
136 }
137
138 #[test]
139 fn all_and_none_flip_every_field() {
140 let mut set = AttrSet::NONE;
141 assert!(!set.any());
142 for (_, v) in set.fields() {
143 *v = true;
144 }
145 assert!(set.any());
146 assert!(set.fields().iter().all(|(_, v)| **v));
147 }
148}