fyroxed_base/ui_scene/
mod.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21pub 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                // Disable update for everything.
104                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                    // Make sure all resources loaded with relative paths only.
188                    // This will make scenes portable.
189                    if let Ok(relative_path) = make_relative_path(&item.path) {
190                        // No model was loaded yet, do it.
191                        if let Some(prefab) = engine
192                            .resource_manager
193                            .try_request::<UserInterface>(relative_path)
194                            .and_then(|m| block_on(m).ok())
195                        {
196                            // Instantiate the model.
197                            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            // Immediately after extract if from the scene to subgraph. This is required to not violate
238            // the rule of one place of execution, only commands allowed to modify the scene.
239            let sub_graph = self.ui.take_reserve_sub_graph(preview.instance);
240
241            let group = vec![
242                Command::new(AddUiPrefabCommand::new(sub_graph)),
243                // We also want to select newly instantiated model.
244                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        // Draw selection on top.
366        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        // Render to texture.
384        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        // Create new render target if preview frame has changed its size.
414        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                            // Prevent reverting property value if there's no parent resource.
581                            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}