diff --git a/examples/worlds/minimal_scene.sdf b/examples/worlds/minimal_scene.sdf index 56d9e0c3bd..5b79b16663 100644 --- a/examples/worlds/minimal_scene.sdf +++ b/examples/worlds/minimal_scene.sdf @@ -45,6 +45,14 @@ Missing for parity with GzScene3D: + + + floating + 5 + 5 + false + + diff --git a/src/gui/plugins/CMakeLists.txt b/src/gui/plugins/CMakeLists.txt index 2b0489a16c..49e4c5e94f 100644 --- a/src/gui/plugins/CMakeLists.txt +++ b/src/gui/plugins/CMakeLists.txt @@ -121,6 +121,7 @@ add_subdirectory(modules) add_subdirectory(align_tool) add_subdirectory(banana_for_scale) add_subdirectory(component_inspector) +add_subdirectory(entity_context_menu) add_subdirectory(entity_tree) add_subdirectory(joint_position_controller) add_subdirectory(lights) diff --git a/src/gui/plugins/entity_context_menu/CMakeLists.txt b/src/gui/plugins/entity_context_menu/CMakeLists.txt new file mode 100644 index 0000000000..85dddd702a --- /dev/null +++ b/src/gui/plugins/entity_context_menu/CMakeLists.txt @@ -0,0 +1,8 @@ +gz_add_gui_plugin(EntityContextMenuPlugin + SOURCES + EntityContextMenuPlugin.cc + QT_HEADERS + EntityContextMenuPlugin.hh + PUBLIC_LINK_LIBS + ${PROJECT_LIBRARY_TARGET_NAME}-rendering +) diff --git a/src/gui/plugins/entity_context_menu/EntityContextMenuPlugin.cc b/src/gui/plugins/entity_context_menu/EntityContextMenuPlugin.cc new file mode 100644 index 0000000000..df036a0fdf --- /dev/null +++ b/src/gui/plugins/entity_context_menu/EntityContextMenuPlugin.cc @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include "EntityContextMenuPlugin.hh" + +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace ignition::gazebo +{ + class EntityContextMenuPrivate + { + /// \brief Perform operations in the render thread. + public: void OnRender(); + + /// \brief Pointer to the rendering scene + public: rendering::ScenePtr scene{nullptr}; + + /// \brief User camera + public: rendering::CameraPtr camera{nullptr}; + + /// \brief Entity context menu hanlder + public: EntityContextMenuHanlder entityContextMenuHanlder; + }; +} + +using namespace ignition; +using namespace gazebo; + +///////////////////////////////////////////////// +void EntityContextMenuPrivate::OnRender() +{ + if (nullptr == this->scene) + { + this->scene = rendering::sceneFromFirstRenderEngine(); + if (nullptr == this->scene) + { + return; + } + + for (unsigned int i = 0; i < this->scene->NodeCount(); ++i) + { + auto cam = std::dynamic_pointer_cast( + this->scene->NodeByIndex(i)); + if (cam) + { + if (std::get(cam->UserData("user-camera"))) + { + this->camera = cam; + + igndbg << "Spawn plugin is using camera [" + << this->camera->Name() << "]" << std::endl; + break; + } + } + } + } +} + +///////////////////////////////////////////////// +EntityContextMenu::EntityContextMenu() + : gui::Plugin(), dataPtr(std::make_unique()) +{ + qmlRegisterType( + "RenderWindowOverlay", 1, 0, "RenderWindowOverlay"); +} + +EntityContextMenu::~EntityContextMenu() = default; + +///////////////////////////////////////////////// +void EntityContextMenu::LoadConfig(const tinyxml2::XMLElement *) +{ + EntityContextMenuItem *renderWindowOverlay = + this->PluginItem()->findChild(); + if (!renderWindowOverlay) + { + ignerr << "Unable to find Render Window Overlay item. " + << "Render window overlay will not be created" << std::endl; + return; + } + + renderWindowOverlay->SetEntityContextMenuHanlder( + this->dataPtr->entityContextMenuHanlder); + + if (this->title.empty()) + this->title = "EntityContextMenu"; + + ignition::gui::App()->findChild + ()->installEventFilter(this); +} + +//////////////////////////////////////////////// +bool EntityContextMenu::eventFilter(QObject *_obj, QEvent *_event) +{ + if (_event->type() == ignition::gui::events::Render::kType) + { + this->dataPtr->OnRender(); + } + else if (_event->type() == ignition::gui::events::RightClickOnScene::kType) + { + ignition::gui::events::RightClickOnScene *_e = + static_cast(_event); + if (_e) + { + this->dataPtr->entityContextMenuHanlder.HandleMouseContextMenu( + _e->Mouse(), this->dataPtr->camera); + } + } + + return QObject::eventFilter(_obj, _event); +} + + +///////////////////////////////////////////////// +EntityContextMenuItem::EntityContextMenuItem(QQuickItem *_parent) + : QQuickItem(_parent) +{ + this->setAcceptedMouseButtons(Qt::AllButtons); + this->setFlag(ItemHasContents); +} + +///////////////////////////////////////////////// +void EntityContextMenuItem::SetEntityContextMenuHanlder(EntityContextMenuHanlder &_entityContextMenuHanlder) +{ + entityContextMenuHanlder = &_entityContextMenuHanlder; + this->connect(entityContextMenuHanlder, + &EntityContextMenuHanlder::ContextMenuRequested, + this, &EntityContextMenuItem::OnContextMenuRequested, Qt::QueuedConnection); +} + +/////////////////////////////////////////////////// +void EntityContextMenuItem::OnContextMenuRequested(QString _entity, int _mouseX, int _mouseY) +{ + emit openContextMenu(std::move(_entity), _mouseX, _mouseY); +} + +///////////////////////////////////////////////// +EntityContextMenuHanlder::EntityContextMenuHanlder() +{ +} + +void EntityContextMenuHanlder::HandleMouseContextMenu(const common::MouseEvent &_mouseEvent, + const rendering::CameraPtr &_camera) +{ + if (!_mouseEvent.Dragging() && + _mouseEvent.Type() == common::MouseEvent::RELEASE && + _mouseEvent.Button() == common::MouseEvent::RIGHT) + { + math::Vector2i dt = + _mouseEvent.PressPos() - _mouseEvent.Pos(); + + // check for click with some tol for mouse movement + if (dt.Length() > 5.0) + return; + + rendering::VisualPtr visual =_camera->Scene()->VisualAt( + _camera, + _mouseEvent.Pos()); + + if (!visual) + return; + + // get model visual + while (visual->HasParent() && visual->Parent() != + visual->Scene()->RootVisual()) + { + visual = std::dynamic_pointer_cast(visual->Parent()); + } + + emit ContextMenuRequested( + visual->Name().c_str(), _mouseEvent.Pos().X(), _mouseEvent.Pos().Y()); + } +} + +// Register this plugin +IGNITION_ADD_PLUGIN(ignition::gazebo::EntityContextMenu, + ignition::gui::Plugin) diff --git a/src/gui/plugins/entity_context_menu/EntityContextMenuPlugin.hh b/src/gui/plugins/entity_context_menu/EntityContextMenuPlugin.hh new file mode 100644 index 0000000000..4e602caad8 --- /dev/null +++ b/src/gui/plugins/entity_context_menu/EntityContextMenuPlugin.hh @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#ifndef IGNITION_GUI_PLUGINS_ENTITY_CONTEXT_MENU_HH_ +#define IGNITION_GUI_PLUGINS_ENTITY_CONTEXT_MENU_HH_ + +#include + +#include + +#include + +namespace ignition +{ +namespace gazebo +{ + class EntityContextMenuPrivate; + + /// \brief This plugin is in charge to show the entity context menu when the + /// right button is clicked on a visual. + class EntityContextMenu : public ignition::gui::Plugin + { + Q_OBJECT + + /// \brief Constructor + public: EntityContextMenu(); + + /// \brief Destructor + public: ~EntityContextMenu() override; + + // Documentation inherited + public: void LoadConfig(const tinyxml2::XMLElement *_pluginElem) override; + + // Documentation inherited + protected: bool eventFilter(QObject *_obj, QEvent *_event) override; + + /// \internal + /// \brief Pointer to private data. + private: std::unique_ptr dataPtr; + }; + + class EntityContextMenuHanlder : public QObject + { + Q_OBJECT + + /// \brief Constructor + public: EntityContextMenuHanlder(); + + /// \brief Handle mouse event for context menu + /// \param[in] _mouseEvent Right click mouse event + /// \param[in] _camera User camera + public: void HandleMouseContextMenu(const common::MouseEvent &_mouseEvent, + const rendering::CameraPtr &_camera); + + /// \brief Signal fired when context menu event is triggered + /// \param[in] _entity Scoped name of entity. + /// \param[in] _mouseX X coordinate of the right click + /// \param[in] _mouseY Y coordinate of the right click + signals: void ContextMenuRequested(QString _entity, int _mouseX, int _mouseY); + }; + + /// \brief A QQUickItem that manages the render window + class EntityContextMenuItem : public QQuickItem + { + Q_OBJECT + + /// \brief Constructor + /// \param[in] _parent Parent item + public: explicit EntityContextMenuItem(QQuickItem *_parent = nullptr); + + /// \brief Set the entity context menu hanlder + /// \param[in] _EntityContextMenuHanlder Entity context menu hanlder + public: void SetEntityContextMenuHanlder( + EntityContextMenuHanlder &_EntityContextMenuHanlder); + + /// \brief Entity context menu hanlder + public: EntityContextMenuHanlder *entityContextMenuHanlder; + + /// \brief Signal fired to open context menu + /// Note that the function name needs to start with lowercase in order for + /// the connection to work on the QML side + /// \param[in] _entity Scoped name of entity. + /// \param[in] _mouseX X coordinate of the right click + /// \param[in] _mouseY Y coordinate of the right click + signals: void openContextMenu(QString _entity, int _mouseX, int _mouseY); // NOLINT + + /// \brief Qt callback when context menu request is received + /// \param[in] _entity Scoped name of entity. + /// \param[in] _mouseX X coordinate of the right click + /// \param[in] _mouseY Y coordinate of the right click + public slots: void OnContextMenuRequested(QString _entity, int _mouseX, int _mouseY); + }; +} +} + +#endif diff --git a/src/gui/plugins/entity_context_menu/EntityContextMenuPlugin.qml b/src/gui/plugins/entity_context_menu/EntityContextMenuPlugin.qml new file mode 100644 index 0000000000..66e2f56d28 --- /dev/null +++ b/src/gui/plugins/entity_context_menu/EntityContextMenuPlugin.qml @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +import QtQuick 2.0 +import QtQuick.Controls 2.0 +import QtQuick.Layouts 1.3 +import RenderWindowOverlay 1.0 +import IgnGazebo 1.0 as IgnGazebo + +Rectangle { + visible: false + color: "transparent" + + RenderWindowOverlay { + id: renderWindowOverlay + objectName: "renderWindowOverlay" + anchors.fill: parent + + Connections { + target: renderWindowOverlay + onOpenContextMenu: + { + entityContextMenu.open(_entity, "model", + _mouseX, _mouseY); + } + } + } + + IgnGazebo.EntityContextMenu { + id: entityContextMenu + anchors.fill: parent + } +} diff --git a/src/gui/plugins/entity_context_menu/EntityContextMenuPlugin.qrc b/src/gui/plugins/entity_context_menu/EntityContextMenuPlugin.qrc new file mode 100644 index 0000000000..96bffedd5e --- /dev/null +++ b/src/gui/plugins/entity_context_menu/EntityContextMenuPlugin.qrc @@ -0,0 +1,5 @@ + + + EntityContextMenuPlugin.qml + +