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}