From ae66550cae5295fc30c391d29cfc7a1376a227e2 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 5 May 2026 18:02:02 +1000 Subject: [PATCH] Interface mockup 3 --- src/interface/CMakeLists.txt | 10 +- .../{MockMainWindow.cpp => MainWindow.cpp} | 262 +++++------------- .../{MockMainWindow.h => MainWindow.h} | 12 +- .../components/CollapsibleSection.cpp | 113 ++++++++ src/interface/components/CollapsibleSection.h | 48 ++++ src/interface/components/PanelChrome.cpp | 117 ++++++++ src/interface/components/PanelChrome.h | 38 +++ src/interface/components/SvgIcon.cpp | 72 +++++ src/interface/components/SvgIcon.h | 41 +++ src/interface/main.cpp | 4 +- .../properties/PropertiesPanelWidget.cpp | 139 +--------- .../SpatialHierarchyPanelWidget.cpp | 43 +-- 12 files changed, 535 insertions(+), 364 deletions(-) rename src/interface/{MockMainWindow.cpp => MainWindow.cpp} (74%) rename src/interface/{MockMainWindow.h => MainWindow.h} (94%) create mode 100644 src/interface/components/CollapsibleSection.cpp create mode 100644 src/interface/components/CollapsibleSection.h create mode 100644 src/interface/components/PanelChrome.cpp create mode 100644 src/interface/components/PanelChrome.h create mode 100644 src/interface/components/SvgIcon.cpp create mode 100644 src/interface/components/SvgIcon.h diff --git a/src/interface/CMakeLists.txt b/src/interface/CMakeLists.txt index 4bbf886756..db55d16807 100644 --- a/src/interface/CMakeLists.txt +++ b/src/interface/CMakeLists.txt @@ -25,8 +25,14 @@ find_package(Qt${QT_VERSION} COMPONENTS Core Gui Widgets Svg REQUIRED PATHS ${QT set(INTERFACE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/MockMainWindow.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/MockMainWindow.h + ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow.h + ${CMAKE_CURRENT_SOURCE_DIR}/components/SvgIcon.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/components/SvgIcon.h + ${CMAKE_CURRENT_SOURCE_DIR}/components/CollapsibleSection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/components/CollapsibleSection.h + ${CMAKE_CURRENT_SOURCE_DIR}/components/PanelChrome.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/components/PanelChrome.h ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/ModelsPanelTypes.h ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/ModelsPanelWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/ModelsPanelWidget.h diff --git a/src/interface/MockMainWindow.cpp b/src/interface/MainWindow.cpp similarity index 74% rename from src/interface/MockMainWindow.cpp rename to src/interface/MainWindow.cpp index 70864390b9..212b552a49 100644 --- a/src/interface/MockMainWindow.cpp +++ b/src/interface/MainWindow.cpp @@ -18,11 +18,13 @@ * * ********************************************************************************/ -#include "MockMainWindow.h" +#include "MainWindow.h" -#include "AppSettings.h" -#include "SceneLoader.h" -#include "ViewportWindow.h" +#include "../ifcviewer/AppSettings.h" +#include "../ifcviewer/SceneLoader.h" +#include "../ifcviewer/ViewportWindow.h" +#include "components/PanelChrome.h" +#include "components/SvgIcon.h" #include "panels/models/ModelsPanelView.h" #include "panels/models/ModelsPanelWidget.h" #include "panels/properties/PropertiesPanelView.h" @@ -32,154 +34,21 @@ #include #include -#include -#include #include -#include -#include #include #include #include -#include -#include -#include #include -#include -#include -#include -#include -#include #include #include #include -#include #include #include -#include #include -namespace { +namespace ifcinterface::shell { -QPixmap renderTintedSvgPixmap(const QString& icon_path, const QString& color, const QSize& size) { - QFile file(icon_path); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - return QIcon(icon_path).pixmap(size); - } - - QString svg = QString::fromUtf8(file.readAll()); - QString tinted = svg; - tinted.replace("currentColor", color, Qt::CaseSensitive); - tinted.replace(QRegularExpression(R"(stroke="[^"]*")"), QString("stroke=\"%1\"").arg(color)); - tinted.replace(QRegularExpression(R"(fill="none")"), "fill=\"none\""); - QByteArray data = tinted.toUtf8(); - QSvgRenderer renderer(data); - QPixmap pixmap(size); - pixmap.fill(Qt::transparent); - QPainter painter(&pixmap); - renderer.render(&painter); - return pixmap; -} - -QIcon makeTintedSvgIcon(const QString& icon_path, const QString& normal = "#39b54a", - const QString& active = "#53c763", const QString& disabled = "#6f7988") { - QFile file(icon_path); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - return QIcon(icon_path); - } - - QIcon icon; - icon.addPixmap(renderTintedSvgPixmap(icon_path, normal, QSize(20, 20)), QIcon::Normal, QIcon::Off); - icon.addPixmap(renderTintedSvgPixmap(icon_path, active, QSize(20, 20)), QIcon::Active, QIcon::Off); - icon.addPixmap(renderTintedSvgPixmap(icon_path, active, QSize(20, 20)), QIcon::Selected, QIcon::Off); - icon.addPixmap(renderTintedSvgPixmap(icon_path, disabled, QSize(20, 20)), QIcon::Disabled, QIcon::Off); - return icon; -} - -QIcon makePanelSvgIcon(const QString& icon_path) { - return makeTintedSvgIcon(icon_path, "#e7ebf2", "#ffffff", "#6f7988"); -} - -class DockTitleBar : public QWidget { -public: - explicit DockTitleBar(const QString& title, bool has_settings = false, QWidget* parent = nullptr) - : QWidget(parent) - { - auto* layout = new QHBoxLayout(this); - layout->setContentsMargins(10, 6, 6, 6); - layout->setSpacing(6); - - auto* text = new QLabel(title.toUpper(), this); - text->setObjectName("dockTitleText"); - - layout->addWidget(text); - layout->addStretch(1); - if (has_settings) { - auto* settings = new QToolButton(this); - settings->setIcon(makePanelSvgIcon(":/icons/settings.svg")); - settings->setAutoRaise(true); - settings->setCursor(Qt::ArrowCursor); - settings->setFixedSize(18, 18); - settings->setObjectName("dockTitleButton"); - settings->setToolTip(QString("%1 settings").arg(title)); - connect(settings, &QToolButton::clicked, this, [this, title]() { - auto* anchor = parentWidget(); - QMenu menu(anchor); - menu.addAction(QString("%1 settings coming soon").arg(title)); - menu.exec(QCursor::pos()); - }); - layout->addWidget(settings); - } - } -}; - -QDockWidget* makeDock(const QString& title, QWidget* content, QWidget* parent, bool has_settings = false) { - auto* dock = new QDockWidget(title, parent); - dock->setObjectName(title); - dock->setFeatures(QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable | - QDockWidget::DockWidgetClosable); - dock->setTitleBarWidget(new DockTitleBar(title, has_settings, dock)); - dock->setWidget(content); - return dock; -} - -QFrame* wrapPanel(QWidget* inner) { - auto* outer = new QFrame(); - auto* outer_layout = new QVBoxLayout(outer); - outer_layout->setContentsMargins(6, 6, 6, 6); - outer_layout->setSpacing(0); - - auto* frame = new QFrame(outer); - frame->setObjectName("panelFrame"); - auto* layout = new QVBoxLayout(frame); - layout->setContentsMargins(8, 8, 8, 8); - layout->setSpacing(0); - layout->addWidget(inner); - - outer_layout->addWidget(frame); - return outer; -} - -QFrame* wrapInspectorPanel(QWidget* inner) { - auto* outer = new QFrame(); - auto* outer_layout = new QVBoxLayout(outer); - outer_layout->setContentsMargins(6, 6, 6, 6); - outer_layout->setSpacing(0); - - auto* frame = new QFrame(outer); - frame->setObjectName("panelFrame"); - auto* layout = new QVBoxLayout(frame); - layout->setContentsMargins(0, 8, 0, 8); - layout->setSpacing(0); - layout->addWidget(inner); - - outer_layout->addWidget(frame); - return outer; -} - -} // namespace - -MockMainWindow::MockMainWindow(QWidget* parent) +MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) { setupChrome(); @@ -191,7 +60,7 @@ MockMainWindow::MockMainWindow(QWidget* parent) resize(1720, 980); } -void MockMainWindow::setupChrome() { +void MainWindow::setupChrome() { setWindowTitle("IfcOpenShell Interface"); setDockOptions(QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks | @@ -459,12 +328,12 @@ void MockMainWindow::setupChrome() { )"); } -QToolButton* MockMainWindow::makeRibbonAction(const QString& text, const QString& icon_path) { +QToolButton* MainWindow::makeRibbonAction(const QString& text, const QString& icon_path) { auto* button = new QToolButton(this); button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); button->setIcon(icon_path.endsWith(".svg") - ? makeTintedSvgIcon(icon_path) - : QIcon(icon_path)); + ? components::icons::makeTintedSvgIcon(icon_path) + : QIcon(icon_path)); button->setIconSize(QSize(20, 20)); button->setText(text); button->setMinimumSize(QSize(68, 54)); @@ -473,7 +342,7 @@ QToolButton* MockMainWindow::makeRibbonAction(const QString& text, const QString return button; } -QWidget* MockMainWindow::makeRibbonGroup(const QString& title, const QList& buttons) { +QWidget* MainWindow::makeRibbonGroup(const QString& title, const QList& buttons) { auto* group = new QFrame(this); group->setObjectName("ribbonGroup"); auto* group_layout = new QVBoxLayout(group); @@ -493,7 +362,7 @@ QWidget* MockMainWindow::makeRibbonGroup(const QString& title, const QListsetContentsMargins(12, 12, 12, 12); @@ -509,12 +378,12 @@ QWidget* MockMainWindow::makeComingSoonPanel(const QString& title) { return widget; } -void MockMainWindow::setStatusMessage(const QString& mode, const QString& detail) { +void MainWindow::setStatusMessage(const QString& mode, const QString& detail) { status_mode_label_->setText(mode); status_selection_label_->setText(detail); } -QToolButton* MockMainWindow::makePanelToggle(const QString& text, QDockWidget* dock) { +QToolButton* MainWindow::makePanelToggle(const QString& text, QDockWidget* dock) { auto* button = makeRibbonAction(text, ":/icons/dm_toggle_openings.png"); button->setCheckable(true); button->setChecked(dock->isVisible()); @@ -529,7 +398,7 @@ QToolButton* MockMainWindow::makePanelToggle(const QString& text, QDockWidget* d return button; } -QWidget* MockMainWindow::buildHomeRibbonPage() { +QWidget* MainWindow::buildHomeRibbonPage() { auto* page = new QFrame(this); page->setObjectName("ribbonPage"); auto* row = new QHBoxLayout(page); @@ -562,7 +431,7 @@ QWidget* MockMainWindow::buildHomeRibbonPage() { }); auto* add_model = makeRibbonAction("Add Model", ":/icons/cube.svg"); - connect(add_model, &QToolButton::clicked, this, &MockMainWindow::onAddFiles); + connect(add_model, &QToolButton::clicked, this, &MainWindow::onAddFiles); auto* sync_models = makeRibbonAction("Sync Models", ":/icons/refresh-double.svg"); connect(sync_models, &QToolButton::clicked, this, [this]() { setStatusMessage("Models", "Sync models coming soon"); @@ -580,7 +449,7 @@ QWidget* MockMainWindow::buildHomeRibbonPage() { return page; } -QWidget* MockMainWindow::buildNavigateRibbonPage() { +QWidget* MainWindow::buildNavigateRibbonPage() { auto* page = new QFrame(this); page->setObjectName("ribbonPage"); auto* row = new QHBoxLayout(page); @@ -643,7 +512,7 @@ QWidget* MockMainWindow::buildNavigateRibbonPage() { return page; } -QWidget* MockMainWindow::buildInspectRibbonPage() { +QWidget* MainWindow::buildInspectRibbonPage() { auto* page = new QFrame(this); page->setObjectName("ribbonPage"); auto* row = new QHBoxLayout(page); @@ -686,7 +555,7 @@ QWidget* MockMainWindow::buildInspectRibbonPage() { return page; } -QWidget* MockMainWindow::buildPanelsRibbonPage() { +QWidget* MainWindow::buildPanelsRibbonPage() { auto* page = new QFrame(this); page->setObjectName("ribbonPage"); auto* row = new QHBoxLayout(page); @@ -712,7 +581,7 @@ QWidget* MockMainWindow::buildPanelsRibbonPage() { return page; } -void MockMainWindow::setupRibbon() { +void MainWindow::setupRibbon() { auto* shell = new QFrame(this); shell->setObjectName("ribbonShell"); @@ -751,7 +620,7 @@ void MockMainWindow::setupRibbon() { setMenuWidget(shell); } -void MockMainWindow::setupViewport() { +void MainWindow::setupViewport() { viewport_ = new ViewportWindow(); viewport_container_ = QWidget::createWindowContainer(viewport_, this); viewport_container_->setMinimumSize(400, 300); @@ -773,29 +642,38 @@ void MockMainWindow::setupViewport() { setCentralWidget(shell); } -void MockMainWindow::setupDocks() { - auto* models_panel = new ifcinterface::panels::models::ModelsPanelWidget(this); - auto* spatial_panel = new ifcinterface::panels::spatial_hierarchy::SpatialHierarchyPanelWidget(this); - auto* properties_panel = new ifcinterface::panels::properties::PropertiesPanelWidget(this); +void MainWindow::setupDocks() { + auto* models_panel = new panels::models::ModelsPanelWidget(this); + auto* spatial_panel = new panels::spatial_hierarchy::SpatialHierarchyPanelWidget(this); + auto* properties_panel = new panels::properties::PropertiesPanelWidget(this); - models_panel_view_ = new ifcinterface::panels::models::ModelsPanelView(models_panel, this); - spatial_panel_view_ = new ifcinterface::panels::spatial_hierarchy::SpatialHierarchyPanelView(spatial_panel, this); - properties_panel_view_ = new ifcinterface::panels::properties::PropertiesPanelView(properties_panel, this); + models_panel_view_ = new panels::models::ModelsPanelView(models_panel, this); + spatial_panel_view_ = new panels::spatial_hierarchy::SpatialHierarchyPanelView(spatial_panel, this); + properties_panel_view_ = new panels::properties::PropertiesPanelView(properties_panel, this); - connect(models_panel_view_, &ifcinterface::panels::models::ModelsPanelView::statusMessageRequested, - this, &MockMainWindow::setStatusMessage); - connect(spatial_panel_view_, &ifcinterface::panels::spatial_hierarchy::SpatialHierarchyPanelView::statusMessageRequested, - this, &MockMainWindow::setStatusMessage); + connect(models_panel_view_, &panels::models::ModelsPanelView::statusMessageRequested, + this, &MainWindow::setStatusMessage); + connect(spatial_panel_view_, &panels::spatial_hierarchy::SpatialHierarchyPanelView::statusMessageRequested, + this, &MainWindow::setStatusMessage); - models_dock_ = makeDock("Models", wrapPanel(models_panel), this, true); - spatial_dock_ = makeDock("Spatial Hierarchy", wrapPanel(spatial_panel), this); - properties_dock_ = makeDock("Properties", wrapInspectorPanel(properties_panel), this); - layers_dock_ = makeDock("Layers", wrapPanel(makeComingSoonPanel("Layers")), this); - stored_views_dock_ = makeDock("Stored Views", wrapPanel(makeComingSoonPanel("Stored Views")), this); - search_dock_ = makeDock("Search and Query", wrapPanel(makeComingSoonPanel("Search and Query")), this); - spreadsheet_dock_ = makeDock("Spreadsheet", wrapPanel(makeComingSoonPanel("Spreadsheet")), this); - clash_dock_ = makeDock("Clash", wrapPanel(makeComingSoonPanel("Clash")), this); - issues_dock_ = makeDock("Issues", wrapPanel(makeComingSoonPanel("Issues")), this); + models_dock_ = components::panel::makeDock( + "Models", components::panel::wrapPanel(models_panel), this, true); + spatial_dock_ = components::panel::makeDock( + "Spatial Hierarchy", components::panel::wrapPanel(spatial_panel), this); + properties_dock_ = components::panel::makeDock( + "Properties", components::panel::wrapInspectorPanel(properties_panel), this); + layers_dock_ = components::panel::makeDock( + "Layers", components::panel::wrapPanel(makeComingSoonPanel("Layers")), this); + stored_views_dock_ = components::panel::makeDock( + "Stored Views", components::panel::wrapPanel(makeComingSoonPanel("Stored Views")), this); + search_dock_ = components::panel::makeDock( + "Search and Query", components::panel::wrapPanel(makeComingSoonPanel("Search and Query")), this); + spreadsheet_dock_ = components::panel::makeDock( + "Spreadsheet", components::panel::wrapPanel(makeComingSoonPanel("Spreadsheet")), this); + clash_dock_ = components::panel::makeDock( + "Clash", components::panel::wrapPanel(makeComingSoonPanel("Clash")), this); + issues_dock_ = components::panel::makeDock( + "Issues", components::panel::wrapPanel(makeComingSoonPanel("Issues")), this); addDockWidget(Qt::LeftDockWidgetArea, models_dock_); addDockWidget(Qt::LeftDockWidgetArea, spatial_dock_); @@ -828,7 +706,7 @@ void MockMainWindow::setupDocks() { resizeDocks({models_dock_, spatial_dock_}, {280, 240}, Qt::Vertical); } -void MockMainWindow::setupStatus() { +void MainWindow::setupStatus() { status_mode_label_ = new QLabel("Ready", this); status_selection_label_ = new QLabel("No selection", this); status_perf_label_ = new QLabel(this); @@ -845,15 +723,15 @@ void MockMainWindow::setupStatus() { }); } -void MockMainWindow::setupLoader() { +void MainWindow::setupLoader() { AppSettings::instance().setLoadDataSource(false); loader_ = new SceneLoader(viewport_, this); - connect(loader_, &SceneLoader::loadStarted, this, &MockMainWindow::onLoadStarted); - connect(loader_, &SceneLoader::loadedFromSidecar, this, &MockMainWindow::onLoadedFromSidecar); - connect(loader_, &SceneLoader::loadedFromStream, this, &MockMainWindow::onLoadedFromStream); - connect(loader_, &SceneLoader::loadCancelled, this, &MockMainWindow::onLoadCancelled); - connect(loader_, &SceneLoader::loadError, this, &MockMainWindow::onLoadError); - connect(loader_, &SceneLoader::allLoadsFinished, this, &MockMainWindow::onAllLoadsFinished); + connect(loader_, &SceneLoader::loadStarted, this, &MainWindow::onLoadStarted); + connect(loader_, &SceneLoader::loadedFromSidecar, this, &MainWindow::onLoadedFromSidecar); + connect(loader_, &SceneLoader::loadedFromStream, this, &MainWindow::onLoadedFromStream); + connect(loader_, &SceneLoader::loadCancelled, this, &MainWindow::onLoadCancelled); + connect(loader_, &SceneLoader::loadError, this, &MainWindow::onLoadError); + connect(loader_, &SceneLoader::allLoadsFinished, this, &MainWindow::onAllLoadsFinished); connect(viewport_, &ViewportWindow::frameStatsUpdated, this, [this](const ViewportWindow::FrameStats& s) { @@ -870,30 +748,30 @@ void MockMainWindow::setupLoader() { }); } -void MockMainWindow::addFiles(const QStringList& paths) { +void MainWindow::addFiles(const QStringList& paths) { if (paths.isEmpty()) return; loader_->addFiles(paths); } -QString MockMainWindow::formatElapsed(qint64 ms) const { +QString MainWindow::formatElapsed(qint64 ms) const { return (ms >= 1000) ? QString::number(ms / 1000.0, 'f', 2) + " s" : QString::number(ms) + " ms"; } -void MockMainWindow::onAddFiles() { +void MainWindow::onAddFiles() { const QStringList paths = QFileDialog::getOpenFileNames( this, "Add IFC Files", QString(), "IFC Viewer Cache (*.ifcview)"); addFiles(paths); } -void MockMainWindow::onLoadStarted(uint32_t /*mid*/, QString display_name) { +void MainWindow::onLoadStarted(uint32_t /*mid*/, QString display_name) { status_mode_label_->setText("Loading"); status_selection_label_->setText(display_name); } -void MockMainWindow::onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms) { +void MainWindow::onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms) { status_mode_label_->setText("Loaded"); status_selection_label_->setText( QString("%1 from cache in %2") @@ -901,7 +779,7 @@ void MockMainWindow::onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms) { .arg(formatElapsed(elapsed_ms))); } -void MockMainWindow::onLoadedFromStream(uint32_t mid, qint64 elapsed_ms) { +void MainWindow::onLoadedFromStream(uint32_t mid, qint64 elapsed_ms) { status_mode_label_->setText("Loaded"); status_selection_label_->setText( QString("%1 streamed in %2") @@ -909,18 +787,20 @@ void MockMainWindow::onLoadedFromStream(uint32_t mid, qint64 elapsed_ms) { .arg(formatElapsed(elapsed_ms))); } -void MockMainWindow::onLoadCancelled(uint32_t mid) { +void MainWindow::onLoadCancelled(uint32_t mid) { status_mode_label_->setText("Cancelled"); status_selection_label_->setText(loader_->displayName(mid)); } -void MockMainWindow::onLoadError(uint32_t /*mid*/, QString message) { +void MainWindow::onLoadError(uint32_t /*mid*/, QString message) { status_mode_label_->setText("Error"); status_selection_label_->setText(message); QMessageBox::warning(this, "IfcInterfaceMockup", message); } -void MockMainWindow::onAllLoadsFinished() { +void MainWindow::onAllLoadsFinished() { status_mode_label_->setText("Loaded"); status_selection_label_->setText(QString("%1 model(s)").arg(loader_->modelCount())); } + +} // namespace ifcinterface::shell diff --git a/src/interface/MockMainWindow.h b/src/interface/MainWindow.h similarity index 94% rename from src/interface/MockMainWindow.h rename to src/interface/MainWindow.h index a038cb68b7..e4a491a403 100644 --- a/src/interface/MockMainWindow.h +++ b/src/interface/MainWindow.h @@ -18,8 +18,8 @@ * * ********************************************************************************/ -#ifndef IFCINTERFACE_MOCKMAINWINDOW_H -#define IFCINTERFACE_MOCKMAINWINDOW_H +#ifndef IFCINTERFACE_SHELL_MAINWINDOW_H +#define IFCINTERFACE_SHELL_MAINWINDOW_H #include #include @@ -35,10 +35,12 @@ namespace ifcinterface::panels::models { class ModelsPanelView; } namespace ifcinterface::panels::spatial_hierarchy { class SpatialHierarchyPanelView; } namespace ifcinterface::panels::properties { class PropertiesPanelView; } -class MockMainWindow : public QMainWindow { +namespace ifcinterface::shell { + +class MainWindow : public QMainWindow { Q_OBJECT public: - explicit MockMainWindow(QWidget* parent = nullptr); + explicit MainWindow(QWidget* parent = nullptr); private: void setupChrome(); @@ -91,4 +93,6 @@ private: ifcinterface::panels::properties::PropertiesPanelView* properties_panel_view_ = nullptr; }; +} // namespace ifcinterface::shell + #endif diff --git a/src/interface/components/CollapsibleSection.cpp b/src/interface/components/CollapsibleSection.cpp new file mode 100644 index 0000000000..3d21d00c99 --- /dev/null +++ b/src/interface/components/CollapsibleSection.cpp @@ -0,0 +1,113 @@ +// This file was generated with the assistance of an AI coding tool. +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#include "CollapsibleSection.h" + +#include "SvgIcon.h" + +#include +#include +#include +#include +#include + +namespace ifcinterface::components::inspector { + +namespace { + +QWidget* makeInspectorFilterField(const QString& placeholder, QWidget* parent = nullptr) { + auto* field = new QLineEdit(parent); + field->setPlaceholderText(placeholder); + field->setClearButtonEnabled(true); + field->addAction(icons::makePanelSvgIcon(":/icons/filter.svg"), QLineEdit::LeadingPosition); + return field; +} + +} // namespace + +CollapsibleSection::CollapsibleSection(const QString& title, const QString& filter_placeholder, QWidget* parent) + : QWidget(parent) +{ + setObjectName("inspectorSection"); + auto* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(6); + + auto* header = new QFrame(this); + header->setObjectName("inspectorSectionHeader"); + auto* header_layout = new QHBoxLayout(header); + header_layout->setContentsMargins(0, 0, 0, 0); + header_layout->setSpacing(6); + + auto* toggle = new QToolButton(header); + toggle->setObjectName("inspectorSectionButton"); + toggle->setText(title); + toggle->setCheckable(true); + toggle->setChecked(true); + toggle->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + toggle->setArrowType(Qt::DownArrow); + header_layout->addWidget(toggle); + header_layout->addStretch(1); + + if (!filter_placeholder.isEmpty()) { + auto* filter_toggle = new QToolButton(header); + filter_toggle->setObjectName("inspectorFilterToggle"); + filter_toggle->setCheckable(true); + filter_toggle->setIcon(icons::makePanelSvgIcon(":/icons/filter.svg")); + filter_toggle->setAutoRaise(true); + header_layout->addWidget(filter_toggle); + + filter_field_ = qobject_cast(makeInspectorFilterField(filter_placeholder, this)); + filter_field_->setVisible(false); + connect(filter_toggle, &QToolButton::toggled, filter_field_, [this](bool visible) { + filter_field_->setVisible(visible); + if (visible) filter_field_->setFocus(); + }); + } + + body_ = new QWidget(this); + body_->setObjectName("inspectorSectionBody"); + body_layout_ = new QVBoxLayout(body_); + body_layout_->setContentsMargins(10, 6, 10, 0); + body_layout_->setSpacing(6); + + connect(toggle, &QToolButton::toggled, body_, [toggle, this](bool expanded) { + toggle->setArrowType(expanded ? Qt::DownArrow : Qt::RightArrow); + body_->setVisible(expanded); + }); + + layout->addWidget(header); + if (filter_field_) { + auto* filter_wrapper = new QWidget(this); + filter_wrapper->setObjectName("inspectorFilterWrapper"); + auto* filter_wrapper_layout = new QVBoxLayout(filter_wrapper); + filter_wrapper_layout->setContentsMargins(10, 0, 10, 0); + filter_wrapper_layout->setSpacing(0); + filter_wrapper_layout->addWidget(filter_field_); + layout->addWidget(filter_wrapper); + } + layout->addWidget(body_); +} + +void CollapsibleSection::addBodyWidget(QWidget* widget) { + body_layout_->addWidget(widget); +} + +} // namespace ifcinterface::components::inspector diff --git a/src/interface/components/CollapsibleSection.h b/src/interface/components/CollapsibleSection.h new file mode 100644 index 0000000000..7e9431f7fb --- /dev/null +++ b/src/interface/components/CollapsibleSection.h @@ -0,0 +1,48 @@ +// This file was generated with the assistance of an AI coding tool. +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#ifndef IFCINTERFACE_COMPONENTS_INSPECTOR_COLLAPSIBLESECTION_H +#define IFCINTERFACE_COMPONENTS_INSPECTOR_COLLAPSIBLESECTION_H + +#include + +class QLineEdit; +class QVBoxLayout; + +namespace ifcinterface::components::inspector { + +class CollapsibleSection : public QWidget { + Q_OBJECT +public: + explicit CollapsibleSection(const QString& title, + const QString& filter_placeholder = {}, + QWidget* parent = nullptr); + + void addBodyWidget(QWidget* widget); + +private: + QWidget* body_ = nullptr; + QVBoxLayout* body_layout_ = nullptr; + QLineEdit* filter_field_ = nullptr; +}; + +} // namespace ifcinterface::components::inspector + +#endif diff --git a/src/interface/components/PanelChrome.cpp b/src/interface/components/PanelChrome.cpp new file mode 100644 index 0000000000..4c970096ea --- /dev/null +++ b/src/interface/components/PanelChrome.cpp @@ -0,0 +1,117 @@ +// This file was generated with the assistance of an AI coding tool. +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#include "PanelChrome.h" + +#include "SvgIcon.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace ifcinterface::components::panel { + +namespace { + +class DockTitleBar : public QWidget { +public: + explicit DockTitleBar(const QString& title, bool has_settings = false, QWidget* parent = nullptr) + : QWidget(parent) + { + auto* layout = new QHBoxLayout(this); + layout->setContentsMargins(10, 6, 6, 6); + layout->setSpacing(6); + + auto* text = new QLabel(title.toUpper(), this); + text->setObjectName("dockTitleText"); + + layout->addWidget(text); + layout->addStretch(1); + if (has_settings) { + auto* settings = new QToolButton(this); + settings->setIcon(icons::makePanelSvgIcon(":/icons/settings.svg")); + settings->setAutoRaise(true); + settings->setCursor(Qt::ArrowCursor); + settings->setFixedSize(18, 18); + settings->setObjectName("dockTitleButton"); + settings->setToolTip(QString("%1 settings").arg(title)); + connect(settings, &QToolButton::clicked, this, [this, title]() { + auto* anchor = parentWidget(); + QMenu menu(anchor); + menu.addAction(QString("%1 settings coming soon").arg(title)); + menu.exec(QCursor::pos()); + }); + layout->addWidget(settings); + } + } +}; + +} // namespace + +QDockWidget* makeDock(const QString& title, QWidget* content, QWidget* parent, bool has_settings) { + auto* dock = new QDockWidget(title, parent); + dock->setObjectName(title); + dock->setFeatures(QDockWidget::DockWidgetMovable | + QDockWidget::DockWidgetFloatable | + QDockWidget::DockWidgetClosable); + dock->setTitleBarWidget(new DockTitleBar(title, has_settings, dock)); + dock->setWidget(content); + return dock; +} + +QFrame* wrapPanel(QWidget* inner) { + auto* outer = new QFrame(); + auto* outer_layout = new QVBoxLayout(outer); + outer_layout->setContentsMargins(6, 6, 6, 6); + outer_layout->setSpacing(0); + + auto* frame = new QFrame(outer); + frame->setObjectName("panelFrame"); + auto* layout = new QVBoxLayout(frame); + layout->setContentsMargins(8, 8, 8, 8); + layout->setSpacing(0); + layout->addWidget(inner); + + outer_layout->addWidget(frame); + return outer; +} + +QFrame* wrapInspectorPanel(QWidget* inner) { + auto* outer = new QFrame(); + auto* outer_layout = new QVBoxLayout(outer); + outer_layout->setContentsMargins(6, 6, 6, 6); + outer_layout->setSpacing(0); + + auto* frame = new QFrame(outer); + frame->setObjectName("panelFrame"); + auto* layout = new QVBoxLayout(frame); + layout->setContentsMargins(0, 8, 0, 8); + layout->setSpacing(0); + layout->addWidget(inner); + + outer_layout->addWidget(frame); + return outer; +} + +} // namespace ifcinterface::components::panel diff --git a/src/interface/components/PanelChrome.h b/src/interface/components/PanelChrome.h new file mode 100644 index 0000000000..cb1a405e16 --- /dev/null +++ b/src/interface/components/PanelChrome.h @@ -0,0 +1,38 @@ +// This file was generated with the assistance of an AI coding tool. +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#ifndef IFCINTERFACE_COMPONENTS_PANEL_PANELCHROME_H +#define IFCINTERFACE_COMPONENTS_PANEL_PANELCHROME_H + +#include + +class QDockWidget; +class QFrame; +class QWidget; + +namespace ifcinterface::components::panel { + +QDockWidget* makeDock(const QString& title, QWidget* content, QWidget* parent, bool has_settings = false); +QFrame* wrapPanel(QWidget* inner); +QFrame* wrapInspectorPanel(QWidget* inner); + +} // namespace ifcinterface::components::panel + +#endif diff --git a/src/interface/components/SvgIcon.cpp b/src/interface/components/SvgIcon.cpp new file mode 100644 index 0000000000..8c6dfd8765 --- /dev/null +++ b/src/interface/components/SvgIcon.cpp @@ -0,0 +1,72 @@ +// This file was generated with the assistance of an AI coding tool. +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#include "SvgIcon.h" + +#include +#include +#include +#include + +namespace ifcinterface::components::icons { + +QPixmap renderTintedSvgPixmap(const QString& icon_path, const QString& color, const QSize& size) { + QFile file(icon_path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return QIcon(icon_path).pixmap(size); + } + + QString svg = QString::fromUtf8(file.readAll()); + svg.replace("currentColor", color, Qt::CaseSensitive); + svg.replace(QRegularExpression(R"(stroke="[^"]*")"), QString("stroke=\"%1\"").arg(color)); + svg.replace(QRegularExpression(R"(fill="none")"), "fill=\"none\""); + + QSvgRenderer renderer(svg.toUtf8()); + QPixmap pixmap(size); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + renderer.render(&painter); + return pixmap; +} + +QIcon makeTintedSvgIcon(const QString& icon_path, const QString& normal, + const QString& active, const QString& disabled) { + QFile file(icon_path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return QIcon(icon_path); + } + + QIcon icon; + icon.addPixmap(renderTintedSvgPixmap(icon_path, normal, QSize(20, 20)), QIcon::Normal, QIcon::Off); + icon.addPixmap(renderTintedSvgPixmap(icon_path, active, QSize(20, 20)), QIcon::Active, QIcon::Off); + icon.addPixmap(renderTintedSvgPixmap(icon_path, active, QSize(20, 20)), QIcon::Selected, QIcon::Off); + icon.addPixmap(renderTintedSvgPixmap(icon_path, disabled, QSize(20, 20)), QIcon::Disabled, QIcon::Off); + return icon; +} + +QIcon makePanelSvgIcon(const QString& icon_path) { + return makeTintedSvgIcon(icon_path, "#e7ebf2", "#ffffff", "#6f7988"); +} + +QPixmap makePanelSvgPixmap(const QString& icon_path, const QSize& size) { + return renderTintedSvgPixmap(icon_path, "#e7ebf2", size); +} + +} // namespace ifcinterface::components::icons diff --git a/src/interface/components/SvgIcon.h b/src/interface/components/SvgIcon.h new file mode 100644 index 0000000000..d1c7e7417d --- /dev/null +++ b/src/interface/components/SvgIcon.h @@ -0,0 +1,41 @@ +// This file was generated with the assistance of an AI coding tool. +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#ifndef IFCINTERFACE_COMPONENTS_ICONS_SVGICON_H +#define IFCINTERFACE_COMPONENTS_ICONS_SVGICON_H + +#include +#include +#include +#include + +namespace ifcinterface::components::icons { + +QPixmap renderTintedSvgPixmap(const QString& icon_path, const QString& color, const QSize& size); +QIcon makeTintedSvgIcon(const QString& icon_path, + const QString& normal = "#39b54a", + const QString& active = "#53c763", + const QString& disabled = "#6f7988"); +QIcon makePanelSvgIcon(const QString& icon_path); +QPixmap makePanelSvgPixmap(const QString& icon_path, const QSize& size); + +} // namespace ifcinterface::components::icons + +#endif diff --git a/src/interface/main.cpp b/src/interface/main.cpp index 7a1e1d1970..57e40f679c 100644 --- a/src/interface/main.cpp +++ b/src/interface/main.cpp @@ -18,7 +18,7 @@ * * ********************************************************************************/ -#include "MockMainWindow.h" +#include "MainWindow.h" #include #include @@ -71,7 +71,7 @@ int main(int argc, char* argv[]) { installUiFont(); - MockMainWindow window; + ifcinterface::shell::MainWindow window; window.show(); return app.exec(); } diff --git a/src/interface/panels/properties/PropertiesPanelWidget.cpp b/src/interface/panels/properties/PropertiesPanelWidget.cpp index 4459faa5dd..c6ef61355b 100644 --- a/src/interface/panels/properties/PropertiesPanelWidget.cpp +++ b/src/interface/panels/properties/PropertiesPanelWidget.cpp @@ -20,63 +20,19 @@ #include "PropertiesPanelWidget.h" -#include +#include "../../components/CollapsibleSection.h" +#include "../../components/SvgIcon.h" + #include #include #include #include #include -#include -#include -#include -#include #include -#include -#include #include namespace { -QPixmap renderPanelSvgPixmap(const QString& icon_path, const QString& color, const QSize& size) { - QFile file(icon_path); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - return QIcon(icon_path).pixmap(size); - } - - QString svg = QString::fromUtf8(file.readAll()); - svg.replace("currentColor", color, Qt::CaseSensitive); - svg.replace(QRegularExpression(R"(stroke="[^"]*")"), QString("stroke=\"%1\"").arg(color)); - svg.replace(QRegularExpression(R"(fill="none")"), "fill=\"none\""); - - QSvgRenderer renderer(svg.toUtf8()); - QPixmap pixmap(size); - pixmap.fill(Qt::transparent); - QPainter painter(&pixmap); - renderer.render(&painter); - return pixmap; -} - -QIcon makePanelSvgIcon(const QString& icon_path) { - QIcon icon; - icon.addPixmap(renderPanelSvgPixmap(icon_path, "#e7ebf2", QSize(20, 20)), QIcon::Normal, QIcon::Off); - icon.addPixmap(renderPanelSvgPixmap(icon_path, "#ffffff", QSize(20, 20)), QIcon::Active, QIcon::Off); - icon.addPixmap(renderPanelSvgPixmap(icon_path, "#ffffff", QSize(20, 20)), QIcon::Selected, QIcon::Off); - icon.addPixmap(renderPanelSvgPixmap(icon_path, "#6f7988", QSize(20, 20)), QIcon::Disabled, QIcon::Off); - return icon; -} - -QPixmap makePanelSvgPixmap(const QString& icon_path, const QSize& size) { - return renderPanelSvgPixmap(icon_path, "#e7ebf2", size); -} - -QWidget* makeInspectorFilterField(const QString& placeholder, QWidget* parent = nullptr) { - auto* field = new QLineEdit(parent); - field->setPlaceholderText(placeholder); - field->setClearButtonEnabled(true); - field->addAction(makePanelSvgIcon(":/icons/filter.svg"), QLineEdit::LeadingPosition); - return field; -} - QWidget* makePropertySetPanel(const ifcinterface::panels::properties::PropertySet& property_set, QWidget* parent = nullptr) { auto* group = new QGroupBox(property_set.title, parent); group->setObjectName("propertySetCard"); @@ -98,75 +54,6 @@ QWidget* makePropertySetPanel(const ifcinterface::panels::properties::PropertySe return group; } -QWidget* makeInspectorSection(const QString& title, - const QString& filter_placeholder, - const QList& groups, - QWidget* parent = nullptr) { - auto* section = new QWidget(parent); - section->setObjectName("inspectorSection"); - auto* layout = new QVBoxLayout(section); - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(6); - - auto* header = new QFrame(section); - header->setObjectName("inspectorSectionHeader"); - auto* header_layout = new QHBoxLayout(header); - header_layout->setContentsMargins(0, 0, 0, 0); - header_layout->setSpacing(6); - - auto* toggle = new QToolButton(header); - toggle->setObjectName("inspectorSectionButton"); - toggle->setText(title); - toggle->setCheckable(true); - toggle->setChecked(true); - toggle->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - toggle->setArrowType(Qt::DownArrow); - header_layout->addWidget(toggle); - header_layout->addStretch(1); - - QLineEdit* filter_field = nullptr; - if (!filter_placeholder.isEmpty()) { - auto* filter_toggle = new QToolButton(header); - filter_toggle->setObjectName("inspectorFilterToggle"); - filter_toggle->setCheckable(true); - filter_toggle->setIcon(makePanelSvgIcon(":/icons/filter.svg")); - filter_toggle->setAutoRaise(true); - header_layout->addWidget(filter_toggle); - - filter_field = qobject_cast(makeInspectorFilterField(filter_placeholder, section)); - filter_field->setVisible(false); - QObject::connect(filter_toggle, &QToolButton::toggled, filter_field, [filter_field](bool visible) { - filter_field->setVisible(visible); - if (visible) filter_field->setFocus(); - }); - } - - auto* body = new QWidget(section); - body->setObjectName("inspectorSectionBody"); - auto* body_layout = new QVBoxLayout(body); - body_layout->setContentsMargins(10, 6, 10, 0); - body_layout->setSpacing(6); - for (auto* group : groups) body_layout->addWidget(group); - - QObject::connect(toggle, &QToolButton::toggled, body, [toggle, body](bool expanded) { - toggle->setArrowType(expanded ? Qt::DownArrow : Qt::RightArrow); - body->setVisible(expanded); - }); - - layout->addWidget(header); - if (filter_field) { - auto* filter_wrapper = new QWidget(section); - filter_wrapper->setObjectName("inspectorFilterWrapper"); - auto* filter_wrapper_layout = new QVBoxLayout(filter_wrapper); - filter_wrapper_layout->setContentsMargins(10, 0, 10, 0); - filter_wrapper_layout->setSpacing(0); - filter_wrapper_layout->addWidget(filter_field); - layout->addWidget(filter_wrapper); - } - layout->addWidget(body); - return section; -} - QWidget* makeAttributeList(const QList& rows, QWidget* parent = nullptr) { auto* panel = new QWidget(parent); panel->setObjectName("attributeList"); @@ -212,7 +99,7 @@ QWidget* makeRelationshipList(const QListsetObjectName("relationshipIconLabel"); - icon->setPixmap(makePanelSvgPixmap(":/icons/cursor-pointer.svg", QSize(14, 14))); + icon->setPixmap(ifcinterface::components::icons::makePanelSvgPixmap(":/icons/cursor-pointer.svg", QSize(14, 14))); icon->setAlignment(Qt::AlignRight | Qt::AlignVCenter); row_layout->addWidget(key); @@ -255,7 +142,7 @@ void PropertiesPanelWidget::setState(const PropertiesPanelState& state) { entity_layout->setContentsMargins(10, 8, 10, 8); entity_layout->setSpacing(10); auto* entity_icon = new QLabel(entity_card); - entity_icon->setPixmap(makePanelSvgPixmap(":/icons/cube-dots.svg", QSize(28, 28))); + entity_icon->setPixmap(components::icons::makePanelSvgPixmap(":/icons/cube-dots.svg", QSize(28, 28))); entity_icon->setAlignment(Qt::AlignCenter); auto* entity_text = new QWidget(entity_card); auto* entity_text_layout = new QVBoxLayout(entity_text); @@ -287,14 +174,14 @@ void PropertiesPanelWidget::setState(const PropertiesPanelState& state) { quantity_set_widgets.append(makePropertySetPanel(property_set, content)); } - auto* attributes_section = makeInspectorSection( - "Attributes", "", {makeAttributeList(state.attributes, content)}, content); - auto* relationships_section = makeInspectorSection( - "Relationships", "", {makeRelationshipList(state.relationships, content)}, content); - auto* properties_section = makeInspectorSection( - "Properties", "Filter properties or sets", property_set_widgets, content); - auto* quantities_section = makeInspectorSection( - "Quantities", "Filter quantities or sets", quantity_set_widgets, content); + auto* attributes_section = new components::inspector::CollapsibleSection("Attributes", "", content); + attributes_section->addBodyWidget(makeAttributeList(state.attributes, content)); + auto* relationships_section = new components::inspector::CollapsibleSection("Relationships", "", content); + relationships_section->addBodyWidget(makeRelationshipList(state.relationships, content)); + auto* properties_section = new components::inspector::CollapsibleSection("Properties", "Filter properties or sets", content); + for (auto* widget : property_set_widgets) properties_section->addBodyWidget(widget); + auto* quantities_section = new components::inspector::CollapsibleSection("Quantities", "Filter quantities or sets", content); + for (auto* widget : quantity_set_widgets) quantities_section->addBodyWidget(widget); content_layout->addWidget(entity_wrapper); content_layout->addWidget(attributes_section); diff --git a/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.cpp b/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.cpp index efb312cd1e..d682c56500 100644 --- a/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.cpp +++ b/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.cpp @@ -20,48 +20,13 @@ #include "SpatialHierarchyPanelWidget.h" -#include +#include "../../components/SvgIcon.h" + #include -#include -#include -#include -#include #include #include #include -namespace { - -QPixmap renderPanelSvgPixmap(const QString& icon_path, const QString& color, const QSize& size) { - QFile file(icon_path); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - return QIcon(icon_path).pixmap(size); - } - - QString svg = QString::fromUtf8(file.readAll()); - svg.replace("currentColor", color, Qt::CaseSensitive); - svg.replace(QRegularExpression(R"(stroke="[^"]*")"), QString("stroke=\"%1\"").arg(color)); - svg.replace(QRegularExpression(R"(fill="none")"), "fill=\"none\""); - - QSvgRenderer renderer(svg.toUtf8()); - QPixmap pixmap(size); - pixmap.fill(Qt::transparent); - QPainter painter(&pixmap); - renderer.render(&painter); - return pixmap; -} - -QIcon makePanelSvgIcon(const QString& icon_path) { - QIcon icon; - icon.addPixmap(renderPanelSvgPixmap(icon_path, "#e7ebf2", QSize(20, 20)), QIcon::Normal, QIcon::Off); - icon.addPixmap(renderPanelSvgPixmap(icon_path, "#ffffff", QSize(20, 20)), QIcon::Active, QIcon::Off); - icon.addPixmap(renderPanelSvgPixmap(icon_path, "#ffffff", QSize(20, 20)), QIcon::Selected, QIcon::Off); - icon.addPixmap(renderPanelSvgPixmap(icon_path, "#6f7988", QSize(20, 20)), QIcon::Disabled, QIcon::Off); - return icon; -} - -} // namespace - namespace ifcinterface::panels::spatial_hierarchy { SpatialHierarchyPanelWidget::SpatialHierarchyPanelWidget(QWidget* parent) @@ -102,8 +67,8 @@ void SpatialHierarchyPanelWidget::addNode(QTreeWidgetItem* parent, const TreeNod auto* item = new QTreeWidgetItem(parent, {node.name, ""}); item->setData(1, Qt::UserRole, node.visible); item->setSizeHint(0, QSize(0, 24)); - item->setIcon(0, makePanelSvgIcon(iconPath(node.kind))); - item->setIcon(1, makePanelSvgIcon(node.visible ? ":/icons/eye.svg" : ":/icons/eye-closed.svg")); + item->setIcon(0, components::icons::makePanelSvgIcon(iconPath(node.kind))); + item->setIcon(1, components::icons::makePanelSvgIcon(node.visible ? ":/icons/eye.svg" : ":/icons/eye-closed.svg")); for (const auto& child : node.children) { addNode(item, child); }