Skip to main content

pfx_editor_viewport/
viewport.rs

1use std::collections::BTreeMap;
2use std::fmt::Debug;
3use std::hash::Hash;
4use std::path::{Path, PathBuf};
5
6use egui::{Color32, Id, Key, Painter, Pos2, Rect, Response, Sense, TextureId, Ui};
7use pfx_editor_doc::{Changed, Files, History};
8use pfx_editor_shell::{Pgpu, Timing, Wake};
9use pfx_editor_style::Theme;
10use pfx_gpu::Gpu;
11use pfx_live::scene::Override;
12use pfx_load::scene::{Camera, Matrix, Scene, SceneEdit, model, multiply};
13
14use crate::camera::{self, Eye, STEP_POINTS, narrow};
15use crate::edit::{self, ScenePatch};
16use crate::gizmo::{Finished, Gizmo, Mode, Pose, poses};
17use crate::lens::{Lens, pixel};
18use crate::live::Live;
19use crate::overlay::{self, linear};
20use crate::pick::{Extents, Picker, Selection, matrix};
21use crate::watched::{self, Watched, worded};
22
23pub fn scrolled(
24    input: &egui::InputState,
25    line: f32,
26    horizontal: egui::Modifiers,
27    vertical: egui::Modifiers,
28) -> f32 {
29    let page = input.viewport_rect().height();
30    input
31        .events
32        .iter()
33        .map(|event| match event {
34            egui::Event::MouseWheel {
35                unit,
36                delta,
37                modifiers,
38                ..
39            } => {
40                let delta = match unit {
41                    egui::MouseWheelUnit::Point => *delta,
42                    egui::MouseWheelUnit::Line => line * *delta,
43                    egui::MouseWheelUnit::Page => page * *delta,
44                };
45                let is_horizontal = modifiers.matches_any(horizontal);
46                let is_vertical = modifiers.matches_any(vertical);
47                if is_horizontal && !is_vertical {
48                    0.0
49                } else if !is_horizontal && is_vertical {
50                    delta.x + delta.y
51                } else {
52                    delta.y
53                }
54            }
55            _ => 0.0,
56        })
57        .sum()
58}
59
60pub const SELECTED: f32 = 0.35;
61pub const HOVERED: f32 = 0.12;
62pub const PAUSED: &str = "paused while a game plays";
63pub const UNSAVED: &str = "holds edits that are not saved; save it before the gizmo writes it";
64const WHOLE: Rect = Rect::from_min_max(Pos2::ZERO, Pos2::new(1.0, 1.0));
65
66#[derive(Clone, Debug, PartialEq, Eq)]
67pub enum Event {
68    Picked(Option<Selection>),
69    Edited(Changed),
70    Refused(String),
71    Reloaded,
72    Broken(String),
73}
74
75#[derive(Clone, Copy, Debug, PartialEq)]
76pub enum Look {
77    Scene,
78    Orbit(Eye),
79}
80
81#[derive(Clone, Copy, Debug, PartialEq)]
82struct Grab {
83    eye: Eye,
84    origin: Pos2,
85    pan: bool,
86}
87
88#[derive(Clone, Copy, Debug, PartialEq)]
89struct Tints {
90    selected: [f32; 3],
91    hovered: [f32; 3],
92}
93
94pub struct Viewport {
95    id: Id,
96    watched: Watched,
97    rest: Option<Scene>,
98    shown: Option<Scene>,
99    picker: Option<Picker>,
100    extents: Extents,
101    previewed: Option<(usize, Pose)>,
102    revision: u64,
103    look: Look,
104    aspect: Option<f32>,
105    gizmo: Gizmo,
106    selection: Option<Selection>,
107    hover: Option<usize>,
108    grab: Option<Grab>,
109    edit: Option<SceneEdit>,
110    image: Option<Rect>,
111    pick: Option<[u32; 2]>,
112    picked: Option<u32>,
113    live: Option<Live>,
114    failure: Option<String>,
115    refusal: Option<String>,
116    pgpu: Pgpu,
117    events: Vec<Event>,
118    tints: Option<Tints>,
119    grid: bool,
120    gpu_ms: Option<f64>,
121    drops: u64,
122}
123
124fn descendants(scene: &Scene, root: &str) -> Vec<String> {
125    let mut found = vec![root.to_string()];
126    let mut index = 0;
127    while index < found.len() {
128        let parent = found[index].clone();
129        for object in &scene.objects {
130            if object.parent.as_deref() == Some(parent.as_str()) && !found.contains(&object.name) {
131                found.push(object.name.clone());
132            }
133        }
134        index += 1;
135    }
136    found
137}
138
139fn relink(scene: &mut Scene, moved: &str) -> Vec<String> {
140    let changed = descendants(scene, moved);
141    for name in &changed {
142        let Some(position) = scene.objects.iter().position(|o| &o.name == name) else {
143            continue;
144        };
145        let object = &scene.objects[position];
146        let local = model(object.at, object.rotate, object.scale);
147        let world = match object
148            .parent
149            .as_deref()
150            .and_then(|parent| scene.object(parent))
151        {
152            Some(parent) => multiply(parent.model, local),
153            None => local,
154        };
155        scene.objects[position].model = world;
156    }
157    changed
158}
159
160fn moved(rest: &Scene, shown: &Scene, object: usize) -> Option<Matrix> {
161    let before = matrix(rest.objects.get(object)?.model);
162    let after = matrix(shown.objects.get(object)?.model);
163    let delta = after * before.inverse();
164    delta.is_finite().then(|| {
165        delta
166            .to_cols_array_2d()
167            .map(|column| column.map(|v| v as f32))
168    })
169}
170
171pub fn posed(rest: &Scene, object: usize, pose: &Pose) -> Scene {
172    let mut scene = rest.clone();
173    let Some(target) = scene.objects.get_mut(object) else {
174        return scene;
175    };
176    target.at = pose.at.to_array().map(narrow);
177    target.rotate = pose.rotate.to_array().map(narrow);
178    target.scale = pose.scale.to_array().map(narrow);
179    let name = target.name.clone();
180    relink(&mut scene, &name);
181    scene
182}
183
184impl Viewport {
185    pub fn open(id_salt: impl Hash + Debug, path: impl Into<PathBuf>) -> Viewport {
186        let watched = Watched::open(path);
187        let mut viewport = Viewport {
188            id: Id::new(id_salt),
189            watched,
190            rest: None,
191            shown: None,
192            picker: None,
193            extents: Extents::new(),
194            previewed: None,
195            revision: 0,
196            look: Look::Scene,
197            aspect: None,
198            gizmo: Gizmo::default(),
199            selection: None,
200            hover: None,
201            grab: None,
202            edit: None,
203            image: None,
204            pick: None,
205            picked: None,
206            live: None,
207            failure: None,
208            refusal: None,
209            pgpu: Pgpu::Free,
210            events: Vec::new(),
211            tints: None,
212            grid: true,
213            gpu_ms: None,
214            drops: 0,
215        };
216        if let Some(scene) = viewport.watched.scene().cloned() {
217            viewport.set_rest(scene);
218        }
219        viewport
220    }
221
222    pub fn id(&self) -> Id {
223        self.id
224    }
225
226    pub fn path(&self) -> &Path {
227        self.watched.path()
228    }
229
230    pub fn scene(&self) -> Option<&Scene> {
231        self.rest.as_ref()
232    }
233
234    pub fn shown(&self) -> Option<&Scene> {
235        self.shown.as_ref()
236    }
237
238    pub fn error(&self) -> Option<&str> {
239        self.watched.error()
240    }
241
242    pub fn failure(&self) -> Option<&str> {
243        self.failure.as_deref()
244    }
245
246    pub fn refusal(&self) -> Option<&str> {
247        self.refusal.as_deref()
248    }
249
250    pub fn files(&self) -> Vec<PathBuf> {
251        self.watched.files()
252    }
253
254    pub fn reloads(&self) -> u64 {
255        self.watched.reloads()
256    }
257
258    pub fn set_wake(&mut self, wake: Wake) {
259        self.watched.set_wake(wake);
260    }
261
262    pub fn set_pgpu(&mut self, pgpu: Pgpu) {
263        self.pgpu = pgpu;
264    }
265
266    pub fn set_grid(&mut self, grid: bool) {
267        self.grid = grid;
268    }
269
270    pub fn timing(&self) -> Timing {
271        Timing {
272            gpu_ms: self.gpu_ms,
273            drops: self.drops,
274            drawn: self.live.as_ref().is_some_and(|live| live.drawn() > 0),
275        }
276    }
277
278    pub fn texture(&self) -> Option<TextureId> {
279        self.live
280            .as_ref()
281            .filter(|live| live.drawn() > 0)
282            .map(Live::texture)
283    }
284
285    pub fn live(&self) -> Option<&Live> {
286        self.live.as_ref()
287    }
288
289    pub fn selection(&self) -> Option<&Selection> {
290        self.selection.as_ref()
291    }
292
293    pub fn hovered(&self) -> Option<usize> {
294        self.hover
295    }
296
297    pub fn gizmo(&self) -> &Gizmo {
298        &self.gizmo
299    }
300
301    pub fn gizmo_mut(&mut self) -> &mut Gizmo {
302        &mut self.gizmo
303    }
304
305    pub fn look(&self) -> Look {
306        self.look
307    }
308
309    pub fn image(&self) -> Option<Rect> {
310        self.image
311    }
312
313    pub fn take_events(&mut self) -> Vec<Event> {
314        std::mem::take(&mut self.events)
315    }
316
317    pub fn select(&mut self, name: Option<&str>) -> bool {
318        let found = match (name, &self.rest) {
319            (Some(name), Some(scene)) => Selection::named(scene, name),
320            _ => None,
321        };
322        let known = name.is_none() || found.is_some();
323        if known && found != self.selection {
324            if self.gizmo.cancel() {
325                self.reshow();
326            }
327            self.selection = found;
328        }
329        known
330    }
331
332    pub fn eye(&self) -> Option<Eye> {
333        match self.look {
334            Look::Orbit(eye) => Some(eye),
335            Look::Scene => self
336                .rest
337                .as_ref()
338                .map(|scene| Eye::of(&scene.camera_or_default())),
339        }
340    }
341
342    pub fn set_frame_aspect(&mut self, aspect: Option<f32>) {
343        self.aspect = aspect;
344    }
345
346    pub fn frame_aspect(&self) -> Option<f32> {
347        self.aspect
348    }
349
350    pub fn passepartout(&self) -> Option<Rect> {
351        match (self.look, self.aspect, self.image) {
352            (Look::Scene, Some(aspect), Some(image)) => camera::passepartout(image, aspect),
353            _ => None,
354        }
355    }
356
357    fn reach(&self) -> Option<[f64; 2]> {
358        Some(camera::reach(self.image?, self.passepartout()?))
359    }
360
361    pub fn view(&self) -> Option<Eye> {
362        let eye = self.eye()?;
363        Some(match self.reach() {
364            Some(reach) => eye.widened(reach),
365            None => eye,
366        })
367    }
368
369    pub fn lens(&self) -> Option<Lens> {
370        Some(Lens::new(&self.view()?, self.image?))
371    }
372
373    pub fn set_eye(&mut self, eye: Eye) {
374        self.look = Look::Orbit(eye);
375    }
376
377    pub fn look_through_scene(&mut self) {
378        self.look = Look::Scene;
379    }
380
381    pub fn camera(&self) -> Option<Camera> {
382        let scene = self.rest.as_ref()?;
383        let base = scene.camera_or_default();
384        Some(match self.look {
385            Look::Scene => match self.reach() {
386                Some(reach) => camera::through(&base, reach),
387                None => base,
388            },
389            Look::Orbit(eye) => {
390                let bounds = self
391                    .picker
392                    .as_ref()
393                    .map_or(crate::camera::EMPTY, Picker::bounds);
394                eye.camera(&base, bounds)
395            }
396        })
397    }
398
399    pub fn frame_selection(&mut self) {
400        let Some(eye) = self.eye() else {
401            return;
402        };
403        let Some(picker) = &self.picker else {
404            return;
405        };
406        let bounds = self
407            .selection
408            .as_ref()
409            .and_then(|selection| picker.bounds_of(selection.index))
410            .unwrap_or_else(|| picker.bounds());
411        let aspect = self.image.map_or(1.6, |image| {
412            f64::from(image.width() / image.height().max(1.0))
413        });
414        self.look = Look::Orbit(eye.framed(bounds, aspect));
415    }
416
417    fn set_rest(&mut self, scene: Scene) {
418        self.gizmo.cancel();
419        self.gizmo.set_poses(poses(&scene));
420        self.selection = self
421            .selection
422            .take()
423            .and_then(|old| Selection::named(&scene, &old.name));
424        self.hover = None;
425        self.rest = Some(scene);
426        self.revision += 1;
427        self.reshow();
428    }
429
430    fn reshow(&mut self) {
431        let Some(rest) = &self.rest else {
432            return;
433        };
434        let preview = self
435            .gizmo
436            .preview()
437            .map(|(object, pose)| (object, pose.clone()));
438        let shown = match &preview {
439            Some((object, pose)) => posed(rest, *object, pose),
440            None => rest.clone(),
441        };
442        self.picker = Some(Picker::of(&shown, &mut self.extents));
443        self.shown = Some(shown);
444        self.previewed = preview;
445    }
446
447    fn follow(&mut self) {
448        if self.gizmo.preview()
449            != self
450                .previewed
451                .as_ref()
452                .map(|(object, pose)| (*object, pose))
453        {
454            self.reshow();
455        }
456    }
457
458    pub fn poll(&mut self) {
459        let event = self.watched.poll();
460        self.take(event);
461    }
462
463    pub fn check(&mut self) {
464        let event = self.watched.check();
465        self.take(event);
466    }
467
468    fn take(&mut self, event: Option<watched::Event>) {
469        match event {
470            Some(watched::Event::Reloaded(reload)) => {
471                let own = self
472                    .rest
473                    .as_ref()
474                    .is_some_and(|rest| rest.files == reload.scene.files);
475                if !own {
476                    self.set_rest(reload.scene);
477                    self.events.push(Event::Reloaded);
478                }
479            }
480            Some(watched::Event::Broken(error)) => self.events.push(Event::Broken(error)),
481            None => {}
482        }
483    }
484
485    pub fn wanted(&self) -> BTreeMap<String, Override> {
486        let mut wanted: BTreeMap<String, Override> = BTreeMap::new();
487        let Some(scene) = &self.rest else {
488            return wanted;
489        };
490        if let Some(tints) = self.tints {
491            if let Some(object) = self.hover.and_then(|index| scene.objects.get(index)) {
492                for name in descendants(scene, &object.name) {
493                    wanted.entry(name).or_default().highlight = Some(tints.hovered);
494                }
495            }
496            if let Some(selection) = &self.selection {
497                for name in descendants(scene, &selection.name) {
498                    wanted.entry(name).or_default().highlight = Some(tints.selected);
499                }
500            }
501        }
502        if let (Some(shown), Some((object, _))) = (&self.shown, &self.previewed)
503            && let Some(name) = scene.objects.get(*object).map(|o| o.name.as_str())
504            && let Some(transform) = moved(scene, shown, *object)
505        {
506            for name in descendants(scene, name) {
507                wanted.entry(name).or_default().transform = Some(transform);
508            }
509        }
510        wanted
511    }
512
513    pub fn show(&mut self, ui: &mut Ui, files: &mut Files, history: &mut History) -> Response {
514        self.poll();
515        let theme = Theme::of(ui.ctx());
516        let accent = linear(theme.accent);
517        self.tints = Some(Tints {
518            selected: accent.map(|v| v * SELECTED),
519            hovered: accent.map(|v| v * HOVERED),
520        });
521        let image = ui.available_rect_before_wrap();
522        ui.allocate_rect(image, Sense::hover());
523        let response = ui.interact(image, self.id, Sense::click_and_drag());
524        if response.clicked() || response.drag_started() {
525            response.request_focus();
526        }
527        self.image = Some(image);
528        self.resolve();
529        if self.rest.is_some() {
530            self.keys(ui, &response);
531            self.steer(ui, &response, image, files, history);
532        }
533        let painter = ui.painter_at(image);
534        self.paint(&painter, image, &theme, response.has_focus());
535        response
536    }
537
538    fn resolve(&mut self) {
539        let Some(id) = self.picked.take() else {
540            return;
541        };
542        let found = self
543            .rest
544            .as_ref()
545            .and_then(|scene| Selection::by_id(scene, id));
546        self.choose(found);
547    }
548
549    fn choose(&mut self, found: Option<Selection>) {
550        if found != self.selection {
551            if self.gizmo.cancel() {
552                self.reshow();
553            }
554            self.selection = found.clone();
555        }
556        self.events.push(Event::Picked(found));
557    }
558
559    fn keys(&mut self, ui: &Ui, response: &Response) {
560        if !(response.has_focus() || response.hovered()) {
561            return;
562        }
563        let pressed = |key: Key| ui.input(|input| input.key_pressed(key));
564        if pressed(Key::Escape) && self.gizmo.cancel() {
565            self.reshow();
566        }
567        if self.gizmo.dragging().is_none() {
568            for (key, mode) in [
569                (Key::G, Mode::Move),
570                (Key::R, Mode::Rotate),
571                (Key::S, Mode::Scale),
572            ] {
573                if pressed(key) {
574                    self.gizmo.mode = mode;
575                }
576            }
577            if pressed(Key::L) {
578                self.gizmo.space = self.gizmo.space.other();
579            }
580        }
581        if pressed(Key::F) {
582            self.frame_selection();
583        }
584        if pressed(Key::Home) {
585            self.look_through_scene();
586        }
587        let Some(eye) = self.eye() else {
588            return;
589        };
590        let turns = [
591            (Key::ArrowLeft, [-STEP_POINTS, 0.0]),
592            (Key::ArrowRight, [STEP_POINTS, 0.0]),
593            (Key::ArrowUp, [0.0, -STEP_POINTS]),
594            (Key::ArrowDown, [0.0, STEP_POINTS]),
595        ];
596        let mut moved = eye;
597        for (key, points) in turns {
598            if pressed(key) {
599                moved = moved.turn(points);
600            }
601        }
602        if pressed(Key::Plus) || pressed(Key::Equals) {
603            moved = moved.dolly(STEP_POINTS * 2.0);
604        }
605        if pressed(Key::Minus) {
606            moved = moved.dolly(-STEP_POINTS * 2.0);
607        }
608        if moved != eye {
609            self.look = Look::Orbit(moved);
610        }
611    }
612
613    fn steer(
614        &mut self,
615        ui: &Ui,
616        response: &Response,
617        image: Rect,
618        files: &mut Files,
619        history: &mut History,
620    ) {
621        let (Some(eye), Some(view)) = (self.eye(), self.view()) else {
622            return;
623        };
624        let lens = Lens::new(&view, image);
625        let (line, horizontal, vertical) = ui.ctx().options(|options| {
626            (
627                options.input_options.line_scroll_speed,
628                options.input_options.horizontal_scroll_modifier,
629                options.input_options.vertical_scroll_modifier,
630            )
631        });
632        let (origin, pointer, hover, middle, shift, ctrl, scroll) = ui.input(|input| {
633            (
634                input.pointer.press_origin(),
635                input.pointer.interact_pos(),
636                input.pointer.hover_pos(),
637                input.pointer.middle_down(),
638                input.modifiers.shift,
639                input.modifiers.ctrl,
640                scrolled(input, line, horizontal, vertical),
641            )
642        });
643        if response.drag_started() {
644            let handled = !middle
645                && !shift
646                && match (origin, self.selection.as_ref().map(|s| s.index)) {
647                    (Some(origin), Some(index)) => self.grip(index, origin, lens, files),
648                    _ => false,
649                };
650            if !handled {
651                self.grab = origin.map(|origin| Grab {
652                    eye,
653                    origin,
654                    pan: middle || shift,
655                });
656            }
657        }
658        if response.dragged()
659            && self.gizmo.dragging().is_some()
660            && let Some(at) = pointer
661        {
662            self.gizmo.to(at, ctrl);
663            self.follow();
664        }
665        if response.dragged()
666            && !self.gizmo.swallowing()
667            && self.gizmo.dragging().is_none()
668            && let Some(grab) = self.grab
669            && let Some(at) = pointer
670        {
671            let moved = [
672                f64::from(at.x - grab.origin.x),
673                f64::from(at.y - grab.origin.y),
674            ];
675            self.look = Look::Orbit(if grab.pan {
676                grab.eye.pan(moved, f64::from(image.height()))
677            } else {
678                grab.eye.turn(moved)
679            });
680        }
681        if response.drag_stopped() {
682            if let Some(finished) = self.gizmo.finish() {
683                self.commit(finished, files, history);
684            }
685            self.grab = None;
686        }
687        if response.clicked()
688            && let Some(at) = pointer
689        {
690            let handle = self
691                .selection
692                .as_ref()
693                .and_then(|selection| self.gizmo.hit(&lens, selection.index, at));
694            if handle.is_none() {
695                self.click(at, image, &lens);
696            }
697        }
698        if self.gizmo.dragging().is_none() {
699            let at = hover.filter(|_| response.hovered());
700            let handle = at
701                .zip(self.selection.as_ref())
702                .and_then(|(at, selection)| self.gizmo.hit(&lens, selection.index, at));
703            self.gizmo.hover(handle);
704            self.hover = match (at, &self.picker) {
705                (Some(at), Some(picker)) if handle.is_none() && self.grab.is_none() => {
706                    picker.cast(&lens.ray(at))
707                }
708                _ => None,
709            };
710        }
711        if response.hovered() && self.grab.is_none() && scroll != 0.0 {
712            self.look = Look::Orbit(eye.dolly(f64::from(scroll)));
713        }
714    }
715
716    fn grip(&mut self, object: usize, origin: Pos2, lens: Lens, files: &Files) -> bool {
717        if self.gizmo.hit(&lens, object, origin).is_none() {
718            return false;
719        }
720        if let Err(error) = self.scene_edit(files) {
721            self.refuse(error);
722            return true;
723        }
724        self.gizmo.begin(object, origin, lens)
725    }
726
727    fn refuse(&mut self, error: String) {
728        self.refusal = Some(error.clone());
729        self.events.push(Event::Refused(error));
730    }
731
732    fn click(&mut self, at: Pos2, image: Rect, lens: &Lens) {
733        if let Some(live) = &self.live
734            && live.drawn() > 0
735            && let Some(at) = pixel(image, at, live.size())
736        {
737            self.pick = Some(at);
738            return;
739        }
740        let found = self
741            .picker
742            .as_ref()
743            .and_then(|picker| picker.cast(&lens.ray(at)));
744        let found = found.and_then(|index| Selection::of(self.rest.as_ref()?, index));
745        self.choose(found);
746    }
747
748    pub fn pick_at(&mut self, at: Pos2) -> Option<Selection> {
749        let lens = self.lens()?;
750        let index = self.picker.as_ref()?.cast(&lens.ray(at))?;
751        Selection::of(self.rest.as_ref()?, index)
752    }
753
754    fn scene_edit(&mut self, files: &Files) -> Result<&mut SceneEdit, String> {
755        let path = self.watched.path().to_path_buf();
756        if self.edit.is_none() {
757            self.edit = Some(SceneEdit::open(&path).map_err(|error| worded(&error, &path))?);
758        }
759        let Some(edit) = self.edit.as_mut() else {
760            return Err(format!("{}: the scene is not open", path.display()));
761        };
762        let behind = edit
763            .files()
764            .any(|file| std::fs::read_to_string(file).ok().as_deref() != edit.text(file))
765            || !edit::stale(edit, files).is_empty();
766        if behind {
767            edit.reload().map_err(|error| worded(&error, &path))?;
768        }
769        if let Some(file) = edit::stale(edit, files).first() {
770            return Err(format!("{}: {UNSAVED}", file.display()));
771        }
772        Ok(edit)
773    }
774
775    fn commit(&mut self, finished: Finished, files: &mut Files, history: &mut History) {
776        let outcome = self.land(&finished, files, history);
777        match outcome {
778            Ok(Some((changed, scene, saved))) => {
779                self.refusal = saved.err();
780                match scene {
781                    Some(scene) => self.set_rest(scene),
782                    None => {
783                        self.gizmo.drop_held();
784                        self.reshow();
785                    }
786                }
787                self.events.push(Event::Edited(changed));
788            }
789            Ok(None) => {
790                self.gizmo.drop_held();
791                self.reshow();
792            }
793            Err(error) => {
794                self.gizmo.drop_held();
795                self.reshow();
796                self.refuse(error);
797            }
798        }
799    }
800
801    #[allow(clippy::type_complexity)]
802    fn land(
803        &mut self,
804        finished: &Finished,
805        files: &mut Files,
806        history: &mut History,
807    ) -> Result<Option<(Changed, Option<Scene>, Result<(), String>)>, String> {
808        let edit = self.scene_edit(files)?;
809        let Some(group) = edit::commit(edit, &finished.label, &finished.start, &finished.pose)
810            .map_err(|error| error.to_string())?
811        else {
812            return Ok(None);
813        };
814        let patch = ScenePatch::onto(files, group);
815        let changed = history
816            .apply(files, patch)
817            .map_err(|error| error.to_string())?;
818        let saved = edit::save(files, &changed).map_err(|error| error.to_string());
819        let scene = self.scene_edit(files).ok().map(|edit| edit.scene().clone());
820        Ok(Some((changed, scene, saved)))
821    }
822
823    fn paint(&self, painter: &Painter, image: Rect, theme: &Theme, focused: bool) {
824        match self.texture() {
825            Some(id) => {
826                painter.image(id, image, WHOLE, Color32::WHITE);
827            }
828            None => overlay::checker(painter, image, theme),
829        }
830        if let (Some(view), Some(picker)) = (self.view(), &self.picker) {
831            let lens = Lens::new(&view, image);
832            if self.grid {
833                overlay::grid(painter, &lens, theme);
834            }
835            if let Some(index) = self.hover
836                && self.selection.as_ref().is_none_or(|s| s.index != index)
837            {
838                overlay::outline(
839                    painter,
840                    &lens,
841                    &picker.outline(index),
842                    theme.accent.gamma_multiply(0.5),
843                    1.0,
844                );
845            }
846            if let Some(selection) = &self.selection {
847                overlay::outline(
848                    painter,
849                    &lens,
850                    &picker.outline(selection.index),
851                    theme.accent,
852                    1.5,
853                );
854            }
855            if let Some(frame) = self.passepartout() {
856                overlay::passepartout(painter, image, frame, theme);
857            }
858            if let Some(selection) = &self.selection {
859                self.gizmo.draw(painter, &lens, selection.index, theme);
860            }
861            overlay::axes(painter, image, &view.basis(), theme);
862            self.gizmo.badge(painter, image, theme);
863        }
864        let notice = match (
865            self.watched.error(),
866            self.failure.as_deref(),
867            self.refusal.as_deref(),
868        ) {
869            (Some(error), _, _) => Some((error, theme.error)),
870            (None, Some(failure), _) => Some((failure, theme.error)),
871            (None, None, Some(refusal)) => Some((refusal, theme.warning)),
872            (None, None, None) if self.pgpu.paused() => Some((PAUSED, theme.text2)),
873            _ => None,
874        };
875        if let Some((text, colour)) = notice {
876            overlay::notice(painter, image, text, colour, theme);
877        }
878        overlay::border(painter, image, theme, focused);
879    }
880
881    pub fn frame(
882        &mut self,
883        gpu: &Gpu,
884        egui: &mut egui_wgpu::Renderer,
885        pixels_per_point: f32,
886    ) -> bool {
887        if self.pgpu.paused() || self.failure.is_some() {
888            return false;
889        }
890        let (Some(image), Some(camera)) = (self.image, self.camera()) else {
891            return false;
892        };
893        if image.width() < 1.0 || image.height() < 1.0 {
894            return false;
895        }
896        let size = [
897            ((image.width() * pixels_per_point).round() as u32).max(1),
898            ((image.height() * pixels_per_point).round() as u32).max(1),
899        ];
900        let wanted = self.wanted();
901        let pick = self.pick.take();
902        let Some(rest) = &self.rest else {
903            return false;
904        };
905        let revision = self.revision;
906        let live = &mut self.live;
907        let outcome = (|| {
908            if live.is_none() {
909                *live = Some(Live::new(gpu, egui, rest, revision, camera, size)?);
910            }
911            let Some(live) = live.as_mut() else {
912                return Err("the live renderer is gone".to_string());
913            };
914            live.resize(egui, size)?;
915            if live.revision() != revision {
916                live.show(rest, revision)?;
917            }
918            live.look(camera)?;
919            live.overrides(&wanted);
920            live.draw(pick)
921        })();
922        match outcome {
923            Ok(drawn) => {
924                if let Some(id) = drawn.picked {
925                    self.picked = Some(id);
926                }
927                if let Some(live) = &self.live {
928                    self.gpu_ms = live.gpu_ms().or(self.gpu_ms);
929                    self.drops = live.drops();
930                }
931                drawn.drew || drawn.awaiting || drawn.picked.is_some()
932            }
933            Err(error) => {
934                self.failure = Some(error);
935                self.live = None;
936                false
937            }
938        }
939    }
940
941    pub fn forget(&mut self) {
942        self.live = None;
943        self.failure = None;
944        self.pick = None;
945    }
946}