Interface mockup 15

This commit is contained in:
Dion Moult
2026-05-11 09:17:45 +10:00
parent 1c598a981e
commit 6df9aeda2c
21 changed files with 556 additions and 88 deletions
+13 -1
View File
@@ -725,7 +725,7 @@ void ViewportWindow::initGL() {
gl_->glEnable(GL_DEPTH_TEST);
gl_->glEnable(GL_MULTISAMPLE);
gl_->glClearColor(0.18f, 0.20f, 0.22f, 1.0f);
gl_->glClearColor(background_color_.redF(), background_color_.greenF(), background_color_.blueF(), 1.0f);
gl_->glCullFace(GL_BACK);
if (AppSettings::instance().backfaceCulling()) gl_->glEnable(GL_CULL_FACE);
else gl_->glDisable(GL_CULL_FACE);
@@ -768,6 +768,18 @@ void ViewportWindow::initGL() {
emit initialized();
}
void ViewportWindow::setBackgroundColor(const QColor& color) {
if (!color.isValid()) return;
const QColor normalized = color.toRgb();
if (background_color_ == normalized) return;
background_color_ = normalized;
if (gl_initialized_ && gl_) {
context_->makeCurrent(this);
gl_->glClearColor(background_color_.redF(), background_color_.greenF(), background_color_.blueF(), 1.0f);
requestUpdate();
}
}
void ViewportWindow::enqueuePendingOperation(PendingOperation op) {
pending_ops_.push_back(std::move(op));
}
+3
View File
@@ -23,6 +23,7 @@
#include <QWindow>
#include <QOpenGLContext>
#include <QtOpenGL/QOpenGLFunctions_4_5_Core>
#include <QColor>
#include <QElapsedTimer>
#include <QMatrix4x4>
#include <QVector3D>
@@ -312,6 +313,7 @@ public:
void setFederatedFalseOrigin(const Eigen::Matrix4d& matrix_meters);
void setModelCoordinateOperation(uint32_t model_id, const Eigen::Matrix4d& matrix_meters);
void setModelTransformation(uint32_t model_id, const Eigen::Matrix4d& matrix_meters);
void setBackgroundColor(const QColor& color);
// Selection. The viewport owns a SelectionState that tracks both
// the multi-set and the "active" (last single-clicked) id. External
@@ -607,6 +609,7 @@ private:
QOpenGLContext* context_ = nullptr;
QOpenGLFunctions_4_5_Core* gl_ = nullptr;
bool gl_initialized_ = false;
QColor background_color_ = QColor("#202329");
std::deque<PendingOperation> pending_ops_;
// Shaders
+2
View File
@@ -27,6 +27,8 @@ set(INTERFACE_FILES
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ElementRegistry.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ElementRegistry.h
${CMAKE_CURRENT_SOURCE_DIR}/InterfaceSettings.cpp
${CMAKE_CURRENT_SOURCE_DIR}/InterfaceSettings.h
${CMAKE_CURRENT_SOURCE_DIR}/SessionState.cpp
${CMAKE_CURRENT_SOURCE_DIR}/SessionState.h
${CMAKE_CURRENT_SOURCE_DIR}/MainWindow.cpp
+189
View File
@@ -0,0 +1,189 @@
// 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 <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#include "InterfaceSettings.h"
#include "components/Style.h"
#include <QColor>
#include <QSettings>
namespace {
constexpr const char* kThemeModeKey = "interface/theme/mode";
constexpr const char* kThemeColorPrefix = "interface/theme/colors/";
using Spec = ifcinterface::InterfaceSettings::ThemeColorSpec;
const std::vector<Spec> kThemeColorSpecs = {
{"app_background", "App Background", ifcinterface::components::style::palette::app_background, "#eef1f5"},
{"border", "Border", ifcinterface::components::style::palette::border, "#c9d1dc"},
{"selection_background", "Selection Background", ifcinterface::components::style::palette::selection_background,
"#2f9e44"},
{"tab_bar_background", "Tab Bar Background", ifcinterface::components::style::palette::tab_bar_background,
"#eef1f5"},
{"tab_background", "Tab Background", ifcinterface::components::style::palette::tab_background, "#e3e8ef"},
{"ribbon_background", "Ribbon Background", ifcinterface::components::style::palette::ribbon_background,
"#e3e8ef"},
{"ribbon_button_hover", "Ribbon Button Hover", ifcinterface::components::style::palette::ribbon_button_hover,
"#d6dde7"},
{"ribbon_button_pressed", "Ribbon Button Pressed", ifcinterface::components::style::palette::ribbon_button_pressed,
"#cad3df"},
{"viewport_shell_background", "Viewport Shell Background",
ifcinterface::components::style::palette::viewport_shell_background, "#dbe1e8"},
{"viewport_background", "Viewport Background", ifcinterface::components::style::palette::viewport_background,
"#eef2f6"},
{"panel_background", "Panel Background", ifcinterface::components::style::palette::panel_background, "#ffffff"},
{"control_background", "Control Background", ifcinterface::components::style::palette::control_background,
"#f5f7fa"},
{"control_border_focus", "Control Border Focus", ifcinterface::components::style::palette::control_border_focus,
"#7c8ca3"},
{"box_background", "Box Background", ifcinterface::components::style::palette::box_background, "#f5f7fa"},
{"scroll_handle", "Scroll Handle", ifcinterface::components::style::palette::scroll_handle, "#b1bac8"},
{"scroll_handle_hover", "Scroll Handle Hover", ifcinterface::components::style::palette::scroll_handle_hover,
"#929daf"},
{"status_background", "Status Background", ifcinterface::components::style::palette::status_background,
"#edf1f5"},
{"section_header_background", "Section Header Background",
ifcinterface::components::style::palette::section_header_background, "#eef2f6"},
{"primary_text", "Primary Text", ifcinterface::components::style::palette::primary_text, "#1f2937"},
{"secondary_text", "Secondary Text", ifcinterface::components::style::palette::secondary_text, "#5b6676"},
{"disabled_text", "Disabled Text", ifcinterface::components::style::palette::disabled_text, "#8b95a3"},
{"warning_text", "Warning Text", ifcinterface::components::style::palette::warning_text, "#b26b00"},
{"selection_text", "Selection Text", ifcinterface::components::style::palette::selection_text, "#ffffff"},
{"hover_text", "Hover Text", ifcinterface::components::style::palette::hover_text, "#0f172a"},
{"icon_color", "Icon Color", "#e7ebf2", "#445066"},
{"icon_active_color", "Icon Active Color", "#ffffff", "#101828"},
{"icon_disabled_color", "Icon Disabled Color", "#6f7988", "#98a2b3"},
{"icon_accent_color", "Accent Icon Color", "#39b54a", "#2f9e44"},
{"icon_accent_active_color", "Accent Icon Active Color", "#53c763", "#267e37"},
};
int colorIndexForKey(const QString& key) {
for (size_t i = 0; i < kThemeColorSpecs.size(); ++i) {
if (QString::fromUtf8(kThemeColorSpecs[i].key) == key) {
return static_cast<int>(i);
}
}
return -1;
}
QString normalizedColor(const QString& value, const QString& fallback) {
const QString trimmed = value.trimmed();
if (!QColor::isValidColorName(trimmed)) return fallback;
return QColor(trimmed).name(QColor::HexRgb);
}
} // namespace
namespace ifcinterface {
InterfaceSettings& InterfaceSettings::instance() {
static InterfaceSettings inst;
return inst;
}
const std::vector<InterfaceSettings::ThemeColorSpec>& InterfaceSettings::themeColorSpecs() {
return kThemeColorSpecs;
}
InterfaceSettings::InterfaceSettings() {
custom_colors_.resize(kThemeColorSpecs.size());
load();
}
InterfaceSettings::ThemeMode InterfaceSettings::themeMode() const {
return theme_mode_;
}
void InterfaceSettings::setThemeMode(ThemeMode mode) {
if (theme_mode_ == mode) return;
theme_mode_ = mode;
persist();
emit themeModeChanged(mode);
emit themeChanged();
}
QString InterfaceSettings::color(const QString& key) const {
const int index = colorIndexForKey(key);
if (index < 0) return {};
const auto& spec = kThemeColorSpecs[static_cast<size_t>(index)];
switch (theme_mode_) {
case ThemeMode::Dark:
return QString::fromUtf8(spec.dark_default);
case ThemeMode::Light:
return QString::fromUtf8(spec.light_default);
case ThemeMode::Custom:
return customColor(key);
}
return QString::fromUtf8(spec.dark_default);
}
QString InterfaceSettings::customColor(const QString& key) const {
const int index = colorIndexForKey(key);
if (index < 0) return {};
const auto& spec = kThemeColorSpecs[static_cast<size_t>(index)];
const QString& stored = custom_colors_[static_cast<size_t>(index)];
return stored.isEmpty() ? QString::fromUtf8(spec.dark_default) : stored;
}
void InterfaceSettings::setCustomColor(const QString& key, const QString& value) {
const int index = colorIndexForKey(key);
if (index < 0) return;
const auto& spec = kThemeColorSpecs[static_cast<size_t>(index)];
const QString normalized = normalizedColor(value, QString::fromUtf8(spec.dark_default));
QString& stored = custom_colors_[static_cast<size_t>(index)];
if (stored == normalized) return;
stored = normalized;
persist();
if (theme_mode_ == ThemeMode::Custom) {
emit themeChanged();
}
}
void InterfaceSettings::load() {
QSettings settings;
int raw_mode = settings.value(kThemeModeKey, static_cast<int>(ThemeMode::Dark)).toInt();
if (raw_mode < static_cast<int>(ThemeMode::Dark) || raw_mode > static_cast<int>(ThemeMode::Custom)) {
raw_mode = static_cast<int>(ThemeMode::Dark);
}
theme_mode_ = static_cast<ThemeMode>(raw_mode);
for (size_t i = 0; i < kThemeColorSpecs.size(); ++i) {
const auto& spec = kThemeColorSpecs[i];
const QString stored = settings.value(QString::fromUtf8(kThemeColorPrefix) + QString::fromUtf8(spec.key),
QString::fromUtf8(spec.dark_default))
.toString();
custom_colors_[i] = normalizedColor(stored, QString::fromUtf8(spec.dark_default));
}
}
void InterfaceSettings::persist() const {
QSettings settings;
settings.setValue(kThemeModeKey, static_cast<int>(theme_mode_));
for (size_t i = 0; i < kThemeColorSpecs.size(); ++i) {
settings.setValue(QString::fromUtf8(kThemeColorPrefix) + QString::fromUtf8(kThemeColorSpecs[i].key),
custom_colors_[i]);
}
}
} // namespace ifcinterface
+72
View File
@@ -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 <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#ifndef IFCINTERFACE_INTERFACESETTINGS_H
#define IFCINTERFACE_INTERFACESETTINGS_H
#include <QObject>
#include <QString>
#include <vector>
namespace ifcinterface {
class InterfaceSettings : public QObject {
Q_OBJECT
public:
enum class ThemeMode {
Dark = 0,
Light = 1,
Custom = 2,
};
Q_ENUM(ThemeMode)
struct ThemeColorSpec {
const char* key;
const char* label;
const char* dark_default;
const char* light_default;
};
static InterfaceSettings& instance();
static const std::vector<ThemeColorSpec>& themeColorSpecs();
ThemeMode themeMode() const;
void setThemeMode(ThemeMode mode);
QString color(const QString& key) const;
QString customColor(const QString& key) const;
void setCustomColor(const QString& key, const QString& value);
signals:
void themeModeChanged(ThemeMode mode);
void themeChanged();
private:
InterfaceSettings();
void load();
void persist() const;
ThemeMode theme_mode_ = ThemeMode::Dark;
std::vector<QString> custom_colors_;
};
} // namespace ifcinterface
#endif
-1
View File
@@ -299,7 +299,6 @@ QWidget* MainWindow::buildPanelsRibbonPage() {
void MainWindow::setupRibbon() {
auto* shell = new QFrame(this);
shell->setObjectName("ribbonShell");
auto* shell_layout = new QVBoxLayout(shell);
shell_layout->setContentsMargins(0, 0, 0, 0);
+1 -1
View File
@@ -36,7 +36,7 @@ QToolButton* makeButton(const QString& text,
auto* button = new QToolButton(parent);
button->setObjectName("ribbonButton");
button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
button->setIcon(components::icons::makeTintedSvgIcon(icon_path));
button->setIcon(components::icons::makeAccentSvgIcon(icon_path));
button->setIconSize(QSize(20, 20));
button->setText(text);
button->setMinimumSize(QSize(90, 68));
+57
View File
@@ -21,6 +21,7 @@
#include "Dialog.h"
#include "Style.h"
#include "Tabs.h"
#include <QDialog>
#include <QFrame>
@@ -83,4 +84,60 @@ void Dialog::addFooterWidget(QWidget* widget) {
footer_layout_->addWidget(widget);
}
TabbedDialog::TabbedDialog(QWidget* parent)
: QDialog(parent)
{
auto* outer_layout = new QVBoxLayout(this);
outer_layout->setContentsMargins(style::metrics::padding,
style::metrics::padding,
style::metrics::padding,
style::metrics::padding);
outer_layout->setSpacing(0);
auto* frame = new QFrame(this);
frame->setObjectName("panel");
auto* frame_layout = new QVBoxLayout(frame);
frame_layout->setContentsMargins(0,
style::metrics::section_body_padding,
0,
style::metrics::section_body_padding);
frame_layout->setSpacing(0);
tabs_ = new TabWidget(frame);
frame_layout->addWidget(tabs_, 1);
auto* footer = new QWidget(frame);
footer_layout_ = new QVBoxLayout(footer);
footer_layout_->setContentsMargins(style::metrics::section_body_padding,
style::metrics::section_body_padding,
style::metrics::section_body_padding,
0);
footer_layout_->setSpacing(style::metrics::section_body_padding);
footer_layout_->setAlignment(Qt::AlignTop);
frame_layout->addWidget(footer);
outer_layout->addWidget(frame);
}
void TabbedDialog::addTab(const QString& title, QWidget* widget) {
auto* scroll = new QScrollArea(tabs_);
scroll->setWidgetResizable(true);
scroll->setFrameShape(QFrame::NoFrame);
auto* scroll_body = new QWidget(scroll);
scroll_body->setObjectName("panelScrollBody");
auto* layout = new QVBoxLayout(scroll_body);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(style::metrics::section_body_padding);
layout->setAlignment(Qt::AlignTop);
layout->addWidget(widget);
scroll->setWidget(scroll_body);
tabs_->addTab(scroll, title);
}
void TabbedDialog::addFooterWidget(QWidget* widget) {
footer_layout_->addWidget(widget);
}
} // namespace ifcinterface::components
+15
View File
@@ -25,6 +25,7 @@
class QVBoxLayout;
class QWidget;
class QTabWidget;
namespace ifcinterface::components {
@@ -43,6 +44,20 @@ private:
QVBoxLayout* footer_layout_ = nullptr;
};
class TabbedDialog : public QDialog {
Q_OBJECT
public:
explicit TabbedDialog(QWidget* parent = nullptr);
void addTab(const QString& title, QWidget* widget);
void addFooterWidget(QWidget* widget);
private:
QTabWidget* tabs_ = nullptr;
QVBoxLayout* footer_layout_ = nullptr;
};
} // namespace ifcinterface::components
#endif
+36 -41
View File
@@ -20,9 +20,12 @@
#include "Style.h"
#include "../InterfaceSettings.h"
namespace ifcinterface::components::style {
QString buildAppStyleSheet() {
const auto& theme = ifcinterface::InterfaceSettings::instance();
QString stylesheet = QStringLiteral(R"(
QMainWindow#appWindow {
background: ${app_background};
@@ -47,15 +50,11 @@ QString buildAppStyleSheet() {
background: ${app_background};
color: ${primary_text};
}
QFrame#ribbonShell {
background: ${ribbon_shell_background};
border-bottom: 1px solid ${border};
}
QTabBar#appTabBar {
background: ${ribbon_shell_background};
background: ${tab_bar_background};
}
QTabBar#appTabBar::tab {
background: transparent;
background: ${tab_background};
color: ${secondary_text};
padding: 8px 14px;
margin-right: 2px;
@@ -66,10 +65,10 @@ QString buildAppStyleSheet() {
border-bottom: 2px solid ${selection_background};
}
QTabBar#appTabBar::tab:hover {
color: ${ribbon_tab_hover_text};
color: ${hover_text};
}
QFrame#ribbonBand {
background: ${ribbon_band_background};
background: ${ribbon_background};
border-top: 1px solid ${border};
}
QTabWidget#appTabWidget::pane {
@@ -107,7 +106,6 @@ QString buildAppStyleSheet() {
border-top: none;
}
QFrame#viewportFrame {
background: ${viewport_background};
border: 1px solid ${border};
}
QDockWidget {
@@ -146,13 +144,12 @@ QString buildAppStyleSheet() {
font-weight: 600;
}
QToolButton#panelTitleButton {
color: ${panel_title_button};
border: none;
background: transparent;
}
QToolButton#panelTitleButton:hover {
color: ${ribbon_tab_hover_text};
background: ${panel_title_button_hover};
color: ${hover_text};
background: ${ribbon_button_hover};
}
QFrame#panel {
background: ${panel_background};
@@ -440,7 +437,7 @@ QString buildAppStyleSheet() {
margin: 0;
}
QToolButton#panelSectionHeaderButton:hover {
color: ${ribbon_tab_hover_text};
color: ${hover_text};
}
QToolButton#panelSectionHeaderButton::menu-indicator {
image: none;
@@ -452,13 +449,13 @@ QString buildAppStyleSheet() {
padding: ${section_header_padding}px;
}
QToolButton#panelSectionFilterToggle:hover {
background: ${panel_title_button_hover};
background: ${ribbon_button_hover};
}
QWidget#panelSectionBody {
background: transparent;
}
QLabel#keyValueValueLabel {
color: ${key_value_value_text};
color: ${primary_text};
background: transparent;
}
)");
@@ -468,33 +465,31 @@ QString buildAppStyleSheet() {
stylesheet.replace("${section_header_padding}", QString::number(metrics::section_header_padding));
stylesheet.replace("${font_small}", QString::number(typography::small));
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("${app_background}", theme.color("app_background"));
stylesheet.replace("${border}", theme.color("border"));
stylesheet.replace("${selection_background}", theme.color("selection_background"));
stylesheet.replace("${selection_text}", theme.color("selection_text"));
stylesheet.replace("${tab_bar_background}", theme.color("tab_bar_background"));
stylesheet.replace("${tab_background}", theme.color("tab_background"));
stylesheet.replace("${hover_text}", theme.color("hover_text"));
stylesheet.replace("${ribbon_background}", theme.color("ribbon_background"));
stylesheet.replace("${ribbon_button_hover}", theme.color("ribbon_button_hover"));
stylesheet.replace("${ribbon_button_pressed}", theme.color("ribbon_button_pressed"));
stylesheet.replace("${viewport_shell_background}", theme.color("viewport_shell_background"));
stylesheet.replace("${viewport_background}", theme.color("viewport_background"));
stylesheet.replace("${panel_background}", theme.color("panel_background"));
stylesheet.replace("${control_background}", theme.color("control_background"));
stylesheet.replace("${control_border_focus}", theme.color("control_border_focus"));
stylesheet.replace("${box_background}", theme.color("box_background"));
stylesheet.replace("${scroll_handle}", theme.color("scroll_handle"));
stylesheet.replace("${scroll_handle_hover}", theme.color("scroll_handle_hover"));
stylesheet.replace("${status_background}", theme.color("status_background"));
stylesheet.replace("${section_header_background}", theme.color("section_header_background"));
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);
stylesheet.replace("${primary_text}", theme.color("primary_text"));
stylesheet.replace("${secondary_text}", theme.color("secondary_text"));
stylesheet.replace("${disabled_text}", theme.color("disabled_text"));
stylesheet.replace("${warning_text}", theme.color("warning_text"));
return stylesheet;
}
+5 -7
View File
@@ -43,15 +43,13 @@ 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 tab_bar_background = "#26292f";
inline constexpr auto tab_background = "#31353d";
inline constexpr auto ribbon_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 viewport_background = "#2e3338";
inline constexpr auto panel_background = "#2b2f36";
inline constexpr auto control_background = "#31353d";
inline constexpr auto control_border_focus = "#5b6472";
@@ -60,13 +58,13 @@ 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";
inline constexpr auto hover_text = "#ffffff";
} // namespace ifcinterface::components::style::palette
+16 -2
View File
@@ -20,6 +20,8 @@
#include "SvgIcon.h"
#include "../InterfaceSettings.h"
#include <QFile>
#include <QPainter>
#include <QRegularExpression>
@@ -61,12 +63,24 @@ QIcon makeTintedSvgIcon(const QString& icon_path, const QString& normal,
return icon;
}
QIcon makeAccentSvgIcon(const QString& icon_path) {
const auto& theme = ifcinterface::InterfaceSettings::instance();
return makeTintedSvgIcon(icon_path,
theme.color("icon_accent_color"),
theme.color("icon_accent_active_color"),
theme.color("icon_disabled_color"));
}
QIcon makeSvgIcon(const QString& icon_path) {
return makeTintedSvgIcon(icon_path, "#e7ebf2", "#ffffff", "#6f7988");
const auto& theme = ifcinterface::InterfaceSettings::instance();
return makeTintedSvgIcon(icon_path,
theme.color("icon_color"),
theme.color("icon_active_color"),
theme.color("icon_disabled_color"));
}
QPixmap makeSvgPixmap(const QString& icon_path, const QSize& size) {
return renderTintedSvgPixmap(icon_path, "#e7ebf2", size);
return renderTintedSvgPixmap(icon_path, ifcinterface::InterfaceSettings::instance().color("icon_color"), size);
}
} // namespace ifcinterface::components::icons
+1
View File
@@ -33,6 +33,7 @@ QIcon makeTintedSvgIcon(const QString& icon_path,
const QString& normal = "#39b54a",
const QString& active = "#53c763",
const QString& disabled = "#6f7988");
QIcon makeAccentSvgIcon(const QString& icon_path);
QIcon makeSvgIcon(const QString& icon_path);
QPixmap makeSvgPixmap(const QString& icon_path, const QSize& size);
+9 -1
View File
@@ -19,6 +19,7 @@
********************************************************************************/
#include "MainWindow.h"
#include "InterfaceSettings.h"
#include "components/Style.h"
#include <QApplication>
@@ -65,7 +66,14 @@ int main(int argc, char* argv[]) {
parser.process(app);
installUiFont();
app.setStyleSheet(ifcinterface::components::style::buildAppStyleSheet());
const auto applyStyle = [&app]() {
app.setStyleSheet(ifcinterface::components::style::buildAppStyleSheet());
};
applyStyle();
QObject::connect(&ifcinterface::InterfaceSettings::instance(),
&ifcinterface::InterfaceSettings::themeChanged,
&app,
applyStyle);
ifcinterface::shell::MainWindow window;
window.show();
@@ -65,7 +65,6 @@ AddModelDialog::AddModelDialog(QWidget* parent)
setObjectName("appDialog");
setWindowTitle("Add Model");
setModal(true);
setStyleSheet(components::style::buildAppStyleSheet());
setupUi();
}
+4
View File
@@ -107,6 +107,10 @@ ModelsPanel::ModelsPanel(QWidget* parent)
void ModelsPanel::setNodes(const QList<TreeNode>& nodes) {
tree_->clear();
const bool has_hierarchy = std::any_of(nodes.begin(), nodes.end(), [](const TreeNode& node) {
return !node.children.isEmpty() || node.kind == ItemKind::Group;
});
tree_->setRootIsDecorated(has_hierarchy);
for (const auto& node : nodes) {
addNode(tree_->invisibleRootItem(), node);
}
@@ -136,7 +136,7 @@ QLabel* makeReadOnlyValue(QWidget* parent) {
} // namespace
SettingsDialog::SettingsDialog(ifcinterface::SessionState* session_state, QWidget* parent)
: components::Dialog(parent, true)
: components::TabbedDialog(parent)
, session_state_(session_state)
, federation_(session_state ? session_state->federation() : nullptr)
, loader_(session_state ? session_state->loader() : nullptr)
@@ -145,7 +145,6 @@ SettingsDialog::SettingsDialog(ifcinterface::SessionState* session_state, QWidge
setWindowTitle("Model Settings");
setModal(true);
resize(980, 560);
setStyleSheet(components::style::buildAppStyleSheet());
setupUi();
}
@@ -158,9 +157,7 @@ void SettingsDialog::showEvent(QShowEvent* event) {
}
void SettingsDialog::setupUi() {
auto* tabs = new components::TabWidget(this);
auto* federation_tab = new QWidget(tabs);
auto* federation_tab = new QWidget(this);
auto* federation_layout = new QVBoxLayout(federation_tab);
federation_layout->setContentsMargins(0, 0, 0, 0);
federation_layout->setSpacing(components::style::metrics::padding);
@@ -213,7 +210,7 @@ void SettingsDialog::setupUi() {
federation_layout->addWidget(origin_section);
federation_layout->addStretch(1);
auto* model_tab = new QWidget(tabs);
auto* model_tab = new QWidget(this);
auto* model_layout = new QVBoxLayout(model_tab);
model_layout->setContentsMargins(0, 0, 0, 0);
model_layout->setSpacing(components::style::metrics::padding);
@@ -324,8 +321,8 @@ void SettingsDialog::setupUi() {
model_layout->addWidget(georef_section);
model_layout->addWidget(table_section);
tabs->addTab(federation_tab, "Federation");
tabs->addTab(model_tab, "Model");
addTab("Federation", federation_tab);
addTab("Model", model_tab);
connect(model_table_, &QTableWidget::currentCellChanged, this,
[this](int /*current_row*/, int /*current_column*/, int /*previous_row*/, int /*previous_column*/) {
@@ -344,7 +341,6 @@ void SettingsDialog::setupUi() {
connect(buttons, &QDialogButtonBox::accepted, this, [this]() { onAccepted(); });
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
addBodyWidget(tabs);
addFooterWidget(buttons);
}
@@ -41,7 +41,7 @@ class SessionState;
namespace ifcinterface::modules::models {
class SettingsDialog : public components::Dialog {
class SettingsDialog : public components::TabbedDialog {
Q_OBJECT
public:
explicit SettingsDialog(ifcinterface::SessionState* session_state, QWidget* parent = nullptr);
+104 -22
View File
@@ -20,19 +20,21 @@
#include "Dialog.h"
#include "../../InterfaceSettings.h"
#include "../../../ifcviewer/AppSettings.h"
#include "../../components/Dialog.h"
#include "../../components/Section.h"
#include "../../components/Style.h"
#include "../../components/SvgIcon.h"
#include "../../components/Tabs.h"
#include "../../components/Style.h"
#include <QCheckBox>
#include <QColorDialog>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QDoubleSpinBox>
#include <QFrame>
#include <QFormLayout>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
@@ -43,13 +45,12 @@
namespace ifcinterface::modules::settings {
SettingsDialog::SettingsDialog(QWidget* parent)
: components::Dialog(parent)
: components::TabbedDialog(parent)
{
setObjectName("appDialog");
setWindowTitle("Settings");
setModal(true);
resize(520, 420);
setStyleSheet(components::style::buildAppStyleSheet());
setupUi();
}
@@ -59,12 +60,11 @@ void SettingsDialog::showEvent(QShowEvent* event) {
}
void SettingsDialog::setupUi() {
auto* tabs = new components::TabWidget(this);
auto* graphics_tab = new QWidget(tabs);
auto* graphics_tab = new QWidget(this);
auto* graphics_layout = new QVBoxLayout(graphics_tab);
graphics_layout->setContentsMargins(0, 0, 0, 0);
graphics_layout->setSpacing(components::style::metrics::padding);
graphics_layout->setAlignment(Qt::AlignTop);
auto* general_section = new components::Section("General", components::SectionHeaderMode::Visible, graphics_tab);
auto* general_body = new QWidget(general_section);
@@ -159,14 +159,15 @@ void SettingsDialog::setupUi() {
// Navigation tab: orbit / pan presets. Selection always stays on
// LMB so click + box-select keep working regardless of preset.
auto* navigation_tab = new QWidget(tabs);
auto* interface_tab = new QWidget(this);
{
auto* layout = new QVBoxLayout(navigation_tab);
auto* layout = new QVBoxLayout(interface_tab);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(components::style::metrics::padding);
layout->setAlignment(Qt::AlignTop);
auto* section = new components::Section(
"Navigation", components::SectionHeaderMode::Visible, navigation_tab);
"Navigation", components::SectionHeaderMode::Visible, interface_tab);
auto* body = new QWidget(section);
auto* form = new QFormLayout(body);
form->setContentsMargins(0, 0, 0, 0);
@@ -187,14 +188,63 @@ void SettingsDialog::setupUi() {
section->addBodyWidget(body);
layout->addWidget(section);
layout->addStretch(1);
auto* theme_section = new components::Section(
"Theme", components::SectionHeaderMode::Visible, interface_tab);
auto* theme_body = new QWidget(theme_section);
auto* theme_layout = new QVBoxLayout(theme_body);
theme_layout->setContentsMargins(0, 0, 0, 0);
theme_layout->setSpacing(components::style::metrics::padding);
auto* theme_form = new QFormLayout();
theme_form->setContentsMargins(0, 0, 0, 0);
theme_form->setHorizontalSpacing(16);
theme_form->setVerticalSpacing(10);
theme_mode_combo_ = new QComboBox(theme_body);
theme_mode_combo_->addItem("Dark", static_cast<int>(ifcinterface::InterfaceSettings::ThemeMode::Dark));
theme_mode_combo_->addItem("Light", static_cast<int>(ifcinterface::InterfaceSettings::ThemeMode::Light));
theme_mode_combo_->addItem("Custom", static_cast<int>(ifcinterface::InterfaceSettings::ThemeMode::Custom));
theme_form->addRow("Preset", theme_mode_combo_);
theme_custom_body_ = new QWidget(theme_body);
auto* custom_grid = new QGridLayout(theme_custom_body_);
custom_grid->setContentsMargins(0, 0, 0, 0);
custom_grid->setHorizontalSpacing(12);
custom_grid->setVerticalSpacing(8);
int row = 0;
for (const auto& spec : ifcinterface::InterfaceSettings::themeColorSpecs()) {
auto* label = new QLabel(QString::fromUtf8(spec.label), theme_custom_body_);
auto* edit = new QLineEdit(theme_custom_body_);
edit->setPlaceholderText("#000000");
auto* pick = new QPushButton("Pick", theme_custom_body_);
connect(pick, &QPushButton::clicked, this, [this, edit]() { pickThemeColor(edit); });
custom_grid->addWidget(label, row, 0);
custom_grid->addWidget(edit, row, 1);
custom_grid->addWidget(pick, row, 2);
theme_color_editors_.push_back({QString::fromUtf8(spec.key), edit});
++row;
}
auto* theme_form_widget = new QWidget(theme_body);
theme_form_widget->setLayout(theme_form);
theme_layout->addWidget(theme_form_widget);
theme_layout->addWidget(theme_custom_body_);
theme_section->addBodyWidget(theme_body);
layout->addWidget(theme_section);
connect(theme_mode_combo_, &QComboBox::currentIndexChanged, this, [this](int) {
updateThemeEditorEnabled();
});
}
auto make_placeholder_tab = [tabs](const QString& title, const QString& detail) {
auto* tab = new QWidget(tabs);
auto make_placeholder_tab = [this](const QString& title, const QString& detail) {
auto* tab = new QWidget(this);
auto* layout = new QVBoxLayout(tab);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(components::style::metrics::padding);
layout->setAlignment(Qt::AlignTop);
auto* section = new components::Section(title, components::SectionHeaderMode::Visible, tab);
auto* body = new QWidget(section);
@@ -215,14 +265,10 @@ void SettingsDialog::setupUi() {
return tab;
};
tabs->addTab(navigation_tab, "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");
addTab("Interface", interface_tab);
addTab("Keybindings", make_placeholder_tab("Keybindings", "Shortcut presets and command bindings will live here."));
addTab("Graphics", graphics_tab);
addTab("About", make_placeholder_tab("About", "Version, credits, and environment information will live here."));
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
if (auto* ok = buttons->button(QDialogButtonBox::Ok)) {
@@ -236,7 +282,6 @@ void SettingsDialog::setupUi() {
connect(buttons, &QDialogButtonBox::accepted, this, &SettingsDialog::onAccepted);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
addBodyWidget(tabs);
addFooterWidget(buttons);
}
@@ -253,6 +298,37 @@ void SettingsDialog::syncFromSettings() {
hiz_enabled_check_->setChecked(AppSettings::instance().hizEnabled());
hiz_resolution_spin_->setValue(AppSettings::instance().hizResolution());
nav_preset_combo_->setCurrentIndex(static_cast<int>(AppSettings::instance().navPreset()));
syncThemeSettings();
}
void SettingsDialog::syncThemeSettings() {
const auto& settings = ifcinterface::InterfaceSettings::instance();
const int idx = theme_mode_combo_->findData(static_cast<int>(settings.themeMode()));
theme_mode_combo_->setCurrentIndex(idx >= 0 ? idx : 0);
for (auto& editor : theme_color_editors_) {
editor.edit->setText(settings.customColor(editor.key));
}
updateThemeEditorEnabled();
}
void SettingsDialog::updateThemeEditorEnabled() {
if (!theme_mode_combo_ || !theme_custom_body_) return;
const auto mode =
static_cast<ifcinterface::InterfaceSettings::ThemeMode>(theme_mode_combo_->currentData().toInt());
const bool is_custom = mode == ifcinterface::InterfaceSettings::ThemeMode::Custom;
theme_custom_body_->setVisible(is_custom);
theme_custom_body_->setEnabled(is_custom);
}
void SettingsDialog::pickThemeColor(QLineEdit* edit) {
QColorDialog dialog(QColor(edit->text()), this);
dialog.setObjectName("appDialog");
dialog.setWindowTitle("Choose Color");
dialog.setOption(QColorDialog::DontUseNativeDialog, true);
if (dialog.exec() != QDialog::Accepted) return;
const QColor color = dialog.selectedColor();
if (!color.isValid()) return;
edit->setText(color.name(QColor::HexRgb));
}
void SettingsDialog::onAccepted() {
@@ -269,6 +345,12 @@ void SettingsDialog::onAccepted() {
AppSettings::instance().setHizResolution(hiz_resolution_spin_->value());
AppSettings::instance().setNavPreset(
static_cast<AppSettings::NavPreset>(nav_preset_combo_->currentIndex()));
auto& interface_settings = ifcinterface::InterfaceSettings::instance();
interface_settings.setThemeMode(
static_cast<ifcinterface::InterfaceSettings::ThemeMode>(theme_mode_combo_->currentData().toInt()));
for (const auto& editor : theme_color_editors_) {
interface_settings.setCustomColor(editor.key, editor.edit->text());
}
accept();
}
+16 -1
View File
@@ -23,16 +23,20 @@
#include "../../components/Dialog.h"
#include <QString>
#include <vector>
class QCheckBox;
class QComboBox;
class QDoubleSpinBox;
class QLineEdit;
class QShowEvent;
class QSpinBox;
class QWidget;
namespace ifcinterface::modules::settings {
class SettingsDialog : public components::Dialog {
class SettingsDialog : public components::TabbedDialog {
Q_OBJECT
public:
explicit SettingsDialog(QWidget* parent = nullptr);
@@ -43,8 +47,16 @@ protected:
private:
void setupUi();
void syncFromSettings();
void syncThemeSettings();
void updateThemeEditorEnabled();
void pickThemeColor(QLineEdit* edit);
void onAccepted();
struct ThemeColorEditor {
QString key;
QLineEdit* edit = nullptr;
};
QLineEdit* geometry_library_edit_ = nullptr;
QCheckBox* show_stats_check_ = nullptr;
QCheckBox* backface_culling_check_ = nullptr;
@@ -57,6 +69,9 @@ private:
QCheckBox* hiz_enabled_check_ = nullptr;
QSpinBox* hiz_resolution_spin_ = nullptr;
QComboBox* nav_preset_combo_ = nullptr;
QComboBox* theme_mode_combo_ = nullptr;
QWidget* theme_custom_body_ = nullptr;
std::vector<ThemeColorEditor> theme_color_editors_;
};
} // namespace ifcinterface::modules::settings
@@ -20,6 +20,7 @@
#include "Controller.h"
#include "../../InterfaceSettings.h"
#include "../../SessionState.h"
#include "../../../ifcviewer/AppSettings.h"
#include "../../../ifcviewer/Federation.h"
@@ -39,6 +40,12 @@ ViewportController::ViewportController(ifcinterface::SessionState* session_state
{
Federation* federation = session_state_->federation();
SceneLoader* loader = session_state_->loader();
connect(&ifcinterface::InterfaceSettings::instance(),
&ifcinterface::InterfaceSettings::themeChanged,
this, [this]() {
viewport_->setBackgroundColor(QColor(ifcinterface::InterfaceSettings::instance().color("viewport_background")));
});
viewport_->setBackgroundColor(QColor(ifcinterface::InterfaceSettings::instance().color("viewport_background")));
connect(federation, &Federation::federatedFalseOriginChanged,
this, &ViewportController::applyFederatedFalseOrigin);
connect(federation, &Federation::configChanged, this, [this]() {