Fix startup theme selector and migration for single-theme setup

- Rework ThemeSelectorWidget to show only KindredCreate theme
  instead of FreeCAD Classic/Light/Dark buttons that no longer exist
- Fix migrateOldTheme() to map old FreeCAD Light/Dark stylesheets
  to KindredCreate instead of throwing an exception
- Remove orphaned FreeCAD Dark.yaml and FreeCAD Light.yaml
  parameter files from Stylesheets build
This commit is contained in:
forbes
2026-01-29 14:23:00 -06:00
parent bb3f3ac6d6
commit e85162947b
6 changed files with 23 additions and 340 deletions

View File

@@ -338,11 +338,8 @@ void StartupPostProcess::migrateOldTheme(const std::string& style)
{ {
auto prefPackManager = Application::Instance->prefPackManager(); auto prefPackManager = Application::Instance->prefPackManager();
if (style == "FreeCAD Light.qss") { if (style == "FreeCAD Light.qss" || style == "FreeCAD Dark.qss") {
prefPackManager->apply("FreeCAD Light"); prefPackManager->apply("KindredCreate");
}
else if (style == "FreeCAD Dark.qss") {
prefPackManager->apply("FreeCAD Dark");
} }
} }

View File

@@ -6,8 +6,6 @@ SET(Stylesheets_Files
) )
SET(Parameters_Files SET(Parameters_Files
"parameters/FreeCAD Dark.yaml"
"parameters/FreeCAD Light.yaml"
) )
SET(Overlay_Stylesheets_Files SET(Overlay_Stylesheets_Files

View File

@@ -1,64 +0,0 @@
3DViewBackgroundRefColor: "@BackgroundColor"
ButtonBackgroundHooverColor: "@ButtonTopBackgroundColor"
ButtonBorderColor: "@GeneralBorderColor"
ButtonBorderHooverColor: "@GeneralBorderColor"
ButtonBottomBackgroundColor: "@PrimaryColorLighten2"
ButtonTopBackgroundColor: "@PrimaryColorLighten3"
CheckedButtonBottomBackgroundColor: "darken(@ButtonBottomBackgroundColor, 5)"
CheckedButtonTopBackgroundColor: "darken(@ButtonTopBackgroundColor, 5)"
DefaultButtonBottomBackgroundColor: "blend(@ButtonBottomBackgroundColor, @AccentColor, 10)"
DefaultButtonTopBackgroundColor: "blend(@ButtonTopBackgroundColor, @AccentColor, 10)"
DefaultButtonBorderColor: "blend(@ButtonBorderColor, @AccentColor, 20)"
CheckBoxBackgroundColor: "@TextEditFieldBackgroundColor"
CheckBoxBorderColor: "@GeneralBorderColor"
DialogBackgroundColor: "@PrimaryColorLighten3"
GeneralAlternateBackgroundColor: "@PrimaryColorLighten2"
GeneralBackgroundColor: "@PrimaryColor"
GeneralBackgroundHoverColor: "@PrimaryColorLighten4"
GeneralBorderColor: "@PrimaryColorDarken5"
GeneralBorderHoverColor: "@PrimaryColorLighten6"
GeneralDisabledBackgroundColor: "@PrimaryColorDarken3"
GeneralGridLinesColor: "@PrimaryColorLighten3"
GeneralHeaderBackgroundColor: "@PrimaryColor"
GroupboxBackgroundColor: "@PrimaryColorLighten4"
GroupboxBorderColor: "lighten(@GroupboxBackgroundColor,20)"
IconsLocationFolderName: "images_classic"
InputFieldBorderRadius: "3px"
MenuBackgroundColor: "@PrimaryColorDarken4"
PrimaryColor: "#191919"
PrimaryColorDarken1: "darken(@PrimaryColor,5)"
PrimaryColorDarken2: "darken(@PrimaryColor,8)"
PrimaryColorDarken3: "darken(@PrimaryColor,15)"
PrimaryColorDarken4: "darken(@PrimaryColor,20)"
PrimaryColorDarken5: "darken(@PrimaryColor,200)"
PrimaryColorDarken6: "darken(@PrimaryColor,5890)"
PrimaryColorLighten1: "lighten(@PrimaryColor,20)"
PrimaryColorLighten2: "lighten(@PrimaryColor,40)"
PrimaryColorLighten3: "lighten(@PrimaryColor,80)"
PrimaryColorLighten4: "lighten(@PrimaryColor,100)"
PrimaryColorLighten5: "lighten(@PrimaryColor,300)"
PrimaryColorLighten6: "lighten(@PrimaryColor,5890)"
RadioButtonBackgroundColor: "@CheckBoxBackgroundColor"
RadioButtonBorderColor: "@GeneralBorderColor"
ScrollbarBackgroundColor: "@GeneralDisabledBackgroundColor"
SketcherConflictingConstraintsColor: "#fc6c6c"
SketcherEmptySketchColor: "#ffffff"
SketcherFullyConstrainedColor: "#8cff5b"
SketcherMalformedConstraintsColor: "#fc6c6c"
SketcherPartiallyRedundantConstraintsColor: "#77cbff"
SketcherRedundantConstraintsColor: "#ffa256"
SketcherSolverFailedColor: "#ff9090"
SketcherUnderConstrainedColor: "#ffffff"
StylesheetIconsColor: "white"
TabbarBackgroundColor: "@PrimaryColorDarken5"
InActiveTabBackgroundColor: "@PrimaryColorDarken2"
ActiveTabBackgroundColor: "@3DViewBackgroundRefColor"
TextDisabledColor: "darken(@TextForegroundColor,40)"
TextEditFieldBackgroundColor: "@PrimaryColor"
TextForegroundColor: "#ffffff"
TextSelectBackgroundColor: "darken(@AccentColor,100)"
TextUrlColor: "#0095ff"
ToolbarButtonsPadding: "2px"
AccentColor: "@ThemeAccentColor1"
AccentBackgroundColor: "blend(@GeneralBackgroundColor,@AccentColor,60)"
AccentHoverColor: "blend(@GeneralBackgroundHoverColor,@AccentColor,30)"

View File

@@ -1,64 +0,0 @@
3DViewBackgroundRefColor: "@BackgroundColor"
ButtonBackgroundHooverColor: "@ButtonTopBackgroundColor"
ButtonBorderColor: "@GeneralBorderColor"
ButtonBorderHooverColor: "@GeneralBorderColor"
ButtonBottomBackgroundColor: "@PrimaryColorDarken1"
ButtonTopBackgroundColor: "@PrimaryColorLighten3"
CheckedButtonBottomBackgroundColor: "darken(@ButtonBottomBackgroundColor, 5)"
CheckedButtonTopBackgroundColor: "darken(@ButtonTopBackgroundColor, 5)"
DefaultButtonBottomBackgroundColor: "blend(@ButtonBottomBackgroundColor, @AccentColor, 5)"
DefaultButtonTopBackgroundColor: "blend(@ButtonTopBackgroundColor, @AccentColor, 5)"
DefaultButtonBorderColor: "blend(@ButtonBorderColor, @AccentColor, 40)"
CheckBoxBackgroundColor: "@TextEditFieldBackgroundColor"
CheckBoxBorderColor: "@GeneralBorderColor"
DialogBackgroundColor: "@PrimaryColorLighten3"
GeneralAlternateBackgroundColor: "@PrimaryColor"
GeneralBackgroundColor: "@PrimaryColor"
GeneralBackgroundHoverColor: "@PrimaryColorLighten5"
GeneralBorderColor: "@PrimaryColorDarken4"
GeneralBorderHoverColor: "@PrimaryColorDarken5"
GeneralDisabledBackgroundColor: "@PrimaryColorDarken3"
GeneralGridLinesColor: "@PrimaryColorDarken4"
GeneralHeaderBackgroundColor: "@PrimaryColorDarken2"
GroupboxBackgroundColor: "@PrimaryColorLighten2"
GroupboxBorderColor: "darken(@GroupboxBackgroundColor,20)"
IconsLocationFolderName: "images_classic"
InputFieldBorderRadius: "3px"
MenuBackgroundColor: "@PrimaryColorLighten1"
PrimaryColor: "#F0F0F0"
PrimaryColorDarken1: "darken(@PrimaryColor,4)"
PrimaryColorDarken2: "darken(@PrimaryColor,8)"
PrimaryColorDarken3: "darken(@PrimaryColor,10)"
PrimaryColorDarken4: "darken(@PrimaryColor,20)"
PrimaryColorDarken5: "darken(@PrimaryColor,200)"
PrimaryColorDarken6: "darken(@PrimaryColor,5890)"
PrimaryColorLighten1: "lighten(@PrimaryColor,2)"
PrimaryColorLighten2: "lighten(@PrimaryColor,3)"
PrimaryColorLighten3: "lighten(@PrimaryColor,4)"
PrimaryColorLighten4: "lighten(@PrimaryColor,5)"
PrimaryColorLighten5: "lighten(@PrimaryColor,6)"
PrimaryColorLighten6: "lighten(@PrimaryColor,7)"
RadioButtonBackgroundColor: "@CheckBoxBackgroundColor"
RadioButtonBorderColor: "@GeneralBorderColor"
ScrollbarBackgroundColor: "@GeneralDisabledBackgroundColor"
SketcherConflictingConstraintsColor: "#9d0000"
SketcherEmptySketchColor: "#000000"
SketcherFullyConstrainedColor: "#278100"
SketcherMalformedConstraintsColor: "#9d0000"
SketcherPartiallyRedundantConstraintsColor: "#005991"
SketcherRedundantConstraintsColor: "#ab4d00"
SketcherSolverFailedColor: "#9d0000"
SketcherUnderConstrainedColor: "#000000"
StylesheetIconsColor: "black"
TabbarBackgroundColor: "@PrimaryColorDarken4"
InActiveTabBackgroundColor: "@PrimaryColorDarken2"
ActiveTabBackgroundColor: "@3DViewBackgroundRefColor"
TextDisabledColor: "darken(@TabbarBackgroundColor,80)"
TextEditFieldBackgroundColor: "@PrimaryColorLighten3"
TextForegroundColor: "#000000"
TextSelectBackgroundColor: "@AccentHoverColor"
TextUrlColor: "#0061a7"
AccentColor: "@ThemeAccentColor1"
AccentBackgroundColor: "blend(@GeneralBackgroundColor,@AccentColor,60)"
AccentHoverColor: "blend(@GeneralBackgroundHoverColor,@AccentColor,30)"
ToolbarButtonsPadding: "2px"

View File

@@ -21,143 +21,34 @@
* * * *
***************************************************************************/ ***************************************************************************/
#include <QGuiApplication>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QString> #include <QString>
#include <QStyleHints>
#include <QToolButton> #include <QToolButton>
#include "ThemeSelectorWidget.h" #include "ThemeSelectorWidget.h"
#include <gsl/pointers> #include <gsl/pointers>
#include <App/Application.h>
#include <Gui/Command.h>
#include <Gui/PreferencePackManager.h> #include <Gui/PreferencePackManager.h>
#include <FCConfig.h>
#ifdef FC_OS_MACOSX
# include <CoreFoundation/CoreFoundation.h>
#endif
using namespace StartGui; using namespace StartGui;
static bool isSystemInDarkMode()
{
// Auto-detect system setting and default to light mode
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
// https://www.qt.io/blog/dark-mode-on-windows-11-with-qt-6.5
const auto scheme = QGuiApplication::styleHints()->colorScheme();
return scheme == Qt::ColorScheme::Dark;
#elif QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
// https://www.qt.io/blog/dark-mode-on-windows-11-with-qt-6.5
const QPalette defaultPalette;
const auto text = defaultPalette.color(QPalette::WindowText);
const auto window = defaultPalette.color(QPalette::Window);
return text.lightness() > window.lightness();
#else
# ifdef FC_OS_MACOSX
auto key = CFSTR("AppleInterfaceStyle");
if (auto value = CFPreferencesCopyAppValue(key, kCFPreferencesAnyApplication)) {
// If the value is "Dark", Dark Mode is enabled
if (CFGetTypeID(value) == CFStringGetTypeID()) {
if (CFStringCompare((CFStringRef)value, CFSTR("Dark"), kCFCompareCaseInsensitive)
== kCFCompareEqualTo) {
CFRelease(value);
return true;
}
}
CFRelease(value);
}
# endif // FC_OS_MACOSX
#endif // QT_VERSION >= 6.4+
return false;
}
static bool shouldHideClassicTheme()
{
// Classic on macOS and windows 11 with qt6(.4+?) doesn't work when system
// is in dark mode and to make matter worse, on macOS there's a setting that
// changes mode depending on time of day.
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) || defined(FC_OS_MACOSX) || defined(FC_OS_WIN32)
return true;
#else
return false;
#endif
}
ThemeSelectorWidget::ThemeSelectorWidget(QWidget* parent) ThemeSelectorWidget::ThemeSelectorWidget(QWidget* parent)
: QWidget(parent) : QWidget(parent)
, _titleLabel {nullptr} , _titleLabel {nullptr}
, _descriptionLabel {nullptr} , _descriptionLabel {nullptr}
, _buttons {nullptr, nullptr, nullptr} , _themeButton {nullptr}
{ {
setObjectName(QLatin1String("ThemeSelectorWidget")); setObjectName(QLatin1String("ThemeSelectorWidget"));
if (shouldHideClassicTheme()) { applyKindredCreateTheme();
preselectThemeFromSystemSettings();
}
setupUi(); setupUi();
qApp->installEventFilter(this); qApp->installEventFilter(this);
} }
void ThemeSelectorWidget::applyKindredCreateTheme()
void ThemeSelectorWidget::setupButtons(QBoxLayout* layout)
{ {
if (!layout) { auto prefPackManager = Gui::Application::Instance->prefPackManager();
return; prefPackManager->apply("KindredCreate");
}
std::map<Theme, QString> themeMap {
{Theme::Classic, tr("FreeCAD Classic")},
{Theme::Dark, tr("FreeCAD Dark")},
{Theme::Light, tr("FreeCAD Light")}
};
std::map<Theme, QIcon> iconMap {
{Theme::Classic, QIcon(QLatin1String(":/thumbnails/Theme_thumbnail_classic.png"))},
{Theme::Light, QIcon(QLatin1String(":/thumbnails/Theme_thumbnail_light.png"))},
{Theme::Dark, QIcon(QLatin1String(":/thumbnails/Theme_thumbnail_dark.png"))}
};
auto hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/MainWindow"
);
auto styleSheetName = QString::fromStdString(hGrp->GetASCII("StyleSheet"));
for (const auto& theme : themeMap) {
auto button = gsl::owner<QToolButton*>(new QToolButton());
if (theme.first == Theme::Classic && shouldHideClassicTheme()) {
button->setVisible(false);
}
button->setCheckable(true);
button->setAutoExclusive(true);
button->setToolButtonStyle(Qt::ToolButtonStyle::ToolButtonTextUnderIcon);
button->setText(theme.second);
button->setIcon(iconMap[theme.first]);
button->setIconSize(iconMap[theme.first].actualSize(QSize(256, 256)));
if (theme.first == Theme::Classic && styleSheetName.isEmpty()) {
button->setChecked(true);
}
else if (theme.first == Theme::Light
&& styleSheetName.contains(
QLatin1String("FreeCAD Light"),
Qt::CaseSensitivity::CaseInsensitive
)) {
button->setChecked(true);
}
else if (theme.first == Theme::Dark
&& styleSheetName.contains(
QLatin1String("FreeCAD Dark"),
Qt::CaseSensitivity::CaseInsensitive
)) {
button->setChecked(true);
}
connect(button, &QToolButton::clicked, this, [this, theme] { themeChanged(theme.first); });
layout->addWidget(button);
_buttons[static_cast<int>(theme.first)] = button;
}
} }
void ThemeSelectorWidget::setupUi() void ThemeSelectorWidget::setupUi()
@@ -169,69 +60,16 @@ void ThemeSelectorWidget::setupUi()
outerLayout->addWidget(_titleLabel); outerLayout->addWidget(_titleLabel);
outerLayout->addLayout(buttonLayout); outerLayout->addLayout(buttonLayout);
outerLayout->addWidget(_descriptionLabel); outerLayout->addWidget(_descriptionLabel);
setupButtons(buttonLayout);
_themeButton = gsl::owner<QToolButton*>(new QToolButton());
_themeButton->setCheckable(true);
_themeButton->setChecked(true);
_themeButton->setToolButtonStyle(Qt::ToolButtonStyle::ToolButtonTextUnderIcon);
_themeButton->setIcon(QIcon(QLatin1String(":/thumbnails/Theme_thumbnail_dark.png")));
_themeButton->setIconSize(_themeButton->icon().actualSize(QSize(256, 256)));
buttonLayout->addWidget(_themeButton);
retranslateUi(); retranslateUi();
connect(_descriptionLabel, &QLabel::linkActivated, this, &ThemeSelectorWidget::onLinkActivated);
}
void ThemeSelectorWidget::onLinkActivated(const QString& link)
{
auto const addonManagerLink = QStringLiteral("freecad:Std_AddonMgr");
if (link != addonManagerLink) {
return;
}
// Set the user preferences to include only preference packs.
// This is a quick and dirty way to open Addon Manager with only themes.
auto pref = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Addons"
);
pref->SetInt("PackageTypeSelection", 3); // 3 stands for Preference Packs
pref->SetInt("StatusSelection", 0); // 0 stands for any installation status
Gui::Application::Instance->commandManager().runCommandByName("Std_AddonMgr");
}
void ThemeSelectorWidget::preselectThemeFromSystemSettings()
{
auto nullStyle("<N/A>");
auto hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/MainWindow"
);
auto styleSheetName = QString::fromStdString(hGrp->GetASCII("StyleSheet", nullStyle));
if (styleSheetName == QString::fromStdString(nullStyle)) {
auto theme = isSystemInDarkMode() ? Theme::Dark : Theme::Light;
themeChanged(theme);
}
}
void ThemeSelectorWidget::themeChanged(Theme newTheme)
{
// Run the appropriate preference pack:
auto prefPackManager = Gui::Application::Instance->prefPackManager();
switch (newTheme) {
case Theme::Classic:
prefPackManager->apply("FreeCAD Classic");
break;
case Theme::Dark:
prefPackManager->apply("FreeCAD Dark");
break;
case Theme::Light:
prefPackManager->apply("FreeCAD Light");
break;
}
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Themes"
);
const unsigned long nonExistentColor = -1434171135;
const unsigned long defaultAccentColor = 1434171135;
unsigned long longAccentColor1 = hGrp->GetUnsigned("ThemeAccentColor1", nonExistentColor);
if (longAccentColor1 == nonExistentColor) {
hGrp->SetUnsigned("ThemeAccentColor1", defaultAccentColor);
hGrp->SetUnsigned("ThemeAccentColor2", defaultAccentColor);
hGrp->SetUnsigned("ThemeAccentColor3", defaultAccentColor);
}
} }
bool ThemeSelectorWidget::eventFilter(QObject* object, QEvent* event) bool ThemeSelectorWidget::eventFilter(QObject* object, QEvent* event)
@@ -245,16 +83,8 @@ bool ThemeSelectorWidget::eventFilter(QObject* object, QEvent* event)
void ThemeSelectorWidget::retranslateUi() void ThemeSelectorWidget::retranslateUi()
{ {
_titleLabel->setText(QLatin1String("<h2>") + tr("Theme") + QLatin1String("</h2>")); _titleLabel->setText(QLatin1String("<h2>") + tr("Theme") + QLatin1String("</h2>"));
if (Gui::Application::Instance->commandManager().getCommandByName("Std_AddonMgr")) {
_descriptionLabel->setText( _descriptionLabel->setText(
tr("Looking for more themes? You can obtain them using " tr("Kindred Create uses the Catppuccin Mocha theme.")
"<a href=\"freecad:Std_AddonMgr\">Addon Manager</a>.")
); );
} _themeButton->setText(tr("Kindred Create", "Visual theme name"));
else {
_descriptionLabel->hide();
}
_buttons[static_cast<int>(Theme::Dark)]->setText(tr("FreeCAD Dark", "Visual theme name"));
_buttons[static_cast<int>(Theme::Light)]->setText(tr("FreeCAD Light", "Visual theme name"));
_buttons[static_cast<int>(Theme::Classic)]->setText(tr("FreeCAD Classic", "Visual theme name"));
} }

View File

@@ -25,23 +25,14 @@
#define FREECAD_START_THEMESELECTORWIDGET_H #define FREECAD_START_THEMESELECTORWIDGET_H
#include <QWidget> #include <QWidget>
#include <array>
class QBoxLayout;
class QLabel; class QLabel;
class QToolButton; class QToolButton;
namespace StartGui namespace StartGui
{ {
enum class Theme /// A widget that displays the active Kindred Create theme.
{
Classic,
Light,
Dark
};
/// A widget to allow selection of the UI theme (color scheme).
class ThemeSelectorWidget: public QWidget class ThemeSelectorWidget: public QWidget
{ {
Q_OBJECT Q_OBJECT
@@ -49,19 +40,14 @@ public:
explicit ThemeSelectorWidget(QWidget* parent = nullptr); explicit ThemeSelectorWidget(QWidget* parent = nullptr);
bool eventFilter(QObject* object, QEvent* event) override; bool eventFilter(QObject* object, QEvent* event) override;
protected:
void themeChanged(Theme newTheme);
private: private:
void retranslateUi(); void retranslateUi();
void setupUi(); void setupUi();
void setupButtons(QBoxLayout* layout); void applyKindredCreateTheme();
void onLinkActivated(const QString& link);
void preselectThemeFromSystemSettings();
QLabel* _titleLabel; QLabel* _titleLabel;
QLabel* _descriptionLabel; QLabel* _descriptionLabel;
std::array<QToolButton*, 3> _buttons; QToolButton* _themeButton;
}; };
} // namespace StartGui } // namespace StartGui