[plasma/union] /: Base style loading on packages rather than plugins

Arjen Hiemstra <[email protected]> Wed, 5 Aug 2026 10:33:23 +0000 (UTC)
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 9b32f4940ead3ef70cb9ca1f0c5db2e82d74766b by Arjen Hiemstra.
Committed on 05/08/2026 at 10:24.
Pushed by ahiemstra into branch 'master'.

Base style loading on packages rather than plugins

This changes the style loading to look for a package by ID, then load
that package and use it to create a style instance. This makes packages
a core part of Union and removes the need for input plugin names when
looking for styles, as the input plugin needed for a style is now
determined by the package.

M  +2    -2    autotests/TestStyle.cpp
M  +1    -2    autotests/output/quick/TestControls.cpp
M  +1    -1    src/InputPlugin.h
M  +7    -8    src/Style.cpp
M  +13   -12   src/Style.h
M  +2    -4    src/StyleCache.cpp
M  +26   -33   src/StyleRegistry.cpp
M  +3    -5    src/StyleRegistry.h
M  +1    -2    src/Style_p.h
M  +5    -11   src/input/css/CssLoader.cpp
M  +3    -2    src/input/css/CssPlugin.cpp
M  +2    -1    src/input/css/CssPlugin.h

https://invent.kde.org/plasma/union/-/commit/9b32f4940ead3ef70cb9ca1f0c5db2e82d74766b

diff --git a/autotests/TestStyle.cpp b/autotests/TestStyle.cpp
index 9cfc7c32..1c8a9de8 100644
--- a/autotests/TestStyle.cpp
+++ b/autotests/TestStyle.cpp
@@ -26,7 +26,7 @@ class TestStyle : public QObject
 private Q_SLOTS:
     void testLoad()
     {
-        auto style = Style::create(u"test"_s, u"test"_s, std::make_unique<TestLoader>());
+        auto style = Style::create(std::filesystem::path(), std::make_unique<TestLoader>());
 
         QVERIFY(style->load());
 
@@ -37,7 +37,7 @@ private Q_SLOTS:
 
     void testMatches()
     {
-        auto style = Style::create(u"test"_s, u"test"_s, std::make_unique<TestLoader>());
+        auto style = Style::create(std::filesystem::path(), std::make_unique<TestLoader>());
         QVERIFY(style->load());
 
         QList<Element::Ptr> elements;
diff --git a/autotests/output/quick/TestControls.cpp b/autotests/output/quick/TestControls.cpp
index f9e4e99d..4fa9fdc1 100644
--- a/autotests/output/quick/TestControls.cpp
+++ b/autotests/output/quick/TestControls.cpp
@@ -32,12 +32,11 @@ private Q_SLOTS:
     void initTestCase()
     {
         qputenv("UNION_DISABLE_INPUT_PLUGINS", "1");
-        qputenv("UNION_STYLE_PLUGIN", "test");
         qputenv("UNION_STYLE_NAME", "test");
 
         Union::StyleRegistry::instance()->load();
 
-        auto testStyle = Union::Style::create(u"test"_s, u"test"_s, std::make_unique<TestStyleLoader>());
+        auto testStyle = Union::Style::create(std::filesystem::path("test"), std::make_unique<TestStyleLoader>());
         Union::StyleRegistry::instance()->addStyle(testStyle);
     }
 
diff --git a/src/InputPlugin.h b/src/InputPlugin.h
index e3fe7296..15aa0f7a 100644
--- a/src/InputPlugin.h
+++ b/src/InputPlugin.h
@@ -44,7 +44,7 @@ public:
      * This should be reimplemented by subclasses and create a new instance of
      * Style using an appropriate StyleLoader and other input specific data.
      */
-    virtual std::shared_ptr<Style> createStyle(const QString &styleName) const = 0;
+    virtual std::shared_ptr<Style> createStyle(const StylePackage &package) const = 0;
 
     /*!
      * Returns an input plugin by type name.
diff --git a/src/Style.cpp b/src/Style.cpp
index 2465be70..c4c698f2 100644
--- a/src/Style.cpp
+++ b/src/Style.cpp
@@ -34,14 +34,14 @@ Style::Style(std::unique_ptr<StylePrivate> &&d)
 
 Style::~Style() = default;
 
-QString Style::name() const
+std::filesystem::path Style::path() const
 {
-    return d->styleName;
+    return d->path;
 }
 
-QString Style::pluginName() const
+QString Style::id() const
 {
-    return d->pluginName;
+    return QString::fromStdString(d->path.filename());
 }
 
 bool Style::hasErrors() const
@@ -95,7 +95,7 @@ QList<StyleRule::Ptr> Union::Style::matches(const QList<Element::Ptr> &elements)
     QList<StyleRule::Ptr> result;
 
     if (d->rules.isEmpty()) {
-        qCInfo(UNION_QUERY) << "No style rules found for theme" << d->styleName << "so we will never match anything!";
+        qCInfo(UNION_QUERY) << "No style rules found for style" << d->path.filename().string() << "so we will never match anything!";
     }
 
     for (auto rule : d->rules) {
@@ -115,11 +115,10 @@ QList<StyleRule::Ptr> Union::Style::matches(const QList<Element::Ptr> &elements)
     return result;
 }
 
-std::shared_ptr<Style> Style::create(const QString &pluginName, const QString &styleName, std::unique_ptr<StyleLoader> &&loader)
+std::shared_ptr<Style> Style::create(const std::filesystem::path &path, std::unique_ptr<StyleLoader> &&loader)
 {
     auto d = std::make_unique<StylePrivate>();
-    d->pluginName = pluginName;
-    d->styleName = styleName;
+    d->path = path;
     d->loader = std::move(loader);
     return std::make_shared<Style>(std::move(d));
 }
diff --git a/src/Style.h b/src/Style.h
index 2bd2609c..32ef1a47 100644
--- a/src/Style.h
+++ b/src/Style.h
@@ -41,20 +41,22 @@ public:
     ~Style() override;
 
     /*!
-     * \property Union::Style::name
+     * \property Union::Style::path
      *
-     * The name of the style.
+     * The path where the package for this style is located.
      */
-    Q_PROPERTY(QString name READ name CONSTANT)
-    QString name() const;
+    Q_PROPERTY(std::filesystem::path path READ path CONSTANT)
+    std::filesystem::path path() const;
 
     /*!
-     * \property Union::Style::pluginName
+     * \property Union::Style::id
      *
-     * The name of the plugin used to load rules for this Style.
+     * The identifier of the style.
+     *
+     * This is effectively the filename of path().
      */
-    Q_PROPERTY(QString pluginName READ pluginName CONSTANT)
-    QString pluginName() const;
+    Q_PROPERTY(QString id READ id CONSTANT)
+    QString id() const;
 
     /*!
      * \property Union::Style::hasErrors
@@ -102,14 +104,13 @@ public:
     QList<StyleRule::Ptr> rules();
 
     /*!
-     * Create a new instance of Style.
+     * Create a new instance of Style from a path.
      *
-     * \a pluginName The name of the plugin responsible for creating this style.
-     * \a styleName The name of this style.
+     * \a path   The path to create a style from.
      * \a loader An instance of StyleLoader responsible for loading the actual
      *           data of this style.
      */
-    static Ptr create(const QString &pluginName, const QString &styleName, std::unique_ptr<StyleLoader> &&loader);
+    static Ptr create(const std::filesystem::path &path, std::unique_ptr<StyleLoader> &&loader);
 
 protected:
     bool eventFilter(QObject *obj, QEvent *event) override;
diff --git a/src/StyleCache.cpp b/src/StyleCache.cpp
index 24e2292e..4a5f48b0 100644
--- a/src/StyleCache.cpp
+++ b/src/StyleCache.cpp
@@ -117,8 +117,7 @@ std::unique_ptr<StylePrivate> StyleCache::load(const StyleId &styleId) const
     }
 
     auto result = std::make_unique<StylePrivate>();
-    reader >> result->pluginName;
-    reader >> result->styleName;
+    reader >> result->path;
 
     if (result->pluginName.toStdString() != styleId.first || result->styleName.toStdString() != styleId.second) {
         qCDebug(UNION_GENERAL) << "Ignoring cache file" << path.string() << "plugin/style name mismatch";
@@ -197,8 +196,7 @@ bool Union::StyleCache::save(const StylePrivate *style) const
     writer << CacheMagic;
     writer << CacheVersion;
 
-    writer << style->pluginName;
-    writer << style->styleName;
+    writer << style->path;
 
     writer << style->cachePaths;
     writer << style->modificationTimes;
diff --git a/src/StyleRegistry.cpp b/src/StyleRegistry.cpp
index 5e5d43ee..53ef8d1f 100644
--- a/src/StyleRegistry.cpp
+++ b/src/StyleRegistry.cpp
@@ -138,7 +138,7 @@ public:
 
     std::unique_ptr<StyleCache> styleCache;
 
-    QHash<StyleCache::StyleId, std::shared_ptr<Style>> styles;
+    QHash<fs::path, Style::Ptr> styles;
 
     std::shared_ptr<PluginRegistry<PlatformPlugin>> platformRegistry;
     std::shared_ptr<PlatformPlugin> platform;
@@ -179,62 +179,55 @@ void StyleRegistry::save()
 
 std::shared_ptr<Style> StyleRegistry::defaultStyle()
 {
-    static auto environmentPlugin = qEnvironmentVariable("UNION_STYLE_PLUGIN", QString{});
     static auto environmentName = qEnvironmentVariable("UNION_STYLE_NAME", QString{});
 
-    auto plugin = environmentPlugin;
-    if (plugin.isEmpty()) {
-        plugin = platform()->defaultInputPlugin();
-    }
-
     auto name = environmentName;
     if (name.isEmpty()) {
         name = platform()->defaultStyleName();
     }
 
-    return style(name, plugin);
+    return style(name);
 }
 
-std::shared_ptr<Style> StyleRegistry::style(const QString &styleName, const QString &pluginName)
+std::shared_ptr<Style> StyleRegistry::style(const QString &styleId)
 {
-    if (!pluginName.isEmpty()) {
-        return d->loadStyle(styleName, pluginName);
+    auto itr = std::ranges::find_if(d->styles, [styleId](const Style::Ptr &style) {
+        return style->id() == styleId;
+    });
+    if (itr != d->styles.end()) {
+        return itr.value();
     }
 
-    // pluginName is empty so we don't know which exact input plugin provides
-    // the style. Search through all input plugins until we find one that
-    // returns a valid style for styleName.
+    auto stylePackage = d->packageHandler->package(styleId);
+    if (!stylePackage.isValid()) {
+        qCWarning(UNION_GENERAL) << "Could not find style" << styleId;
+        return nullptr;
+    }
 
-    // First search through already-loaded plugins
-    const auto objects = d->inputRegistry->pluginObjects();
-    for (const auto &object : objects) {
-        if (auto style = d->loadStyle(styleName, object); style) {
-            return style;
-        }
+    auto style = stylePackage.load();
+    if (!style) {
+        qCWarning(UNION_GENERAL) << "Style" << styleId << "failed to load";
+        return nullptr;
     }
 
-    // Nothing found in loaded plugins, try and load each available plugin and
-    // see if that returns something.
-    const auto plugins = d->inputRegistry->plugins();
-    for (const auto &plugin : plugins) {
-        if (auto style = d->loadStyle(styleName, plugin.name); style) {
-            return style;
-        }
+    if (!style->load()) {
+        qCWarning(UNION_GENERAL) << "Style" << styleId << "failed to load";
+        return nullptr;
     }
 
-    qCWarning(UNION_GENERAL) << "Requested style" << styleName << "which could not be found in any plugin!";
-    return nullptr;
+    d->styles.insert(stylePackage.path(), style);
+    qCDebug(UNION_GENERAL) << "Loaded style" << styleId << "from" << stylePackage.path().string();
+    return style;
 }
 
 void Union::StyleRegistry::addStyle(const std::shared_ptr<Style> &style)
 {
-    auto styleId = std::make_pair(style->pluginName().toStdString(), style->name().toStdString());
-    if (d->styles.contains(styleId)) {
-        qCWarning(UNION_GENERAL) << "A style from plugin" << style->pluginName() << "with name" << style->name() << "is already registered";
+    if (d->styles.contains(style->path())) {
+        qCWarning(UNION_GENERAL) << "A style with path" << style->path().string() << "is already registered";
         return;
     }
 
-    d->styles.insert(styleId, style);
+    d->styles.insert(style->path(), style);
 }
 
 std::shared_ptr<PlatformPlugin> StyleRegistry::platform() const
diff --git a/src/StyleRegistry.h b/src/StyleRegistry.h
index b4082598..4d71837a 100644
--- a/src/StyleRegistry.h
+++ b/src/StyleRegistry.h
@@ -55,13 +55,11 @@ public:
      * Get a style instance by name.
      *
      * \a styleName  The name of the style to retrieve.
-     * \a pluginName The name of the input plugin that provides the style. If
-     *               empty, the style will be searched for in available plugins.
      *
-     * Returns a Style instance that matches `styleName` and `pluginName`, or
-     * `nullptr` if it could not be found.
+     * Returns a Style instance that matches `styleName`, or `nullptr` if it
+     * could not be found.
      */
-    std::shared_ptr<Style> style(const QString &styleName, const QString &pluginName = QString{});
+    std::shared_ptr<Style> style(const QString &styleId);
 
     /*!
      * Programatically add a style to the registry.
diff --git a/src/Style_p.h b/src/Style_p.h
index dc77cbab..9e40eed3 100644
--- a/src/Style_p.h
+++ b/src/Style_p.h
@@ -18,8 +18,7 @@ class StylePrivate
 public:
     std::unique_ptr<StyleLoader> loader;
 
-    QString pluginName;
-    QString styleName;
+    std::filesystem::path path;
 
     bool modified = false;
     bool hasErrors = false;
diff --git a/src/input/css/CssLoader.cpp b/src/input/css/CssLoader.cpp
index 6e6cfa2f..f46e2488 100644
--- a/src/input/css/CssLoader.cpp
+++ b/src/input/css/CssLoader.cpp
@@ -383,15 +383,9 @@ inline void setDirectionValue(T *output, const std::string &baseName, const cssp
     }
 }
 
-bool CssLoader::load(Style::Ptr theme)
+bool CssLoader::load(Style::Ptr style)
 {
-    QString relativeStylePath = u"union/css/styles/"_s + theme->name() + u"/style.css"_s;
-    m_stylePath = fs::path(QStandardPaths::locate(QStandardPaths::GenericDataLocation, relativeStylePath, QStandardPaths::LocateFile).toStdString());
-
-    if (!fs::exists(m_stylePath)) {
-        qCWarning(UNION_CSS) << theme->name() << "does not exist!";
-        return false;
-    }
+    m_stylePath = style->path() / "contents" / "css" / "style.css";
 
     cssparser::StyleSheet styleSheet(m_stylePath);
 
@@ -402,11 +396,11 @@ bool CssLoader::load(Style::Ptr theme)
 
     const auto paths = styleSheet.paths();
     for (const auto &path : paths) {
-        theme->addCachePath(path);
+        style->addCachePath(path);
     }
 
     if (styleSheet.errors().size() > 0) {
-        theme->setHasErrors(true);
+        style->setHasErrors(true);
 
         qCWarning(UNION_CSS) << "Errors encountered while parsing CSS:";
         for (const auto &error : styleSheet.errors()) {
@@ -425,7 +419,7 @@ bool CssLoader::load(Style::Ptr theme)
         auto properties = std::make_unique<StylePropertyGroup>();
         createProperties(properties.get(), rule.properties());
         styleRule->setProperties(std::move(properties));
-        theme->insert(styleRule);
+        style->insert(styleRule);
     }
 
     return true;
diff --git a/src/input/css/CssPlugin.cpp b/src/input/css/CssPlugin.cpp
index 467451bb..2e880924 100644
--- a/src/input/css/CssPlugin.cpp
+++ b/src/input/css/CssPlugin.cpp
@@ -4,6 +4,7 @@
 #include "CssPlugin.h"
 
 #include <Style.h>
+#include <StylePackage.h>
 
 #include "CssLoader.h"
 
@@ -25,9 +26,9 @@ Union::StylePackage::Error CssPlugin::validatePackage(const Union::StylePackage
     return StylePackage::Error::None;
 }
 
-std::shared_ptr<Union::Style> CssPlugin::createStyle(const QString &styleName) const
+std::shared_ptr<Union::Style> CssPlugin::createStyle(const Union::StylePackage &package) const
 {
-    return Union::Style::create(name(), styleName, std::make_unique<CssLoader>());
+    return Union::Style::create(package.path(), std::make_unique<CssLoader>());
 }
 
 #include "moc_CssPlugin.cpp"
diff --git a/src/input/css/CssPlugin.h b/src/input/css/CssPlugin.h
index 256ac2c6..8a31ef95 100644
--- a/src/input/css/CssPlugin.h
+++ b/src/input/css/CssPlugin.h
@@ -8,6 +8,7 @@
 namespace Union
 {
 class Style;
+class StylePackage;
 }
 
 class CssPlugin : public Union::InputPlugin
@@ -20,5 +21,5 @@ public:
 
     Union::StylePackage::Error validatePackage(const Union::StylePackage &package) override;
 
-    std::shared_ptr<Union::Style> createStyle(const QString &styleName) const override;
+    std::shared_ptr<Union::Style> createStyle(const Union::StylePackage &package) const override;
 };