forked from qt-creator/qt-creator
QmlDesigner: Add headless QML theme generator
Task-number: QDS-11956 Change-Id: Ibc8058dd5e8ac5f8363efdd9007d267ce2bf9685 Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
@@ -516,6 +516,7 @@ add_qtc_plugin(QmlDesigner
|
||||
${CMAKE_CURRENT_LIST_DIR}/components/propertyeditor
|
||||
${CMAKE_CURRENT_LIST_DIR}/components/stateseditor
|
||||
${CMAKE_CURRENT_LIST_DIR}/components/texteditor
|
||||
${CMAKE_CURRENT_LIST_DIR}/components/designsystem
|
||||
PUBLIC_INCLUDES
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
${CMAKE_CURRENT_LIST_DIR}/designercore #can not be a public dependency -> EXCLUDE_FROM_INSTALL in QmlDesignerCore
|
||||
@@ -1157,6 +1158,14 @@ extend_qtc_plugin(QmlDesigner
|
||||
toolbarbackend.h
|
||||
)
|
||||
|
||||
extend_qtc_plugin(QmlDesigner
|
||||
SOURCES_PREFIX components/designsystem
|
||||
SOURCES
|
||||
dsconstants.h
|
||||
dsthememanager.h dsthememanager.cpp
|
||||
dsthemegroup.cpp dsthemegroup.h
|
||||
)
|
||||
|
||||
add_qtc_plugin(assetexporterplugin
|
||||
PLUGIN_CLASS AssetExporterPlugin
|
||||
CONDITION TARGET QmlDesigner
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "nodeinstanceglobal.h"
|
||||
namespace QmlDesigner
|
||||
{
|
||||
|
||||
using ThemeId = ushort;
|
||||
|
||||
enum class GroupType { Colors, Flags, Numbers, Strings };
|
||||
|
||||
class ThemeProperty
|
||||
{
|
||||
public:
|
||||
bool isValid() const { return !name.trimmed().isEmpty() && value.isValid(); }
|
||||
|
||||
PropertyName name;
|
||||
QVariant value;
|
||||
bool isBinding = false;
|
||||
};
|
||||
|
||||
constexpr const char *GroupId(const GroupType type) {
|
||||
if (type == GroupType::Colors) return "colors";
|
||||
if (type == GroupType::Flags) return "flags";
|
||||
if (type == GroupType::Numbers) return "numbers";
|
||||
if (type == GroupType::Strings) return "strings";
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
}
|
||||
184
src/plugins/qmldesigner/components/designsystem/dsthemegroup.cpp
Normal file
184
src/plugins/qmldesigner/components/designsystem/dsthemegroup.cpp
Normal file
@@ -0,0 +1,184 @@
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "dsthemegroup.h"
|
||||
|
||||
#include <model.h>
|
||||
#include <nodeproperty.h>
|
||||
#include <variantproperty.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
namespace QmlDesigner {
|
||||
using namespace std;
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(dsLog, "qtc.designer.designSystem", QtInfoMsg)
|
||||
|
||||
std::optional<TypeName> groupTypeName(GroupType type)
|
||||
{
|
||||
switch (type) {
|
||||
case QmlDesigner::GroupType::Colors: return "color"; break;
|
||||
case QmlDesigner::GroupType::Flags: return "bool"; break;
|
||||
case QmlDesigner::GroupType::Numbers: return "real"; break;
|
||||
case QmlDesigner::GroupType::Strings: return "string"; break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QDebug &operator<<(QDebug &s, const ThemeProperty &p)
|
||||
{
|
||||
s << "{Name:" << p.name << ", Value:" << p.value << ", isBinding:" << p.isBinding << "}";
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
DSThemeGroup::DSThemeGroup(GroupType type):
|
||||
m_type(type)
|
||||
{}
|
||||
|
||||
DSThemeGroup::~DSThemeGroup() {}
|
||||
|
||||
bool DSThemeGroup::addProperty(ThemeId theme, const ThemeProperty &prop)
|
||||
{
|
||||
if (!prop.isValid()) {
|
||||
qCDebug(dsLog) << "Add property failed. Invalid property." << prop;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_values.contains(prop.name))
|
||||
m_values[prop.name] = make_unique<ThemeValues>();
|
||||
|
||||
const auto &tValues = m_values.at(prop.name);
|
||||
if (tValues->contains(theme)) {
|
||||
qCDebug(dsLog) << "Add property failed. Duplicate property name." << prop;
|
||||
return false;
|
||||
}
|
||||
|
||||
tValues->insert({theme, {prop.value, prop.isBinding}});
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<ThemeProperty> DSThemeGroup::propertyValue(ThemeId theme, const PropertyName &name) const
|
||||
{
|
||||
if (!m_values.contains(name))
|
||||
return {};
|
||||
|
||||
const auto &tValues = m_values.at(name);
|
||||
const auto itr = tValues->find(theme);
|
||||
if (itr != tValues->end()) {
|
||||
auto &[value, isBindind] = itr->second;
|
||||
return ThemeProperty{name, value, isBindind};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void DSThemeGroup::updateProperty(ThemeId theme, PropertyName newName, const ThemeProperty &prop)
|
||||
{
|
||||
if (!m_values.contains(prop.name)) {
|
||||
qCDebug(dsLog) << "Property update failure. Can't find property" << prop;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ThemeProperty{newName, prop.value, prop.isBinding}.isValid()) {
|
||||
qCDebug(dsLog) << "Property update failure. Invalid property" << prop << newName;
|
||||
return;
|
||||
}
|
||||
|
||||
if (newName != prop.name && m_values.contains(newName)) {
|
||||
qCDebug(dsLog) << "Property update failure. Property name update already exists" << newName
|
||||
<< prop;
|
||||
return;
|
||||
}
|
||||
|
||||
auto &tValues = m_values.at(prop.name);
|
||||
const auto itr = tValues->find(theme);
|
||||
if (itr == tValues->end()) {
|
||||
qCDebug(dsLog) << "Property update failure. No property for the theme" << theme << prop;
|
||||
return;
|
||||
}
|
||||
|
||||
tValues->at(theme) = {prop.value, prop.isBinding};
|
||||
if (newName != prop.name) {
|
||||
m_values[newName] = std::move(tValues);
|
||||
m_values.erase(prop.name);
|
||||
}
|
||||
}
|
||||
|
||||
void DSThemeGroup::removeProperty(const PropertyName &name)
|
||||
{
|
||||
m_values.erase(name);
|
||||
}
|
||||
|
||||
size_t DSThemeGroup::count(ThemeId theme) const
|
||||
{
|
||||
return std::accumulate(m_values.cbegin(),
|
||||
m_values.cend(),
|
||||
0,
|
||||
[theme](size_t c, const GroupProperties::value_type &p) {
|
||||
return c + (p.second->contains(theme) ? 1 : 0);
|
||||
});
|
||||
}
|
||||
|
||||
size_t DSThemeGroup::count() const
|
||||
{
|
||||
return m_values.size();
|
||||
}
|
||||
|
||||
void DSThemeGroup::removeTheme(ThemeId theme)
|
||||
{
|
||||
for (auto itr = m_values.cbegin(); itr != m_values.cend();) {
|
||||
itr->second->erase(theme);
|
||||
itr = itr->second->size() == 0 ? m_values.erase(itr) : std::next(itr);
|
||||
}
|
||||
}
|
||||
|
||||
void DSThemeGroup::duplicateValues(ThemeId from, ThemeId to)
|
||||
{
|
||||
for (auto itr = m_values.cbegin(); itr != m_values.cend(); ++itr) {
|
||||
const auto &[propName, values] = *itr;
|
||||
if (!values)
|
||||
continue;
|
||||
|
||||
auto fromValueItr = values->find(from);
|
||||
if (fromValueItr != values->end())
|
||||
(*values)[to] = fromValueItr->second;
|
||||
}
|
||||
}
|
||||
|
||||
void DSThemeGroup::decorate(ThemeId theme, ModelNode themeNode)
|
||||
{
|
||||
if (!count(theme))
|
||||
return; // No props for this theme in this group.
|
||||
|
||||
const auto groupName = GroupId(m_type);
|
||||
const auto typeName = groupTypeName(m_type);
|
||||
auto groupNode = themeNode.model()->createModelNode("QtObject");
|
||||
auto groupProperty = themeNode.nodeProperty(groupName);
|
||||
|
||||
if (!groupProperty || !typeName || !groupNode) {
|
||||
qCDebug(dsLog) << "Adding group node failed." << groupName << theme;
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto itr = m_values.cbegin(); itr != m_values.cend(); ++itr) {
|
||||
const auto &[propName, values] = *itr;
|
||||
auto themeValue = values->find(theme);
|
||||
if (themeValue != values->end()) {
|
||||
const auto &propData = themeValue->second;
|
||||
if (propData.isBinding) {
|
||||
auto bindingProp = groupNode.bindingProperty(propName);
|
||||
if (bindingProp)
|
||||
bindingProp.setDynamicTypeNameAndExpression(*typeName, propData.value.toString());
|
||||
} else {
|
||||
auto nodeProp = groupNode.variantProperty(propName);
|
||||
if (nodeProp)
|
||||
nodeProp.setDynamicTypeNameAndValue(*typeName, propData.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
groupProperty.setDynamicTypeNameAndsetModelNode("QtObject", groupNode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "dsconstants.h"
|
||||
#include "nodeinstanceglobal.h"
|
||||
#include <modelnode.h>
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
class DSThemeGroup
|
||||
{
|
||||
struct PropertyData
|
||||
{
|
||||
QVariant value;
|
||||
bool isBinding = false;
|
||||
};
|
||||
|
||||
using ThemeValues = std::map<ThemeId, PropertyData>;
|
||||
using GroupProperties = std::map<PropertyName, std::unique_ptr<ThemeValues>>;
|
||||
|
||||
public:
|
||||
DSThemeGroup(GroupType type);
|
||||
~DSThemeGroup();
|
||||
|
||||
bool addProperty(ThemeId, const ThemeProperty &prop);
|
||||
std::optional<ThemeProperty> propertyValue(ThemeId theme, const PropertyName &name) const;
|
||||
|
||||
void updateProperty(ThemeId theme, PropertyName newName, const ThemeProperty &prop);
|
||||
void removeProperty(const PropertyName &name);
|
||||
|
||||
GroupType type() const { return m_type; }
|
||||
|
||||
size_t count(ThemeId theme) const;
|
||||
size_t count() const;
|
||||
bool isEmpty() const;
|
||||
|
||||
void removeTheme(ThemeId theme);
|
||||
|
||||
void duplicateValues(ThemeId from, ThemeId to);
|
||||
void decorate(ThemeId theme, ModelNode themeNode);
|
||||
|
||||
private:
|
||||
const GroupType m_type;
|
||||
GroupProperties m_values;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include "dsthememanager.h"
|
||||
|
||||
#include "dsconstants.h"
|
||||
#include "dsthemegroup.h"
|
||||
|
||||
#include <nodeproperty.h>
|
||||
#include <model.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(dsLog, "qtc.designer.designSystem", QtInfoMsg)
|
||||
}
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
DSThemeManager::DSThemeManager() {}
|
||||
|
||||
DSThemeManager::~DSThemeManager() {}
|
||||
|
||||
std::optional<ThemeId> DSThemeManager::addTheme(const ThemeName &themeName)
|
||||
{
|
||||
if (themeName.trimmed().isEmpty() || themeId(themeName)) {
|
||||
qCDebug(dsLog) << "Can not add new Theme. Duplicate theme name";
|
||||
return {};
|
||||
}
|
||||
|
||||
const ThemeId newThemeId = m_themes.empty() ? 1 : m_themes.rbegin()->first + 1;
|
||||
m_themes.insert({newThemeId, themeName});
|
||||
|
||||
// Copy the new theme properties from an old theme(first one).
|
||||
if (m_themes.size() > 1)
|
||||
duplicateTheme(m_themes.begin()->first, newThemeId);
|
||||
|
||||
return newThemeId;
|
||||
}
|
||||
|
||||
std::optional<ThemeId> DSThemeManager::themeId(const ThemeName &themeName) const
|
||||
{
|
||||
for (auto &[id, name] : m_themes) {
|
||||
if (themeName == name)
|
||||
return id;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t DSThemeManager::themeCount() const
|
||||
{
|
||||
return m_themes.size();
|
||||
}
|
||||
|
||||
void DSThemeManager::removeTheme(ThemeId id)
|
||||
{
|
||||
if (!m_themes.contains(id))
|
||||
return;
|
||||
|
||||
for (auto groupItr = m_groups.begin(); groupItr != m_groups.end(); ++groupItr)
|
||||
groupItr->second->removeTheme(id);
|
||||
|
||||
m_themes.erase(id);
|
||||
}
|
||||
|
||||
void DSThemeManager::duplicateTheme(ThemeId from, ThemeId to)
|
||||
{
|
||||
for (auto groupItr = m_groups.begin(); groupItr != m_groups.end(); ++groupItr)
|
||||
groupItr->second->duplicateValues(from, to);
|
||||
}
|
||||
|
||||
std::optional<ThemeProperty> DSThemeManager::property(ThemeId themeId,
|
||||
GroupType gType,
|
||||
const PropertyName &name) const
|
||||
{
|
||||
if (m_themes.contains(themeId)) {
|
||||
auto groupItr = m_groups.find(gType);
|
||||
if (groupItr != m_groups.end())
|
||||
return groupItr->second->propertyValue(themeId, name);
|
||||
}
|
||||
|
||||
qCDebug(dsLog) << "Error fetching property: {" << themeId << GroupId(gType) << name << "}";
|
||||
return {};
|
||||
}
|
||||
|
||||
bool DSThemeManager::addProperty(GroupType gType, const ThemeProperty &p)
|
||||
{
|
||||
if (!m_themes.size()) {
|
||||
qCDebug(dsLog) << "Can not add proprty. Themes empty";
|
||||
return false;
|
||||
}
|
||||
|
||||
// A property is added to all themes.
|
||||
DSThemeGroup *dsGroup = propertyGroup(gType);
|
||||
QTC_ASSERT(dsGroup, return false);
|
||||
|
||||
bool success = true;
|
||||
for (auto itr = m_themes.begin(); itr != m_themes.end(); ++itr)
|
||||
success &= dsGroup->addProperty(itr->first, p);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void DSThemeManager::removeProperty(GroupType gType, const PropertyName &name)
|
||||
{
|
||||
// A property is removed from all themes.
|
||||
DSThemeGroup *dsGroup = propertyGroup(gType);
|
||||
QTC_ASSERT(dsGroup, return);
|
||||
dsGroup->removeProperty(name);
|
||||
}
|
||||
|
||||
void DSThemeManager::updateProperty(ThemeId id, GroupType gType, const ThemeProperty &p)
|
||||
{
|
||||
updateProperty(id, gType, p, p.name);
|
||||
}
|
||||
|
||||
void DSThemeManager::updateProperty(ThemeId id,
|
||||
GroupType gType,
|
||||
const ThemeProperty &p,
|
||||
const PropertyName &newName)
|
||||
{
|
||||
if (!m_themes.contains(id))
|
||||
return;
|
||||
|
||||
DSThemeGroup *dsGroup = propertyGroup(gType);
|
||||
QTC_ASSERT(dsGroup, return);
|
||||
|
||||
dsGroup->updateProperty(id, newName, p);
|
||||
}
|
||||
|
||||
void DSThemeManager::decorate(ModelNode rootNode) const
|
||||
{
|
||||
if (!m_themes.size())
|
||||
return;
|
||||
|
||||
auto p = rootNode.bindingProperty("currentTheme");
|
||||
p.setDynamicTypeNameAndExpression("QtObject", QString::fromLatin1(m_themes.begin()->second));
|
||||
addGroupAliases(rootNode);
|
||||
|
||||
auto model = rootNode.model();
|
||||
for (auto itr = m_themes.begin(); itr != m_themes.end(); ++itr) {
|
||||
auto themeNode = model->createModelNode("QtObject");
|
||||
auto themeProperty = model->rootModelNode().nodeProperty(itr->second);
|
||||
themeProperty.setDynamicTypeNameAndsetModelNode("QtObject", themeNode);
|
||||
|
||||
// Add property groups
|
||||
for (auto groupItr = m_groups.begin(); groupItr != m_groups.end(); ++groupItr)
|
||||
groupItr->second->decorate(itr->first, themeNode);
|
||||
}
|
||||
}
|
||||
|
||||
DSThemeGroup *DSThemeManager::propertyGroup(GroupType type)
|
||||
{
|
||||
auto itr = m_groups.find(type);
|
||||
if (itr == m_groups.end())
|
||||
itr = m_groups.insert({type, std::make_unique<DSThemeGroup>(type)}).first;
|
||||
|
||||
return itr->second.get();
|
||||
}
|
||||
|
||||
void DSThemeManager::addGroupAliases(ModelNode rootNode) const
|
||||
{
|
||||
QSet<PropertyName> groupNames;
|
||||
for (auto groupItr = m_groups.begin(); groupItr != m_groups.end(); ++groupItr) {
|
||||
DSThemeGroup *group = groupItr->second.get();
|
||||
const PropertyName groupName = GroupId(group->type());
|
||||
if (group->count())
|
||||
groupNames.insert(groupName);
|
||||
}
|
||||
|
||||
for (const auto &name : groupNames) {
|
||||
auto p = rootNode.bindingProperty(name);
|
||||
auto binding = QString("currentTheme.%1").arg(QString::fromLatin1(name));
|
||||
p.setDynamicTypeNameAndExpression("QtObject", binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <qmldesignercorelib_global.h>
|
||||
|
||||
#include "dsconstants.h"
|
||||
|
||||
#include <modelnode.h>
|
||||
|
||||
namespace QmlDesigner {
|
||||
class DSThemeGroup;
|
||||
|
||||
using ThemeName = PropertyName;
|
||||
|
||||
class DSTheme;
|
||||
class QMLDESIGNERCORE_EXPORT DSThemeManager
|
||||
{
|
||||
|
||||
public:
|
||||
DSThemeManager();
|
||||
~DSThemeManager();
|
||||
|
||||
std::optional<ThemeId> addTheme(const ThemeName &themeName);
|
||||
std::optional<ThemeId> themeId(const ThemeName &themeName) const;
|
||||
void removeTheme(ThemeId id);
|
||||
size_t themeCount() const;
|
||||
|
||||
void duplicateTheme(ThemeId from, ThemeId to);
|
||||
|
||||
bool addProperty(GroupType gType, const ThemeProperty &p);
|
||||
std::optional<ThemeProperty> property(ThemeId themeId,
|
||||
GroupType gType,
|
||||
const PropertyName &name) const;
|
||||
void removeProperty(GroupType gType, const PropertyName &p);
|
||||
void updateProperty(ThemeId id, GroupType gType, const ThemeProperty &p);
|
||||
void updateProperty(ThemeId id, GroupType gType, const ThemeProperty &p, const PropertyName &newName);
|
||||
|
||||
void decorate(ModelNode rootNode) const;
|
||||
|
||||
private:
|
||||
DSThemeGroup *propertyGroup(GroupType type);
|
||||
void addGroupAliases(ModelNode rootNode) const;
|
||||
|
||||
private:
|
||||
std::map<ThemeId, ThemeName> m_themes;
|
||||
std::map<GroupType, std::unique_ptr<DSThemeGroup>> m_groups;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user