From f07afda09c752fbba2bb225b160bfb26164e9abb Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 6 May 2026 21:35:12 +1000 Subject: [PATCH] Interface mockup 8 --- src/interface/CMakeLists.txt | 36 +-- src/interface/MainWindow.cpp | 19 +- src/interface/components/KeyValueTable.cpp | 2 +- src/interface/components/Panel.cpp | 2 +- src/interface/components/Section.cpp | 110 +++----- src/interface/components/Section.h | 11 +- src/interface/components/Style.cpp | 249 +++++++++++------- src/interface/components/Style.h | 23 ++ src/interface/components/SvgIcon.cpp | 4 +- src/interface/components/SvgIcon.h | 4 +- src/interface/icons/check.svg | 3 + src/interface/icons/xmark-circle.svg | 5 + src/interface/interface_resources.qrc | 2 + .../properties/PropertiesPanelWidget.cpp | 139 ---------- .../{PropertiesPanelTypes.h => Types.h} | 0 .../{PropertiesPanelView.cpp => View.cpp} | 4 +- .../{PropertiesPanelView.h => View.h} | 2 +- src/interface/panels/properties/Widget.cpp | 219 +++++++++++++++ .../{PropertiesPanelWidget.h => Widget.h} | 19 +- src/interface/panels/settings/Dialog.cpp | 222 ++++++++++++++++ src/interface/panels/settings/Dialog.h | 59 +++++ .../{SpatialHierarchyPanelTypes.h => Types.h} | 0 ...SpatialHierarchyPanelView.cpp => View.cpp} | 4 +- .../{SpatialHierarchyPanelView.h => View.h} | 2 +- ...ialHierarchyPanelWidget.cpp => Widget.cpp} | 8 +- ...SpatialHierarchyPanelWidget.h => Widget.h} | 2 +- .../todo/{TodoPanelWidget.cpp => Widget.cpp} | 4 +- .../todo/{TodoPanelWidget.h => Widget.h} | 0 28 files changed, 804 insertions(+), 350 deletions(-) create mode 100644 src/interface/icons/check.svg create mode 100644 src/interface/icons/xmark-circle.svg delete mode 100644 src/interface/panels/properties/PropertiesPanelWidget.cpp rename src/interface/panels/properties/{PropertiesPanelTypes.h => Types.h} (100%) rename src/interface/panels/properties/{PropertiesPanelView.cpp => View.cpp} (98%) rename src/interface/panels/properties/{PropertiesPanelView.h => View.h} (98%) create mode 100644 src/interface/panels/properties/Widget.cpp rename src/interface/panels/properties/{PropertiesPanelWidget.h => Widget.h} (71%) create mode 100644 src/interface/panels/settings/Dialog.cpp create mode 100644 src/interface/panels/settings/Dialog.h rename src/interface/panels/spatial_hierarchy/{SpatialHierarchyPanelTypes.h => Types.h} (100%) rename src/interface/panels/spatial_hierarchy/{SpatialHierarchyPanelView.cpp => View.cpp} (97%) rename src/interface/panels/spatial_hierarchy/{SpatialHierarchyPanelView.h => View.h} (98%) rename src/interface/panels/spatial_hierarchy/{SpatialHierarchyPanelWidget.cpp => Widget.cpp} (93%) rename src/interface/panels/spatial_hierarchy/{SpatialHierarchyPanelWidget.h => Widget.h} (98%) rename src/interface/panels/todo/{TodoPanelWidget.cpp => Widget.cpp} (97%) rename src/interface/panels/todo/{TodoPanelWidget.h => Widget.h} (100%) diff --git a/src/interface/CMakeLists.txt b/src/interface/CMakeLists.txt index 370dabb600..71460abcf4 100644 --- a/src/interface/CMakeLists.txt +++ b/src/interface/CMakeLists.txt @@ -39,23 +39,25 @@ set(INTERFACE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/components/Section.h ${CMAKE_CURRENT_SOURCE_DIR}/components/Panel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/components/Panel.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 - ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/ModelsPanelView.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/ModelsPanelView.h - ${CMAKE_CURRENT_SOURCE_DIR}/panels/todo/TodoPanelWidget.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/panels/todo/TodoPanelWidget.h - ${CMAKE_CURRENT_SOURCE_DIR}/panels/properties/PropertiesPanelTypes.h - ${CMAKE_CURRENT_SOURCE_DIR}/panels/properties/PropertiesPanelWidget.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/panels/properties/PropertiesPanelWidget.h - ${CMAKE_CURRENT_SOURCE_DIR}/panels/properties/PropertiesPanelView.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/panels/properties/PropertiesPanelView.h - ${CMAKE_CURRENT_SOURCE_DIR}/panels/spatial_hierarchy/SpatialHierarchyPanelTypes.h - ${CMAKE_CURRENT_SOURCE_DIR}/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.h - ${CMAKE_CURRENT_SOURCE_DIR}/panels/spatial_hierarchy/SpatialHierarchyPanelView.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/panels/spatial_hierarchy/SpatialHierarchyPanelView.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/Types.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/Widget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/Widget.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/View.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels/models/View.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/todo/Widget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels/todo/Widget.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/properties/Types.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/properties/Widget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels/properties/Widget.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/properties/View.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels/properties/View.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/settings/Dialog.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels/settings/Dialog.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/spatial_hierarchy/Types.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/spatial_hierarchy/Widget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels/spatial_hierarchy/Widget.h + ${CMAKE_CURRENT_SOURCE_DIR}/panels/spatial_hierarchy/View.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/panels/spatial_hierarchy/View.h ${CMAKE_CURRENT_SOURCE_DIR}/interface_resources.qrc ) diff --git a/src/interface/MainWindow.cpp b/src/interface/MainWindow.cpp index 7f141bd9b5..45e55b1574 100644 --- a/src/interface/MainWindow.cpp +++ b/src/interface/MainWindow.cpp @@ -27,13 +27,14 @@ #include "components/Panel.h" #include "components/Style.h" #include "components/SvgIcon.h" -#include "panels/todo/TodoPanelWidget.h" -#include "panels/models/ModelsPanelView.h" -#include "panels/models/ModelsPanelWidget.h" -#include "panels/properties/PropertiesPanelView.h" -#include "panels/properties/PropertiesPanelWidget.h" -#include "panels/spatial_hierarchy/SpatialHierarchyPanelView.h" -#include "panels/spatial_hierarchy/SpatialHierarchyPanelWidget.h" +#include "panels/todo/Widget.h" +#include "panels/models/View.h" +#include "panels/models/Widget.h" +#include "panels/properties/View.h" +#include "panels/properties/Widget.h" +#include "panels/settings/Dialog.h" +#include "panels/spatial_hierarchy/View.h" +#include "panels/spatial_hierarchy/Widget.h" #include #include @@ -169,7 +170,8 @@ QWidget* MainWindow::buildHomeRibbonPage() { auto* settings_button = makeRibbonAction("Settings", ":/icons/settings.svg"); connect(settings_button, &QToolButton::clicked, this, [this]() { - setStatusMessage("Settings", "Settings coming soon"); + panels::settings::SettingsDialog dialog(this); + dialog.exec(); }); row->addWidget(makeRibbonGroup("PROJECT", {new_project, open_project, open_cloud, open_recent, save_project, save_project_as})); @@ -454,7 +456,6 @@ void MainWindow::setupStatus() { } void MainWindow::setupLoader() { - AppSettings::instance().setLoadDataSource(false); loader_ = new SceneLoader(viewport_, this); element_registry_->bindLoader(loader_); connect(loader_, &SceneLoader::loadStarted, this, &MainWindow::onLoadStarted); diff --git a/src/interface/components/KeyValueTable.cpp b/src/interface/components/KeyValueTable.cpp index 068e66ae58..48c84acc36 100644 --- a/src/interface/components/KeyValueTable.cpp +++ b/src/interface/components/KeyValueTable.cpp @@ -61,7 +61,7 @@ KeyValueTable::KeyValueTable(const QList& rows, QWidget* paren icon->setObjectName(row_data.trailing_icon_object_name.isEmpty() ? "keyValueTrailingIconLabel" : row_data.trailing_icon_object_name); - icon->setPixmap(icons::makePanelSvgPixmap(row_data.trailing_icon_path, QSize(14, 14))); + icon->setPixmap(icons::makeSvgPixmap(row_data.trailing_icon_path, QSize(14, 14))); icon->setAlignment(Qt::AlignRight | Qt::AlignVCenter); layout->addWidget(icon, row_index, 2, Qt::AlignRight | Qt::AlignTop); } diff --git a/src/interface/components/Panel.cpp b/src/interface/components/Panel.cpp index b080f1867b..30ccebfa14 100644 --- a/src/interface/components/Panel.cpp +++ b/src/interface/components/Panel.cpp @@ -52,7 +52,7 @@ public: layout->addStretch(1); if (has_settings) { auto* settings = new QToolButton(this); - settings->setIcon(icons::makePanelSvgIcon(":/icons/settings.svg")); + settings->setIcon(icons::makeSvgIcon(":/icons/settings.svg")); settings->setAutoRaise(true); settings->setCursor(Qt::ArrowCursor); settings->setFixedSize(18, 18); diff --git a/src/interface/components/Section.cpp b/src/interface/components/Section.cpp index ab4c5bed79..2837dee1dc 100644 --- a/src/interface/components/Section.cpp +++ b/src/interface/components/Section.cpp @@ -21,30 +21,15 @@ #include "Section.h" #include "Style.h" -#include "SvgIcon.h" #include #include -#include #include #include namespace ifcinterface::components { -namespace { - -QWidget* makeSectionFilterField(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 - -Section::Section(const QString& title, SectionHeaderMode header_mode, - const QString& filter_placeholder, QWidget* parent) +Section::Section(const QString& title, SectionHeaderMode header_mode, QWidget* parent) : QWidget(parent) { setObjectName("panelSection"); @@ -55,70 +40,26 @@ Section::Section(const QString& title, SectionHeaderMode header_mode, if (header_mode == SectionHeaderMode::Visible) { auto* header = new QFrame(this); header->setObjectName("panelSectionHeader"); - auto* header_layout = new QHBoxLayout(header); - header_layout->setContentsMargins(0, 0, 0, 0); - header_layout->setSpacing(style::metrics::padding); + header_layout_ = new QHBoxLayout(header); + header_layout_->setContentsMargins(0, 0, 0, 0); + header_layout_->setSpacing(style::metrics::padding); - auto* toggle = new QToolButton(header); - toggle->setObjectName("panelSectionHeaderButton"); - 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("panelSectionFilterToggle"); - 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(makeSectionFilterField(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(); - }); - } + toggle_button_ = new QToolButton(header); + toggle_button_->setObjectName("panelSectionHeaderButton"); + toggle_button_->setText(title); + toggle_button_->setCheckable(true); + toggle_button_->setChecked(true); + toggle_button_->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + toggle_button_->setArrowType(Qt::DownArrow); + header_layout_->addWidget(toggle_button_); + header_layout_->addStretch(1); layout->addWidget(header); - if (filter_field_) { - auto* filter_wrapper = new QWidget(this); - filter_wrapper->setObjectName("panelSectionFilterWrapper"); - auto* filter_wrapper_layout = new QVBoxLayout(filter_wrapper); - filter_wrapper_layout->setContentsMargins(style::metrics::section_body_padding, - 0, - style::metrics::section_body_padding, - 0); - filter_wrapper_layout->setSpacing(0); - filter_wrapper_layout->addWidget(filter_field_); - layout->addWidget(filter_wrapper); - } - connect(toggle, &QToolButton::toggled, this, [this, toggle](bool expanded) { - toggle->setArrowType(expanded ? Qt::DownArrow : Qt::RightArrow); + connect(toggle_button_, &QToolButton::toggled, this, [this](bool expanded) { + toggle_button_->setArrowType(expanded ? Qt::DownArrow : Qt::RightArrow); body_->setVisible(expanded); - if (filter_field_) { - auto* wrapper = filter_field_->parentWidget(); - if (wrapper) wrapper->setVisible(expanded && filter_field_->isVisible()); - } }); - } else if (!filter_placeholder.isEmpty()) { - filter_field_ = qobject_cast(makeSectionFilterField(filter_placeholder, this)); - auto* filter_wrapper = new QWidget(this); - filter_wrapper->setObjectName("panelSectionFilterWrapper"); - auto* filter_wrapper_layout = new QVBoxLayout(filter_wrapper); - filter_wrapper_layout->setContentsMargins(style::metrics::section_body_padding, - 0, - style::metrics::section_body_padding, - 0); - filter_wrapper_layout->setSpacing(0); - filter_wrapper_layout->addWidget(filter_field_); - layout->addWidget(filter_wrapper); } body_ = new QWidget(this); @@ -136,4 +77,25 @@ void Section::addBodyWidget(QWidget* widget) { body_layout_->addWidget(widget); } +void Section::clearBody() { + while (auto* item = body_layout_->takeAt(0)) { + if (auto* widget = item->widget()) widget->deleteLater(); + delete item; + } +} + +void Section::addHeaderWidget(QWidget* widget) { + if (!header_layout_) return; + header_layout_->addWidget(widget); +} + +bool Section::isExpanded() const { + return !toggle_button_ || toggle_button_->isChecked(); +} + +void Section::setExpanded(bool expanded) { + if (!toggle_button_) return; + toggle_button_->setChecked(expanded); +} + } // namespace ifcinterface::components diff --git a/src/interface/components/Section.h b/src/interface/components/Section.h index 4dacae514d..bf206b500a 100644 --- a/src/interface/components/Section.h +++ b/src/interface/components/Section.h @@ -23,7 +23,8 @@ #include -class QLineEdit; +class QHBoxLayout; +class QToolButton; class QVBoxLayout; namespace ifcinterface::components { @@ -38,15 +39,19 @@ class Section : public QWidget { public: explicit Section(const QString& title, SectionHeaderMode header_mode = SectionHeaderMode::Visible, - const QString& filter_placeholder = {}, QWidget* parent = nullptr); void addBodyWidget(QWidget* widget); + void clearBody(); + void addHeaderWidget(QWidget* widget); + bool isExpanded() const; + void setExpanded(bool expanded); private: QWidget* body_ = nullptr; QVBoxLayout* body_layout_ = nullptr; - QLineEdit* filter_field_ = nullptr; + QHBoxLayout* header_layout_ = nullptr; + QToolButton* toggle_button_ = nullptr; }; } // namespace ifcinterface::components diff --git a/src/interface/components/Style.cpp b/src/interface/components/Style.cpp index bb77d62865..eb00179f96 100644 --- a/src/interface/components/Style.cpp +++ b/src/interface/components/Style.cpp @@ -23,41 +23,49 @@ namespace ifcinterface::components::style { QString buildAppStyleSheet() { - return QString(R"( + QString stylesheet = QStringLiteral(R"( QMainWindow#appWindow { - background: #26292f; - color: %6; - selection-background-color: #39b54a; - selection-color: #14161a; + background: ${app_background}; + color: ${primary_text}; + selection-background-color: ${selection_background}; + selection-color: ${selection_text}; + } + QDialog#appDialog { + background: ${app_background}; + color: ${primary_text}; } QFrame#ribbonShell { - background: #2d3138; - border-bottom: 1px solid #1b1d22; + background: ${ribbon_shell_background}; + border-bottom: 1px solid ${border}; } QTabBar::tab { background: transparent; - color: #8d97a7; + color: ${secondary_text}; padding: 8px 14px; margin-right: 2px; border-bottom: 2px solid transparent; } QTabBar::tab:selected { - color: #f2f5fa; - border-bottom: 2px solid #39b54a; + color: ${primary_text}; + border-bottom: 2px solid ${selection_background}; } QTabBar::tab:hover { - color: #ffffff; + color: ${ribbon_tab_hover_text}; } QFrame#ribbonBand { - background: #31353d; - border-top: 1px solid #3b4048; + background: ${ribbon_band_background}; + border-top: 1px solid ${border}; + } + QTabWidget::pane { + border: none; + background: transparent; } QFrame#ribbonPage { background: transparent; } QFrame#ribbonGroup { background: transparent; - border-right: 1px solid #434852; + border-right: 1px solid ${border}; } QLabel#ribbonGroupLabel { font-size: 9px; @@ -69,27 +77,27 @@ QString buildAppStyleSheet() { border: none; padding: 6px 4px 4px 4px; font-size: 11px; - color: #d6dce6; + color: ${primary_text}; } QToolButton#ribbonButton:hover { - background: #3a3f48; + background: ${ribbon_button_hover}; } QToolButton#ribbonButton:pressed { - background: #24282f; + background: ${ribbon_button_pressed}; } QFrame#viewportShell { - background: #202329; - border-top: 1px solid #1d2025; + background: ${viewport_shell_background}; + border-top: none; } QFrame#viewportFrame { - background: #1a1d22; - border: 1px solid #333942; + background: ${viewport_background}; + border: 1px solid ${border}; } QDockWidget { - color: #d0d5dd; + color: ${primary_text}; } QLabel { - color: %6; + color: ${primary_text}; background: transparent; } QAbstractItemView, @@ -97,83 +105,127 @@ QString buildAppStyleSheet() { QListWidget, QTableWidget, QLineEdit, + QSpinBox, + QDoubleSpinBox, + QCheckBox, + QPushButton, QToolButton { - color: %6; + color: ${primary_text}; } QLabel[textRole="secondary"] { - color: %7; + color: ${secondary_text}; } QLabel[textRole="disabled"] { - color: %8; + color: ${disabled_text}; } QLabel[textRole="warning"] { - color: %9; + color: ${warning_text}; } QLabel#panelTitleText { - color: #dfe4ec; + color: ${primary_text}; font-size: 10px; font-weight: 700; letter-spacing: 0.08em; } QToolButton#panelTitleButton { - color: #8e97a5; + color: ${panel_title_button}; border: none; background: transparent; } QToolButton#panelTitleButton:hover { - color: #ffffff; - background: #353a42; + color: ${ribbon_tab_hover_text}; + background: ${panel_title_button_hover}; } QFrame#panel { - background: #2b2f36; - border: 1px solid #3e444e; - border-radius: %1px; + background: ${panel_background}; + border: 1px solid ${border}; + border-radius: ${panel_radius}px; } QTreeWidget, QListWidget, QTableWidget, QAbstractScrollArea { - background: #2b2f36; + background: ${panel_background}; border: none; outline: none; - gridline-color: #333842; + gridline-color: ${border}; } QTreeWidget::viewport, QListWidget::viewport, QTableWidget::viewport { - background: #2b2f36; + background: ${panel_background}; } QHeaderView::section { - background: #31353d; - color: #b5becc; + background: ${control_background}; + color: ${primary_text}; border: none; - border-bottom: 1px solid #434a55; + border-bottom: 1px solid ${border}; padding: 7px 8px; font-weight: 600; } QTableCornerButton::section { - background: #31353d; + background: ${control_background}; border: none; } QScrollArea { - background: #2b2f36; + background: ${panel_background}; border: none; } QScrollArea > QWidget > QWidget { - background: #2b2f36; + background: ${panel_background}; } QLineEdit { - background: #31353d; - border: 1px solid #434a55; - border-radius: %1px; - padding: %2px %2px; - color: #d9dfeb; + background: ${control_background}; + border: 1px solid ${border}; + border-radius: ${panel_radius}px; + padding: ${padding}px ${padding}px; + color: ${primary_text}; } QLineEdit:focus { - border: 1px solid #5b6472; + border: 1px solid ${control_border_focus}; } - QFrame#entityClassCard { - background: #26292f; - border: 1px solid #404650; - border-radius: %1px; + QSpinBox, QDoubleSpinBox { + background: ${control_background}; + border: 1px solid ${border}; + border-radius: ${panel_radius}px; + padding: ${padding}px ${padding}px; + } + QSpinBox:focus, QDoubleSpinBox:focus { + border: 1px solid ${control_border_focus}; + } + QCheckBox { + background: transparent; + spacing: 8px; + } + QCheckBox::indicator { + width: 16px; + height: 16px; + border: 1px solid ${border}; + border-radius: ${panel_radius}px; + background: ${control_background}; + } + QCheckBox::indicator:hover { + background: ${ribbon_button_hover}; + } + QCheckBox::indicator:checked { + border: 1px solid ${selection_background}; + background: ${selection_background}; + } + QPushButton { + background: ${control_background}; + border: 1px solid ${border}; + border-radius: ${panel_radius}px; + padding: ${padding}px ${padding}px; + } + QPushButton:hover { + background: ${ribbon_button_hover}; + } + QPushButton:pressed { + background: ${ribbon_button_pressed}; + } + QFrame#entityClassBox, + QGroupBox#propertySetBox { + background: ${box_background}; + border: 1px solid ${border}; + border-radius: ${panel_radius}px; } QLabel#entityClassLabel { - color: #eef2f8; + color: ${primary_text}; font-weight: 700; background: transparent; } @@ -194,13 +246,13 @@ QString buildAppStyleSheet() { margin: 0 2px 2px 2px; } QScrollBar::handle:vertical, QScrollBar::handle:horizontal { - background: #525a67; - border-radius: %1px; + background: ${scroll_handle}; + border-radius: ${panel_radius}px; min-height: 24px; min-width: 24px; } QScrollBar::handle:vertical:hover, QScrollBar::handle:horizontal:hover { - background: #697385; + background: ${scroll_handle_hover}; } QScrollBar::add-line, QScrollBar::sub-line, QScrollBar::add-page, QScrollBar::sub-page { @@ -208,35 +260,29 @@ QString buildAppStyleSheet() { border: none; } QStatusBar { - background: #24272c; - border-top: 1px solid #1a1c20; + background: ${status_background}; } QStatusBar QLabel { - color: %7; + color: ${secondary_text}; background: transparent; border: none; padding: 2px 8px; } QGroupBox { background: transparent; - border: 1px solid #404650; - border-radius: %1px; + border: 1px solid ${border}; + border-radius: ${panel_radius}px; margin-top: 10px; padding-top: 10px; } - QGroupBox#propertySetCard { - background: #26292f; - border: 1px solid #404650; - border-radius: %1px; - } - QGroupBox#propertySetCard::title { + QGroupBox#propertySetBox::title { subcontrol-origin: margin; - left: %2px; + left: ${padding}px; padding: 0 4px; - color: #d5dbe5; + color: ${primary_text}; } - QGroupBox#propertySetCard > QWidget { - background: #26292f; + QGroupBox#propertySetBox > QWidget { + background: ${box_background}; } QWidget#panelSection { background: transparent; @@ -248,22 +294,22 @@ QString buildAppStyleSheet() { background: transparent; } QWidget#panelScrollBody { - background: #2b2f36; + background: ${panel_background}; } QFrame#panelSectionHeader { - background: #26292f; + background: ${section_header_background}; } QToolButton#panelSectionHeaderButton { background: transparent; border: none; - color: #e1e7f0; + color: ${primary_text}; font-weight: 700; text-align: left; - padding: %3px; + padding: ${section_header_padding}px; margin: 0; } QToolButton#panelSectionHeaderButton:hover { - color: #ffffff; + color: ${ribbon_tab_hover_text}; } QToolButton#panelSectionHeaderButton::menu-indicator { image: none; @@ -272,26 +318,53 @@ QString buildAppStyleSheet() { QToolButton#panelSectionFilterToggle { background: transparent; border: none; - padding: %3px; + padding: ${section_header_padding}px; } QToolButton#panelSectionFilterToggle:hover { - background: #353a42; + background: ${panel_title_button_hover}; } QWidget#panelSectionBody { background: transparent; } QLabel#keyValueValueLabel { - color: #dce2eb; + color: ${key_value_value_text}; background: transparent; } - )") - .arg(metrics::panel_radius) - .arg(metrics::padding) - .arg(metrics::section_header_padding) - .arg(palette::primary_text) - .arg(palette::secondary_text) - .arg(palette::disabled_text) - .arg(palette::warning_text); + )"); + + stylesheet.replace("${panel_radius}", QString::number(metrics::panel_radius)); + stylesheet.replace("${padding}", QString::number(metrics::padding)); + stylesheet.replace("${section_header_padding}", QString::number(metrics::section_header_padding)); + + stylesheet.replace("${app_background}", palette::app_background); + stylesheet.replace("${border}", palette::border); + stylesheet.replace("${selection_background}", palette::selection_background); + stylesheet.replace("${selection_text}", palette::selection_text); + stylesheet.replace("${ribbon_shell_background}", palette::ribbon_shell_background); + stylesheet.replace("${ribbon_tab_hover_text}", palette::ribbon_tab_hover_text); + stylesheet.replace("${ribbon_band_background}", palette::ribbon_band_background); + stylesheet.replace("${ribbon_button_hover}", palette::ribbon_button_hover); + stylesheet.replace("${ribbon_button_pressed}", palette::ribbon_button_pressed); + stylesheet.replace("${viewport_shell_background}", palette::viewport_shell_background); + stylesheet.replace("${viewport_background}", palette::viewport_background); + stylesheet.replace("${panel_title_button}", palette::panel_title_button); + stylesheet.replace("${panel_title_button_hover}", palette::panel_title_button_hover); + stylesheet.replace("${panel_background}", palette::panel_background); + stylesheet.replace("${control_background}", palette::control_background); + stylesheet.replace("${control_border_focus}", palette::control_border_focus); + stylesheet.replace("${box_background}", palette::box_background); + stylesheet.replace("${scroll_handle}", palette::scroll_handle); + stylesheet.replace("${scroll_handle_hover}", palette::scroll_handle_hover); + stylesheet.replace("${status_background}", palette::status_background); + stylesheet.replace("${section_header_background}", palette::section_header_background); + stylesheet.replace("${key_value_value_text}", palette::key_value_value_text); + + stylesheet.replace("${primary_text}", palette::primary_text); + stylesheet.replace("${secondary_text}", palette::secondary_text); + stylesheet.replace("${disabled_text}", palette::disabled_text); + stylesheet.replace("${warning_text}", palette::warning_text); + + return stylesheet; } } // namespace ifcinterface::components::style diff --git a/src/interface/components/Style.h b/src/interface/components/Style.h index a521dfced3..84f6426d3e 100644 --- a/src/interface/components/Style.h +++ b/src/interface/components/Style.h @@ -34,10 +34,33 @@ inline constexpr int panel_radius = 3; namespace ifcinterface::components::style::palette { +inline constexpr auto app_background = "#26292f"; +inline constexpr auto border = "#3e444e"; +inline constexpr auto selection_background = "#39b54a"; +inline constexpr auto ribbon_shell_background = "#2d3138"; +inline constexpr auto ribbon_tab_hover_text = "#ffffff"; +inline constexpr auto ribbon_band_background = "#31353d"; +inline constexpr auto ribbon_button_hover = "#3a3f48"; +inline constexpr auto ribbon_button_pressed = "#24282f"; +inline constexpr auto viewport_shell_background = "#202329"; +inline constexpr auto viewport_background = "#1a1d22"; +inline constexpr auto panel_title_button = "#8e97a5"; +inline constexpr auto panel_title_button_hover = "#353a42"; +inline constexpr auto panel_background = "#2b2f36"; +inline constexpr auto control_background = "#31353d"; +inline constexpr auto control_border_focus = "#5b6472"; +inline constexpr auto box_background = "#26292f"; +inline constexpr auto scroll_handle = "#525a67"; +inline constexpr auto scroll_handle_hover = "#697385"; +inline constexpr auto status_background = "#26292f"; +inline constexpr auto section_header_background = "#26292f"; +inline constexpr auto key_value_value_text = "#dce2eb"; + inline constexpr auto primary_text = "#d0d5dd"; inline constexpr auto secondary_text = "#9aa4b3"; inline constexpr auto disabled_text = "#8f98a6"; inline constexpr auto warning_text = "#e4b35a"; +inline constexpr auto selection_text = "#14161a"; } // namespace ifcinterface::components::style::palette diff --git a/src/interface/components/SvgIcon.cpp b/src/interface/components/SvgIcon.cpp index 8c6dfd8765..2c6470f240 100644 --- a/src/interface/components/SvgIcon.cpp +++ b/src/interface/components/SvgIcon.cpp @@ -61,11 +61,11 @@ QIcon makeTintedSvgIcon(const QString& icon_path, const QString& normal, return icon; } -QIcon makePanelSvgIcon(const QString& icon_path) { +QIcon makeSvgIcon(const QString& icon_path) { return makeTintedSvgIcon(icon_path, "#e7ebf2", "#ffffff", "#6f7988"); } -QPixmap makePanelSvgPixmap(const QString& icon_path, const QSize& size) { +QPixmap makeSvgPixmap(const QString& icon_path, const QSize& size) { return renderTintedSvgPixmap(icon_path, "#e7ebf2", size); } diff --git a/src/interface/components/SvgIcon.h b/src/interface/components/SvgIcon.h index d1c7e7417d..c8d3bb949a 100644 --- a/src/interface/components/SvgIcon.h +++ b/src/interface/components/SvgIcon.h @@ -33,8 +33,8 @@ 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); +QIcon makeSvgIcon(const QString& icon_path); +QPixmap makeSvgPixmap(const QString& icon_path, const QSize& size); } // namespace ifcinterface::components::icons diff --git a/src/interface/icons/check.svg b/src/interface/icons/check.svg new file mode 100644 index 0000000000..50579a0557 --- /dev/null +++ b/src/interface/icons/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/interface/icons/xmark-circle.svg b/src/interface/icons/xmark-circle.svg new file mode 100644 index 0000000000..371b4878ea --- /dev/null +++ b/src/interface/icons/xmark-circle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/interface/interface_resources.qrc b/src/interface/interface_resources.qrc index 453e64d9e7..fcb8a40df5 100644 --- a/src/interface/interface_resources.qrc +++ b/src/interface/interface_resources.qrc @@ -41,5 +41,7 @@ icons/cube-dots.svg icons/cursor-pointer.svg icons/sidebar-expand.svg + icons/check.svg + icons/xmark-circle.svg diff --git a/src/interface/panels/properties/PropertiesPanelWidget.cpp b/src/interface/panels/properties/PropertiesPanelWidget.cpp deleted file mode 100644 index 406884c7e7..0000000000 --- a/src/interface/panels/properties/PropertiesPanelWidget.cpp +++ /dev/null @@ -1,139 +0,0 @@ -// 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 "PropertiesPanelWidget.h" - -#include "../../components/KeyValueTable.h" -#include "../../components/Section.h" -#include "../../components/SvgIcon.h" - -#include -#include -#include -#include -#include - -namespace { - -QWidget* makePropertySetPanel(const ifcinterface::panels::properties::PropertySet& property_set, QWidget* parent = nullptr) { - auto* group = new QGroupBox(property_set.title, parent); - group->setObjectName("propertySetCard"); - auto* layout = new QVBoxLayout(group); - layout->setContentsMargins(10, 10, 10, 10); - layout->setSpacing(0); - - QList rows; - for (const auto& row : property_set.rows) { - rows.append({row.key, row.value, "keyValueValueLabel", "", "", 0}); - } - layout->addWidget(new ifcinterface::components::KeyValueTable(rows, group)); - return group; -} - -QWidget* makeAttributeList(const QList& rows, QWidget* parent = nullptr) { - QList table_rows; - for (const auto& row : rows) { - table_rows.append({row.key, row.value, "keyValueValueLabel", "", "", 0}); - } - return new ifcinterface::components::KeyValueTable(table_rows, parent); -} - -QWidget* makeRelationshipList(const QList& rows, QWidget* parent = nullptr) { - QList table_rows; - for (const auto& row_data : rows) { - table_rows.append({row_data.key, - row_data.value, - "keyValueValueLabel", - ":/icons/cursor-pointer.svg", - "keyValueTrailingIconLabel", - 72}); - } - return new ifcinterface::components::KeyValueTable(table_rows, parent); -} - -} // namespace - -namespace ifcinterface::panels::properties { - -PropertiesPanelWidget::PropertiesPanelWidget(QWidget* parent) - : QWidget(parent) -{ - content_layout_ = new QVBoxLayout(this); - content_layout_->setContentsMargins(0, 0, 0, 0); - content_layout_->setSpacing(12); -} - -void PropertiesPanelWidget::render(const PropertiesPanelState& state) { - while (auto* item = content_layout_->takeAt(0)) { - if (auto* widget = item->widget()) widget->deleteLater(); - delete item; - } - - auto* entity_card = new QFrame(this); - entity_card->setObjectName("entityClassCard"); - auto* entity_layout = new QHBoxLayout(entity_card); - entity_layout->setContentsMargins(10, 8, 10, 8); - entity_layout->setSpacing(10); - auto* entity_icon = new QLabel(entity_card); - 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); - entity_text_layout->setContentsMargins(0, 0, 0, 0); - entity_text_layout->setSpacing(2); - auto* entity_class = new QLabel(state.entity.entity_class, entity_text); - entity_class->setObjectName("entityClassLabel"); - auto* entity_type = new QLabel(state.entity.predefined_type, entity_text); - entity_type->setProperty("textRole", "secondary"); - entity_text_layout->addWidget(entity_class); - entity_text_layout->addWidget(entity_type); - entity_layout->addWidget(entity_icon, 0, Qt::AlignVCenter); - entity_layout->addWidget(entity_text, 1, Qt::AlignVCenter); - - QList property_set_widgets; - for (const auto& property_set : state.property_sets) { - property_set_widgets.append(makePropertySetPanel(property_set, this)); - } - - QList quantity_set_widgets; - for (const auto& property_set : state.quantity_sets) { - quantity_set_widgets.append(makePropertySetPanel(property_set, this)); - } - - auto* entity_section = new components::Section("", components::SectionHeaderMode::Hidden, "", this); - entity_section->addBodyWidget(entity_card); - auto* attributes_section = new components::Section("Attributes", components::SectionHeaderMode::Visible, "", this); - attributes_section->addBodyWidget(makeAttributeList(state.attributes, this)); - auto* relationships_section = new components::Section("Relationships", components::SectionHeaderMode::Visible, "", this); - relationships_section->addBodyWidget(makeRelationshipList(state.relationships, this)); - auto* properties_section = new components::Section("Properties", components::SectionHeaderMode::Visible, "Filter properties or sets", this); - for (auto* widget : property_set_widgets) properties_section->addBodyWidget(widget); - auto* quantities_section = new components::Section("Quantities", components::SectionHeaderMode::Visible, "Filter quantities or sets", this); - for (auto* widget : quantity_set_widgets) quantities_section->addBodyWidget(widget); - - content_layout_->addWidget(entity_section); - content_layout_->addWidget(attributes_section); - content_layout_->addWidget(relationships_section); - content_layout_->addWidget(properties_section); - content_layout_->addWidget(quantities_section); - content_layout_->addStretch(1); -} - -} // namespace ifcinterface::panels::properties diff --git a/src/interface/panels/properties/PropertiesPanelTypes.h b/src/interface/panels/properties/Types.h similarity index 100% rename from src/interface/panels/properties/PropertiesPanelTypes.h rename to src/interface/panels/properties/Types.h diff --git a/src/interface/panels/properties/PropertiesPanelView.cpp b/src/interface/panels/properties/View.cpp similarity index 98% rename from src/interface/panels/properties/PropertiesPanelView.cpp rename to src/interface/panels/properties/View.cpp index 82fcee6f57..3b58dc4ca9 100644 --- a/src/interface/panels/properties/PropertiesPanelView.cpp +++ b/src/interface/panels/properties/View.cpp @@ -18,9 +18,9 @@ * * ********************************************************************************/ -#include "PropertiesPanelView.h" +#include "View.h" -#include "PropertiesPanelWidget.h" +#include "Widget.h" #include "../../ElementRegistry.h" #include "../../../ifcviewer/AppSettings.h" diff --git a/src/interface/panels/properties/PropertiesPanelView.h b/src/interface/panels/properties/View.h similarity index 98% rename from src/interface/panels/properties/PropertiesPanelView.h rename to src/interface/panels/properties/View.h index 65abe82f4f..c38120fc5a 100644 --- a/src/interface/panels/properties/PropertiesPanelView.h +++ b/src/interface/panels/properties/View.h @@ -21,7 +21,7 @@ #ifndef IFCINTERFACE_PANELS_PROPERTIESPANELVIEW_H #define IFCINTERFACE_PANELS_PROPERTIESPANELVIEW_H -#include "PropertiesPanelTypes.h" +#include "Types.h" #include diff --git a/src/interface/panels/properties/Widget.cpp b/src/interface/panels/properties/Widget.cpp new file mode 100644 index 0000000000..0afb0bc89f --- /dev/null +++ b/src/interface/panels/properties/Widget.cpp @@ -0,0 +1,219 @@ +// 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 "Widget.h" + +#include "../../components/KeyValueTable.h" +#include "../../components/Section.h" +#include "../../components/Style.h" +#include "../../components/SvgIcon.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +QWidget* makePropertySetPanel(const ifcinterface::panels::properties::PropertySet& property_set, QWidget* parent = nullptr) { + auto* group = new QGroupBox(property_set.title, parent); + group->setObjectName("propertySetBox"); + auto* layout = new QVBoxLayout(group); + layout->setContentsMargins(10, 10, 10, 10); + layout->setSpacing(0); + + QList rows; + for (const auto& row : property_set.rows) { + rows.append({row.key, row.value, "keyValueValueLabel", "", "", 0}); + } + layout->addWidget(new ifcinterface::components::KeyValueTable(rows, group)); + return group; +} + +QWidget* makeAttributeList(const QList& rows, QWidget* parent = nullptr) { + QList table_rows; + for (const auto& row : rows) { + table_rows.append({row.key, row.value, "keyValueValueLabel", "", "", 0}); + } + return new ifcinterface::components::KeyValueTable(table_rows, parent); +} + +QWidget* makeRelationshipList(const QList& rows, QWidget* parent = nullptr) { + QList table_rows; + for (const auto& row_data : rows) { + table_rows.append({row_data.key, + row_data.value, + "keyValueValueLabel", + ":/icons/cursor-pointer.svg", + "keyValueTrailingIconLabel", + 72}); + } + return new ifcinterface::components::KeyValueTable(table_rows, parent); +} + +QWidget* makeFilterWrapper(QLineEdit** field_out, QWidget* parent = nullptr) { + auto* wrapper = new QWidget(parent); + wrapper->setObjectName("panelSectionFilterWrapper"); + auto* layout = new QVBoxLayout(wrapper); + layout->setContentsMargins(ifcinterface::components::style::metrics::section_body_padding, + 0, + ifcinterface::components::style::metrics::section_body_padding, + 0); + layout->setSpacing(0); + + auto* field = new QLineEdit(wrapper); + field->setClearButtonEnabled(true); + field->addAction(ifcinterface::components::icons::makeSvgIcon(":/icons/filter.svg"), QLineEdit::LeadingPosition); + field->setVisible(false); + layout->addWidget(field); + + if (field_out) *field_out = field; + return wrapper; +} + +} // namespace + +namespace ifcinterface::panels::properties { + +PropertiesPanelWidget::PropertiesPanelWidget(QWidget* parent) + : QWidget(parent) +{ + content_layout_ = new QVBoxLayout(this); + content_layout_->setContentsMargins(0, 0, 0, 0); + content_layout_->setSpacing(12); + + auto* entity_box = new QFrame(this); + entity_box->setObjectName("entityClassBox"); + auto* entity_layout = new QHBoxLayout(entity_box); + entity_layout->setContentsMargins(10, 8, 10, 8); + entity_layout->setSpacing(10); + auto* entity_icon = new QLabel(entity_box); + entity_icon->setPixmap(components::icons::makeSvgPixmap(":/icons/cube-dots.svg", QSize(28, 28))); + entity_icon->setAlignment(Qt::AlignCenter); + auto* entity_text = new QWidget(entity_box); + auto* entity_text_layout = new QVBoxLayout(entity_text); + entity_text_layout->setContentsMargins(0, 0, 0, 0); + entity_text_layout->setSpacing(2); + entity_class_label_ = new QLabel(entity_text); + entity_class_label_->setObjectName("entityClassLabel"); + entity_type_label_ = new QLabel(entity_text); + entity_type_label_->setProperty("textRole", "secondary"); + entity_text_layout->addWidget(entity_class_label_); + entity_text_layout->addWidget(entity_type_label_); + entity_layout->addWidget(entity_icon, 0, Qt::AlignVCenter); + entity_layout->addWidget(entity_text, 1, Qt::AlignVCenter); + + entity_section_ = new components::Section("", components::SectionHeaderMode::Hidden, this); + entity_section_->addBodyWidget(entity_box); + attributes_section_ = new components::Section("Attributes", components::SectionHeaderMode::Visible, this); + relationships_section_ = new components::Section("Relationships", components::SectionHeaderMode::Visible, this); + properties_section_ = new components::Section("Properties", components::SectionHeaderMode::Visible, this); + quantities_section_ = new components::Section("Quantities", components::SectionHeaderMode::Visible, this); + + properties_filter_toggle_ = new QToolButton(properties_section_); + properties_filter_toggle_->setObjectName("panelSectionFilterToggle"); + properties_filter_toggle_->setCheckable(true); + properties_filter_toggle_->setIcon(components::icons::makeSvgIcon(":/icons/filter.svg")); + properties_filter_toggle_->setAutoRaise(true); + properties_section_->addHeaderWidget(properties_filter_toggle_); + + quantities_filter_toggle_ = new QToolButton(quantities_section_); + quantities_filter_toggle_->setObjectName("panelSectionFilterToggle"); + quantities_filter_toggle_->setCheckable(true); + quantities_filter_toggle_->setIcon(components::icons::makeSvgIcon(":/icons/filter.svg")); + quantities_filter_toggle_->setAutoRaise(true); + quantities_section_->addHeaderWidget(quantities_filter_toggle_); + + properties_filter_wrapper_ = makeFilterWrapper(&properties_filter_field_, properties_section_); + properties_filter_field_->setPlaceholderText("Filter properties or sets"); + quantities_filter_wrapper_ = makeFilterWrapper(&quantities_filter_field_, quantities_section_); + quantities_filter_field_->setPlaceholderText("Filter quantities or sets"); + + connect(properties_filter_toggle_, &QToolButton::toggled, properties_filter_field_, [this](bool visible) { + properties_filter_field_->setVisible(visible); + properties_filter_wrapper_->setVisible(visible); + if (visible) properties_filter_field_->setFocus(); + }); + connect(quantities_filter_toggle_, &QToolButton::toggled, quantities_filter_field_, [this](bool visible) { + quantities_filter_field_->setVisible(visible); + quantities_filter_wrapper_->setVisible(visible); + if (visible) quantities_filter_field_->setFocus(); + }); + + properties_filter_wrapper_->setVisible(false); + quantities_filter_wrapper_->setVisible(false); + + content_layout_->addWidget(entity_section_); + content_layout_->addWidget(attributes_section_); + content_layout_->addWidget(relationships_section_); + content_layout_->addWidget(properties_section_); + content_layout_->addWidget(quantities_section_); + content_layout_->addStretch(1); +} + +void PropertiesPanelWidget::render(const PropertiesPanelState& state) { + const bool attributes_expanded = attributes_section_->isExpanded(); + const bool relationships_expanded = relationships_section_->isExpanded(); + const bool properties_expanded = properties_section_->isExpanded(); + const bool quantities_expanded = quantities_section_->isExpanded(); + const bool properties_filter_visible = properties_filter_toggle_->isChecked(); + const bool quantities_filter_visible = quantities_filter_toggle_->isChecked(); + const QString properties_filter_text = properties_filter_field_->text(); + const QString quantities_filter_text = quantities_filter_field_->text(); + + entity_class_label_->setText(state.entity.entity_class); + entity_type_label_->setText(state.entity.predefined_type); + + attributes_section_->clearBody(); + relationships_section_->clearBody(); + properties_section_->clearBody(); + quantities_section_->clearBody(); + + QList property_set_widgets; + for (const auto& property_set : state.property_sets) { + property_set_widgets.append(makePropertySetPanel(property_set, this)); + } + + QList quantity_set_widgets; + for (const auto& property_set : state.quantity_sets) { + quantity_set_widgets.append(makePropertySetPanel(property_set, this)); + } + + attributes_section_->addBodyWidget(makeAttributeList(state.attributes, this)); + relationships_section_->addBodyWidget(makeRelationshipList(state.relationships, this)); + properties_section_->addBodyWidget(properties_filter_wrapper_); + for (auto* widget : property_set_widgets) properties_section_->addBodyWidget(widget); + quantities_section_->addBodyWidget(quantities_filter_wrapper_); + for (auto* widget : quantity_set_widgets) quantities_section_->addBodyWidget(widget); + + attributes_section_->setExpanded(attributes_expanded); + relationships_section_->setExpanded(relationships_expanded); + properties_section_->setExpanded(properties_expanded); + quantities_section_->setExpanded(quantities_expanded); + properties_filter_field_->setText(properties_filter_text); + quantities_filter_field_->setText(quantities_filter_text); + properties_filter_toggle_->setChecked(properties_filter_visible); + quantities_filter_toggle_->setChecked(quantities_filter_visible); +} + +} // namespace ifcinterface::panels::properties diff --git a/src/interface/panels/properties/PropertiesPanelWidget.h b/src/interface/panels/properties/Widget.h similarity index 71% rename from src/interface/panels/properties/PropertiesPanelWidget.h rename to src/interface/panels/properties/Widget.h index 95b584bd50..f5dc78b19a 100644 --- a/src/interface/panels/properties/PropertiesPanelWidget.h +++ b/src/interface/panels/properties/Widget.h @@ -21,11 +21,15 @@ #ifndef IFCINTERFACE_PANELS_PROPERTIESPANELWIDGET_H #define IFCINTERFACE_PANELS_PROPERTIESPANELWIDGET_H -#include "PropertiesPanelTypes.h" +#include "Types.h" #include class QVBoxLayout; +class QLabel; +class QLineEdit; +class QToolButton; +namespace ifcinterface::components { class Section; } namespace ifcinterface::panels::properties { @@ -38,6 +42,19 @@ public: private: QVBoxLayout* content_layout_ = nullptr; + QLabel* entity_class_label_ = nullptr; + QLabel* entity_type_label_ = nullptr; + components::Section* entity_section_ = nullptr; + components::Section* attributes_section_ = nullptr; + components::Section* relationships_section_ = nullptr; + components::Section* properties_section_ = nullptr; + components::Section* quantities_section_ = nullptr; + QWidget* properties_filter_wrapper_ = nullptr; + QWidget* quantities_filter_wrapper_ = nullptr; + QLineEdit* properties_filter_field_ = nullptr; + QLineEdit* quantities_filter_field_ = nullptr; + QToolButton* properties_filter_toggle_ = nullptr; + QToolButton* quantities_filter_toggle_ = nullptr; }; } // namespace ifcinterface::panels::properties diff --git a/src/interface/panels/settings/Dialog.cpp b/src/interface/panels/settings/Dialog.cpp new file mode 100644 index 0000000000..e181d25db1 --- /dev/null +++ b/src/interface/panels/settings/Dialog.cpp @@ -0,0 +1,222 @@ +// 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 "Dialog.h" + +#include "../../../ifcviewer/AppSettings.h" +#include "../../components/Section.h" +#include "../../components/Style.h" +#include "../../components/SvgIcon.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ifcinterface::panels::settings { + +SettingsDialog::SettingsDialog(QWidget* parent) + : QDialog(parent) +{ + setObjectName("appDialog"); + setWindowTitle("Settings"); + setModal(true); + resize(520, 420); + setStyleSheet(components::style::buildAppStyleSheet()); + setupUi(); +} + +void SettingsDialog::showEvent(QShowEvent* event) { + syncFromSettings(); + QDialog::showEvent(event); +} + +void SettingsDialog::setupUi() { + auto* root = new QVBoxLayout(this); + root->setContentsMargins(12, 12, 12, 12); + root->setSpacing(0); + + auto* panel = new QFrame(this); + panel->setObjectName("panel"); + auto* panel_layout = new QVBoxLayout(panel); + panel_layout->setContentsMargins(0, components::style::metrics::section_body_padding, 0, + components::style::metrics::section_body_padding); + panel_layout->setSpacing(12); + + auto* tabs = new QTabWidget(panel); + + auto* graphics_tab = new QWidget(tabs); + auto* graphics_layout = new QVBoxLayout(graphics_tab); + graphics_layout->setContentsMargins(0, 0, 0, 0); + graphics_layout->setSpacing(12); + + auto* general_section = new components::Section("General", components::SectionHeaderMode::Visible, graphics_tab); + auto* general_body = new QWidget(general_section); + auto* general_form = new QFormLayout(general_body); + general_form->setContentsMargins(0, 0, 0, 0); + general_form->setHorizontalSpacing(16); + general_form->setVerticalSpacing(10); + + geometry_library_edit_ = new QLineEdit(general_body); + geometry_library_edit_->setMinimumWidth(300); + general_form->addRow("Geometry Library", geometry_library_edit_); + + show_stats_check_ = new QCheckBox(general_body); + general_form->addRow("Show Performance Stats", show_stats_check_); + + backface_culling_check_ = new QCheckBox(general_body); + backface_culling_check_->setToolTip( + "Skip triangles facing away from the camera. Big FPS win on closed solids; " + "disable if you see holes in open geometry."); + general_form->addRow("Backface Culling", backface_culling_check_); + + general_section->addBodyWidget(general_body); + + auto* loading_section = new components::Section("Loading", components::SectionHeaderMode::Visible, graphics_tab); + auto* loading_body = new QWidget(loading_section); + auto* loading_form = new QFormLayout(loading_body); + loading_form->setContentsMargins(0, 0, 0, 0); + loading_form->setHorizontalSpacing(16); + loading_form->setVerticalSpacing(10); + + load_data_source_checkbox_ = new QCheckBox(loading_body); + load_data_source_checkbox_->setToolTip( + "Keep the .ifc/.rdb open after loading so element properties can be queried. " + "Disable for geometry-only viewing."); + loading_form->addRow("Load Property Data Source", load_data_source_checkbox_); + + apply_coordinate_operation_check_ = new QCheckBox(loading_body); + apply_coordinate_operation_check_->setToolTip( + "Apply each model's IfcCoordinateOperation after load so it lands in " + "georeferenced map coordinates."); + loading_form->addRow("Apply Coordinate Operation", apply_coordinate_operation_check_); + + void_limit_spin_ = new QSpinBox(loading_body); + void_limit_spin_->setRange(0, 100000); + loading_form->addRow("Void Limit", void_limit_spin_); + + deflection_tolerance_spin_ = new QDoubleSpinBox(loading_body); + deflection_tolerance_spin_->setRange(0.000001, 1000.0); + deflection_tolerance_spin_->setDecimals(6); + deflection_tolerance_spin_->setSingleStep(0.001); + loading_form->addRow("Deflection Tolerance", deflection_tolerance_spin_); + + angular_tolerance_spin_ = new QDoubleSpinBox(loading_body); + angular_tolerance_spin_->setRange(0.000001, 3.141592); + angular_tolerance_spin_->setDecimals(6); + angular_tolerance_spin_->setSingleStep(0.05); + loading_form->addRow("Angular Tolerance", angular_tolerance_spin_); + + auto* description = new QLabel( + "These settings are shared with the viewer backend and persist via QSettings.", + loading_body); + description->setProperty("textRole", "secondary"); + description->setWordWrap(true); + loading_form->addRow(QString(), description); + + loading_section->addBodyWidget(loading_body); + graphics_layout->addWidget(general_section); + graphics_layout->addWidget(loading_section); + graphics_layout->addStretch(1); + + auto make_placeholder_tab = [tabs](const QString& title, const QString& detail) { + auto* tab = new QWidget(tabs); + auto* layout = new QVBoxLayout(tab); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(12); + + auto* section = new components::Section(title, components::SectionHeaderMode::Visible, tab); + auto* body = new QWidget(section); + auto* body_layout = new QVBoxLayout(body); + body_layout->setContentsMargins(0, 0, 0, 0); + body_layout->setSpacing(8); + + auto* heading = new QLabel(title, body); + auto* content = new QLabel(detail, body); + content->setProperty("textRole", "secondary"); + content->setWordWrap(true); + + body_layout->addWidget(heading); + body_layout->addWidget(content); + section->addBodyWidget(body); + layout->addWidget(section); + layout->addStretch(1); + return tab; + }; + + tabs->addTab(make_placeholder_tab("Navigation", "Navigation preferences and interaction modes will live here."), + "Navigation"); + tabs->addTab(make_placeholder_tab("Keybindings", "Shortcut presets and command bindings will live here."), + "Keybindings"); + tabs->addTab(graphics_tab, "Graphics"); + tabs->addTab(make_placeholder_tab("Theme", "Theme, density, and UI appearance settings will live here."), + "Theme"); + tabs->addTab(make_placeholder_tab("About", "Version, credits, and environment information will live here."), + "About"); + + auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, panel); + if (auto* ok = buttons->button(QDialogButtonBox::Ok)) { + ok->setText("OK"); + ok->setIcon(components::icons::makeSvgIcon(":/icons/check.svg")); + } + if (auto* cancel = buttons->button(QDialogButtonBox::Cancel)) { + cancel->setText("Cancel"); + cancel->setIcon(components::icons::makeSvgIcon(":/icons/xmark-circle.svg")); + } + connect(buttons, &QDialogButtonBox::accepted, this, &SettingsDialog::onAccepted); + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); + + panel_layout->addWidget(tabs); + panel_layout->addWidget(buttons); + root->addWidget(panel); +} + +void SettingsDialog::syncFromSettings() { + geometry_library_edit_->setText(AppSettings::instance().geometryLibrary()); + show_stats_check_->setChecked(AppSettings::instance().showStats()); + backface_culling_check_->setChecked(AppSettings::instance().backfaceCulling()); + load_data_source_checkbox_->setChecked(AppSettings::instance().loadDataSource()); + apply_coordinate_operation_check_->setChecked(AppSettings::instance().applyCoordinateOperation()); + void_limit_spin_->setValue(AppSettings::instance().voidLimit()); + deflection_tolerance_spin_->setValue(AppSettings::instance().deflectionTolerance()); + angular_tolerance_spin_->setValue(AppSettings::instance().angularTolerance()); +} + +void SettingsDialog::onAccepted() { + AppSettings::instance().setGeometryLibrary(geometry_library_edit_->text()); + AppSettings::instance().setShowStats(show_stats_check_->isChecked()); + AppSettings::instance().setBackfaceCulling(backface_culling_check_->isChecked()); + AppSettings::instance().setLoadDataSource(load_data_source_checkbox_->isChecked()); + AppSettings::instance().setApplyCoordinateOperation(apply_coordinate_operation_check_->isChecked()); + AppSettings::instance().setVoidLimit(void_limit_spin_->value()); + AppSettings::instance().setDeflectionTolerance(deflection_tolerance_spin_->value()); + AppSettings::instance().setAngularTolerance(angular_tolerance_spin_->value()); + accept(); +} + +} // namespace ifcinterface::panels::settings diff --git a/src/interface/panels/settings/Dialog.h b/src/interface/panels/settings/Dialog.h new file mode 100644 index 0000000000..3b34ad8687 --- /dev/null +++ b/src/interface/panels/settings/Dialog.h @@ -0,0 +1,59 @@ +// 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_PANELS_SETTINGSDIALOG_H +#define IFCINTERFACE_PANELS_SETTINGSDIALOG_H + +#include + +class QCheckBox; +class QDoubleSpinBox; +class QLineEdit; +class QShowEvent; +class QSpinBox; + +namespace ifcinterface::panels::settings { + +class SettingsDialog : public QDialog { + Q_OBJECT +public: + explicit SettingsDialog(QWidget* parent = nullptr); + +protected: + void showEvent(QShowEvent* event) override; + +private: + void setupUi(); + void syncFromSettings(); + void onAccepted(); + + QLineEdit* geometry_library_edit_ = nullptr; + QCheckBox* show_stats_check_ = nullptr; + QCheckBox* backface_culling_check_ = nullptr; + QCheckBox* load_data_source_checkbox_ = nullptr; + QCheckBox* apply_coordinate_operation_check_ = nullptr; + QSpinBox* void_limit_spin_ = nullptr; + QDoubleSpinBox* deflection_tolerance_spin_ = nullptr; + QDoubleSpinBox* angular_tolerance_spin_ = nullptr; +}; + +} // namespace ifcinterface::panels::settings + +#endif diff --git a/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelTypes.h b/src/interface/panels/spatial_hierarchy/Types.h similarity index 100% rename from src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelTypes.h rename to src/interface/panels/spatial_hierarchy/Types.h diff --git a/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelView.cpp b/src/interface/panels/spatial_hierarchy/View.cpp similarity index 97% rename from src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelView.cpp rename to src/interface/panels/spatial_hierarchy/View.cpp index a3948558b8..65077906e8 100644 --- a/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelView.cpp +++ b/src/interface/panels/spatial_hierarchy/View.cpp @@ -18,9 +18,9 @@ * * ********************************************************************************/ -#include "SpatialHierarchyPanelView.h" +#include "View.h" -#include "SpatialHierarchyPanelWidget.h" +#include "Widget.h" namespace ifcinterface::panels::spatial_hierarchy { diff --git a/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelView.h b/src/interface/panels/spatial_hierarchy/View.h similarity index 98% rename from src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelView.h rename to src/interface/panels/spatial_hierarchy/View.h index 1924b7f9f3..4df2ddc124 100644 --- a/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelView.h +++ b/src/interface/panels/spatial_hierarchy/View.h @@ -21,7 +21,7 @@ #ifndef IFCINTERFACE_PANELS_SPATIALHIERARCHYPANELVIEW_H #define IFCINTERFACE_PANELS_SPATIALHIERARCHYPANELVIEW_H -#include "SpatialHierarchyPanelTypes.h" +#include "Types.h" #include diff --git a/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.cpp b/src/interface/panels/spatial_hierarchy/Widget.cpp similarity index 93% rename from src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.cpp rename to src/interface/panels/spatial_hierarchy/Widget.cpp index 5c48a1b24a..72230dd247 100644 --- a/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.cpp +++ b/src/interface/panels/spatial_hierarchy/Widget.cpp @@ -18,7 +18,7 @@ * * ********************************************************************************/ -#include "SpatialHierarchyPanelWidget.h" +#include "Widget.h" #include "../../components/Section.h" #include "../../components/SvgIcon.h" @@ -37,7 +37,7 @@ SpatialHierarchyPanelWidget::SpatialHierarchyPanelWidget(QWidget* parent) layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); - auto* section = new components::Section("", components::SectionHeaderMode::Hidden, "", this); + auto* section = new components::Section("", components::SectionHeaderMode::Hidden, this); tree_ = new QTreeWidget(section); tree_->setColumnCount(2); @@ -71,8 +71,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, components::icons::makePanelSvgIcon(iconPath(node.kind))); - item->setIcon(1, components::icons::makePanelSvgIcon(node.visible ? ":/icons/eye.svg" : ":/icons/eye-closed.svg")); + item->setIcon(0, components::icons::makeSvgIcon(iconPath(node.kind))); + item->setIcon(1, components::icons::makeSvgIcon(node.visible ? ":/icons/eye.svg" : ":/icons/eye-closed.svg")); for (const auto& child : node.children) { addNode(item, child); } diff --git a/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.h b/src/interface/panels/spatial_hierarchy/Widget.h similarity index 98% rename from src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.h rename to src/interface/panels/spatial_hierarchy/Widget.h index b5e15720ce..134b78c5ce 100644 --- a/src/interface/panels/spatial_hierarchy/SpatialHierarchyPanelWidget.h +++ b/src/interface/panels/spatial_hierarchy/Widget.h @@ -21,7 +21,7 @@ #ifndef IFCINTERFACE_PANELS_SPATIALHIERARCHYPANELWIDGET_H #define IFCINTERFACE_PANELS_SPATIALHIERARCHYPANELWIDGET_H -#include "SpatialHierarchyPanelTypes.h" +#include "Types.h" #include diff --git a/src/interface/panels/todo/TodoPanelWidget.cpp b/src/interface/panels/todo/Widget.cpp similarity index 97% rename from src/interface/panels/todo/TodoPanelWidget.cpp rename to src/interface/panels/todo/Widget.cpp index 45ff4c043c..1f2e58d3dc 100644 --- a/src/interface/panels/todo/TodoPanelWidget.cpp +++ b/src/interface/panels/todo/Widget.cpp @@ -18,7 +18,7 @@ * * ********************************************************************************/ -#include "TodoPanelWidget.h" +#include "Widget.h" #include "../../components/Section.h" @@ -34,7 +34,7 @@ TodoPanelWidget::TodoPanelWidget(const QString& title, QWidget* parent) layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); - auto* section = new components::Section("", components::SectionHeaderMode::Hidden, "", this); + auto* section = new components::Section("", components::SectionHeaderMode::Hidden, this); auto* body = new QWidget(section); auto* body_layout = new QVBoxLayout(body); diff --git a/src/interface/panels/todo/TodoPanelWidget.h b/src/interface/panels/todo/Widget.h similarity index 100% rename from src/interface/panels/todo/TodoPanelWidget.h rename to src/interface/panels/todo/Widget.h