From 9fa41b68028724f4b42a6c2fb41723295109e2dd Mon Sep 17 00:00:00 2001 From: Louise Poubel Date: Thu, 3 Mar 2022 11:06:24 -0800 Subject: [PATCH 1/6] Backport GridConfig improvements to Citadel's Grid3D (#363) Signed-off-by: Louise Poubel --- src/plugins/grid_3d/Grid3D.cc | 630 ++++++++++++++------------------- src/plugins/grid_3d/Grid3D.hh | 104 ++++-- src/plugins/grid_3d/Grid3D.qml | 369 ++++++++++++++++++- 3 files changed, 710 insertions(+), 393 deletions(-) diff --git a/src/plugins/grid_3d/Grid3D.cc b/src/plugins/grid_3d/Grid3D.cc index d9072ae67..177e75b22 100644 --- a/src/plugins/grid_3d/Grid3D.cc +++ b/src/plugins/grid_3d/Grid3D.cc @@ -15,97 +15,75 @@ * */ -#include -#include #include #include #include +#include +#include +#include +#include #include #include #include - -// TODO(louise) Remove these pragmas once ign-rendering is disabling the -// warnings -#ifdef _WIN32 -#pragma warning(push) -#pragma warning(disable: 4251) -#endif - #include -#include #include #include -#include - -#ifdef _MSC_VER -#pragma warning(pop) -#endif #include "Grid3D.hh" -// Default cell count -static const int kDefaultCellCount{20}; - -// Default vertical cell count -static const int kDefaultVertCellCount{0}; - -// Default cell length -static const double kDefaultCellLength{1.0}; - -// Default pose -static const ignition::math::Pose3d kDefaultPose{ignition::math::Pose3d::Zero}; - -// Default color -static const ignition::math::Color kDefaultColor{ - ignition::math::Color(0.7f, 0.7f, 0.7f, 1.0f)}; - namespace ignition { namespace gui { namespace plugins { - /// \brief Holds configuration for a grid - struct GridInfo + struct GridParam { - /// \brief Number of cells in the horizontal - int cellCount{kDefaultCellCount}; + /// \brief Horizontal cell count + int hCellCount{20}; - /// \brief Number of cells in the vertical - int vertCellCount{kDefaultVertCellCount}; + /// \brief Vertical cell count + int vCellCount{0}; - /// \brief Cell length, both horizontal and vertical - double cellLength{kDefaultCellLength}; + /// \brief Cell length + double cellLength{1.0}; - /// \brief Grid pose in the world - math::Pose3d pose{kDefaultPose}; + /// \brief 3D pose + math::Pose3d pose{math::Pose3d::Zero}; - /// \brief Grid ambient color - math::Color color{kDefaultColor}; + /// \brief Grid color + math::Color color{math::Color(0.7f, 0.7f, 0.7f, 1.0f)}; }; class Grid3DPrivate { - /// brief Parent window - public: QQuickWindow *quickWindow = nullptr; + /// \brief List of grid names. + public: QStringList nameList; + + /// \brief + std::string name; + + /// \brief Grid parameters + public: GridParam gridParam; + + /// \brief Grids to add at startup + public: std::vector startupGrids; - /// \brief We keep a pointer to the engine and rely on it not being - /// destroyed, since it is a singleton. - public: rendering::RenderEngine *engine = nullptr; + /// \brief Pointer to selected grid + rendering::GridPtr grid{nullptr}; - /// \brief We keep the scene name rather than a shared pointer because we - /// don't want to share ownership. - public: std::string sceneName{""}; + /// \brief Pointer to scene + rendering::ScenePtr scene{nullptr}; - /// \brief Engine name received at startup - public: std::string engineName{""}; + /// \brief Flag that indicates whether there are new updates to be rendered. + public: bool dirty{false}; - /// \brief Grids received from config file on startup - public: std::vector startupGrids; + /// \brief True if name list needs to be refreshed. + public: bool refreshList{true}; - /// \brief Keep track of grids we currently found on the scene - public: std::vector grids; + /// \brief Visible state + bool visible{true}; }; } } @@ -117,14 +95,12 @@ using namespace plugins; ///////////////////////////////////////////////// Grid3D::Grid3D() - : Plugin(), dataPtr(new Grid3DPrivate) + : Plugin(), dataPtr(std::make_unique()) { } ///////////////////////////////////////////////// -Grid3D::~Grid3D() -{ -} +Grid3D::~Grid3D() = default; ///////////////////////////////////////////////// void Grid3D::LoadConfig(const tinyxml2::XMLElement *_pluginElem) @@ -135,37 +111,36 @@ void Grid3D::LoadConfig(const tinyxml2::XMLElement *_pluginElem) // Configuration if (_pluginElem) { - // All grids managed belong to the same engine and scene - auto elem = _pluginElem->FirstChildElement("engine"); - if (nullptr != elem && nullptr != elem->GetText()) - this->dataPtr->engineName = elem->GetText(); - - elem = _pluginElem->FirstChildElement("scene"); - if (nullptr != elem && nullptr != elem->GetText()) - this->dataPtr->sceneName = elem->GetText(); - // For grids to be inserted at startup for (auto insertElem = _pluginElem->FirstChildElement("insert"); insertElem != nullptr; insertElem = insertElem->NextSiblingElement("insert")) { - GridInfo gridInfo; + GridParam gridParam; + // Both cell_count and horizontal_cell_count apply to horizontal for + // backwards compatibility if (auto cellCountElem = insertElem->FirstChildElement("cell_count")) - cellCountElem->QueryIntText(&gridInfo.cellCount); + cellCountElem->QueryIntText(&gridParam.hCellCount); + + if (auto cellCountElem = insertElem->FirstChildElement( + "horizontal_cell_count")) + { + cellCountElem->QueryIntText(&gridParam.hCellCount); + } if (auto vElem = insertElem->FirstChildElement("vertical_cell_count")) - vElem->QueryIntText(&gridInfo.vertCellCount); + vElem->QueryIntText(&gridParam.vCellCount); if (auto lengthElem = insertElem->FirstChildElement("cell_length")) - lengthElem->QueryDoubleText(&gridInfo.cellLength); + lengthElem->QueryDoubleText(&gridParam.cellLength); - elem = insertElem->FirstChildElement("pose"); + auto elem = insertElem->FirstChildElement("pose"); if (nullptr != elem && nullptr != elem->GetText()) { std::stringstream poseStr; poseStr << std::string(elem->GetText()); - poseStr >> gridInfo.pose; + poseStr >> gridParam.pose; } elem = insertElem->FirstChildElement("color"); @@ -173,350 +148,273 @@ void Grid3D::LoadConfig(const tinyxml2::XMLElement *_pluginElem) { std::stringstream colorStr; colorStr << std::string(elem->GetText()); - colorStr >> gridInfo.color; + colorStr >> gridParam.color; } - this->dataPtr->startupGrids.push_back(gridInfo); + this->dataPtr->startupGrids.push_back(gridParam); } } - // TODO(anyone): remove - just for testing when inserting plugin - GridInfo gridInfo; - this->dataPtr->startupGrids.push_back(gridInfo); + ignition::gui::App()->findChild< + ignition::gui::MainWindow *>()->installEventFilter(this); +} - this->connect(this->PluginItem(), &QQuickItem::windowChanged, - [=](QQuickWindow *_window) +///////////////////////////////////////////////// +bool Grid3D::eventFilter(QObject *_obj, QEvent *_event) +{ + if (_event->type() == ignition::gui::events::Render::kType) + { + if (nullptr == this->dataPtr->scene) + this->dataPtr->scene = rendering::sceneFromFirstRenderEngine(); + + if (nullptr != this->dataPtr->scene) { - if (!_window) - { - igndbg << "Changed to null window" << std::endl; - return; - } + // Create grid setup at startup + this->CreateGrids(); - this->dataPtr->quickWindow = _window; + // Update combo box + this->RefreshList(); - // Initialize after Scene3D plugins - this->connect(this->dataPtr->quickWindow, &QQuickWindow::beforeRendering, - this, &Grid3D::Initialize, Qt::DirectConnection); - }); + // Update selected grid + this->UpdateGrid(); + } + } + + // Standard event processing + return QObject::eventFilter(_obj, _event); } ///////////////////////////////////////////////// -void Grid3D::Initialize() +void Grid3D::CreateGrids() { - // Render engine - auto loadedEngNames = rendering::loadedEngines(); - if (loadedEngNames.empty()) - { - // Keep trying until an engine is loaded + if (this->dataPtr->startupGrids.empty()) return; - } - if (this->dataPtr->engineName.empty()) + for (const auto &gridParam : this->dataPtr->startupGrids) { - this->dataPtr->engineName = loadedEngNames[0]; - } + auto grid = this->dataPtr->scene->CreateGrid(); + grid->SetCellCount(gridParam.hCellCount); + grid->SetVerticalCellCount(gridParam.vCellCount); + grid->SetCellLength(gridParam.cellLength); + + auto gridVis = this->dataPtr->scene->CreateVisual(); + this->dataPtr->scene->RootVisual()->AddChild(gridVis); + gridVis->SetLocalPose(gridParam.pose); + gridVis->AddGeometry(grid); - if (this->dataPtr->engineName != loadedEngNames[0]) - { - ignwarn << "Trying to load engine [" + this->dataPtr->engineName - + "] but [" + loadedEngNames[0] - + "] is already loaded." << std::endl; + auto mat = this->dataPtr->scene->CreateMaterial(); + mat->SetAmbient(gridParam.color); + mat->SetDiffuse(gridParam.color); + mat->SetSpecular(gridParam.color); + gridVis->SetMaterial(mat); - this->disconnect(this->dataPtr->quickWindow, &QQuickWindow::beforeRendering, - this, &Grid3D::Initialize); + this->dataPtr->dirty = true; - return; + igndbg << "Created grid [" << grid->Name() << "]" << std::endl; } + this->dataPtr->startupGrids.clear(); +} - if (nullptr == this->dataPtr->engine) - this->dataPtr->engine = rendering::engine(this->dataPtr->engineName); - - if (nullptr == this->dataPtr->engine) - { - ignwarn << "Failed to get engine [" + this->dataPtr->engineName - + "]" << std::endl; - - this->disconnect(this->dataPtr->quickWindow, &QQuickWindow::beforeRendering, - this, &Grid3D::Initialize); +///////////////////////////////////////////////// +void Grid3D::UpdateGrid() +{ + // Connect to a grid + if (!this->dataPtr->grid) + this->ConnectToGrid(); + // If not connected, don't update + if (!this->dataPtr->grid) return; - } - if (this->dataPtr->engine->SceneCount() == 0) - { - // Scene may not be loaded yet, keep trying + if (!this->dataPtr->dirty) return; - } - // Scene - rendering::ScenePtr scene; - if (!this->dataPtr->sceneName.empty()) + this->dataPtr->grid->SetVerticalCellCount( + this->dataPtr->gridParam.vCellCount); + this->dataPtr->grid->SetCellCount( + this->dataPtr->gridParam.hCellCount); + this->dataPtr->grid->SetCellLength( + this->dataPtr->gridParam.cellLength); + + auto visual = this->dataPtr->grid->Parent(); + if (visual) { - scene = this->dataPtr->engine->SceneByName(this->dataPtr->sceneName); + visual->SetLocalPose(this->dataPtr->gridParam.pose); + + auto mat = visual->Material(); + if (mat) + { + mat->SetAmbient(this->dataPtr->gridParam.color); + mat->SetDiffuse(this->dataPtr->gridParam.color); + mat->SetSpecular(this->dataPtr->gridParam.color); + } + else + { + ignerr << "Grid visual missing material" << std::endl; + } + + visual->SetVisible(this->dataPtr->visible); } else { - scene = this->dataPtr->engine->SceneByIndex(0); + ignerr << "Grid missing parent visual" << std::endl; } - if (!scene) - { - // Scene may not be loaded yet, keep trying + + this->dataPtr->dirty = false; +} + +///////////////////////////////////////////////// +void Grid3D::ConnectToGrid() +{ + if (this->dataPtr->name.empty()) return; - } - auto root = scene->RootVisual(); + if (this->dataPtr->grid) + return; - // Initial grids - for (const auto &g : this->dataPtr->startupGrids) + for (unsigned int i = 0; i < this->dataPtr->scene->VisualCount(); ++i) { - auto grid = scene->CreateGrid(); - grid->SetCellCount(g.cellCount); - grid->SetVerticalCellCount(g.vertCellCount); - grid->SetCellLength(g.cellLength); - - auto gridVis = scene->CreateVisual(); - root->AddChild(gridVis); - gridVis->SetLocalPose(g.pose); - gridVis->AddGeometry(grid); + auto vis = this->dataPtr->scene->VisualByIndex(i); + if (!vis || vis->GeometryCount() == 0) + continue; + for (unsigned int j = 0; j < vis->GeometryCount(); ++j) + { + auto grid = std::dynamic_pointer_cast( + vis->GeometryByIndex(j)); + if (grid && grid->Name() == this->dataPtr->name) + { + this->dataPtr->grid = grid; + + igndbg << "Connected to grid [" << grid->Name() << "]" << std::endl; + + // TODO(chapulina) Set to the grid's visible state when that's available + // through ign-rendering's API + this->dataPtr->visible = true; + grid->Parent()->SetVisible(true); + + this->dataPtr->gridParam.hCellCount = grid->CellCount(); + this->dataPtr->gridParam.vCellCount = grid->VerticalCellCount(); + this->dataPtr->gridParam.cellLength = grid->CellLength(); + this->dataPtr->gridParam.pose = grid->Parent()->LocalPose(); + this->dataPtr->gridParam.color = grid->Parent()->Material()->Ambient(); + this->newParams( + grid->CellCount(), + grid->VerticalCellCount(), + grid->CellLength(), + convert(grid->Parent()->LocalPose().Pos()), + convert(grid->Parent()->LocalPose().Rot().Euler()), + convert(grid->Parent()->Material()->Ambient())); + } + } + } +} - auto mat = scene->CreateMaterial(); - mat->SetAmbient(g.color); - gridVis->SetMaterial(mat); +///////////////////////////////////////////////// +void Grid3D::OnName(const QString &_name) +{ + this->dataPtr->name = _name.toStdString(); - igndbg << "Created grid [" << grid->Name() << "]" << std::endl; - } + // Set it to null so we load the new grid + this->dataPtr->grid = nullptr; + + // Don't change the grid we're about to connected to + this->dataPtr->dirty = false; +} + +///////////////////////////////////////////////// +QStringList Grid3D::NameList() const +{ + return this->dataPtr->nameList; +} + +///////////////////////////////////////////////// +void Grid3D::SetNameList(const QStringList &_nameList) +{ + this->dataPtr->nameList = _nameList; + this->NameListChanged(); +} + +///////////////////////////////////////////////// +void Grid3D::UpdateVCellCount(int _cellCount) +{ + this->dataPtr->gridParam.vCellCount = _cellCount; + this->dataPtr->dirty = true; +} - this->disconnect(this->dataPtr->quickWindow, &QQuickWindow::beforeRendering, - this, &Grid3D::Initialize); +///////////////////////////////////////////////// +void Grid3D::UpdateHCellCount(int _cellCount) +{ + this->dataPtr->gridParam.hCellCount = _cellCount; + this->dataPtr->dirty = true; +} + +///////////////////////////////////////////////// +void Grid3D::UpdateCellLength(double _length) +{ + this->dataPtr->gridParam.cellLength = _length; + this->dataPtr->dirty = true; +} - this->Refresh(); +///////////////////////////////////////////////// +void Grid3D::SetPose( + double _x, double _y, double _z, + double _roll, double _pitch, double _yaw) +{ + this->dataPtr->gridParam.pose = math::Pose3d(_x, _y, _z, _roll, _pitch, _yaw); + this->dataPtr->dirty = true; } ///////////////////////////////////////////////// -void Grid3D::Refresh() +void Grid3D::SetColor(double _r, double _g, double _b, double _a) { -// auto mainLayout = this->layout(); -// // Clear previous layout -// if (mainLayout) -// { -// while (mainLayout->count() != 1) -// { -// auto item = mainLayout->takeAt(1); -// if (qobject_cast(item->widget())) -// { -// delete item->widget(); -// delete item; -// } -// } -// } -// // Creating layout for the first time -// else -// { -// mainLayout = new QVBoxLayout(); -// mainLayout->setContentsMargins(0, 0, 0, 0); -// mainLayout->setSpacing(0); -// this->setLayout(mainLayout); -// -// auto addButton = new QPushButton("New grid"); -// addButton->setObjectName("addGridButton"); -// addButton->setToolTip("Add a new grid with default values"); -// this->connect(addButton, SIGNAL(clicked()), this, SLOT(OnAdd())); -// -// auto refreshButton = new QPushButton("Refresh"); -// refreshButton->setObjectName("refreshGridButton"); -// refreshButton->setToolTip("Refresh the list of grids"); -// this->connect(refreshButton, SIGNAL(clicked()), this, SLOT(Refresh())); -// -// auto buttonsLayout = new QHBoxLayout(); -// buttonsLayout->addWidget(addButton); -// buttonsLayout->addWidget(refreshButton); -// -// auto buttonsWidget = new QWidget(); -// buttonsWidget->setLayout(buttonsLayout); -// -// mainLayout->addWidget(buttonsWidget); -// } -// -// auto scene = this->dataPtr->engine->SceneByName(this->dataPtr->sceneName); -// // Scene has been destroyed -// if (!scene) -// { -// // Delete buttons -// auto item = mainLayout->takeAt(0); -// if (item) -// { -// delete item->widget(); -// delete item; -// } -// -// // Add message -// auto msg = new QLabel(QString::fromStdString( -// "Scene \"" + this->dataPtr->sceneName + "\" has been destroyed.\n" -// + "Create a new scene and then open a new Grid plugin.")); -// mainLayout->addWidget(msg); -// mainLayout->setAlignment(msg, Qt::AlignCenter); -// return; -// } -// -// -// // Search for all grids currently in the scene -// this->dataPtr->grids.clear(); -// for (unsigned int i = 0; i < scene->VisualCount(); ++i) -// { -// auto vis = scene->VisualByIndex(i); -// if (!vis || vis->GeometryCount() == 0) -// continue; -// -// rendering::GridPtr grid; -// for (unsigned int j = 0; j < vis->GeometryCount(); ++j) -// { -// grid = std::dynamic_pointer_cast( -// vis->GeometryByIndex(j)); -// if (grid) -// break; -// } -// if (!grid) -// continue; -// -// this->dataPtr->grids.push_back(grid); -// auto gridName = QString::fromStdString(grid->Name()); -// -// auto cellCountWidget = new NumberWidget("Horizontal cell count", -// NumberType::UINT); -// cellCountWidget->SetValue(QVariant::fromValue(grid->CellCount())); -// cellCountWidget->setProperty("gridName", gridName); -// cellCountWidget->setObjectName("cellCountWidget"); -// this->connect(cellCountWidget, SIGNAL(ValueChanged(QVariant)), this, -// SLOT(OnChange(QVariant))); -// -// auto vertCellCountWidget = new NumberWidget("Vertical cell count", -// NumberType::UINT); -// vertCellCountWidget->SetValue( -// QVariant::fromValue(grid->VerticalCellCount())); -// vertCellCountWidget->setProperty("gridName", gridName); -// vertCellCountWidget->setObjectName("vertCellCountWidget"); -// this->connect(vertCellCountWidget, SIGNAL(ValueChanged(QVariant)), this, -// SLOT(OnChange(QVariant))); -// -// auto cellLengthWidget = new NumberWidget("Cell length", -// NumberType::DOUBLE); -// cellLengthWidget->SetValue(QVariant::fromValue(grid->CellLength())); -// cellLengthWidget->setProperty("gridName", gridName); -// cellLengthWidget->setObjectName("cellLengthWidget"); -// this->connect(cellLengthWidget, SIGNAL(ValueChanged(QVariant)), this, -// SLOT(OnChange(QVariant))); -// -// auto poseWidget = new Pose3dWidget(); -// poseWidget->SetValue(QVariant::fromValue(grid->Parent()->WorldPose())); -// poseWidget->setProperty("gridName", gridName); -// poseWidget->setObjectName("poseWidget"); -// this->connect(poseWidget, SIGNAL(ValueChanged(QVariant)), this, -// SLOT(OnChange(QVariant))); -// -// auto colorWidget = new ColorWidget(); -// colorWidget->SetValue(QVariant::fromValue(grid->Material()->Ambient())); -// colorWidget->setProperty("gridName", gridName); -// colorWidget->setObjectName("colorWidget"); -// this->connect(colorWidget, SIGNAL(ValueChanged(QVariant)), this, -// SLOT(OnChange(QVariant))); -// -// auto deleteButton = new QPushButton("Delete grid"); -// deleteButton->setToolTip("Delete grid " + gridName); -// deleteButton->setProperty("gridName", gridName); -// deleteButton->setObjectName("deleteButton"); -// this->connect(deleteButton, SIGNAL(clicked()), this, SLOT(OnDelete())); -// -// auto collapsible = new CollapsibleWidget(grid->Name()); -// collapsible->AppendContent(cellCountWidget); -// collapsible->AppendContent(vertCellCountWidget); -// collapsible->AppendContent(cellLengthWidget); -// collapsible->AppendContent(poseWidget); -// collapsible->AppendContent(colorWidget); -// collapsible->AppendContent(deleteButton); -// -// mainLayout->addWidget(collapsible); -// } -// -// auto spacer = new QWidget(); -// spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); -// mainLayout->addWidget(spacer); + this->dataPtr->gridParam.color = math::Color(_r, _g, _b, _a); + this->dataPtr->dirty = true; } ///////////////////////////////////////////////// -void Grid3D::OnChange(const QVariant & /*_value*/) +void Grid3D::OnShow(bool _checked) { -// auto gridName = -// this->sender()->property("gridName").toString().toStdString(); -// auto type = this->sender()->objectName().toStdString(); -// -// for (auto grid : this->dataPtr->grids) -// { -// if (grid->Name() != gridName) -// continue; -// -// if (type == "cellCountWidget") -// grid->SetCellCount(_value.toInt()); -// else if (type == "vertCellCountWidget") -// grid->SetVerticalCellCount(_value.toInt()); -// else if (type == "cellLengthWidget") -// grid->SetCellLength(_value.toDouble()); -// else if (type == "poseWidget") -// grid->Parent()->SetWorldPose(_value.value()); -// else if (type == "colorWidget") -// grid->Material()->SetAmbient(_value.value()); -// -// break; -// } + this->dataPtr->visible = _checked; + this->dataPtr->dirty = true; } ///////////////////////////////////////////////// -void Grid3D::OnDelete() +void Grid3D::OnRefresh() { -// auto gridName = -// this->sender()->property("gridName").toString().toStdString(); -// -// for (auto grid : this->dataPtr->grids) -// { -// if (grid->Name() != gridName) -// continue; -// -// grid->Scene()->DestroyVisual(grid->Parent()); -// this->dataPtr->grids.erase(std::remove(this->dataPtr->grids.begin(), -// this->dataPtr->grids.end(), grid), -// this->dataPtr->grids.end()); -// -// this->Refresh(); -// break; -// } + this->dataPtr->refreshList = true; } ///////////////////////////////////////////////// -void Grid3D::OnAdd() +void Grid3D::RefreshList() { -// auto scene = this->dataPtr->engine->SceneByName(this->dataPtr->sceneName); -// if (!scene) -// { -// return; -// } -// -// auto root = scene->RootVisual(); -// -// auto grid = scene->CreateGrid(); -// grid->SetCellCount(kDefaultCellCount); -// grid->SetVerticalCellCount(kDefaultVertCellCount); -// grid->SetCellLength(kDefaultCellLength); -// -// auto gridVis = scene->CreateVisual(); -// root->AddChild(gridVis); -// gridVis->SetLocalPose(kDefaultPose); -// gridVis->AddGeometry(grid); -// -// auto mat = scene->CreateMaterial(); -// mat->SetAmbient(kDefaultColor); -// gridVis->SetMaterial(mat); -// -// this->Refresh(); + if (!this->dataPtr->refreshList) + return; + this->dataPtr->refreshList = false; + + // Clear + this->dataPtr->nameList.clear(); + + // Get updated list + for (unsigned int i = 0; i < this->dataPtr->scene->VisualCount(); ++i) + { + auto vis = this->dataPtr->scene->VisualByIndex(i); + if (!vis || vis->GeometryCount() == 0) + continue; + for (unsigned int j = 0; j < vis->GeometryCount(); ++j) + { + auto grid = std::dynamic_pointer_cast( + vis->GeometryByIndex(j)); + if (grid) + { + this->dataPtr->nameList.push_back(QString::fromStdString(grid->Name())); + } + } + } + + // Select first one + if (this->dataPtr->nameList.count() > 0) + this->OnName(this->dataPtr->nameList.at(0)); + this->NameListChanged(); } // Register this plugin diff --git a/src/plugins/grid_3d/Grid3D.hh b/src/plugins/grid_3d/Grid3D.hh index 41464dfc2..1d26a3740 100644 --- a/src/plugins/grid_3d/Grid3D.hh +++ b/src/plugins/grid_3d/Grid3D.hh @@ -20,7 +20,6 @@ #include -#include "ignition/gui/qt.h" #include "ignition/gui/Plugin.hh" namespace ignition @@ -31,24 +30,20 @@ namespace plugins { class Grid3DPrivate; + // TODO(chapulina) Delete this plugin when forward porting to `ign-gui6` in + // favor of `GridConfig` + /// \brief Manages grids in an Ignition Rendering scene. This plugin can be /// used for: - /// * Adding grids /// * Introspecting grids /// * Editing grids - /// * Deleting grids /// /// ## Configuration /// - /// * \ : Optional render engine name, defaults to the first loaded - /// engine. - /// * \ : Optional scene name, defaults to the first loaded scene. - /// * \ : Set to true so the plugin closes after grids given by - /// \ tags are added to the scene. /// * \ : One grid will be inserted at startup for each \ /// tag. - /// * \ : Number of cells in the horizontal direction, defaults - /// to 20. + /// * \ : Number of cells in the horizontal + /// direction, defaults to 20. /// * \ : Number of cells in the vertical direction, /// defaults to 0; /// * \ : Length of each cell, defaults to 1. @@ -58,30 +53,97 @@ namespace plugins { Q_OBJECT + /// \brief Name list + Q_PROPERTY( + QStringList nameList + READ NameList + WRITE SetNameList + NOTIFY NameListChanged + ) + /// \brief Constructor public: Grid3D(); /// \brief Destructor - public: virtual ~Grid3D(); + public: ~Grid3D() override; // Documentation inherited public: virtual void LoadConfig(const tinyxml2::XMLElement *_pluginElem) override; - private slots: void Initialize(); + // Documentation inherited + protected: bool eventFilter(QObject *_obj, QEvent *_event) override; + + /// \brief Create grids defined at startup + public: void CreateGrids(); + + /// \brief Update grid + public: void UpdateGrid(); + + /// \brief Callback to retrieve existing grid. + public: void ConnectToGrid(); + + /// \brief Refresh list of grids. This is called in the rendering thread. + public: void RefreshList(); + + /// \brief Callback when refresh button is pressed. + public slots: void OnRefresh(); + + /// \brief Callback when a new name is chosen on the combo box. + /// \param[in] _name Grid name + public slots: void OnName(const QString &_name); + + /// \brief Get the list of grid names + /// \return List of grids. + public slots: QStringList NameList() const; + + /// \brief Set the list of names + /// \param[in] _nameList List of names + public slots: void SetNameList(const QStringList &_nameList); + + /// \brief Notify that name list has changed + signals: void NameListChanged(); + + /// \brief Callback to update vertical cell count + /// \param[in] _cellCount new vertical cell count + public slots: void UpdateVCellCount(int _cellCount); + + /// \brief Callback to update horizontal cell count + /// \param[in] _cellCount new horizontal cell count + public slots: void UpdateHCellCount(int _cellCount); + + /// \brief Callback to update cell length + /// \param[in] _length new cell length + public slots: void UpdateCellLength(double _length); - /// \brief Called when a value changes on a widget - /// \param[in] _value New value - private slots: void OnChange(const QVariant &_value); + /// \brief Callback to update grid pose + /// \param[in] _x, _y, _z cartesion coordinates + /// \param[in] _roll, _pitch, _yaw principal coordinates + public slots: void SetPose(double _x, double _y, double _z, + double _roll, double _pitch, double _yaw); - /// \brief Callback when a delete button is pressed. - private slots: void OnDelete(); + /// \brief Callback to update grid color + /// \param[in] _r, _g, _b, _a RGB color model with fourth alpha channel + public slots: void SetColor(double _r, double _g, double _b, double _a); - /// \brief Callback when the add button is pressed. - private slots: void OnAdd(); + /// \brief Callback when checkbox is clicked. + /// \param[in] _checked indicates show or hide grid + public slots: void OnShow(bool _checked); - /// \brief Callback when the refresh button is pressed. - private slots: void Refresh(); + /// \brief Notify QML that grid values have changed. + /// \param[in] _hCellCount Horizontal cell count + /// \param[in] _vCellCount Vertical cell count + /// \param[in] _cellLength Cell length + /// \param[in] _pos XYZ Position + /// \param[in] _rot RPY orientation + /// \param[in] _color Grid color + signals: void newParams( + int _hCellCount, + int _vCellCount, + double _cellLength, + QVector3D _pos, + QVector3D _rot, + QColor _color); /// \internal /// \brief Pointer to private data. diff --git a/src/plugins/grid_3d/Grid3D.qml b/src/plugins/grid_3d/Grid3D.qml index 1603eae1d..bbea66d0e 100644 --- a/src/plugins/grid_3d/Grid3D.qml +++ b/src/plugins/grid_3d/Grid3D.qml @@ -14,13 +14,370 @@ * limitations under the License. * */ -import QtQuick 2.0 -import QtQuick.Controls 2.0 +import QtQuick 2.9 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtQuick.Dialogs 1.0 import QtQuick.Layouts 1.3 +import "qrc:/qml" -Rectangle { - Layout.minimumWidth: 100 - Layout.minimumHeight: 100 -} +GridLayout { + columns: 4 + columnSpacing: 10 + Layout.minimumWidth: 300 + Layout.minimumHeight: 625 + anchors.fill: parent + anchors.leftMargin: 10 + anchors.rightMargin: 10 + + // Get number of decimal digits based on a widget's width + // TODO(chapulina) Move this to a common place so all widgets can use it + function getDecimals(_width) { + if (_width <= 80) + return 2; + else if (_width <= 100) + return 4; + return 6; + } + + Connections { + target: Grid3D + onNewParams: { + horizontalCellCount.value = _hCellCount; + verticalCellCount.value = _vCellCount; + cellLength.value = _cellLength; + x.value = _pos.x; + y.value = _pos.y; + z.value = _pos.z; + roll.value = _rot.x; + pitch.value = _rot.y; + yaw.value = _rot.z; + r.value = _color.r; + g.value = _color.g; + b.value = _color.b; + a.value = _color.a; + } + } + + ComboBox { + id: combo + Layout.columnSpan: 2 + Layout.fillWidth: true + model: Grid3D.nameList + onCurrentIndexChanged: { + if (currentIndex < 0) + return; + + Grid3D.OnName(textAt(currentIndex)); + } + popup.width: parent.width + ToolTip.visible: hovered + ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval + ToolTip.text: qsTr("Grids in the scene") + } + + RoundButton { + text: "\u21bb" + Layout.columnSpan: 1 + Material.background: Material.primary + onClicked: { + Grid3D.OnRefresh(); + } + ToolTip.visible: hovered + ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval + ToolTip.text: qsTr("Refresh list of grids") + } + + CheckBox { + Layout.alignment: Qt.AlignHCenter + id: showgrid + Layout.columnSpan: 1 + text: qsTr("Show") + checked: true + onClicked: { + Grid3D.OnShow(checked) + } + } + + Text { + Layout.columnSpan: 4 + text: "Cell Count" + color: "dimgrey" + font.bold: true + } + + Text { + Layout.columnSpan: 2 + id: vercelltext + color: "dimgrey" + text: "Vertical" + } + + IgnSpinBox { + Layout.columnSpan: 2 + Layout.fillWidth: true + id: verticalCellCount + maximumValue: Number.MAX_VALUE + minimumValue: 0 + value: 0 + onEditingFinished: Grid3D.UpdateVCellCount(verticalCellCount.value) + } + + Text { + Layout.columnSpan: 2 + id: honcelltext + color: "dimgrey" + text: "Horizontal" + } + + IgnSpinBox { + Layout.columnSpan: 2 + Layout.fillWidth: true + id: horizontalCellCount + maximumValue: Number.MAX_VALUE + minimumValue: 1 + value: 20 + onEditingFinished: Grid3D.UpdateHCellCount(horizontalCellCount.value) + } + + Text { + Layout.columnSpan: 4 + id: celllengthtext + text: "Cell Length" + color: "dimgrey" + font.bold: true + } + + Text { + Layout.columnSpan: 2 + id: length + color: "dimgrey" + text: "Length (m)" + } + IgnSpinBox { + Layout.columnSpan: 2 + Layout.fillWidth: true + id: cellLength + maximumValue: Number.MAX_VALUE + minimumValue: 0.0000001 + value: 1.00 + decimals: getDecimals(cellLength.width) + stepSize: 0.01 + onEditingFinished: Grid3D.UpdateCellLength(cellLength.value) + } + + Text { + Layout.columnSpan: 2 + id: cartesian + color: "dimgrey" + font.bold: true + text: "Position (m)" + } + + Text { + Layout.columnSpan: 2 + id: principal + text: "Rotation (rad)" + color: "dimgrey" + font.bold: true + } + + Text { + text: "X" + color: "dimgrey" + } + + IgnSpinBox { + Layout.fillWidth: true + id: x + value: 0.00 + maximumValue: Number.MAX_VALUE + minimumValue: -Number.MAX_VALUE + decimals: getDecimals(x.width) + stepSize: 0.01 + onEditingFinished: Grid3D.SetPose(x.value, y.value, z.value, roll.value, pitch.value, yaw.value) + } + + Text { + text: "Roll" + color: "dimgrey" + } + + IgnSpinBox { + Layout.fillWidth: true + id: roll + maximumValue: 6.28 + minimumValue: 0.00 + value: 0.00 + decimals: getDecimals(roll.width) + stepSize: 0.01 + onEditingFinished: Grid3D.SetPose(x.value, y.value, z.value, roll.value, pitch.value, yaw.value) + } + Text { + text: "Y" + color: "dimgrey" + } + + IgnSpinBox { + Layout.fillWidth: true + id: y + value: 0.00 + maximumValue: Number.MAX_VALUE + minimumValue: -Number.MAX_VALUE + decimals: getDecimals(y.width) + stepSize: 0.01 + onEditingFinished: Grid3D.SetPose(x.value, y.value, z.value, roll.value, pitch.value, yaw.value) + } + + Text { + text: "Pitch" + color: "dimgrey" + } + + IgnSpinBox { + Layout.fillWidth: true + id: pitch + maximumValue: 6.28 + minimumValue: 0.00 + value: 0.00 + decimals: getDecimals(pitch.width) + stepSize: 0.01 + onEditingFinished: Grid3D.SetPose(x.value, y.value, z.value, roll.value, pitch.value, yaw.value) + } + + Text { + text: "Z" + color: "dimgrey" + } + + IgnSpinBox { + Layout.fillWidth: true + id: z + value: 0.00 + maximumValue: Number.MAX_VALUE + minimumValue: -Number.MAX_VALUE + decimals: getDecimals(z.width) + stepSize: 0.01 + onEditingFinished: Grid3D.SetPose(x.value, y.value, z.value, roll.value, pitch.value, yaw.value) + } + + Text { + text: "Yaw" + color: "dimgrey" + } + + IgnSpinBox { + Layout.fillWidth: true + id: yaw + maximumValue: 6.28 + minimumValue: 0.00 + value: 0.00 + decimals: getDecimals(yaw.width) + stepSize: 0.01 + onEditingFinished: Grid3D.SetPose(x.value, y.value, z.value, roll.value, pitch.value, yaw.value) + } + + Text { + Layout.columnSpan: 4 + text: "Color" + color: "dimgrey" + font.bold: true + } + + Text { + text: "R" + color: "dimgrey" + } + + IgnSpinBox { + Layout.fillWidth: true + id: r + maximumValue: 1.00 + minimumValue: 0.00 + value: 0.7 + stepSize: 0.01 + decimals: getDecimals(r.width) + onEditingFinished: Grid3D.SetColor(r.value, g.value, b.value, a.value) + } + + Text { + text: "G" + color: "dimgrey" + } + + IgnSpinBox { + Layout.fillWidth: true + id: g + maximumValue: 1.00 + minimumValue: 0.00 + value: 0.7 + stepSize: 0.01 + decimals: getDecimals(g.width) + onEditingFinished: Grid3D.SetColor(r.value, g.value, b.value, a.value) + } + + Text { + text: "B" + color: "dimgrey" + } + + IgnSpinBox { + Layout.fillWidth: true + id: b + maximumValue: 1.00 + minimumValue: 0.00 + value: 0.7 + stepSize: 0.01 + decimals: getDecimals(b.width) + onEditingFinished: Grid3D.SetColor(r.value, g.value, b.value, a.value) + } + + Text { + text: "A" + color: "dimgrey" + } + + IgnSpinBox { + Layout.fillWidth: true + id: a + maximumValue: 1.00 + minimumValue: 0.00 + value: 1.0 + stepSize: 0.01 + decimals: getDecimals(a.width) + onEditingFinished: Grid3D.SetColor(r.value, g.value, b.value, a.value) + } + + Button { + Layout.alignment: Qt.AlignHCenter + Layout.columnSpan: 4 + id: color + text: qsTr("Custom Color") + onClicked: colorDialog.open() + + ColorDialog { + id: colorDialog + title: "Choose a grid color" + visible: false + onAccepted: { + r.value = colorDialog.color.r + g.value = colorDialog.color.g + b.value = colorDialog.color.b + a.value = colorDialog.color.a + Grid3D.SetColor(colorDialog.color.r, colorDialog.color.g, colorDialog.color.b, colorDialog.color.a) + colorDialog.close() + } + onRejected: { + colorDialog.close() + } + } + } + + // Bottom spacer + Item { + Layout.columnSpan: 4 + Layout.fillHeight: true + } +} From fc2f870b7e7c383ec1f7218e4d4522d722094221 Mon Sep 17 00:00:00 2001 From: Louise Poubel Date: Thu, 3 Mar 2022 11:06:54 -0800 Subject: [PATCH 2/6] Improve KeyPublisher's usability (#362) Signed-off-by: Louise Poubel --- src/plugins/key_publisher/KeyPublisher.qml | 29 +++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/plugins/key_publisher/KeyPublisher.qml b/src/plugins/key_publisher/KeyPublisher.qml index 9cf4da049..e08a9eedd 100644 --- a/src/plugins/key_publisher/KeyPublisher.qml +++ b/src/plugins/key_publisher/KeyPublisher.qml @@ -15,13 +15,30 @@ * */ -import QtQuick 2.0 -import QtQuick.Controls 2.0 +import QtQuick 2.9 +import QtQuick.Controls 2.1 import QtQuick.Layouts 1.3 -Rectangle { - visible: false - Layout.minimumWidth: 100 - Layout.minimumHeight: 100 +GridLayout { + columns: 1 + columnSpacing: 10 + Layout.minimumWidth: 350 + Layout.minimumHeight: 200 + anchors.fill: parent + anchors.margins: 10 + + Label { + Layout.columnSpan: 1 + Layout.fillWidth: true + wrapMode: Text.WordWrap + text: "Keystrokes are being published to topic:\n'/keyboard/keypress'." + } + + + Item { + Layout.columnSpan: 1 + width: 10 + Layout.fillHeight: true + } } From 755b995d823e7e5ed8d7d4d2eb4002323da2dfba Mon Sep 17 00:00:00 2001 From: Louise Poubel Date: Mon, 7 Mar 2022 10:17:07 -0800 Subject: [PATCH 3/6] Fix menu scrolling when a new plugin is added (#368) Signed-off-by: Louise Poubel --- include/ignition/gui/qml/IgnSplit.qml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/include/ignition/gui/qml/IgnSplit.qml b/include/ignition/gui/qml/IgnSplit.qml index baa41e33e..d41ca0a45 100644 --- a/include/ignition/gui/qml/IgnSplit.qml +++ b/include/ignition/gui/qml/IgnSplit.qml @@ -276,14 +276,15 @@ SplitView { * Callback when the children array has been changed. */ onChildrenChanged: { - if (children.length === 0) - return; - // Propagate child's minimum size changes to the item. - Layout.minimumWidth = Qt.binding(function() { + newItem.Layout.minimumWidth = Qt.binding(function() { + if (children.length === 0 || children[0] === undefined) + return 0; return children[0].Layout.minimumWidth }); - Layout.minimumHeight = Qt.binding(function() { + newItem.Layout.minimumHeight = Qt.binding(function() { + if (children.length === 0 || children[0] === undefined) + return 0; return children[0].Layout.minimumHeight }); } @@ -357,8 +358,9 @@ SplitView { Layout.minimumWidth = child.Layout.minimumWidth; } heightSum += child.height; - minHeightSum += child.height < child.Layout.minimumHeight ? - child.height : child.Layout.minimumHeight; + + var collapsed = child.Layout.maximumHeight == 50 + minHeightSum += collapsed ? child.height : child.Layout.minimumHeight } // Minimum height to show all children From 5428d26ec8c4ab69e6bbfc8233ed76aab8df4ec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Hern=C3=A1ndez=20Cordero?= Date: Wed, 9 Mar 2022 21:01:25 +0100 Subject: [PATCH 4/6] Added Snackbar qtquick object (#369) Signed-off-by: ahcorde Signed-off-by: Louise Poubel Co-authored-by: Louise Poubel --- include/ignition/gui/MainWindow.hh | 6 ++ include/ignition/gui/qml/IgnSnackBar.qml | 87 +++++++++++++++++++++++ include/ignition/gui/qml/Main.qml | 29 ++------ include/ignition/gui/resources.qrc | 2 + src/plugins/image_display/ImageDisplay.cc | 4 ++ src/plugins/screenshot/Screenshot.cc | 3 + src/plugins/screenshot/Screenshot.qml | 16 ----- src/plugins/teleop/Teleop.cc | 15 +++- 8 files changed, 121 insertions(+), 41 deletions(-) create mode 100644 include/ignition/gui/qml/IgnSnackBar.qml diff --git a/include/ignition/gui/MainWindow.hh b/include/ignition/gui/MainWindow.hh index 300e54d72..16ad1f9d3 100644 --- a/include/ignition/gui/MainWindow.hh +++ b/include/ignition/gui/MainWindow.hh @@ -568,6 +568,12 @@ namespace ignition /// \brief Displays a message to the user signals: void notify(const QString &_message); + /// \brief Displays a message to the user + /// \param[in] _message Message to show + /// \param[in] _duration Time in milliseconds that the message will + /// appear + signals: void notifyWithDuration(const QString &_message, int _duration); + /// \internal /// \brief Private data pointer private: std::unique_ptr dataPtr; diff --git a/include/ignition/gui/qml/IgnSnackBar.qml b/include/ignition/gui/qml/IgnSnackBar.qml new file mode 100644 index 000000000..456629f20 --- /dev/null +++ b/include/ignition/gui/qml/IgnSnackBar.qml @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2022 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 QtGraphicalEffects 1.0 +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.2 +import QtQuick.Dialogs 1.0 +import QtQuick.Layouts 1.3 +import QtQuick.Window 2.2 + +Popup { + id: snackbar + modal: duration == 0 + focus: duration == 0 + x: (window.width - width) / 2 + y: window.height - window.height / 6 + width: window.width - window.width / 6 + contentHeight: notificationColumn.height + + background: Rectangle { + color: Material.background + layer.enabled: true + layer.effect: DropShadow { + color: "#aa000000" + samples: 9 + spread: 0 + radius: 8.0 + } + } + + // Duration of the snackbar. If duration is equal to zero then + // you should click somewhere in Ignition Gazebo to close it. + property int duration: 4000 + + function setText(_message) { + notificationText.text = _message + if (duration > 0) + { + timer.restart() + } + } + + function setTextDuration(_message, _duration) { + notificationText.text = _message + duration = _duration + if (duration > 0) + { + timer.restart() + } + } + + Column { + id: notificationColumn + spacing: 20 + + Label { + id: notificationText + width: snackbar.availableWidth + wrapMode: Label.Wrap + font.pixelSize: 18 + } + } + Timer { + id: timer + interval: snackbar.duration + onTriggered: { + if (!running) { + snackbar.close(); + } + } + } +} diff --git a/include/ignition/gui/qml/Main.qml b/include/ignition/gui/qml/Main.qml index 9d4f1b256..600f955c2 100644 --- a/include/ignition/gui/qml/Main.qml +++ b/include/ignition/gui/qml/Main.qml @@ -102,7 +102,11 @@ ApplicationWindow Connections { target: MainWindow onNotify: { - notificationText.text = _message + notificationDialog.setText(_message) + notificationDialog.open() + } + onNotifyWithDuration: { + notificationDialog.setTextDuration(_message, _duration) notificationDialog.open() } } @@ -319,29 +323,8 @@ ApplicationWindow } } - /** - * TODO: change to a snackbar / toast - */ - Dialog { + IgnSnackBar { id: notificationDialog - modal: true - focus: true - x: (window.width - width) / 2 - y: window.height / 6 - width: Math.min(window.width, window.height) / 3 * 2 - contentHeight: notificationColumn.height - - Column { - id: notificationColumn - spacing: 20 - - Label { - id: notificationText - width: notificationDialog.availableWidth - wrapMode: Label.Wrap - font.pixelSize: 18 - } - } } Timer { diff --git a/include/ignition/gui/resources.qrc b/include/ignition/gui/resources.qrc index 934624c10..1a7e9d551 100644 --- a/include/ignition/gui/resources.qrc +++ b/include/ignition/gui/resources.qrc @@ -6,6 +6,7 @@ qml/IgnCardSettings.qml qml/IgnHelpers.qml qml/IgnRulers.qml + qml/IgnSnackBar.qml qml/IgnSortFilterModel.qml qml/IgnSpinBox.qml qml/IgnSplit.qml @@ -24,6 +25,7 @@ qml/qmldir + qml/IgnSnackBar.qml qml/IgnSpinBox.qml diff --git a/src/plugins/image_display/ImageDisplay.cc b/src/plugins/image_display/ImageDisplay.cc index 0bbe17dd3..176dd015d 100644 --- a/src/plugins/image_display/ImageDisplay.cc +++ b/src/plugins/image_display/ImageDisplay.cc @@ -31,6 +31,7 @@ #include #include "ignition/gui/Application.hh" +#include "ignition/gui/MainWindow.hh" namespace ignition { @@ -195,7 +196,10 @@ void ImageDisplay::OnTopic(const QString _topic) this)) { ignerr << "Unable to subscribe to topic [" << topic << "]" << std::endl; + return; } + App()->findChild()->notifyWithDuration( + QString::fromStdString("Subscribed to: " + topic), 4000); } ///////////////////////////////////////////////// diff --git a/src/plugins/screenshot/Screenshot.cc b/src/plugins/screenshot/Screenshot.cc index 12563fa9e..90d2fda4f 100644 --- a/src/plugins/screenshot/Screenshot.cc +++ b/src/plugins/screenshot/Screenshot.cc @@ -173,6 +173,9 @@ void Screenshot::SaveScreenshot() this->dataPtr->dirty = false; this->SetSavedScreenshotPath(QString::fromStdString(savePath)); + + App()->findChild()->notifyWithDuration( + QString::fromStdString("Saved image to:" + savePath), 4000); } ///////////////////////////////////////////////// diff --git a/src/plugins/screenshot/Screenshot.qml b/src/plugins/screenshot/Screenshot.qml index 58cfc5949..97c6347b6 100644 --- a/src/plugins/screenshot/Screenshot.qml +++ b/src/plugins/screenshot/Screenshot.qml @@ -30,11 +30,6 @@ ToolBar { color: "transparent" } - Connections { - target: Screenshot - onSavedScreenshot: savedPathPopup.open() - } - RowLayout { spacing: 2 @@ -85,16 +80,5 @@ ToolBar { close() } } - - Popup { - id: savedPathPopup - parent: ApplicationWindow.overlay - x: 0 - y: 100 - Text { - text: "Screenshot saved: " + Screenshot.savedScreenshotPath - } - } - } } diff --git a/src/plugins/teleop/Teleop.cc b/src/plugins/teleop/Teleop.cc index 1af41287f..5dcf5b3f1 100644 --- a/src/plugins/teleop/Teleop.cc +++ b/src/plugins/teleop/Teleop.cc @@ -140,8 +140,19 @@ void Teleop::OnTopicSelection(const QString &_topic) this->dataPtr->node.Advertise (this->dataPtr->topic); if(!this->dataPtr->cmdVelPub) - ignerr << "Error when advertising topic: " << - this->dataPtr->topic << std::endl; + { + App()->findChild()->notifyWithDuration( + QString::fromStdString("Error when advertising topic: " + + this->dataPtr->topic), 4000); + ignerr << "Error when advertising topic: " << + this->dataPtr->topic << std::endl; + } + else + { + App()->findChild()->notifyWithDuration( + QString::fromStdString("Subscribing to topic: '" + + this->dataPtr->topic + "'"), 4000); + } } ///////////////////////////////////////////////// From c14f3790c695ec316fa6b14fddb8567bb5f41126 Mon Sep 17 00:00:00 2001 From: Louise Poubel Date: Fri, 25 Mar 2022 09:15:41 -0700 Subject: [PATCH 5/6] Fix some Qt warnings (#376) Signed-off-by: Louise Poubel --- src/Application.cc | 4 ++++ src/plugins/world_control/WorldControl.qml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Application.cc b/src/Application.cc index 904713e8a..95d1e2a55 100644 --- a/src/Application.cc +++ b/src/Application.cc @@ -89,6 +89,10 @@ Application::Application(int &_argc, char **_argv, const WindowType _type) { igndbg << "Initializing application." << std::endl; + this->setOrganizationName("Ignition"); + this->setOrganizationDomain("ignitionrobotics.org"); + this->setApplicationName("Ignition GUI"); + // Configure console common::Console::SetPrefix("[GUI] "); diff --git a/src/plugins/world_control/WorldControl.qml b/src/plugins/world_control/WorldControl.qml index d9ba8d299..63a343b9d 100644 --- a/src/plugins/world_control/WorldControl.qml +++ b/src/plugins/world_control/WorldControl.qml @@ -91,7 +91,7 @@ RowLayout { visible: showPlay text: paused ? playIcon : pauseIcon checkable: true - anchors.verticalCenter: parent.verticalCenter + Layout.alignment : Qt.AlignVCenter Layout.minimumWidth: width Layout.leftMargin: 10 onClicked: { From 3cd66a8572b85e5b0a3df8c70acc97c3e5aee614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Hern=C3=A1ndez=20Cordero?= Date: Fri, 25 Mar 2022 22:31:25 +0100 Subject: [PATCH 6/6] Added array to snackbar qml (#370) Signed-off-by: ahcorde Signed-off-by: Louise Poubel Co-authored-by: Louise Poubel --- include/ignition/gui/MainWindow.hh | 5 + include/ignition/gui/qml/IgnSnackBar.qml | 138 +++++++++++++++++----- include/ignition/gui/qml/Main.qml | 4 +- src/plugins/image_display/ImageDisplay.cc | 2 +- src/plugins/screenshot/Screenshot.cc | 2 +- src/plugins/teleop/Teleop.cc | 4 +- 6 files changed, 120 insertions(+), 35 deletions(-) diff --git a/include/ignition/gui/MainWindow.hh b/include/ignition/gui/MainWindow.hh index 16ad1f9d3..39ad7619a 100644 --- a/include/ignition/gui/MainWindow.hh +++ b/include/ignition/gui/MainWindow.hh @@ -566,9 +566,14 @@ namespace ignition signals: void configChanged(); /// \brief Displays a message to the user + /// The message will appear in a snackbar, this message requires to + /// click on the botton "Dismiss" to close the dialog. signals: void notify(const QString &_message); /// \brief Displays a message to the user + /// The message will appear in a snackbar, this message disappear when + /// the duration is over, or if the user clicks outside or escape before + /// that. /// \param[in] _message Message to show /// \param[in] _duration Time in milliseconds that the message will /// appear diff --git a/include/ignition/gui/qml/IgnSnackBar.qml b/include/ignition/gui/qml/IgnSnackBar.qml index 456629f20..f4f2dc3c8 100644 --- a/include/ignition/gui/qml/IgnSnackBar.qml +++ b/include/ignition/gui/qml/IgnSnackBar.qml @@ -23,6 +23,20 @@ import QtQuick.Dialogs 1.0 import QtQuick.Layouts 1.3 import QtQuick.Window 2.2 +/* + To use the snackbar you need to call the methods in the MainWindow class: + - notify(message) + - notifyWithDuration(message, duration) + +For example: + // This code will show the message "Message" during one second + App()->findChild()->notifyWithDuration("Message", 1000); + + // This code will show the message "Message2" but the dialog will be there + // until you press the button "Dismiss" + App()->findChild()->notifyWithDuration("Message2"); +*/ + Popup { id: snackbar modal: duration == 0 @@ -30,7 +44,28 @@ Popup { x: (window.width - width) / 2 y: window.height - window.height / 6 width: window.width - window.width / 6 - contentHeight: notificationColumn.height + contentHeight: Math.max(dismissButton.height, notificationText.height) + padding: 10 + + // If the popup has a Dismiss button, only close by pressing that. + // Otherwise, use the default behavior. + closePolicy: duration == 0 ? Popup.NoAutoClose : + Popup.CloseOnEscape | Popup.CloseOnPressOutside + + // Array that contains a dictionary with two keys "text" and "duration" + // This structure keeps the message to show using FIFO + property var popupArray: [] + + // Duration of the snackbar. If duration is equal to zero then + // you should click on the button "Dismiss" to close the dialog", + // otherwise you need to wait the duration defined. + property int duration: 0 + + // This method is called when the dialog is closed + onClosed: { + timer.stop() + checkArray(); + } background: Rectangle { color: Material.background @@ -43,45 +78,92 @@ Popup { } } - // Duration of the snackbar. If duration is equal to zero then - // you should click somewhere in Ignition Gazebo to close it. - property int duration: 4000 + // this function is called when notify() or notifyWithDuration() are called + function setTextDuration(_message, _duration) { + popupArray.push({"text": _message, "duration": _duration}) + checkArray(); + } - function setText(_message) { - notificationText.text = _message - if (duration > 0) + // This method check if the popupArray has remaining messages to show. + function checkArray() + { + if (popupArray.length == 0) { - timer.restart() + return } - } - function setTextDuration(_message, _duration) { - notificationText.text = _message - duration = _duration - if (duration > 0) + if(!timer.running) { - timer.restart() + if (popupArray.length > 0) + { + var values = popupArray[0] + notificationText.text = values.text + duration = values.duration + snackbar.open() + + // Note that objects cannot be individually added to or removed from + // the list once created; to modify the contents of a list, it must be + // reassigned to a new list. + var newpopupArray = [] + for (var i = 1; i < popupArray.length; i++) + { + newpopupArray.push(popupArray[i]) + } + + if (newpopupArray != undefined) + { + popupArray = newpopupArray + } + else + { + popupArray = [] + } + if (duration > 0) + { + timer.restart() + } + } } } - Column { - id: notificationColumn - spacing: 20 + contentItem: RowLayout { + id: contentLayout + height: dismissButton.height + anchors.verticalCenter: snackbar.verticalCenter - Label { + Text { id: notificationText - width: snackbar.availableWidth - wrapMode: Label.Wrap - font.pixelSize: 18 + color: Material.theme == Material.Light ? "black" : "white" + wrapMode: Text.Wrap + font.pixelSize: 15 + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + } + Button { + id: dismissButton + visible: duration == 0 + flat: true + Layout.margins: 0 + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + background: Rectangle { + color: parent.down ? Material.color(Material.accent, Material.Shade400) : + (parent.hovered ? Material.color(Material.accent, Material.Shade200) : + "transparent") + } + font.pixelSize: 12 + text: "Dismiss" + onClicked: snackbar.close() } } Timer { - id: timer - interval: snackbar.duration - onTriggered: { - if (!running) { - snackbar.close(); - } - } + id: timer + interval: snackbar.duration + onTriggered: { + if (!running) { + snackbar.close(); + } + checkArray(); + } } + } diff --git a/include/ignition/gui/qml/Main.qml b/include/ignition/gui/qml/Main.qml index 600f955c2..67aa99e87 100644 --- a/include/ignition/gui/qml/Main.qml +++ b/include/ignition/gui/qml/Main.qml @@ -102,12 +102,10 @@ ApplicationWindow Connections { target: MainWindow onNotify: { - notificationDialog.setText(_message) - notificationDialog.open() + notificationDialog.setTextDuration(_message, 0) } onNotifyWithDuration: { notificationDialog.setTextDuration(_message, _duration) - notificationDialog.open() } } diff --git a/src/plugins/image_display/ImageDisplay.cc b/src/plugins/image_display/ImageDisplay.cc index 176dd015d..7215fc151 100644 --- a/src/plugins/image_display/ImageDisplay.cc +++ b/src/plugins/image_display/ImageDisplay.cc @@ -199,7 +199,7 @@ void ImageDisplay::OnTopic(const QString _topic) return; } App()->findChild()->notifyWithDuration( - QString::fromStdString("Subscribed to: " + topic), 4000); + QString::fromStdString("Subscribed to: " + topic + ""), 4000); } ///////////////////////////////////////////////// diff --git a/src/plugins/screenshot/Screenshot.cc b/src/plugins/screenshot/Screenshot.cc index 90d2fda4f..4ea87c1de 100644 --- a/src/plugins/screenshot/Screenshot.cc +++ b/src/plugins/screenshot/Screenshot.cc @@ -175,7 +175,7 @@ void Screenshot::SaveScreenshot() this->SetSavedScreenshotPath(QString::fromStdString(savePath)); App()->findChild()->notifyWithDuration( - QString::fromStdString("Saved image to:" + savePath), 4000); + QString::fromStdString("Saved image to: " + savePath + ""), 4000); } ///////////////////////////////////////////////// diff --git a/src/plugins/teleop/Teleop.cc b/src/plugins/teleop/Teleop.cc index 5dcf5b3f1..89b7b9255 100644 --- a/src/plugins/teleop/Teleop.cc +++ b/src/plugins/teleop/Teleop.cc @@ -150,8 +150,8 @@ void Teleop::OnTopicSelection(const QString &_topic) else { App()->findChild()->notifyWithDuration( - QString::fromStdString("Subscribing to topic: '" + - this->dataPtr->topic + "'"), 4000); + QString::fromStdString("Subscribing to topic: '" + + this->dataPtr->topic + "'"), 4000); } }