From 392af501d16dfe435ca548c410da9b919cb5af44 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 8 Jul 2026 20:37:17 +1000 Subject: [PATCH] properties: real empty states + smaller base UI font - Replace the mock IfcWall placeholder with a "No item selected" empty state; the panel only fills in class/attributes/relationships/psets from a resolved object, and safely stays empty otherwise. - Show "No properties" / "No quantities" placeholders (muted, themed via secondary_text) when those sets are empty. - Drop the base application font 10pt -> 9pt to fit more data. Panel titles keep their own explicit size and are unaffected. Co-Authored-By: Claude Opus 4.8 --- src/bonsaiviewer/components/Style.cpp | 4 ++ src/bonsaiviewer/main.cpp | 5 +- src/bonsaiviewer/modules/properties/Panel.cpp | 12 ++++ src/bonsaiviewer/modules/properties/View.cpp | 60 +++---------------- 4 files changed, 27 insertions(+), 54 deletions(-) diff --git a/src/bonsaiviewer/components/Style.cpp b/src/bonsaiviewer/components/Style.cpp index fd1bcffc1a..069197428f 100644 --- a/src/bonsaiviewer/components/Style.cpp +++ b/src/bonsaiviewer/components/Style.cpp @@ -143,6 +143,10 @@ QString buildAppStyleSheet() { font-size: ${font_small}px; font-weight: 600; } + QLabel#panelSectionEmptyLabel { + color: ${secondary_text}; + padding: 4px 10px; + } QToolButton#panelTitleButton { border: none; background: transparent; diff --git a/src/bonsaiviewer/main.cpp b/src/bonsaiviewer/main.cpp index 59ef765e19..a77fe2896a 100644 --- a/src/bonsaiviewer/main.cpp +++ b/src/bonsaiviewer/main.cpp @@ -41,7 +41,10 @@ void installUiFont() { } } if (!family.isEmpty()) { - QApplication::setFont(QFont(family, 10)); + // Slightly smaller base font to fit more data. Panel titles keep their + // own explicit size (QLabel#panelTitleText in Style.cpp), so they're + // unaffected by this. + QApplication::setFont(QFont(family, 9)); } } diff --git a/src/bonsaiviewer/modules/properties/Panel.cpp b/src/bonsaiviewer/modules/properties/Panel.cpp index d45d858681..7820c1b69e 100644 --- a/src/bonsaiviewer/modules/properties/Panel.cpp +++ b/src/bonsaiviewer/modules/properties/Panel.cpp @@ -71,6 +71,12 @@ QWidget* makeRelationshipList(const QListsetObjectName("panelSectionEmptyLabel"); + return label; +} + QWidget* makeFilterWrapper(QLineEdit** field_out, QWidget* parent = nullptr) { auto* wrapper = new QWidget(parent); wrapper->setObjectName("panelSectionFilterWrapper"); @@ -176,6 +182,9 @@ void PropertiesPanel::render(const PropertiesPanelState& state) { }); properties_section->addBodyWidget(properties_filter_wrapper); for (auto* widget : property_set_widgets) properties_section->addBodyWidget(widget); + if (property_set_widgets.isEmpty()) { + properties_section->addBodyWidget(makeEmptyStateLabel("No properties", this)); + } properties_section->setExpanded(properties_expanded_); properties_filter_toggle->setChecked(properties_filter_visible_); @@ -203,6 +212,9 @@ void PropertiesPanel::render(const PropertiesPanelState& state) { }); quantities_section->addBodyWidget(quantities_filter_wrapper); for (auto* widget : quantity_set_widgets) quantities_section->addBodyWidget(widget); + if (quantity_set_widgets.isEmpty()) { + quantities_section->addBodyWidget(makeEmptyStateLabel("No quantities", this)); + } quantities_section->setExpanded(quantities_expanded_); quantities_filter_toggle->setChecked(quantities_filter_visible_); diff --git a/src/bonsaiviewer/modules/properties/View.cpp b/src/bonsaiviewer/modules/properties/View.cpp index fae5095935..de9d820b61 100644 --- a/src/bonsaiviewer/modules/properties/View.cpp +++ b/src/bonsaiviewer/modules/properties/View.cpp @@ -102,60 +102,14 @@ PropertiesPanelView::PropertiesPanelView(PropertiesPanel* widget, void PropertiesPanelView::refresh(uint32_t object_id) { auto* registry = session_state_->elementRegistry(); + + // Empty default: nothing selected → "No item selected" with empty sections. + // Real data is filled in below when an object resolves. PropertiesPanelState state; - state.entity = {"IfcWall", "SOLIDWALL"}; - state.attributes = { - {"GlobalId", "2Q$n5SLPP9Q8B7wQKjKfUQ"}, - {"Name", "Core-EXT-204"}, - {"Description", "External load-bearing wall"}, - }; - state.relationships = { - {"Type", "Basic Wall: Exterior - 200mm"}, - {"Container", "Level 02"}, - }; - state.property_sets = { - {"Pset_WallCommon", - {{"Reference", "Core-EXT-204"}, - {"Status", "Reviewed"}, - {"Fire Rating", "120 min"}, - {"LoadBearing", "True"}}}, - {"Identity Data", - {{"Type", "IfcWall"}, - {"Name", "Core-EXT-204"}, - {"Owner", "Architecture"}, - {"Phase", "Construction"}}}, - {"BIM Collaboration", - {{"Issue Count", "2 open"}, - {"Last Review", "2026-04-30"}, - {"Assigned To", "Design Coordination"}}}, - }; - state.quantity_sets = { - {"BaseQuantities", - {{"Length", "6.20 m"}, - {"Height", "3.45 m"}, - {"Width", "0.30 m"}, - {"Volume", "6.42 m3"}}}, - {"Finish Quantities", - {{"NetSideArea", "21.39 m2"}, - {"GrossArea", "22.10 m2"}, - {"Paint Coverage", "42.78 m2"}}}, - }; + state.entity = {"No item selected", ""}; - if (!registry) { - widget_->render(state); - return; - } - - // A real project is loaded — don't leak the placeholder attributes / - // relationships / predefined type. They're populated below from live IFC - // data, or from the cached basics for geometry-only elements. - state.entity.predefined_type.clear(); - state.attributes.clear(); - state.relationships.clear(); - state.property_sets.clear(); - state.quantity_sets.clear(); - - auto entity = registry->findEntity(object_id); + auto entity = registry ? registry->findEntity(object_id) + : std::optional{}; if (entity) { state.entity.entity_class = QString::fromStdString(entity->declaration().name()); if (auto predefined_type = get_predefined_type(*entity)) { @@ -183,7 +137,7 @@ void PropertiesPanelView::refresh(uint32_t object_id) { // occurrence values inheriting from the type. state.property_sets = toPropertySets(get_psets(*entity, /*psets_only=*/true, /*qtos_only=*/false)); state.quantity_sets = toPropertySets(get_psets(*entity, /*psets_only=*/false, /*qtos_only=*/true)); - } else { + } else if (registry) { // No live IFC source for this object — typical when a pure-geometry // .ifcview sidecar was loaded without its .ifc/.rdb sibling. Fall back // to the basic info cached in the element registry so the panel still