1pub mod clipboard;
22pub mod commands;
23pub mod interaction;
24pub mod menu;
25pub mod selection;
26pub mod utils;
27
28use crate::{
29 asset::item::AssetItem,
30 command::{make_command, Command, CommandGroup, CommandStack},
31 fyrox::{
32 core::{
33 algebra::{Vector2, Vector3},
34 color::Color,
35 futures::executor::block_on,
36 log::Log,
37 make_relative_path,
38 math::Rect,
39 pool::{ErasedHandle, Handle},
40 reflect::Reflect,
41 },
42 engine::Engine,
43 fxhash::FxHashSet,
44 graph::{BaseSceneGraph, SceneGraph, SceneGraphNode},
45 gui::{
46 absm::AnimationBlendingStateMachine,
47 animation::AnimationPlayer,
48 brush::Brush,
49 draw::{CommandTexture, Draw},
50 inspector::PropertyChanged,
51 message::{KeyCode, MessageDirection, MouseButton},
52 UiNode, UiUpdateSwitches, UserInterface, UserInterfaceResourceExtension,
53 },
54 renderer::framework::gpu_texture::PixelKind,
55 resource::texture::{TextureKind, TextureResource, TextureResourceExtension},
56 scene::SceneContainer,
57 },
58 message::MessageSender,
59 plugins::{
60 absm::{command::fetch_machine, selection::SelectedEntity},
61 animation::{self, command::fetch_animations_container},
62 inspector::editors::handle::HandlePropertyEditorMessage,
63 },
64 scene::{
65 commands::ChangeSelectionCommand, controller::SceneController, selector::HierarchyNode,
66 Selection,
67 },
68 settings::{keys::KeyBindings, Settings},
69 ui_scene::{
70 clipboard::Clipboard,
71 commands::{
72 graph::AddUiPrefabCommand, widget::RevertWidgetPropertyCommand, UiSceneContext,
73 },
74 selection::UiSelection,
75 },
76 Message,
77};
78use std::{fs::File, io::Write, path::Path};
79
80pub struct PreviewInstance {
81 pub instance: Handle<UiNode>,
82 pub nodes: FxHashSet<Handle<UiNode>>,
83}
84
85pub struct UiScene {
86 pub ui: UserInterface,
87 pub render_target: TextureResource,
88 pub message_sender: MessageSender,
89 pub clipboard: Clipboard,
90 pub preview_instance: Option<PreviewInstance>,
91 pub ui_update_switches: UiUpdateSwitches,
92}
93
94impl UiScene {
95 pub fn new(ui: UserInterface, message_sender: MessageSender) -> Self {
96 Self {
97 ui,
98 render_target: TextureResource::new_render_target(200, 200),
99 message_sender,
100 clipboard: Default::default(),
101 preview_instance: None,
102 ui_update_switches: UiUpdateSwitches {
103 node_overrides: Some(Default::default()),
105 },
106 }
107 }
108
109 fn select_object(&mut self, handle: ErasedHandle) {
110 if self.ui.try_get(handle.into()).is_some() {
111 self.message_sender
112 .do_command(ChangeSelectionCommand::new(Selection::new(
113 UiSelection::single_or_empty(handle.into()),
114 )))
115 }
116 }
117}
118
119impl SceneController for UiScene {
120 fn on_key_up(
121 &mut self,
122 _key: KeyCode,
123 _engine: &mut Engine,
124 _key_bindings: &KeyBindings,
125 ) -> bool {
126 false
127 }
128
129 fn on_key_down(
130 &mut self,
131 _key: KeyCode,
132 _engine: &mut Engine,
133 _key_bindings: &KeyBindings,
134 ) -> bool {
135 false
136 }
137
138 fn on_mouse_move(
139 &mut self,
140 _pos: Vector2<f32>,
141 _offset: Vector2<f32>,
142 _screen_bounds: Rect<f32>,
143 _engine: &mut Engine,
144 _settings: &Settings,
145 ) {
146 }
147
148 fn on_mouse_up(
149 &mut self,
150 _button: MouseButton,
151 _pos: Vector2<f32>,
152 _screen_bounds: Rect<f32>,
153 _engine: &mut Engine,
154 _settings: &Settings,
155 ) {
156 }
157
158 fn on_mouse_down(
159 &mut self,
160 _button: MouseButton,
161 _pos: Vector2<f32>,
162 _screen_bounds: Rect<f32>,
163 _engine: &mut Engine,
164 _settings: &Settings,
165 ) {
166 }
167
168 fn on_mouse_wheel(&mut self, _amount: f32, _engine: &mut Engine, _settings: &Settings) {}
169
170 fn on_mouse_leave(&mut self, _engine: &mut Engine, _settings: &Settings) {}
171
172 fn on_drag_over(
173 &mut self,
174 handle: Handle<UiNode>,
175 screen_bounds: Rect<f32>,
176 engine: &mut Engine,
177 settings: &Settings,
178 ) {
179 match self.preview_instance.as_ref() {
180 None => {
181 if let Some(item) = engine
182 .user_interfaces
183 .first_mut()
184 .node(handle)
185 .cast::<AssetItem>()
186 {
187 if let Ok(relative_path) = make_relative_path(&item.path) {
190 if let Some(prefab) = engine
192 .resource_manager
193 .try_request::<UserInterface>(relative_path)
194 .and_then(|m| block_on(m).ok())
195 {
196 let (instance, _) = prefab.instantiate(&mut self.ui);
198
199 let nodes = self
200 .ui
201 .traverse_handle_iter(instance)
202 .collect::<FxHashSet<Handle<UiNode>>>();
203
204 self.preview_instance = Some(PreviewInstance { instance, nodes });
205 }
206 }
207 }
208 }
209 Some(preview) => {
210 let cursor_pos = engine.user_interfaces.first_mut().cursor_position();
211 let rel_pos = cursor_pos - screen_bounds.position;
212
213 let root = self.ui.node_mut(preview.instance);
214 root.set_desired_local_position(
215 settings
216 .move_mode_settings
217 .try_snap_vector_to_grid(Vector3::new(rel_pos.x, rel_pos.y, 0.0))
218 .xy(),
219 );
220 root.invalidate_layout();
221 }
222 }
223 }
224
225 fn on_drop(
226 &mut self,
227 handle: Handle<UiNode>,
228 _screen_bounds: Rect<f32>,
229 _engine: &mut Engine,
230 _settings: &Settings,
231 ) {
232 if handle.is_none() {
233 return;
234 }
235
236 if let Some(preview) = self.preview_instance.take() {
237 let sub_graph = self.ui.take_reserve_sub_graph(preview.instance);
240
241 let group = vec![
242 Command::new(AddUiPrefabCommand::new(sub_graph)),
243 Command::new(ChangeSelectionCommand::new(Selection::new(
245 UiSelection::single_or_empty(preview.instance),
246 ))),
247 ];
248
249 self.message_sender.do_command(CommandGroup::from(group));
250 }
251 }
252
253 fn render_target(&self, _engine: &Engine) -> Option<TextureResource> {
254 Some(self.render_target.clone())
255 }
256
257 fn extension(&self) -> &str {
258 "ui"
259 }
260
261 fn save(
262 &mut self,
263 path: &Path,
264 settings: &Settings,
265 _engine: &mut Engine,
266 ) -> Result<String, String> {
267 match self.ui.save(path) {
268 Ok(visitor) => {
269 if settings.debugging.save_scene_in_text_form {
270 let text = visitor.save_text();
271 let mut path = path.to_path_buf();
272 path.set_extension("txt");
273 if let Ok(mut file) = File::create(path) {
274 Log::verify(file.write_all(text.as_bytes()));
275 }
276 }
277
278 Ok(format!(
279 "Ui scene was successfully saved to {}",
280 path.display()
281 ))
282 }
283 Err(e) => Err(format!(
284 "Unable to save the ui scene to {} file. Reason: {:?}",
285 path.display(),
286 e
287 )),
288 }
289 }
290
291 fn do_command(
292 &mut self,
293 command_stack: &mut CommandStack,
294 command: Command,
295 selection: &mut Selection,
296 _engine: &mut Engine,
297 ) {
298 UiSceneContext::exec(
299 &mut self.ui,
300 selection,
301 self.message_sender.clone(),
302 &mut self.clipboard,
303 |ctx| {
304 command_stack.do_command(command, ctx);
305 },
306 );
307
308 self.ui.invalidate_layout();
309 }
310
311 fn undo(
312 &mut self,
313 command_stack: &mut CommandStack,
314 selection: &mut Selection,
315 _engine: &mut Engine,
316 ) {
317 UiSceneContext::exec(
318 &mut self.ui,
319 selection,
320 self.message_sender.clone(),
321 &mut self.clipboard,
322 |ctx| command_stack.undo(ctx),
323 );
324
325 self.ui.invalidate_layout();
326 }
327
328 fn redo(
329 &mut self,
330 command_stack: &mut CommandStack,
331 selection: &mut Selection,
332 _engine: &mut Engine,
333 ) {
334 UiSceneContext::exec(
335 &mut self.ui,
336 selection,
337 self.message_sender.clone(),
338 &mut self.clipboard,
339 |ctx| command_stack.redo(ctx),
340 );
341
342 self.ui.invalidate_layout();
343 }
344
345 fn clear_command_stack(
346 &mut self,
347 command_stack: &mut CommandStack,
348 selection: &mut Selection,
349 _scenes: &mut SceneContainer,
350 ) {
351 UiSceneContext::exec(
352 &mut self.ui,
353 selection,
354 self.message_sender.clone(),
355 &mut self.clipboard,
356 |ctx| command_stack.clear(ctx),
357 );
358
359 self.ui.invalidate_layout();
360 }
361
362 fn on_before_render(&mut self, editor_selection: &Selection, engine: &mut Engine) {
363 self.ui.draw();
364
365 if let Some(selection) = editor_selection.as_ui() {
367 for node in selection.widgets.iter() {
368 if let Some(node) = self.ui.try_get(*node) {
369 let bounds = node.screen_bounds();
370 let clip_bounds = node.clip_bounds();
371 let drawing_context = self.ui.get_drawing_context_mut();
372 drawing_context.push_rect(&bounds, 1.0);
373 drawing_context.commit(
374 clip_bounds,
375 Brush::Solid(Color::GREEN),
376 CommandTexture::None,
377 None,
378 );
379 }
380 }
381 }
382
383 Log::verify(
385 engine
386 .graphics_context
387 .as_initialized_mut()
388 .renderer
389 .render_ui_to_texture(
390 self.render_target.clone(),
391 self.ui.screen_size(),
392 self.ui.get_drawing_context(),
393 Color::DIM_GRAY,
394 PixelKind::RGBA8,
395 ),
396 );
397 }
398
399 fn on_after_render(&mut self, _engine: &mut Engine) {}
400
401 fn update(
402 &mut self,
403 _editor_selection: &Selection,
404 _engine: &mut Engine,
405 dt: f32,
406 _path: Option<&Path>,
407 _settings: &mut Settings,
408 screen_bounds: Rect<f32>,
409 ) -> Option<TextureResource> {
410 self.ui
411 .update(screen_bounds.size, dt, &self.ui_update_switches);
412
413 let mut new_render_target = None;
415 if let TextureKind::Rectangle { width, height } =
416 self.render_target.clone().data_ref().kind()
417 {
418 let frame_size = screen_bounds.size;
419 if width != frame_size.x as u32 || height != frame_size.y as u32 {
420 self.render_target =
421 TextureResource::new_render_target(frame_size.x as u32, frame_size.y as u32);
422 new_render_target = Some(self.render_target.clone());
423
424 self.ui.invalidate_layout();
425 }
426 }
427
428 while self.ui.poll_message().is_some() {}
429
430 new_render_target
431 }
432
433 fn is_interacting(&self) -> bool {
434 false
435 }
436
437 fn on_destroy(
438 &mut self,
439 command_stack: &mut CommandStack,
440 _engine: &mut Engine,
441 selection: &mut Selection,
442 ) {
443 UiSceneContext::exec(
444 &mut self.ui,
445 selection,
446 self.message_sender.clone(),
447 &mut self.clipboard,
448 |ctx| command_stack.clear(ctx),
449 );
450 }
451
452 fn on_message(
453 &mut self,
454 message: &Message,
455 _selection: &Selection,
456 engine: &mut Engine,
457 ) -> bool {
458 match message {
459 Message::SelectObject { handle } => {
460 self.select_object(*handle);
461 }
462 Message::SyncNodeHandleName { view, handle } => {
463 engine
464 .user_interfaces
465 .first_mut()
466 .send_message(HandlePropertyEditorMessage::name(
467 *view,
468 MessageDirection::ToWidget,
469 self.ui
470 .try_get((*handle).into())
471 .map(|n| n.name().to_owned()),
472 ));
473 }
474 Message::ProvideSceneHierarchy { view } => {
475 engine.user_interfaces.first_mut().send_message(
476 HandlePropertyEditorMessage::hierarchy(
477 *view,
478 MessageDirection::ToWidget,
479 HierarchyNode::from_ui_node(self.ui.root(), Handle::NONE, &self.ui),
480 ),
481 );
482 }
483 _ => {}
484 }
485
486 false
487 }
488
489 fn command_names(
490 &mut self,
491 command_stack: &mut CommandStack,
492 selection: &mut Selection,
493 _engine: &mut Engine,
494 ) -> Vec<String> {
495 command_stack
496 .commands
497 .iter_mut()
498 .map(|c| {
499 let mut name = String::new();
500 UiSceneContext::exec(
501 &mut self.ui,
502 selection,
503 self.message_sender.clone(),
504 &mut self.clipboard,
505 |ctx| {
506 name = c.name(ctx);
507 },
508 );
509 name
510 })
511 .collect::<Vec<_>>()
512 }
513
514 fn first_selected_entity(
515 &self,
516 selection: &Selection,
517 _scenes: &SceneContainer,
518 callback: &mut dyn FnMut(&dyn Reflect),
519 ) {
520 if let Some(selection) = selection.as_ui() {
521 if let Some(first) = selection.widgets.first() {
522 if let Some(node) = self.ui.try_get(*first).map(|n| n as &dyn Reflect) {
523 (callback)(node)
524 }
525 }
526 } else if let Some(selection) = selection.as_animation() {
527 if let Some(animation) = self
528 .ui
529 .try_get_of_type::<AnimationPlayer>(selection.animation_player)
530 .and_then(|player| player.animations().try_get(selection.animation))
531 {
532 if let Some(animation::selection::SelectedEntity::Signal(id)) =
533 selection.entities.first()
534 {
535 if let Some(signal) = animation.signals().iter().find(|s| s.id == *id) {
536 (callback)(signal as &dyn Reflect);
537 }
538 }
539 }
540 } else if let Some(selection) = selection.as_absm() {
541 if let Some(node) = self
542 .ui
543 .try_get_of_type::<AnimationBlendingStateMachine>(selection.absm_node_handle)
544 {
545 if let Some(first) = selection.entities.first() {
546 let machine = node.machine();
547 if let Some(layer_index) = selection.layer {
548 if let Some(layer) = machine.layers().get(layer_index) {
549 match first {
550 SelectedEntity::Transition(transition) => {
551 (callback)(&layer.transitions()[*transition] as &dyn Reflect)
552 }
553 SelectedEntity::State(state) => {
554 (callback)(&layer.states()[*state] as &dyn Reflect)
555 }
556 SelectedEntity::PoseNode(pose) => {
557 (callback)(&layer.nodes()[*pose] as &dyn Reflect)
558 }
559 };
560 }
561 }
562 }
563 }
564 }
565 }
566
567 fn on_property_changed(
568 &mut self,
569 args: &PropertyChanged,
570 selection: &Selection,
571 _engine: &mut Engine,
572 ) {
573 let group = if let Some(selection) = selection.as_ui() {
574 selection
575 .widgets
576 .iter()
577 .filter_map(|&node_handle| {
578 if let Some(node) = self.ui.try_get(node_handle) {
579 if args.is_inheritable() {
580 if node.resource().is_some() {
582 Some(Command::new(RevertWidgetPropertyCommand::new(
583 args.path(),
584 node_handle,
585 )))
586 } else {
587 None
588 }
589 } else {
590 make_command(args, move |ctx| {
591 ctx.get_mut::<UiSceneContext>().ui.node_mut(node_handle)
592 })
593 }
594 } else {
595 None
596 }
597 })
598 .collect::<Vec<_>>()
599 } else if let Some(selection) = selection.as_animation() {
600 if self
601 .ui
602 .try_get_of_type::<AnimationPlayer>(selection.animation_player)
603 .and_then(|player| player.animations().try_get(selection.animation))
604 .is_some()
605 {
606 let animation_player = selection.animation_player;
607 let animation = selection.animation;
608 selection
609 .entities
610 .iter()
611 .filter_map(|e| {
612 if let &animation::selection::SelectedEntity::Signal(id) = e {
613 make_command(args, move |ctx| {
614 fetch_animations_container(animation_player, ctx)[animation]
615 .signals_mut()
616 .iter_mut()
617 .find(|s| s.id == id)
618 .unwrap()
619 })
620 } else {
621 None
622 }
623 })
624 .collect()
625 } else {
626 vec![]
627 }
628 } else if let Some(selection) = selection.as_absm() {
629 if self
630 .ui
631 .try_get(selection.absm_node_handle)
632 .and_then(|n| n.component_ref::<AnimationBlendingStateMachine>())
633 .is_some()
634 {
635 if let Some(layer_index) = selection.layer {
636 let absm_node_handle = selection.absm_node_handle;
637 selection
638 .entities
639 .iter()
640 .filter_map(|ent| match *ent {
641 SelectedEntity::Transition(transition) => {
642 make_command(args, move |ctx| {
643 let machine = fetch_machine(ctx, absm_node_handle);
644 &mut machine.layers_mut()[layer_index].transitions_mut()
645 [transition]
646 })
647 }
648 SelectedEntity::State(state) => make_command(args, move |ctx| {
649 let machine = fetch_machine(ctx, absm_node_handle);
650 &mut machine.layers_mut()[layer_index].states_mut()[state]
651 }),
652 SelectedEntity::PoseNode(pose) => make_command(args, move |ctx| {
653 let machine = fetch_machine(ctx, absm_node_handle);
654 &mut machine.layers_mut()[layer_index].nodes_mut()[pose]
655 }),
656 })
657 .collect()
658 } else {
659 vec![]
660 }
661 } else {
662 vec![]
663 }
664 } else {
665 vec![]
666 };
667
668 if group.is_empty() {
669 if !args.is_inheritable() {
670 Log::err(format!("Failed to handle a property {}", args.path()))
671 }
672 } else if group.len() == 1 {
673 self.message_sender
674 .send(Message::DoCommand(group.into_iter().next().unwrap()))
675 } else {
676 self.message_sender.do_command(CommandGroup::from(group));
677 }
678 }
679
680 fn provide_docs(&self, selection: &Selection, _engine: &Engine) -> Option<String> {
681 if let Some(selection) = selection.as_ui() {
682 selection
683 .widgets
684 .first()
685 .and_then(|h| self.ui.try_get(*h).map(|n| n.doc().to_string()))
686 } else {
687 None
688 }
689 }
690}