From 0fe852630db4d8b3cb02bcad94be0c491bd94372 Mon Sep 17 00:00:00 2001
From: Kubat <mael.martin31@gmail.com>
Date: Wed, 8 Sep 2021 11:49:02 +0200
Subject: [PATCH] UI: Import and modify QtCreator's theming system (simplify it
 a bit)

---
 CMakeLists.txt           |  24 +-
 PreCompiledHeaders.cmake |   4 +
 src/UI/Theme/Theme.cc    | 306 +++++++++++++++++++++++++
 src/UI/Theme/Theme.hh    | 475 +++++++++++++++++++++++++++++++++++++++
 src/UI/Theme/ThemeMac.hh |   6 +
 src/UI/Theme/ThemeMac.mm |  21 ++
 6 files changed, 830 insertions(+), 6 deletions(-)
 create mode 100644 src/UI/Theme/Theme.cc
 create mode 100644 src/UI/Theme/Theme.hh
 create mode 100644 src/UI/Theme/ThemeMac.hh
 create mode 100644 src/UI/Theme/ThemeMac.mm

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 498df5aa..bfa3728e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,6 +5,20 @@ project(Vivy VERSION 0.1 LANGUAGES CXX)
 cmake_policy(SET CMP0100 NEW) # Let cmake use moc and uic for .hh files
 cmake_policy(SET CMP0009 NEW) # Do not follow symlinks with GLOB_RECURSE
 
+# Don't forget for specific things
+if(WIN32)
+    message("You are building on windows, pay attenion to the dependencies")
+endif()
+if(MSVC OR MSYS OR MINGW)
+    message("You are building with a windows compiler")
+endif()
+if(APPLE)
+    message("You are building on MacOS X")
+endif()
+if(UNIX AND NOT APPLE)
+    message("You are building on Linux, FreeBSD or any other toaster OS")
+endif()
+
 # Pass -fPIC
 set(CMAKE_POSITION_INDEPENDENT_CODE ON)
 
@@ -20,11 +34,6 @@ set(THREADS_PREFER_PTHREAD_FLAG ON)
 find_package(QT NAMES Qt6 Qt5      COMPONENTS Widgets REQUIRED)
 find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED)
 
-if(WIN32)
-    message("You are building on windows, pay attenion to the dependencies")
-    # Needed setup for Vivy to compile on Windows goes here
-endif()
-
 # Find others dependencies
 find_library(AVCODEC_LIBRARY    avcodec     4.0 REQUIRED)
 find_library(AVUTIL_LIBRARY     avutil      4.0 REQUIRED)
@@ -41,7 +50,10 @@ add_subdirectory(
 # Grab all files
 file(GLOB_RECURSE Vivy_SRC CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc")
 file(GLOB_RECURSE Vivy_INC CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.hh")
-set(PROJECT_SOURCES ${Vivy_SRC} ${Vivy_INC})
+if(APPLE)
+    file(GLOB_RECURSE Vivy_APPLE_SRC CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.mm")
+endif()
+set(PROJECT_SOURCES ${Vivy_SRC} ${Vivy_INC} ${Vivy_APPLE_SRC})
 
 # Add the Vivy executable
 if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
diff --git a/PreCompiledHeaders.cmake b/PreCompiledHeaders.cmake
index b04fef69..f1d1768e 100644
--- a/PreCompiledHeaders.cmake
+++ b/PreCompiledHeaders.cmake
@@ -37,6 +37,7 @@ set(QT_STRUCT_INC
     PRIVATE
     <qglobal.h>
     <QThread>
+    <QOperatingSystemVersion>
     <QtGlobal>
     <QObject>
     <QRegularExpression>
@@ -60,7 +61,9 @@ set(QT_STRUCT_INC
     <QList>
     <QVector>
     <QMap>
+    <QSettings>
     <QStringList>
+    <QMetaEnum>
     <QJsonObject>
     <QJsonArray>
     <QJsonDocument>
@@ -72,6 +75,7 @@ set(QT_WIDGET_INC
     <QCoreApplication>
     <QTextDocument>
     <QColor>
+    <QPalette>
     <QWidget>
     <QIcon>
     <QFont>
diff --git a/src/UI/Theme/Theme.cc b/src/UI/Theme/Theme.cc
new file mode 100644
index 00000000..12d07e2b
--- /dev/null
+++ b/src/UI/Theme/Theme.cc
@@ -0,0 +1,306 @@
+#include "Theme.hh"
+#include "../../Lib/HostOsInfo.hh"
+#ifdef Q_OS_MACOS
+#import "ThemeMac.hh"
+#endif
+
+namespace Vivy
+{
+Theme::ThemePrivate::ThemePrivate() noexcept
+{
+    const QMetaObject &m = Theme::staticMetaObject;
+    colors.resize(m.enumerator(m.indexOfEnumerator("Color")).keyCount());
+    imageFiles.resize(m.enumerator(m.indexOfEnumerator("ImageFile")).keyCount());
+    gradients.resize(m.enumerator(m.indexOfEnumerator("Gradient")).keyCount());
+    flags.resize(m.enumerator(m.indexOfEnumerator("Flag")).keyCount());
+}
+
+void
+Theme::applyToApplication() const noexcept
+{
+#ifdef Q_OS_MACOS
+    // Match the native UI theme and palette with the creator
+    // theme by forcing light aqua for light creator themes.
+    if (!flag(Theme::DarkUserInterface))
+        Internal::forceMacOSLightAquaApperance();
+#endif
+
+    // if (flag(Theme::ApplyThemePaletteGlobally))
+    QApplication::setPalette(palette());
+}
+
+Theme::Theme(const QString &id, QObject *parent) noexcept
+    : QObject(parent)
+    , d(new ThemePrivate)
+{
+    d->id = id;
+}
+
+Theme::~Theme() noexcept { delete d; }
+
+QStringList
+Theme::preferredStyles() const noexcept
+{
+    return d->preferredStyles;
+}
+
+QString
+Theme::defaultTextEditorColorScheme() const noexcept
+{
+    return d->defaultTextEditorColorScheme;
+}
+
+QString
+Theme::id() const noexcept
+{
+    return d->id;
+}
+
+bool
+Theme::flag(Theme::Flag f) const noexcept
+{
+    return d->flags[f];
+}
+
+QColor
+Theme::color(Theme::Color role) const noexcept
+{
+    return d->colors[role].first;
+}
+
+QString
+Theme::imageFile(Theme::ImageFile imageFile, const QString &fallBack) const noexcept
+{
+    const QString &file = d->imageFiles.at(imageFile);
+    return file.isEmpty() ? fallBack : file;
+}
+
+QGradientStops
+Theme::gradient(Theme::Gradient role) const noexcept
+{
+    return d->gradients[role];
+}
+
+QPair<QColor, QString>
+Theme::readNamedColor(const QString &color) const
+{
+    if (d->palette.contains(color))
+        return qMakePair(d->palette[color], color);
+    if (color == QLatin1String("style"))
+        return qMakePair(QColor(), QString());
+
+    const QColor col('#' + color);
+    if (!col.isValid()) {
+        qWarning("Color \"%s\" is neither a named color nor a valid color", qPrintable(color));
+        return qMakePair(Qt::black, QString());
+    }
+    return qMakePair(col, QString());
+}
+
+QString
+Theme::filePath() const noexcept
+{
+    return d->fileName;
+}
+
+QString
+Theme::displayName() const noexcept
+{
+    return d->displayName;
+}
+
+void
+Theme::setDisplayName(const QString &name) noexcept
+{
+    d->displayName = name;
+}
+
+void
+Theme::readSettings(QSettings *const settings) noexcept
+{
+    d->fileName          = settings->fileName();
+    const QMetaObject &m = *metaObject();
+
+    {
+        d->displayName =
+            settings->value(QLatin1String("ThemeName"), QLatin1String("unnamed")).toString();
+        d->preferredStyles = settings->value(QLatin1String("PreferredStyles")).toStringList();
+        d->preferredStyles.removeAll(QString());
+        d->defaultTextEditorColorScheme =
+            settings->value(QLatin1String("DefaultTextEditorColorScheme")).toString();
+    }
+    {
+        settings->beginGroup(QLatin1String("Palette"));
+        const QStringList allKeys = settings->allKeys();
+        for (const QString &key : allKeys)
+            d->palette[key] = readNamedColor(settings->value(key).toString()).first;
+        settings->endGroup();
+    }
+    {
+        settings->beginGroup(QLatin1String("Colors"));
+        QMetaEnum e = m.enumerator(m.indexOfEnumerator("Color"));
+        for (int i = 0, total = e.keyCount(); i < total; ++i) {
+            const QString key = QLatin1String(e.key(i));
+            if (!settings->contains(key)) {
+                if (i < PaletteWindow || i > PalettePlaceholderTextDisabled)
+                    qWarning("Theme \"%s\" misses color setting for key \"%s\".",
+                             qPrintable(d->fileName), qPrintable(key));
+                continue;
+            }
+            d->colors[i] = readNamedColor(settings->value(key).toString());
+        }
+        settings->endGroup();
+    }
+    {
+        settings->beginGroup(QLatin1String("ImageFiles"));
+        QMetaEnum e = m.enumerator(m.indexOfEnumerator("ImageFile"));
+        for (int i = 0, total = e.keyCount(); i < total; ++i) {
+            const QString key = QLatin1String(e.key(i));
+            d->imageFiles[i]  = settings->value(key).toString();
+        }
+        settings->endGroup();
+    }
+    {
+        settings->beginGroup(QLatin1String("Gradients"));
+        QMetaEnum e = m.enumerator(m.indexOfEnumerator("Gradient"));
+        for (int i = 0, total = e.keyCount(); i < total; ++i) {
+            const QString key = QLatin1String(e.key(i));
+            QGradientStops stops;
+            int size = settings->beginReadArray(key);
+            for (int j = 0; j < size; ++j) {
+                settings->setArrayIndex(j);
+                TODO(Assert things when needed !);
+                settings->contains(QLatin1String("pos")); // TODO: Assert it
+                const double pos = settings->value(QLatin1String("pos")).toDouble();
+                settings->contains(QLatin1String("color")); // TODO: Assert it
+                const QColor c('#' + settings->value(QLatin1String("color")).toString());
+                stops.append(qMakePair(pos, c));
+            }
+            settings->endArray();
+            d->gradients[i] = stops;
+        }
+        settings->endGroup();
+    }
+    {
+        settings->beginGroup(QLatin1String("Flags"));
+        QMetaEnum e = m.enumerator(m.indexOfEnumerator("Flag"));
+        for (int i = 0, total = e.keyCount(); i < total; ++i) {
+            const QString key = QLatin1String(e.key(i));
+            TODO(Assert it);
+            settings->contains(key); // TODO: Assert it!
+            d->flags[i] = settings->value(key).toBool();
+        }
+        settings->endGroup();
+    }
+}
+
+bool
+Theme::systemUsesDarkMode() noexcept
+{
+    if (Utils::HostOsInfo::isWindowsHost()) {
+        constexpr char regkey[] =
+            "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
+        bool ok;
+        const auto setting =
+            QSettings(regkey, QSettings::NativeFormat).value("AppsUseLightTheme").toInt(&ok);
+        return ok && setting == 0;
+    } else if (Utils::HostOsInfo::isMacHost()) {
+        return false;
+    } else if (Utils::HostOsInfo::isLinuxHost()) {
+        return true;
+    }
+    return false;
+}
+
+// If you copy QPalette, default values stay at default, even if that default is different
+// within the context of different widgets. Create deep copy.
+static QPalette
+copyPalette(const QPalette &p) noexcept
+{
+    QPalette res;
+    for (int group = 0; group < QPalette::NColorGroups; ++group) {
+        for (int role = 0; role < QPalette::NColorRoles; ++role) {
+            res.setBrush(QPalette::ColorGroup(group), QPalette::ColorRole(role),
+                         p.brush(QPalette::ColorGroup(group), QPalette::ColorRole(role)));
+        }
+    }
+    return res;
+}
+
+QPalette
+Theme::initialPalette() noexcept
+{
+    static QPalette palette = copyPalette(QApplication::palette());
+    return palette;
+}
+
+QPalette
+Theme::palette() const noexcept
+{
+    QPalette pal = initialPalette();
+    // if (!flag(DerivePaletteFromTheme))
+    //     return pal;
+
+    const static struct {
+        Color themeColor;
+        QPalette::ColorRole paletteColorRole;
+        QPalette::ColorGroup paletteColorGroup;
+        bool setColorRoleAsBrush;
+    } mapping[] = {
+        { PaletteWindow, QPalette::Window, QPalette::All, false },
+        { PaletteWindowDisabled, QPalette::Window, QPalette::Disabled, false },
+        { PaletteWindowText, QPalette::WindowText, QPalette::All, true },
+        { PaletteWindowTextDisabled, QPalette::WindowText, QPalette::Disabled, true },
+        { PaletteBase, QPalette::Base, QPalette::All, false },
+        { PaletteBaseDisabled, QPalette::Base, QPalette::Disabled, false },
+        { PaletteAlternateBase, QPalette::AlternateBase, QPalette::All, false },
+        { PaletteAlternateBaseDisabled, QPalette::AlternateBase, QPalette::Disabled, false },
+        { PaletteToolTipBase, QPalette::ToolTipBase, QPalette::All, true },
+        { PaletteToolTipBaseDisabled, QPalette::ToolTipBase, QPalette::Disabled, true },
+        { PaletteToolTipText, QPalette::ToolTipText, QPalette::All, false },
+        { PaletteToolTipTextDisabled, QPalette::ToolTipText, QPalette::Disabled, false },
+        { PaletteText, QPalette::Text, QPalette::All, true },
+        { PaletteTextDisabled, QPalette::Text, QPalette::Disabled, true },
+        { PaletteButton, QPalette::Button, QPalette::All, false },
+        { PaletteButtonDisabled, QPalette::Button, QPalette::Disabled, false },
+        { PaletteButtonText, QPalette::ButtonText, QPalette::All, true },
+        { PaletteButtonTextDisabled, QPalette::ButtonText, QPalette::Disabled, true },
+        { PaletteBrightText, QPalette::BrightText, QPalette::All, false },
+        { PaletteBrightTextDisabled, QPalette::BrightText, QPalette::Disabled, false },
+        { PaletteHighlight, QPalette::Highlight, QPalette::All, true },
+        { PaletteHighlightDisabled, QPalette::Highlight, QPalette::Disabled, true },
+        { PaletteHighlightedText, QPalette::HighlightedText, QPalette::All, true },
+        { PaletteHighlightedTextDisabled, QPalette::HighlightedText, QPalette::Disabled, true },
+        { PaletteLink, QPalette::Link, QPalette::All, false },
+        { PaletteLinkDisabled, QPalette::Link, QPalette::Disabled, false },
+        { PaletteLinkVisited, QPalette::LinkVisited, QPalette::All, false },
+        { PaletteLinkVisitedDisabled, QPalette::LinkVisited, QPalette::Disabled, false },
+        { PaletteLight, QPalette::Light, QPalette::All, false },
+        { PaletteLightDisabled, QPalette::Light, QPalette::Disabled, false },
+        { PaletteMidlight, QPalette::Midlight, QPalette::All, false },
+        { PaletteMidlightDisabled, QPalette::Midlight, QPalette::Disabled, false },
+        { PaletteDark, QPalette::Dark, QPalette::All, false },
+        { PaletteDarkDisabled, QPalette::Dark, QPalette::Disabled, false },
+        { PaletteMid, QPalette::Mid, QPalette::All, false },
+        { PaletteMidDisabled, QPalette::Mid, QPalette::Disabled, false },
+        { PaletteShadow, QPalette::Shadow, QPalette::All, false },
+        { PaletteShadowDisabled, QPalette::Shadow, QPalette::Disabled, false },
+        { PalettePlaceholderText, QPalette::PlaceholderText, QPalette::All, false },
+        { PalettePlaceholderTextDisabled, QPalette::PlaceholderText, QPalette::Disabled, false },
+    };
+
+    for (auto entry : mapping) {
+        const QColor themeColor = color(entry.themeColor);
+        // Use original color if color is not defined in theme.
+        if (themeColor.isValid()) {
+            if (entry.setColorRoleAsBrush)
+                // TODO: Find out why sometimes setBrush is used
+                pal.setBrush(entry.paletteColorGroup, entry.paletteColorRole, themeColor);
+            else
+                pal.setColor(entry.paletteColorGroup, entry.paletteColorRole, themeColor);
+        }
+    }
+
+    return pal;
+}
+}
diff --git a/src/UI/Theme/Theme.hh b/src/UI/Theme/Theme.hh
new file mode 100644
index 00000000..86a3dc48
--- /dev/null
+++ b/src/UI/Theme/Theme.hh
@@ -0,0 +1,475 @@
+#pragma once
+
+// #include "../../Lib/Log.hh"
+
+namespace Vivy
+{
+class ThemePrivate;
+class Theme;
+
+class Theme final : public QObject {
+    Q_OBJECT
+
+    struct ThemePrivate final {
+        ThemePrivate() noexcept;
+
+        QString id;
+        QString fileName;
+        QString displayName;
+        QStringList preferredStyles;
+        QString defaultTextEditorColorScheme;
+        QVector<QPair<QColor, QString>> colors;
+        QVector<QString> imageFiles;
+        QVector<QGradientStops> gradients;
+        QVector<bool> flags;
+        QMap<QString, QColor> palette;
+    };
+
+public:
+    Theme(const QString &id, QObject *parent = nullptr) noexcept;
+    ~Theme() noexcept override;
+
+    void applyToApplication() const noexcept;
+
+    enum Color {
+        BackgroundColorAlternate,
+        BackgroundColorDark,
+        BackgroundColorHover,
+        BackgroundColorNormal,
+        BackgroundColorSelected,
+        BackgroundColorDisabled,
+        BadgeLabelBackgroundColorChecked,
+        BadgeLabelBackgroundColorUnchecked,
+        BadgeLabelTextColorChecked,
+        BadgeLabelTextColorUnchecked,
+        CanceledSearchTextColor,
+        ComboBoxArrowColor,
+        ComboBoxArrowColorDisabled,
+        ComboBoxTextColor,
+        DetailsButtonBackgroundColorHover,
+        DetailsWidgetBackgroundColor,
+        DockWidgetResizeHandleColor,
+        DoubleTabWidget1stSeparatorColor,
+        DoubleTabWidget1stTabActiveTextColor,
+        DoubleTabWidget1stTabBackgroundColor,
+        DoubleTabWidget1stTabInactiveTextColor,
+        DoubleTabWidget2ndSeparatorColor,
+        DoubleTabWidget2ndTabActiveTextColor,
+        DoubleTabWidget2ndTabBackgroundColor,
+        DoubleTabWidget2ndTabInactiveTextColor,
+        EditorPlaceholderColor,
+        FancyToolBarSeparatorColor,
+        FancyTabBarBackgroundColor,
+        FancyTabBarSelectedBackgroundColor,
+        FancyTabWidgetDisabledSelectedTextColor,
+        FancyTabWidgetDisabledUnselectedTextColor,
+        FancyTabWidgetEnabledSelectedTextColor,
+        FancyTabWidgetEnabledUnselectedTextColor,
+        FancyToolButtonHoverColor,
+        FancyToolButtonSelectedColor,
+        FutureProgressBackgroundColor,
+        InfoBarBackground,
+        InfoBarText, // TODO: Deprecate. Unused.
+        MenuBarEmptyAreaBackgroundColor,
+        MenuBarItemBackgroundColor,
+        MenuBarItemTextColorDisabled,
+        MenuBarItemTextColorNormal,
+        MenuItemTextColorDisabled,
+        MenuItemTextColorNormal,
+        MiniProjectTargetSelectorBackgroundColor, // TODO: Deprecate. -> Utils::StyleHelper().baseColor()
+        MiniProjectTargetSelectorBorderColor,
+        MiniProjectTargetSelectorSummaryBackgroundColor, // TODO: Deprecate. -> Utils::StyleHelper().baseColor()
+        MiniProjectTargetSelectorTextColor,
+        OutputPaneButtonFlashColor,
+        OutputPaneToggleButtonTextColorChecked,
+        OutputPaneToggleButtonTextColorUnchecked,
+        PanelStatusBarBackgroundColor,
+        PanelsWidgetSeparatorLineColor, // TODO: Deprecate. Unused.
+        PanelTextColorDark,
+        PanelTextColorMid,
+        PanelTextColorLight,
+        ProgressBarColorError,
+        ProgressBarColorFinished,
+        ProgressBarColorNormal,
+        ProgressBarTitleColor,
+        ProgressBarBackgroundColor,
+        SplitterColor,
+        TextColorDisabled,
+        TextColorError,
+        TextColorHighlight,
+        TextColorHighlightBackground,
+        TextColorLink,
+        TextColorLinkVisited,
+        TextColorNormal,
+        ToggleButtonBackgroundColor,
+        ToolBarBackgroundColor,
+        TreeViewArrowColorNormal,
+        TreeViewArrowColorSelected,
+
+        /* Palette for QPalette */
+
+        PaletteWindow,
+        PaletteWindowText,
+        PaletteBase,
+        PaletteAlternateBase,
+        PaletteToolTipBase,
+        PaletteToolTipText,
+        PaletteText,
+        PaletteButton,
+        PaletteButtonText,
+        PaletteBrightText,
+        PaletteHighlight,
+        PaletteHighlightedText,
+        PaletteLink,
+        PaletteLinkVisited,
+
+        PaletteLight,
+        PaletteMidlight,
+        PaletteDark,
+        PaletteMid,
+        PaletteShadow,
+
+        PaletteWindowDisabled,
+        PaletteWindowTextDisabled,
+        PaletteBaseDisabled,
+        PaletteAlternateBaseDisabled,
+        PaletteToolTipBaseDisabled,
+        PaletteToolTipTextDisabled,
+        PaletteTextDisabled,
+        PaletteButtonDisabled,
+        PaletteButtonTextDisabled,
+        PaletteBrightTextDisabled,
+        PaletteHighlightDisabled,
+        PaletteHighlightedTextDisabled,
+        PaletteLinkDisabled,
+        PaletteLinkVisitedDisabled,
+
+        PaletteLightDisabled,
+        PaletteMidlightDisabled,
+        PaletteDarkDisabled,
+        PaletteMidDisabled,
+        PaletteShadowDisabled,
+
+        PalettePlaceholderText,
+        PalettePlaceholderTextDisabled,
+
+        /* Icons */
+
+        IconsBaseColor,
+        IconsDisabledColor,
+        IconsInfoColor,
+        IconsInfoToolBarColor,
+        IconsWarningColor,
+        IconsWarningToolBarColor,
+        IconsErrorColor,
+        IconsErrorToolBarColor,
+        IconsRunColor,
+        IconsRunToolBarColor,
+        IconsStopColor,
+        IconsStopToolBarColor,
+        IconsInterruptColor,
+        IconsInterruptToolBarColor,
+        IconsDebugColor,
+        IconsNavigationArrowsColor,
+        IconsBuildHammerHandleColor,
+        IconsBuildHammerHeadColor,
+        IconsModeWelcomeActiveColor,
+        IconsModeEditActiveColor,
+        IconsModeDesignActiveColor,
+        IconsModeDebugActiveColor,
+        IconsModeProjectActiveColor,
+        IconsModeAnalyzeActiveColor,
+        IconsModeHelpActiveColor,
+
+        /* Code model Icons */
+
+        IconsCodeModelKeywordColor,
+        IconsCodeModelClassColor,
+        IconsCodeModelStructColor,
+        IconsCodeModelFunctionColor,
+        IconsCodeModelVariableColor,
+        IconsCodeModelEnumColor,
+        IconsCodeModelMacroColor,
+        IconsCodeModelAttributeColor,
+        IconsCodeModelUniformColor,
+        IconsCodeModelVaryingColor,
+        IconsCodeModelOverlayBackgroundColor,
+        IconsCodeModelOverlayForegroundColor,
+
+        /* Code model text marks */
+
+        CodeModel_Error_TextMarkColor,
+        CodeModel_Warning_TextMarkColor,
+
+        /* Output panes */
+
+        OutputPanes_DebugTextColor,
+        OutputPanes_ErrorMessageTextColor,
+        OutputPanes_MessageOutput,
+        OutputPanes_NormalMessageTextColor,
+        OutputPanes_StdErrTextColor,
+        OutputPanes_StdOutTextColor,
+        OutputPanes_WarningMessageTextColor,
+        OutputPanes_TestPassTextColor,
+        OutputPanes_TestFailTextColor,
+        OutputPanes_TestXFailTextColor,
+        OutputPanes_TestXPassTextColor,
+        OutputPanes_TestSkipTextColor,
+        OutputPanes_TestWarnTextColor,
+        OutputPanes_TestFatalTextColor,
+        OutputPanes_TestDebugTextColor,
+
+        /* Debugger Log Window */
+
+        Debugger_LogWindow_LogInput,
+        Debugger_LogWindow_LogStatus,
+        Debugger_LogWindow_LogTime,
+
+        /* Debugger Watch Item */
+
+        Debugger_WatchItem_ValueNormal,
+        Debugger_WatchItem_ValueInvalid,
+        Debugger_WatchItem_ValueChanged,
+
+        /* Welcome Plugin */
+
+        Welcome_TextColor,
+        Welcome_ForegroundPrimaryColor,
+        Welcome_ForegroundSecondaryColor,
+        Welcome_BackgroundColor,
+        Welcome_ButtonBackgroundColor,
+        Welcome_DividerColor,
+        Welcome_LinkColor,
+        Welcome_HoverColor,
+        Welcome_DisabledLinkColor,
+
+        /* Timeline Library */
+        Timeline_TextColor,
+        Timeline_BackgroundColor1,
+        Timeline_BackgroundColor2,
+        Timeline_DividerColor,
+        Timeline_HighlightColor,
+        Timeline_PanelBackgroundColor,
+        Timeline_PanelHeaderColor,
+        Timeline_HandleColor,
+        Timeline_RangeColor,
+
+        /* VcsBase Plugin */
+        VcsBase_FileStatusUnknown_TextColor,
+        VcsBase_FileAdded_TextColor,
+        VcsBase_FileModified_TextColor,
+        VcsBase_FileDeleted_TextColor,
+        VcsBase_FileRenamed_TextColor,
+        VcsBase_FileUnmerged_TextColor,
+
+        /* Bookmarks Plugin */
+        Bookmarks_TextMarkColor,
+
+        /* TextEditor Plugin */
+        TextEditor_SearchResult_ScrollBarColor,
+        TextEditor_CurrentLine_ScrollBarColor,
+
+        /* Debugger Plugin */
+        Debugger_Breakpoint_TextMarkColor,
+
+        /* ProjectExplorer Plugin */
+        ProjectExplorer_TaskError_TextMarkColor,
+        ProjectExplorer_TaskWarn_TextMarkColor,
+
+        /* QmlDesigner Plugin */
+        QmlDesigner_BackgroundColor,
+        QmlDesigner_HighlightColor,
+        QmlDesigner_FormEditorSelectionColor,
+        QmlDesigner_FormEditorForegroundColor,
+        QmlDesigner_BackgroundColorDarker,
+        QmlDesigner_BackgroundColorDarkAlternate,
+        QmlDesigner_TabLight,
+        QmlDesigner_TabDark,
+        QmlDesigner_ButtonColor,
+        QmlDesigner_BorderColor,
+        QmlDesigner_FormeditorBackgroundColor,
+        QmlDesigner_AlternateBackgroundColor,
+        QmlDesigner_ScrollBarHandleColor,
+
+        /* Palette for DS Controls */
+
+        DSpanelBackground,
+        DSinteraction,
+        DSerrorColor,
+        DSdisabledColor,
+        DScontrolBackground,
+        DScontrolBackgroundInteraction,
+        DScontrolBackgroundDisabled,
+        DScontrolBackgroundGlobalHover,
+        DScontrolBackgroundHover,
+        DScontrolOutline,
+        DScontrolOutlineInteraction,
+        DScontrolOutlineDisabled,
+        DStextColor,
+        DStextColorDisabled,
+        DStextSelectionColor,
+        DStextSelectedTextColor,
+
+        DSplaceholderTextColor,
+        DSplaceholderTextColorInteraction,
+
+        DSiconColor,
+        DSiconColorHover,
+        DSiconColorInteraction,
+        DSiconColorDisabled,
+        DSiconColorSelected,
+        DSlinkIndicatorColor,
+        DSlinkIndicatorColorHover,
+        DSlinkIndicatorColorInteraction,
+        DSlinkIndicatorColorDisabled,
+        DSpopupBackground,
+        DSpopupOverlayColor,
+        DSsliderActiveTrack,
+        DSsliderActiveTrackHover,
+        DSsliderActiveTrackFocus,
+        DSsliderInactiveTrack,
+        DSsliderInactiveTrackHover,
+        DSsliderInactiveTrackFocus,
+        DSsliderHandle,
+        DSsliderHandleHover,
+        DSsliderHandleFocus,
+        DSsliderHandleInteraction,
+        DSscrollBarTrack,
+        DSscrollBarHandle,
+        DSsectionHeadBackground,
+        DSstateDefaultHighlight,
+        DSstateSeparatorColor,
+        DSstateBackgroundColor,
+        DSstatePreviewOutline,
+        DSchangedStateText,
+        DS3DAxisXColor,
+        DS3DAxisYColor,
+        DS3DAxisZColor,
+        DSactionBinding,
+        DSactionAlias,
+        DSactionKeyframe,
+        DSactionJIT,
+
+        DStableHeaderBackground,
+        DStableHeaderText,
+
+        DSdockContainerBackground,
+        DSdockContainerSplitter,
+        DSdockAreaBackground,
+
+        DSdockWidgetBackground,
+        DSdockWidgetSplitter,
+        DSdockWidgetTitleBar,
+
+        DStitleBarText,
+        DStitleBarIcon,
+        DStitleBarButtonHover,
+        DStitleBarButtonPress,
+
+        DStabContainerBackground,
+        DStabSplitter,
+
+        DStabInactiveBackground,
+        DStabInactiveText,
+        DStabInactiveIcon,
+        DStabInactiveButtonHover,
+        DStabInactiveButtonPress,
+
+        DStabActiveBackground,
+        DStabActiveText,
+        DStabActiveIcon,
+        DStabActiveButtonHover,
+        DStabActiveButtonPress,
+
+        DStabFocusBackground,
+        DStabFocusText,
+        DStabFocusIcon,
+        DStabFocusButtonHover,
+        DStabFocusButtonPress,
+
+        DSnavigatorBranch,
+        DSnavigatorBranchIndicator,
+        DSnavigatorItemBackground,
+        DSnavigatorItemBackgroundHover,
+        DSnavigatorItemBackgroundSelected,
+        DSnavigatorText,
+        DSnavigatorTextHover,
+        DSnavigatorTextSelected,
+        DSnavigatorIcon,
+        DSnavigatorIconHover,
+        DSnavigatorIconSelected,
+        DSnavigatorAliasIconChecked,
+        DSnavigatorDropIndicatorBackground,
+        DSnavigatorDropIndicatorOutline,
+
+        DSheaderViewBackground,
+        DStableViewAlternateBackground,
+
+        DStoolTipBackground,
+        DStoolTipOutline,
+        DStoolTipText,
+
+        DSUnimportedModuleColor
+    };
+
+    enum Gradient {
+        DetailsWidgetHeaderGradient,
+    };
+
+    enum ImageFile {
+        IconOverlayCSource,
+        IconOverlayCppHeader,
+        IconOverlayCppSource,
+        IconOverlayPri,
+        IconOverlayPrf,
+        IconOverlayPro,
+        StandardPixmapFileIcon,
+        StandardPixmapDirIcon
+    };
+
+    enum Flag {
+        DrawTargetSelectorBottom,
+        DrawSearchResultWidgetFrame,
+        DrawIndicatorBranch,
+        DrawToolBarHighlights,
+        DrawToolBarBorders,
+        ComboBoxDrawTextShadow,
+        DerivePaletteFromTheme,
+        ApplyThemePaletteGlobally,
+        FlatToolBars,
+        FlatSideBarIcons,
+        FlatProjectsMode,
+        FlatMenuBar,
+        ToolBarIconShadow,
+        WindowColorAsBase,
+        DarkUserInterface
+    };
+
+    Q_ENUM(Color)
+    Q_ENUM(ImageFile)
+    Q_ENUM(Gradient)
+    Q_ENUM(Flag)
+
+    Q_INVOKABLE bool flag(Flag f) const noexcept;
+    Q_INVOKABLE QColor color(Color role) const noexcept;
+    QString imageFile(ImageFile imageFile, const QString &fallBack) const noexcept;
+    QGradientStops gradient(Gradient role) const noexcept;
+    QPalette palette() const noexcept;
+    QStringList preferredStyles() const noexcept;
+    QString defaultTextEditorColorScheme() const noexcept;
+
+    QString id() const noexcept;
+    QString filePath() const noexcept;
+    QString displayName() const noexcept;
+    void setDisplayName(const QString &displayName) noexcept;
+
+    void readSettings(QSettings *const settings) noexcept;
+
+    static bool systemUsesDarkMode() noexcept;
+    static QPalette initialPalette() noexcept;
+
+private:
+    QPair<QColor, QString> readNamedColor(const QString &color) const;
+    ThemePrivate *d;
+};
+}
diff --git a/src/UI/Theme/ThemeMac.hh b/src/UI/Theme/ThemeMac.hh
new file mode 100644
index 00000000..93d1e0ea
--- /dev/null
+++ b/src/UI/Theme/ThemeMac.hh
@@ -0,0 +1,6 @@
+#pragma once
+
+namespace Vivy::Utils
+{
+void forceMacOSLightAquaApperance();
+}
diff --git a/src/UI/Theme/ThemeMac.mm b/src/UI/Theme/ThemeMac.mm
new file mode 100644
index 00000000..9b2d3d6f
--- /dev/null
+++ b/src/UI/Theme/ThemeMac.mm
@@ -0,0 +1,21 @@
+#include "ThemeMac.hh"
+#include <AppKit/AppKit.h>
+
+#if !QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14)
+@interface NSApplication (MojaveForwardDeclarations)
+@property (strong) NSAppearance *appearance NS_AVAILABLE_MAC(10_14);
+@end
+#endif
+
+namespace Vivy::Utils {
+void forceMacOSLightAquaApperance()
+{
+#if __has_builtin(__builtin_available)
+    if (__builtin_available(macOS 10.14, *))
+#else // Xcode 8
+    if (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::MacOS, 10, 14, 0))
+#endif
+        NSApp.appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua];
+}
+
+}
-- 
GitLab