fyroxed_base 1.0.0

A scene editor for Fyrox game engine
Documentation
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

use crate::fyrox::{
    core::{
        algebra::Point2, algebra::Vector2, pool::Handle, uuid::Uuid, uuid_provider,
        TypeUuidProvider,
    },
    engine::Engine,
    graph::SceneGraph,
    gui::{BuildContext, UiNode},
};
use crate::{
    command::{Command, CommandGroup},
    interaction::{make_interaction_mode_button, InteractionMode},
    message::MessageSender,
    scene::{commands::ChangeSelectionCommand, controller::SceneController, Selection},
    settings::Settings,
    ui_scene::{commands::widget::MoveWidgetCommand, UiScene},
};
use fyrox::gui::button::Button;

struct Entry {
    widget: Handle<UiNode>,
    initial_local_position: Vector2<f32>,
    new_local_position: Vector2<f32>,
    delta: Vector2<f32>,
}

struct MoveContext {
    entries: Vec<Entry>,
}

pub struct MoveWidgetsInteractionMode {
    move_context: Option<MoveContext>,
    sender: MessageSender,
}

impl MoveWidgetsInteractionMode {
    pub fn new(sender: MessageSender) -> Self {
        Self {
            move_context: None,
            sender,
        }
    }
}

uuid_provider!(MoveWidgetsInteractionMode = "e5c09b04-5c31-4044-ac48-5227ab4a4b83");

impl InteractionMode for MoveWidgetsInteractionMode {
    fn on_left_mouse_button_down(
        &mut self,
        editor_selection: &Selection,
        controller: &mut dyn SceneController,
        _engine: &mut Engine,
        mouse_position: Vector2<f32>,
        _frame_size: Vector2<f32>,
        _settings: &Settings,
    ) {
        let Some(ui_scene) = controller.downcast_ref::<UiScene>() else {
            return;
        };

        if let Some(selection) = editor_selection.as_ui() {
            let mut in_bounds = false;
            let entries = selection
                .widgets
                .iter()
                .filter_map(|w| {
                    if let Ok(widget_ref) = ui_scene.ui.try_get_node(*w) {
                        if !in_bounds && widget_ref.screen_bounds().contains(mouse_position) {
                            in_bounds = true;
                        }

                        Some(Entry {
                            widget: *w,
                            initial_local_position: widget_ref.desired_local_position(),
                            new_local_position: widget_ref.desired_local_position(),
                            delta: mouse_position - widget_ref.screen_position(),
                        })
                    } else {
                        None
                    }
                })
                .collect();

            if in_bounds {
                self.move_context = Some(MoveContext { entries });
            }
        }
    }

    fn on_left_mouse_button_up(
        &mut self,
        editor_selection: &Selection,
        controller: &mut dyn SceneController,
        engine: &mut Engine,
        mouse_pos: Vector2<f32>,
        _frame_size: Vector2<f32>,
        _settings: &Settings,
    ) {
        let Some(ui_scene) = controller.downcast_ref::<UiScene>() else {
            return;
        };

        if let Some(context) = self.move_context.take() {
            if context
                .entries
                .iter()
                .any(|e| e.new_local_position != e.initial_local_position)
            {
                let commands = context
                    .entries
                    .into_iter()
                    .map(|e| {
                        Command::new(MoveWidgetCommand::new(
                            e.widget,
                            e.initial_local_position,
                            e.new_local_position,
                        ))
                    })
                    .collect::<Vec<_>>();
                self.sender.do_command(CommandGroup::from(commands));
            }
        } else {
            let picked = ui_scene.ui.hit_test(mouse_pos);
            if picked.is_some() {
                let mut new_selection = if let (Some(current), true) = (
                    editor_selection.as_ui(),
                    engine
                        .user_interfaces
                        .first_mut()
                        .keyboard_modifiers()
                        .control,
                ) {
                    current.clone()
                } else {
                    Default::default()
                };
                new_selection.insert_or_exclude(picked);
                self.sender
                    .do_command(ChangeSelectionCommand::new(Selection::new(new_selection)));
            }
        }
    }

    fn on_mouse_move(
        &mut self,
        _mouse_offset: Vector2<f32>,
        mouse_position: Vector2<f32>,
        _editor_selection: &Selection,
        controller: &mut dyn SceneController,
        _engine: &mut Engine,
        _frame_size: Vector2<f32>,
        _settings: &Settings,
    ) {
        let Some(ui_scene) = controller.downcast_mut::<UiScene>() else {
            return;
        };

        if let Some(move_context) = self.move_context.as_mut() {
            for entry in move_context.entries.iter_mut() {
                let new_screen_space_position = mouse_position - entry.delta;
                let parent_inv_transform = ui_scene
                    .ui
                    .try_get_node(ui_scene.ui.node(entry.widget).parent)
                    .ok()
                    .and_then(|w| w.visual_transform().try_inverse())
                    .unwrap_or_default();
                let new_local_position = parent_inv_transform.transform_point(&Point2::new(
                    new_screen_space_position.x,
                    new_screen_space_position.y,
                ));
                ui_scene
                    .ui
                    .node_mut(entry.widget)
                    .set_desired_local_position(new_local_position.coords);
                ui_scene.ui.invalidate_layout();
                entry.new_local_position = new_local_position.coords;
            }
        }
    }

    fn make_button(&mut self, ctx: &mut BuildContext, selected: bool) -> Handle<Button> {
        let move_mode_tooltip =
            "Move Object(s) - Shortcut: [2]\n\nMovement interaction mode allows you to move selected \
        objects. Keep in mind that movement always works in local coordinates!\n\n\
        This also allows you to select an object or add an object to current selection using Ctrl+Click";

        make_interaction_mode_button(
            ctx,
            include_bytes!("../../../resources/move_arrow.png"),
            move_mode_tooltip,
            selected,
        )
    }

    fn uuid(&self) -> Uuid {
        Self::type_uuid()
    }
}