ukui-quick/0000775000175000017500000000000015167362530011577 5ustar fengfengukui-quick/framework/0000775000175000017500000000000015153756415013600 5ustar fengfengukui-quick/framework/ukui-quick-framework.pc.in0000664000175000017500000000047715153755732020624 0ustar fengfengprefix=/usr exec_prefix=${prefix} libdir=${prefix}/lib/@CMAKE_LIBRARY_ARCHITECTURE@ includedir=${prefix}/include/ukui-quick/framework Name: ukui-quick-framework Description: ukui-quick-framework header files URL: https://www.ukui.org/ Version: @VERSION@ Cflags: -I${includedir} Libs: -L${libdir} -lukui-quick-frameworkukui-quick/framework/config/0000775000175000017500000000000015153755732015046 5ustar fengfengukui-quick/framework/config/config.h0000664000175000017500000001102215153755732016460 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_CONFIG_H #define UKUI_QUICK_CONFIG_H #include #include #include #include #include namespace UkuiQuick { class ConfigIFace : public QObject { Q_OBJECT public: explicit ConfigIFace(QObject *parent = nullptr); Q_INVOKABLE virtual QStringList keys() const = 0; Q_INVOKABLE virtual QVariant getValue(const QString &key) const = 0; Q_INVOKABLE virtual void setValue(const QString &key, const QVariant &value) = 0; /** * 从数据源同步数据或写入数据 */ Q_INVOKABLE virtual void forceSync() = 0; Q_SIGNALS: void configChanged(const QString &key); }; class Config; /** * 一个组必须是一个数组,数组中的对象顺序不限 * 第一个String是组的名称 * 第二个String是组内对象的主键名称,唯一标识一个对象 */ typedef QHash ConfigGroupInfo; typedef QList ConfigList; /** * 一个配置文件节点。 * 如果从文件或者一个Map生成,那么就是一个根节点。 * 如果由父配置构造,那么就是一个子节点,并且有一个指向父节点的指针。 * * 构造方式: * 1.从文件加载 * 2.从VariantMap加载 * 3.从父节点获取 * */ class ConfigPrivate; class Config : public ConfigIFace { Q_OBJECT public: /** * 从文件加载数据 * @param file 一个json文件 * @param groupInfo 分组信息 * @param parent 父对象 */ explicit Config(const QString &file, QObject *parent = nullptr); /** * 从VariantMap加载数据 * @param data 数据 * @param groupInfo 分组信息 * @param parent 父节点 */ explicit Config(const QVariantMap &data, QObject *parent = nullptr); ~Config() override; // Apis const QVariantMap &data() const; bool contains(const QString &key) const; QStringList keys() const override; QVariant getValue(const QString &key) const override; void setValue(const QString &key, const QVariant &value) override; void forceSync() override; bool isRoot() const; bool isNull() const; const QVariant &id() const; const QString &group() const; const ConfigGroupInfo &groupInfo() const; // void setGroupInfo(const ConfigGroupInfo &groupInfo); void addGroupInfo(const QString &group, const QString &key); // TODO: remove group // void removeGroupInfo(const QString &group); ConfigList children(const QString &group) const; int numberOfChildren(const QString &group) const; Config *child(const QString &group, const QVariant &id) const; /** * 添加子节点 * @param group 组名 * @param childData 数据 */ void addChild(const QString &group, const QVariantMap &childData); /** * 删除一个子节点 * @param group 组名 * @param id 主键值 */ void removeChild(const QString &group, const QVariant &id); /** * 删除一个key * @param key 要删除的键 */ void removeKey(const QString &key); Q_SIGNALS: void childAdded(const QString &group, const QVariant &id); void childRemoved(const QString &group, const QVariant &id); public: bool event(QEvent *event) override; private: /** * 从父节点继承数据 * 作为私有构造函数,由内部函数调用 * @param data 数据 * @param groupInfo 分组信息 * @param parent 父节点 */ explicit Config(const QVariantMap &data, Config *parent = nullptr); void extractChildren(); void extractGroup(const QString &group, const QString &key); void setId(const QVariant &id); void setGroup(const QString &group); void requestUpdate(); private: ConfigPrivate *d {nullptr}; friend class ConfigPrivate; }; } // UkuiQuick #endif //UKUI_QUICK_CONFIG_H ukui-quick/framework/config/config-loader.h0000664000175000017500000000403515153755732017732 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_CONFIG_LOADER_H #define UKUI_QUICK_CONFIG_LOADER_H #include #include "config.h" namespace UkuiQuick { /** * 获取从文件系统加载配置文件 * 此处负责处理文件的加载路径 * 根据Domain进行映射:domain { * Local: ~/.config/org.ukui/appid/id.json * Global: ~/.config/org.ukui/_ukui-config-global/id.json * } */ class ConfigLoader { public: enum Domain { Local = 0, Global }; enum Type { Json = 0, Ini }; /** * 加载配置文件 * @param id 配置文件ID,对应id.json * @param app 应用名称 * @param domain { * Local: ~/.config/org.ukui/appid/id.json * Global: ~/.config/org.ukui/_ukui-config-global/id.json * } * @return config */ static Config *getConfig(const QString &id, Domain domain = Global, const QString &appid = QString()); static QString getFullFileName(const QString &id, Type type = Json, Domain domain = Global, const QString &appid = QString()); static QString globalConfigPath(); static QString localConfigPath(); static QString getConfigFileName(const QString &id, Type type = Json); private: static QMap> globalCache; }; } // UkuiQuick #endif //UKUI_QUICK_CONFIG_LOADER_H ukui-quick/framework/config/config.cpp0000664000175000017500000003177115153755732017030 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include //#include //#include namespace UkuiQuick { ConfigIFace::ConfigIFace(QObject *parent) : QObject(parent) { } // ====== ConfigPrivate ====== // class ConfigPrivate { public: explicit ConfigPrivate(QString file); explicit ConfigPrivate(QVariantMap d, Config *p); void loadConfigFile(); void save(); static QFileInfo getFileInfo(const QString &filename); QJsonObject toJsonObject(); static Config *findConfig(const ConfigList &configList, const QString &key, const QVariant &value); // 配置文件全路径 QString configFile; QString configLockFile; bool requestUpdate {false}; // 所属组 QString group; // 当前配置的ID值 QVariant id; // 当前节点的分组信息 ConfigGroupInfo groupInfo; // 配置数据核心 QVariantMap data; // 全部子节点 QHash children; // 父节点,如果是来自于父配置的话 Config *parentConfig {nullptr}; private: friend class Config; }; ConfigPrivate::ConfigPrivate(QString file) : configFile(std::move(file)), configLockFile(file + QStringLiteral(".lock")), id(QStringLiteral("_root")) { loadConfigFile(); } ConfigPrivate::ConfigPrivate(QVariantMap d, Config *p) : data(std::move(d)), parentConfig(p) { } QFileInfo ConfigPrivate::getFileInfo(const QString &filename) { QFileInfo fileInfo(filename); QDir dir = fileInfo.dir(); if (!dir.exists()) { bool mpb = dir.mkpath(fileInfo.path()); qDebug() << "Config: mkPath:" << fileInfo.path() << mpb; } return fileInfo; } void ConfigPrivate::loadConfigFile() { QByteArray jsonData("{}"); QFileInfo fileInfo = ConfigPrivate::getFileInfo(configFile); if (!fileInfo.exists()) { QFile file(fileInfo.absoluteFilePath()); if (file.open(QFile::ReadWrite | QFile::Text)) { file.write(jsonData); file.flush(); file.close(); } qDebug() << "Config: File does not exist, initializing to default values:" << jsonData << fileInfo.fileName(); return; } if (!fileInfo.isReadable()) { qWarning() << "Config: Permission denied to read file:" << configFile; return; } QFile file(fileInfo.absoluteFilePath()); if (file.open(QFile::ReadOnly)) { jsonData = file.readAll(); file.close(); } QJsonParseError parseError {}; QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData, &parseError); if (jsonDocument.isNull()) { qWarning() << "Config: Failed to parse JSON:" << parseError.errorString() << configFile; return; } if (!jsonDocument.isObject()) { qWarning() << "Config: Configuration file format error." << configFile; return; } data = jsonDocument.object().toVariantMap(); } void ConfigPrivate::save() { if (configFile.isEmpty()) { return; } // 1.读取文件信息,是否只读 QFileInfo fileInfo = getFileInfo(configFile); if (!fileInfo.isWritable()) { qWarning() << "Config: No permission to write to the file:" << fileInfo.fileName(); return; } // 2.获取lockfile信息 QLockFile lockFile(configLockFile); if (!lockFile.lock()) { return; } QJsonDocument jsonDocument(toJsonObject()); if (jsonDocument.isObject()) { QFile file(fileInfo.absoluteFilePath()); if (file.open(QFile::ReadWrite | QFile::Truncate | QFile::Text)) { qint64 writeLength = file.write(jsonDocument.toJson()); if (writeLength == -1) { qWarning() << "Config::save: File write failure," << file.errorString() << configFile; } else { qDebug() << "Config::save: File write succeed," << writeLength; file.flush(); } file.close(); } else { qWarning() << "Config::save: File open failed," << file.errorString() << configFile; } } else { qWarning() << "Config::save: Failed to parse JSON"; } lockFile.unlock(); } QJsonObject ConfigPrivate::toJsonObject() { if (children.isEmpty()) { return QJsonObject::fromVariantMap(data); } QJsonObject tempObj = QJsonObject::fromVariantMap(data); QHashIterator iterator(children); while (iterator.hasNext()) { iterator.next(); QJsonArray jsonArray; for (const auto &config : iterator.value()) { jsonArray.append(config->d->toJsonObject()); } tempObj.insert(iterator.key(), jsonArray); } return tempObj; } Config *ConfigPrivate::findConfig(const ConfigList& configList, const QString &key, const QVariant &value) { auto it = std::find_if(configList.constBegin(), configList.constEnd(), [&key, &value] (const Config *config) { return config->getValue(key) == value; }); if (it == configList.constEnd()) { return nullptr; } return *it; } /*! \class UkuiQuick::Config \inheaderfile config.h \inmodule UkuiQuickFramework \brief 配置类,该类用于加载配置文件内容. 一个配置文件节点,如果从文件或者一个Map生成,那么就是一个根节点。 如果由父配置构造,那么就是一个子节点,并且有一个指向父节点的指针。 构造方式: \list \li 1.从文件加载 \li 2.从VariantMap加载 \li 3.从父节点获取 \endlist */ /*! \typedef ConfigGroupInfo \relates QString 同 QHash */ /*! \typedef ConfigList \relates Config 同 QList */ // ====== Config ====== // /*! \brief 从文件加载配置. \a file 配置文件路径 */ Config::Config(const QString &file, QObject *parent) : ConfigIFace(parent), d(new ConfigPrivate(file)) { } /*! \brief 从VariantMap加载配置. \a data 配置数据 */ Config::Config(const QVariantMap &data, QObject *parent) : ConfigIFace(parent), d(new ConfigPrivate(data, nullptr)) { } Config::Config(const QVariantMap &data, Config *parent) : ConfigIFace(parent), d(new ConfigPrivate(data, parent)) { } Config::~Config() { if (d) { delete d; d = nullptr; } } /*! \brief 提取所有子节点配置 根据当前配置的groupInfo信息,遍历所有分组并提取对应的子节点配置。 每个分组会调用extractGroup方法处理具体的子节点提取逻辑。 \note 如果groupInfo为空则直接返回,不做任何处理 */ void Config::extractChildren() { if (d->groupInfo.isEmpty()) { return; } QHashIterator iterator(d->groupInfo); while (iterator.hasNext()) { iterator.next(); extractGroup(iterator.key(), iterator.value()); } } /*! \brief 提取指定分组配置 */ void Config::extractGroup(const QString &group, const QString &key) { if (d->data.contains(group)) { auto &value = d->data[group]; QJsonArray jsonArray = value.toJsonArray(); for (const auto &jsonObjectRef : jsonArray) { if (!jsonObjectRef.isObject()) { continue; } QVariantMap data = jsonObjectRef.toObject().toVariantMap(); addChild(group, data); } // 清空生成子Config的节点 value = QVariantList(); } } /*! \brief 返回当前配置ID值 */ const QVariant &Config::id() const { return d->id; } /*! \brief 返回当前配置分组 */ const QString &Config::group() const { return d->group; } void Config::setId(const QVariant &id) { if (d->id == id) { return; } d->id = id; } void Config::setGroup(const QString &group) { if (d->group == group) { return; } d->group = group; } /*! \brief 返回配置分组信息 */ const ConfigGroupInfo &Config::groupInfo() const { return d->groupInfo; } //void Config::setGroupInfo(const ConfigGroupInfo &groupInfo) //{ // if (d->groupInfo.isEmpty() && d->children.isEmpty()) { // d->groupInfo = groupInfo; // // 将子组存放到children中 // extractChildren(); // } //} /*! \brief 添加分组信息 */ void Config::addGroupInfo(const QString &group, const QString &key) { if (key.isEmpty() || d->groupInfo.contains(group)) { return; } d->groupInfo.insert(group, key); extractGroup(group, key); } /*! \brief 返回指定分组的子节点列表 */ ConfigList Config::children(const QString &group) const { if (d->children.contains(group)) { return d->children[group]; } return {}; } /*! \brief 返回指定分组的子节点数量 */ int Config::numberOfChildren(const QString &group) const { if (d->children.contains(group)) { return static_cast(d->children[group].count()); } return 0; } Config *Config::child(const QString &group, const QVariant &id) const { if (!d->children.contains(group)) { return nullptr; } const auto key = d->groupInfo.value(group); const ConfigList &children = d->children[group]; return ConfigPrivate::findConfig(children, key, id); } /*! \brief 返回配置是否为根节点 */ bool Config::isRoot() const { return d->parentConfig == nullptr; } /*! \brief 返回配置是否为空 */ bool Config::isNull() const { return d->data.isEmpty(); } /*! \brief 强制同步配置数据 */ void Config::forceSync() { requestUpdate(); } /*! \brief 返回所有键值列表 */ QStringList Config::keys() const { return d->data.keys(); } /*! \brief 返回配置数据 */ const QVariantMap &Config::data() const { return d->data; } /*! \brief 根据指定键值返回配置值 */ QVariant Config::getValue(const QString &key) const { return d->data.value(key); } /*! \brief 设置指定键的值 */ void Config::setValue(const QString &key, const QVariant &value) { if (d->groupInfo.contains(key)) { return; } d->data.insert(key, value); Q_EMIT configChanged(key); requestUpdate(); } /*! \brief 添加子节点配置. \a group 分组名称,\a childData 子节点数据 */ void Config::addChild(const QString &group, const QVariantMap &childData) { const QString key = d->groupInfo.value(group); if (key.isEmpty() || !childData.contains(key)) { return; } const auto id = childData.value(key); if (id.isNull()) { return; } auto &children = d->children[group]; auto config = ConfigPrivate::findConfig(children, key, id); if (config) { return; } // create childData config = new Config(childData, this); config->setGroup(group); config->setId(id); children.append(config); requestUpdate(); Q_EMIT childAdded(group, id); } /*! \brief 删除指定分组的子节点配置 \a group 分组名称,\a id 子节点ID */ void Config::removeChild(const QString &group, const QVariant &id) { if (!d->children.contains(group)) { return; } auto &children = d->children[group]; const auto config = ConfigPrivate::findConfig(children, d->groupInfo[group], id); if (!config) { return; } children.removeOne(config); requestUpdate(); Q_EMIT childRemoved(group, id); } /*! \brief 删除指定键值的配置 */ void Config::removeKey(const QString& key) { d->data.remove(key); requestUpdate(); } bool Config::event(QEvent *event) { if (event->type() == QEvent::UpdateRequest) { d->save(); d->requestUpdate = false; } return QObject::event(event); } void Config::requestUpdate() { if (d->parentConfig) { d->parentConfig->requestUpdate(); return; } // do sync if (!d->requestUpdate) { d->requestUpdate = true; auto ev = new QEvent(QEvent::UpdateRequest); QCoreApplication::postEvent(this, ev); } } /*! \brief 配置是否包含指定键值 */ bool Config::contains(const QString &key) const { return d->data.contains(key); } } // UkuiQuick ukui-quick/framework/config/config-property-map.cpp0000664000175000017500000000422515153755732021457 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: baijunjie * */ #include "config-property-map.h" #include #include using namespace UkuiQuick; class UkuiQuick::ConfigPropertyMapPrivate { public: explicit ConfigPropertyMapPrivate(ConfigPropertyMap *map) : q(map) {}; void loadConfig() const; ConfigPropertyMap *q; QPointer config; }; void ConfigPropertyMapPrivate::loadConfig() const { if (!config) { return; } for (const auto& key : config->keys()) { q->insert(key, config->getValue(key)); } } /*! \class UkuiQuick::ConfigPropertyMap \inmodule UkuiQuickFramework \inheaderfile config-property-map.h \brief 提供配置属性映射功能,用于将配置数据与QML属性进行绑定. */ ConfigPropertyMap::ConfigPropertyMap(Config *config, QObject *parent) : QQmlPropertyMap(this, parent), d(new ConfigPropertyMapPrivate(this)) { Q_ASSERT(config); d->config = config; d->loadConfig(); connect(d->config, &Config::configChanged, this, [this](const QString& key) { insert(key, d->config->getValue(key)); }); connect(this, &ConfigPropertyMap::valueChanged, this, [this] (const QString &key, const QVariant &value) { d->config->setValue(key, value); }); } QVariant ConfigPropertyMap::updateValue(const QString &key, const QVariant &input) { d->config->setValue(key, input); d->config->forceSync(); return input; } #include "moc_config-property-map.cpp" ukui-quick/framework/config/config-property-map.h0000664000175000017500000000242515153755732021124 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: baijunjie * */ #ifndef UKUI_QUICK_CONFIGPROPERTYMAP_H #define UKUI_QUICK_CONFIGPROPERTYMAP_H #include #include #include "config.h" namespace UkuiQuick { class ConfigPropertyMapPrivate; class ConfigPropertyMap : public QQmlPropertyMap { Q_OBJECT public: explicit ConfigPropertyMap(Config* config, QObject *parent = nullptr); protected: QVariant updateValue(const QString &key, const QVariant &input) override; private: std::unique_ptr const d; }; } #endif //UKUI_QUICK_CONFIGPROPERTYMAP_H ukui-quick/framework/config/ini-config.h0000664000175000017500000000252615153755732017246 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_INI_CONFIG_H #define UKUI_QUICK_INI_CONFIG_H #include "config.h" class QSettings; namespace UkuiQuick { class IniConfigPrivate; class IniConfig : public ConfigIFace { Q_OBJECT public: explicit IniConfig(const QString &filename, QObject *parent = nullptr); QStringList keys() const override; QVariant getValue(const QString &key) const override; void setValue(const QString &key, const QVariant &value) override; void forceSync() override; QSettings *settings() const; private: IniConfigPrivate *d {nullptr}; }; } // UkuiQuick #endif //UKUI_QUICK_INI_CONFIG_H ukui-quick/framework/config/ini-config.cpp0000664000175000017500000000445115153755732017600 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "ini-config.h" #include #include namespace UkuiQuick { class IniConfigPrivate { public: explicit IniConfigPrivate(const QString &file, IniConfig *q); QSettings *settings {nullptr}; }; IniConfigPrivate::IniConfigPrivate(const QString &file, IniConfig *q) { settings = new QSettings(file, QSettings::IniFormat, q); } // ===== IniConfig ===== // /*! \class UkuiQuick::IniConfig \inmodule UkuiQuickFramework \inheaderfile ini-config.h \brief 基于 INI 格式文件的配置实现. */ IniConfig::IniConfig(const QString &filename, QObject *parent) : ConfigIFace(parent), d(new IniConfigPrivate(filename, this)) { } /*! \brief 根据 \a key 获取指定键的值. */ QVariant IniConfig::getValue(const QString &key) const { if (d->settings) { return d->settings->value(key); } return {}; } /*! \brief 设置指定键 \a key 的值 \a value. */ void IniConfig::setValue(const QString &key, const QVariant &value) { if (d->settings) { d->settings->setValue(key, value); Q_EMIT configChanged(key); } } /*! \brief 调用该函数会立即将内存中的配置同步到文件中. */ void IniConfig::forceSync() { if (d->settings) { d->settings->sync(); } } /*! \brief 获取内部的 \c QSettings 对象. */ QSettings *IniConfig::settings() const { return d->settings; } /*! \brief 获取所有配置键名. */ QStringList IniConfig::keys() const { if (d->settings) { return d->settings->allKeys(); } return {}; } } // UkuiQuick ukui-quick/framework/config/config-loader.cpp0000664000175000017500000000710115153755732020262 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "config-loader.h" #include #include #include #include #include #include /*! \class UkuiQuick::ConfigLoader \inheaderfile config-loader.h \inmodule UkuiQuickFramework \brief 配置加载器,该类用于加载配置文件. */ /*! \enum ConfigLoader::Domain \brief 配置文件域. \value Local 本地配置文件域 \value Global 全局配置文件域 */ /*! \enum ConfigLoader::Type \brief 配置文件格式. \value Json JSON格式 \value Ini INI格式 */ namespace UkuiQuick { QMap> ConfigLoader::globalCache = QMap>(); /*! \fn Config *ConfigLoader::getConfig(const QString &id, ConfigLoader::Domain domain, const QString &appid) \brief 获取配置文件. \a id 配置文件 \ cid ,\a domain 配置文件域 , \a appid 应用 \c id */ Config *ConfigLoader::getConfig(const QString &id, ConfigLoader::Domain domain, const QString &appid) { QString filename = getFullFileName(id, Json, domain, appid); if (filename.isEmpty()) { return nullptr; } const QString cacheKey = (domain == Local) ? (appid + QStringLiteral("_") + id) : id; auto &cache = ConfigLoader::globalCache[domain]; if (cache.contains(cacheKey)) { return cache.value(cacheKey); } auto config = new Config(filename); cache.insert(cacheKey, config); return config; } /*! \brief 获取配置文件的完整路径. \a id 配置文件 \c id, \a type 配置文件格式, \a domain 配置文件域, \a appid 应用 \c id */ QString ConfigLoader::getFullFileName(const QString &id, Type type, Domain domain, const QString &appid) { const QString filename = ConfigLoader::getConfigFileName(id, type); if (filename.isEmpty()) { return {}; } if (domain == Global) { return ConfigLoader::globalConfigPath() + filename; } if (appid.isEmpty()) { return {}; } return ConfigLoader::localConfigPath() + appid + QStringLiteral("/") + filename; } /*! \brief 获取全局配置文件的完整路径. */ QString ConfigLoader::globalConfigPath() { return QDir::homePath() + QStringLiteral("/.config/org.ukui/_ukui-config-global/"); } /*! \brief 获取本地配置文件的完整路径. */ QString ConfigLoader::localConfigPath() { return QDir::homePath() + QStringLiteral("/.config/org.ukui/"); } /*! \brief 获取配置文件的文件名. \a id 配置文件 \c id, \a type 配置文件格式 */ QString ConfigLoader::getConfigFileName(const QString &id, ConfigLoader::Type type) { if (id.isEmpty()) { return {}; } if (type == Json) { return id + QStringLiteral(".json"); } return id + QStringLiteral(".conf"); } } // UkuiQuick ukui-quick/framework/doc/0000775000175000017500000000000015153755732014346 5ustar fengfengukui-quick/framework/doc/images/0000775000175000017500000000000015153755732015613 5ustar fengfengukui-quick/framework/doc/images/model_view.png0000664000175000017500000020266215153755732020463 0ustar fengfengPNG  IHDR; cHRMz&u0`:pQ<bKGDtIME!(5:-IDATxw|7KzB/ޛM&bE ;(**6AQT{・mwg~l/" %>rwɽ{3fp!B1z/!BB!fl!B36BpB!PÌ !B(aƆB!0cC! wߕs" !r# 77H3A/ !*:Q1B(c\;QVV$6SBwΘ=""** E+0ƴ\---J0!>׎`y36Pp7ڬ Brڴn3sLAt-33o߾G5[,}!q9 Ok7^?Աi:um+uz !s8%{!9kߋ7SN%^8`s.43=N},o*(^/+^`ȨVڲK(!&TU#>ڵiǎGDDf:ӹ[I:]0 ěSO<^6>iub*Sz_#BaZmv[Vfu8ѱ1x! !8NX,z^eI_ϫ{PJ9gs*P؍OCEN)9ꤔ*EN819'a8 BMMPҫHщB!0cC! w!B;B!fl!B36BpB!PÌ !B(aƆB!0cC! w!B >0Jnd@r;@s9ʴ-qB] aI h7689 /cX-P0q F(*Ws$QwZVBw1?2MrPڍUQ3ιNI:YCпz:B@ɵr~{V!:_D]elkWTbѢE<^Bch2~[o+Wc(3[̿c+V0o&~פU+!7\UC `0)CW'?rbcȊf )r0Nw.*[2h +BH(Ƈ֯a>R9D7pe9 aDVRƖغu*ι^p¨_1[Czxc~{8T8вde \Y$O;|rG2g|;+'3O9! /!T@)PsMSY^H̽sIΜKEy!E FqкCsKRū?Ҭuf͛j\ӽ~RK"ŋ)ؾE|ƦuK\|P˷DQP($w6MTLLeѰ|`0xE j11h-f!1L  -nn}LUK%jԢaa!@~ZLA?qu:)Rʯ34j(BYgm6NbZs f}ԔK>.!h2z=%@,6kDdimWθQoyh#}-f9$H^y/Dl"tn2c㜋t1959cJ)yAι(gO&K 0}@-_jTsI'ed.s^Rr{1/=cB-REQl1 TsUU 6n˖*S| TU[S>7w*P=Wt)u7?}7o{11Udb2]٢(g@%ո-SUUU8h4 11$rί~Fx}>(j !w71Y̖-@{5!UcF %}v}! s Ppc/(2u7-Z7s DI<{\fzftlt:DMSŵg ɪj1J{RB)%4(m)\fjEì8%TUU*P5n[y׺;Sj(ayGNBig*Ӫ"".$=IJYƟ_0уGMf%i\^'\V*!~@u򯸦d} $*CfE,V=/$:NeQ>GNj\ў{&cZrڣ>IΤq GN>Mmrg<3=jM93~S>4Zb6&]Q*P9BEǦlٴ' ǟn2rrv}w?OlʇyotZ*MR1+Ύ#B&ac~.^joZ^*7<9)ʻ/uwΘh8uķ>% z 8&=RpBB8pBH~&5$1θdܲ~kVFn0:vP\igQy}kT>q14z.W1rx+J־$ ~@vytz\OiW%ȃ \ggMA0Mp~ݨ@)!`]8w|ro| `h" [w^A ݶyB"0ب,j!^3B!W@9pEUb9X?H\.&XR=4ܙ>n:֬RЍӌM+&=bY?} n';3;'+G yY&ٮ͢'U8c DFD p`{ܪP:u6uɟ~AYOMIJ%>:bhdD);X?rA B0b M;o'B9zA{ _Зt%c\יf[x=!O>v؆՛:tk }aGA 8ۍ&;CqιdN;j$Ir\ hrAOxlq:$"Fv1l#U/@m ,tC\T%)PX ӌM?uZZ"l:$䪵<5Im4nvhaj[,;}d1d;{m=6:z>78Y Wwl)L} /^ lG;vC]LMN3+9'@BgLEncd6_a &isƵAC"J C}SBـ? B-2-+}>kn0<|^m nj*7&ӟ_OGnHό$ܙ9ݢ*j0p!$BY_䮾Ĺ R* ]vgg#+WU*9?h85nQflZXBL|NmynUbc/㷳23^XNfXꛏEFsnt:]҅w_ o nB,+Qёm:6*_zK4 t:c3&ժ_Ut[vee&oy @1&N %Ƙvo/mї:J295i4oݬbBOi/G餧G=ܘ.+JhW3wUO9Йg7⡬ FCn_@3Aڭ& h{қEhFl0S/YcL["!ts4c A xXBH,i),ujg(+Qf:6[j\QEe'BTEmZS2fƘA2l۸=#=3*&T^a[`_x<3T`HMk6z.[Jej x> AcÄ ˆ\T-lu@g,br0[LEKzrJlvkBфGiET (u-*VvuͦX[M "+^wԸM?tn>i_y4jPIOMKIP\H ]kmHRAsE4&)opJߤ/ DZQnZfl@@UȨB퍌?,J= 8rν>CC>8:N?RT/ڬUڼvhv~M[5.W\04['o>vι >oPסii |] !>{m{Zm> ZyL-fVD'\lG^۴vK~]TE!Щ?M {V+VbDIwl9ffKCW [C\?ǎ=G8csT=:>*:sqfErK x\NAD*ynZglZ\STY#"͉rHۨ_xAQY!D+4Z1=m&WZlmд~~>磷>@@GuIKIۨn TP2Y#mO|2h(0:\Bq{}^/@[r}RW:W_!S|qΏ~jK9 eG3j== zw +Wܵu7Vfv\NKz b`0hM;RY~dYVd`06~bVU0 gfޠb吢lP0TL$8]u2J_o>XmsgϿ䘬*N[ѱQ&`ЋT˺s,t8Zoq2<_mXR*bVN֪ū !{u*Sw ~[gPo 8p( A>,G /O0ih#|Qsٙs.H3ǹyVJ)7M+rJs8K-g-!C*%߳y6sTB[oݽ}n=nkԼ %};ܲkٙ9zsЪ5ԨS-2&ʙTj x!ޢJ~e4Tt%幢|,GpKxnuK͜]\n)<</]HK{N8`]L|1Y_4ihO?e4swͻI9LU)=hBܕcFĉ//RH[|B(48qʇmh f럋9x,Bv4l֠ak۰jc= x9AA {L%Ttq< !ϔהEܧrJ1Qz0er-VЖ Le![H+ʁ;rYY !(`B36N*-pp>B6|_!q&A%$J F^ B"Woڸfdzp sIzPohb%xH[Q/gKN=XϿ=:P0}*T 4[b?wz|1$] !t@N8-JDTR2o%0 FCj#Bb dŹ 99Ԍ{T _M߷GEQhަY=2BFEG&EWXvZn={l:1śt$;#1nGFDPf8HD=7j}l`ۿ p6R 8.kTˣ-{i ^pu:w1[͑PUUŘ"J)Y\2ʔ,]h>k;"Xv5EQ8| z*P@h-˝;vΟjuY!' !1bmu*Zo8qΝoC{ըS]UUMFQ flS(5xp!YBk=7~m)E) e˗q$:r{ًc=.PI>*j~]%*=W^~zVmX;Կ$ %PzjƽP~u ?9}>TU%QV4hɢii+PΙ`8u^[d %y0*Qxf dVq7FqRQҒӼnon_? PۓFcUCFs.Qc[o+X\ U*TDqfY[zI&ٕTFЈsٶiN $Iޘvy5ۢM3/x'`67o<@ ܰ2 l1)y̅u+6ԯ_sy=>Bf@pnBιNx'kTp݂pcsā|KBN6GO⇣5kݤߐrH6MFYygjҢM3ϓ9>[7lz\?@eY5B% 9. R n;-%PRb#'F?=EQ4L`pkjH:zEf`!tn2cȪ\l)Ŭ(J/UR\L78r"b4:&h"liqBfZ4b VUAYô%Áz(CUUBy,x!٤JL:@˓.I니hԢQ嫔/XZ*Wnx/\9!REm(=EUQ4[>+#[jB\Z·ϥ%JQzڷZukfNj֬[h2=Ŷv?j>`ʟqO; DQt:\*W5JGnAޏ$r"5#-`4*SRj%V^YU\sΝĄYyf/rG"Jߤ^^)Рi҉ę fD2RA2TY1 Ri@1aC9tb3_%˔hڪ/{Ƙ^ԯ[~ϼԈW۹r ^f|3s]=wһɓz靬Aqkm;v~;i*"tU,ӷW+[[U߫Vg:*8rp#eʗ9yT($춉N0L*rTh;;aЭZM;ٳ}odTį?ֺ} dK)_yܟYmVݡ{{W( Z&$"N|zU\&Ap]MZ6Yf 9t:#C,;N'iDTs-QRz 'JbTt${?xYx~bbuB`0xټQ JA_:oؼss.غC^TE5MioYfݺ~LUF2%^ ^ ,Vx#B3olۭݺ(ހ[߮Ϛ|1Y4-s~>.aozrcZD sr.$9ySpbvfv҅IRr=UUUӿJ=1 j/$r0$@%ږ^B{=^͚x%IKcpSQsUQc1,9'*Ċ \kH .74Ho |aO yhhUWcKdZmS}@E1[n_>sj~1H[22^}oJoIDIܷs!z6)_ H_֜CݪشQ˨Əyz೪QTb6KNVΉ#'O?e4^\BFkUOqLJig^0c|=>{ܙg/qycLR:^SNV预H)Brc.fί \QtEX-Aι¹`DTD|8X >UUA,[@1lQ/g3TRڼZBH0vPJbB\[wMT.3~3.}|c!9q…spAAƼ;fо/ 鴉F > !ZV^ Tٴig.-1`—h4o$\g' ب@nO捾q/wAhhtQ(.jͪNz+b{eYUTC7_|đREU)!:iO(P|+UZl26ǽP^w̅og| (Yc}t:vAٛsωTUJ ޶q_4^9DhT~& C&ѤY#ye3 TEB@۵ɀ g/M⥊3`ڣzN1`_\ǒ?yiރy??g q>x+~ڄɔx>2`CgO=9`_Sx) Ao @PJ ;Έ* ~[+UXQ? ZԱSzcTb8.JЭtApM[6pƼ_oX)bN3YLeKx_ޝ&"xiə@MggfiWBQj%ĝ>qLJ-_\UL,VnAP@ B3A\-I"6vm9$ lj[mݸ]Ċѱy{?# +˳~_80Mq޽O7_W3[VE䱁OZmm%Eѓ@jV^))LeN?q䆕nvEA5su1q ߱eKop1ϧua 0c}}ܑsy_y?Os7O0] !T`=.$ }rp>u5z:d2֯rZQ]փ{j{zHzjzVMZm^Q{|>y,کgOxtLĿLbWS| V|$*Rm?yfa=g=>ψON~ʔ,4LfgF0wjnVm_** n ^i1F)=}tlZjn~"d.}~B%AEL(9WfbbjԨLe p`!Y5P09qHAE]n7ϿOVρk[䏥Z{vjK_5ӆ&sFu\nZ]}ڙHKR|i1(!vDDF0δNEC CWφeYNXJ%EUk݊۟קŝkK- ?nE B[Ɨ}|v{(`SBTUy}7RE+?-2 A^vָ֧V*F k+$po܍4%ȋ{ܝ=gŁBUS+y)zNBׂB!PÌ !B(aƆB!0cC! w!B;B!fl!B36BpB!PÌ !B(aƆB!0cC! w!B;B!fl!B36BpB!PÌ !B(aƆB!0cC! w!B;B!fl!B36BpB!PÌ !B(aƆB!0cC! w!B;B!fl!BNp/3@7! @`Vp`p+x0c8 D ~/]"CqPθ bp+(  (ιN ~֡A laұ{^z=}9/θNoȁâ(r-|JֽOO3;sN) =[w[?,as|W~оos.ުv頻*`p KZpx<:VP`v0E?x=ۗn!Ú6~T|81Ecu`޹kݾtC~E}-[>1uÌNу;viYIp 1VT|Q{!SO Ҳ<(+BQ|r:RRU l3;pAS@%A?1FUB,9Dz$I,(J"cP8sa5nB9µrpYBBPV`ƆB!0cC! w!B;B!fl!B36BpB!PÌ !B(aƆB!0cC! w%BCTxa^TU 71Ƙv9WUU;{<"t->"]ސuIыEݢ@u!t"<{flnI}Fl 6FhKrF BII)c Dtڿ ހ9E#)\-6^(vN1z"V8d4JZ0$f}N6!s n%p#.c6/w/&pΗ,]&N':tl:H)uK1QƼxYZt3>:, h/;vcgnaR(nW^{gڌ_@^Ҧt?P;\@UUq?$Ѽ߆E+KUUI/\v-S8[1.^!>ن ۏ9Ӊ(&'ZIVUgi!E{>:ٳz=͋os. 97o2ǜ'-yc6;wرH~{flsn6'N\d|ч HvW^Re0fAy >Ō1A[%A4E-(I(&pѣ_Z%QzҥݻΟ?bW^-8gL* Bnd!H3EUEQ",5k>pBUjblVI ҥJׯ߀1jg0%E|HU297uf=lٲu% ",I.^8"YΦ%azhJRUU zh:8dÃغuI72%p8 ~ߑ@uc1nu 2ͤ~嗓M:0`޼?rF!55u1nlp; ͂ @ 9#c$j!`zQ;v^/J)%ÝN(*tbgt:fL… _~y*0 sUf͚K9tc,/Rq !kJڨBnLDι$¼Gj]c 9r$9!s*>|p͚(@=DQUA k~A$IMVVݑ#/]XLP(dcbbLFrgԬU+..28|СCL&SE_xad5#ϟ>}N,--nݺfOQU5+YDǝNgݺuu:'{9~؉3z>..1عs]bzƍկ_1 ^ob;w uY-۰aG"EW1 ћ6mZd2MBzoϞ=d2YQmFQJEMMM+Z4ܹK/^[nMwԺut`.]fڴ(5y_D6MzOЭ(b@{8*J"%D17qε(sk.|v/Τ 5k )Rd߾}ڷ3 ,(S{7yd81cFwMQZ;~||!(%&N5굝;w/^MQuϜO{晧U~ܹSNVZmo-]3cN'˲0cC:9'N7m裏ggH8e7'e~7->~XŊZnx Η/_~i1ɤwMkiѶetlʥ˳27o2.>nْ`V^ٮ]UVɲt:UӒSms. 6.M=!A*Wrzܹ˦M;oy%J2kg1g̘1Zj,dB ",^n„u۹sڵos&~X|'N1[nIk3fE?#>I˔) *fͷg̘^BǏ=Ӄ=cǾP$##ҥ?4u-vcذ<,'OEk6x啗/^*Q/*U:%9QQQg̴ Y<$^oR&M;|Wݻw͚C!۽3pUfggػwObDն{$%%=`ɲ ?8c˖-+W2Ǣ?`0.&eM݌g֮V/:uj`+l?%f{ケsfK}6?0&GGGB$ FMnWE2F]h4t:_|@gWeYQdtGܹoo>#F<[T1cF‹=sNݻ޺uK͚5l٠0s\Ie?Ԛ5kuģ8oO0 ό=}_|&===Ok֬nיIIڵ=p@5\Z?G=럇\jE2x|+Zl܅Y~~]vլY322~:uԀ_پr*E5EK:M=~~rBB!^tiQSRR z}}8yd515 <_~Ӱn? J)?~c=6e7}^}R |ujT8y6Сoӻw?\\OeB!~bm߾O>ժa˞aÆԮ]Fifeʔ-Zܷ`a>ow=ԤI}5~-qfҥK%K^oEf6nѥo^^7_} } (PD`0x艴t^# 6:tX(H o7ojԨs BYǎ%`fSɒ%۶m'3!O}!I@z>%%c?;EDDiraw_~ͅu~?wV(tA@+Z-PQ(H'OJMM)]L:uϹKYYUVӂ!^lif֭ )SnS) qq¶m[cgΜ޽{hr8gϞ*VHQXtiÆժmѤNjWXQZyԮ]Z )S6-- rKi9c(oռE9|-jQJ).G]ڭ[IUWL k֬9-K0ڻw=;QJyQ]V3pHtқ7ol6(׭Hl6_l֭<#ݺu1c+@@d91bN1J c,ҥddd8. (F~TUEh4>J)cvs-Y7Nw̥gyZj={:~X(s-7"nb1* n(jfff>}kժy%I IY2Z9g`Pe˕ K.]vmz<`+zKcV5}J޼`0*}~m)cB4L͛Vݨ}DQd*#@!]xSn6_J*w\mڴhbk֬-[r0 1U:W_{sڳgO@)))Zp6K8NB(c< CBBXRҥҥ^RZPUlΪnV u{l  nڴrPɋ+v1AQTQB!EU՜XJJJFZOc*+ށ[S5k-^dRqqqz=)YyWnk(Q|׮]Ç J1=p@J̙ʕN+V<~ӏ{";LTYzll~/[ؽxTaÆ'i:t\FFF n-K g3z֩*s\O=9,*Bvv6,22(1ڤII&l:+6GN\(^xz&MT(kzqF <7Q)W_6mk>}^~m׮"S8`0;P((8y?򻬬UP_uRJ2ԨYcǎ,TQF ,3(ZyϞ=#y1c^IOOKRRɓ'V˙ӧ/^MnڴYVgϾ? !!l6E+7egguW D?<>=>>oo߾+W~ n; v-ބIOLLHOz_O믿.LáE!2 \.^5f+'}ӯ_xsRz]NN(W|/ƍwi]9993аz1M㌛ysFc۶T(%0Fv{R;thE( ĖILlӦ^of&JE/ZHMnxx≚5k-[Vݻw]pܹS|Kвiv:gffn߾jnl*Wkcbb*UVh\N]ϟ?wRJ6f3w:Կ9!$..Fs^lML,o6[6oTL;* lٲnz=\Trh(_|J9'EZ HSF Qgj֬aѢ%7lXp85kV\YUEQd@$I_Lr*e^NcǎժUf-ݻرc*TlԨ`Z5jԌSYfhU5jԌ8vdjjj˖)R$ԨQl6]97腩S۴^ժ!?VJi0OQ ۝;wn%[1[Uf9Hv_x`P*^Utrʕ*Sl6Ç>}f[)UdTϟKIIp9?~]_mڴQQ/!N'ԩSoǎ튢ԭ[j5ݻG׏j*dw533cM4}׵5j#TV{5DGG-qFҵS%׮]0|'+GXd*URJjԨiUl5j#+U*W~-[?Vڵk QQQիW7݆Y\%]j'xR\Kө>}] 6-m >vgcoXf۹:X<11QZOUUaݺuO=]kIp&BH_am&!T@%)7 td{vӺy~tď֯_@zUι jch5- y7|LkXDrtXD@peUҞ/ ׫h2۫d9QB l q[c1Ƙbپe/طs-쨪bZI/(R5B+ze:7kԨ߾·*T8~&L(<ە;,wfG־4!1o+wgc+ R(3mK/RFϼDAQ]Ae嶇v6/R{.wPqj8Q @k?ʋTZu(%J|QM6KMMYdرQ B/8 Z-.Gfƫ;78kǜ3-=vEQP?.ks9@i\ބ[_z?kq}ű/W\nڴ.]zŒ%K=ē6Ojw\ysc[n狀Gza8P"&\~#Ƙb: \xbL<|n7)?Ji  /n,^?{flK*Ǵ[<VB%oZ` 6 ǼBj'!*0c+luB 6B!PÌ !B(aƆB!0cC! w!B;B!fl!B3;;Cap+hU wLEs o1 0aֆi[1AflwNL I-ι ԼmBiMŌ48F8E(`p+0cs(^ xdp[}N1 P7 !{}h 4ismF)#jߧ}mڶky3"e2Шycσ9RӠQI'o{rtAEQU{r"J,˅#|޸q?YI~BWE)=Z48ӗF=c~LinNE؝STU IG;-Ud P[Eq|Wϝ9* )şsJdTOtqf;b@Akb-_NQeL>*Pw`N=?{N)18&_L凟zkA |K[ nVkbr 1iR)b* nV*$IQv>қۊݣZnr(] _aLz#BRt;DQPPᜩjt wFUNu-zk |=%I2+e[RпS $Ac} !1(1r37F=39ϟKlόx.5'EI1J@~ t7u"Qwo䋩)o}^8<.R|{uk9NQ zp\ O(hJ92+W +fz2!`N: 7/}ѱL$n_5B)1"ҝO?_/5Ƥ3縳Azҧm-'vFDE|~*wѽ36tCr#-l! سc۟7p^œJa!әYN_ާ]ȈHwc҆ OsUUm6料?jؽZY̜`  B+/>~[NoD`s-7GۣYۯ!(( =;v|һ?2i%˖Isr΅|h {&}=9X]E#8/XcÌ ]@ pБ6[LsgN5Ko0d2Q{%$Iiδ{=Zv>z(!Sc,?Hf]wp~Q+egN58k嫟p~Bi4 4$IJs9F-j͠3bdC5usEVVbф՛+Y|IwZmo;w킬̡6Gs1!;rl.uyKn[_C4WZ(,_wL ?4TfC!iB kU鬩?xΔiQvex[և*+3≧,qBmf-O?i`᭯w+i@(ǚl|7m8u0cGiA-! #ޢS7ٸyTgx;'uRA^|sx[EtR-aӺ -59eƷ2ظ4WaЫA:?_~c҆#8!^Mq1.Z:ǟ/Z؊]kkT`GAz1vnYidt ϿL=BNUUJi-\٧acsh+U[*ANW~\8>ԭ_˰SU5x>Š-kV(L +rJ.xwGÌE=y}Nh^|᭣^~:οo`p{x>ߧm$ٌ}EVQ0FѸEw+ ܙ EQt8kV=ZvڥG3 {B7AQhlV-yu+l۪SvDŽ-$IJs>s'MGK* Y]1TUΟ=@˶^&\{=Õ.-qsDItflvyt~{=m#733؃?:p[Ls!K I2e?`8)Fr6l6mj5X{:wJ;4$;Ӻw9ɽtp_= TFr'NM{Ez~/8܁aЫ" ΀c_/]YqlF8*Z*Jb=vu/<,!7lqXIRC~4';Sn)Yr]u(b4mmEcG?.M!G+]EńEV$IZaIW*?b@~;NsUUln'mg<«/qTg #$IΔ9'+Cny}pmpş8g޾iKo~'9r6"닏O} ږ-ؾYL*:-m kXklkv]/QS]~]o =.ʏ1(J5`4[hjY71\+à"b3AFn?dTGѭŒPxG#ѧ[1ח]YT4W_h'U (J"(b0lK-jR?^3vkִR~}ƨ-:9Nрa " EVL&S5ꛯlZdzr!C3ݙ? J->˛5}άu1̔Rc=N+ۏ'$_L٩˳C~\qTW"+a@[Õ27Zk뾮zIa " m{=mggLW^&B8n뷕|^"M[M͆nEXطd|ߤo.͕(a͏Rj(Zy#ǞX%1!t/bh-qzu)o22rA 7|k7^{5ފ[ţW0U"B-aM*_xłMK'~,@A󣔆AAXfѣ^&(*.҆нBUTBH-s;tz]6Zy(`d6-ذ'O9-ގIIH1UQlъ<6lؠ.?>tjUOur  }~{}5?[B1*j-JƾrVEKzbף w5Av˖yo#>q˺({4.nBk U2g#^VC[˕, nXKj]EdTßJ%(*8窢f57_|-.!uipe~%1˙y}&|zn>#h[@n){Vg?sԠG:g}nţy|/EtTTeŝӫoP/ˁ_y}! A(|Ž-$IJs=SvH:I0  fnͫ6vjwg2@kQY4irX{, ThhGF٢Q/:bmv}w-à"Jb;u+UԷ}/ U<70c+TU%$?Ժn:cOftYLA ;n$Iw):޹{G=ZM j2b1kvl6Ò/JLW n4ǟY#?gíэ+BC mI{yԋ]vh-'ve+UUUpu]A-~@A|AO:n۱bKUTJi-c_[WYMil UQmX?0SGэ*̟NQ^c]rG,ݾvպ c;ђO=ݡAۍGz^AĊ] <9^|~Of}Y~4Gf!HL|11[خ}u-ә.b.' [2`#~gVPJ4g*!䞚a$Jb+zppZ{^ɀC ZlF4kfu޷dϾL }Yn/mrUu3}XA#T=@h,&`7^x~(]t;H_NxҤ쎍ڮݿIɲ|OEy "UQ$.Ot̹7>zܯ7EIJw;z. v?0T']Hܺ;8ΏNwS޳ !Rn%˔ M WXA-FF<ݥi: n;_YLWf+$RGۮk-;(NEqfF5X-6mѬtOѳW+- #ANs !:@86el1GY1QY}G0Mol!F#qxtTU&ӢF_e_OBve%y_pGc8c!>{]V^/&,Y2bTW*c*mxo?=0\] flw;")#.zu|lGæRڮR Òm+m³YpB1TkCxG 5zrW>}2ݙ~߽> zUZLu7.˩}m=@WuE ŝF=BR%VY_BLocޜap#Afy5+7zͷS)!th{Z6 iMwԨbg]Aa#=g4g(7AlV՛EFGxf$]k۲k^L˘={y6"b3e֟~C{o?v Q]Ng 7/}Wg1K#tGicm~զ[vӽg Wa'IR3mؐ}:'"0?sC'6 g~ۭsgȑKlgv]1OȈ:tJwcOB5ι6MqʷߌukڳJb,_f ~mO?6r*N9fH}lwxj3f:ծԞ:wJsʲ͑$)Ùm6o#SE[®m;[m6ygb%JHs˻il_sgzݞ' 5*3T 훷5`[c95E$9z7h-SUX->2dHvjĮN]d(Bc,nѪ+Ə/V=vCqQhW_ʋ X3Ul$9S?dNV_xkE=.=&0f'@_qu6U(U)˗vPJ@DdjX\Ϟ3pxGQhlϝӣ*TJe*ex2ߣۃwDIJu=6'+}nڬ~sbnQ9v5z~]QiTQ .z?9sHv^{~2q̬^]8|[zy\iR b;uԮC6ziЭPU1gs:C ߩo>ݷ၎鮴` 9D gFN=𭞭ddL&b@ap1LfS5ɟ7jn;{"{ҿ6r*t !tsc6j}6Ӧ]7vHpoUϏڷGU='sFcF##>5e?II>iwl=3A{9ҲC6^pz< ss8|9fy <yj\AWrNь{(iAH}͗]Zwԥg3 A߫ X9gTDQ$I"J`zfv7h^{SR8&10),+Txʹs.l[}ۯLb= s18A$IRL5lSf~oO:i֛,zYem .o~Y{ ^~O> 7g:眩0LfDAroNVvjJJfZw9$)-%uȓ6?Yޕ\ι`0Mq Q1&0(PJo]ѢjgopIO) X,#pwȕq>EQǓsΝ=*T0stݚ^`2LfSbEc"zЅ zB%,R*˲laiJU,O|pشDNef=~dӺ 7m;vfUt! FM% x.QU9$~l3UQaFZX89$ p׿#T`d2-߹yER\)XG"gh6[yvnݾy_:{!E* fQhxq΍F#clDn&+r(}FFժ_q&MZ6/U,A9/ܓUAzŋϮ:-غu,gk7<őw-mޅKOWQ熔)Xhq"I>V΁s!WUA\:uĶ|ǯTbz`- 5FFG-۱eƑQ/8:ř Sti-OjgMqUWBb ԰D EzI~ȣMڹ#ˠ(tу[x'o_٠CC߱b7.(f;5jtzp-xܞB߅5S$>=EbJ=ӧ% PdT/E7VKlVKtTu* 5c'..^OgNz>|b3Ecfy8aҬDQt\JZuu:QM@w(!Fٲs>ٱ~G跆|D":\9x@e֢z}(*]U8flX6؄}=˺O65$pP aP> Ea̽^dKxeԳ?&}~) #f\=ME3F߽En]{;0iCw眩j-Rg_|ru \K* ݐ!sJ!0] <$`\mؠvj>~ӿ|{W'vm::9,֞'I\~Zw[},8^6voOؿko*O_ӴE+.jݲөfdȡTЖ.6JQ) Kː].CVkV9EF'+>+ F$e:3[4m9cC{ Zau=F}RgK&suѩ|Vtɴ49;[QU&( ь))2'qdH9q6,L骢<>`X9V0شNc|azr}7NM]nR*i-!H)utC:ܽm_*d$IJwwӶǾ{#Q8NU B}ƿ P0jmNfR*",#SQ#I5P{1*0 kn\wVpӊFmF#oXq]͚VKOW9rB)mZwxC|9l8DTgdtn~-ʔr\ׅn;EQl6[fFfKD's)*RJN 8{Q --dY-^/:5˟TWjSJf~}5%V,^}lZf0{t|tlj/ԯ[-%%+A &'Z4ׅM<Գd!֞Tg#_z:4hiغkNm^a놝֬\Iٲ$JZ/ WF̝O|1yVq/?!\ f?=`][#qp#Rb5Zb63ͅ;CPݼs{4EQQ\o^>o VEUUՒܶ^G>7u,'hI[_:<*Jb3vm&-~0E=#ɳjzY@@w'+U;O;OD[bZ87PMWuԸT$-:ƘN HI $I`zW-1k=v>IL} ׭E'ƹ$IYuC"+qᄏ}UwB )8z (ڕ{tlq\Ü6)/˛/3czl6)Oafz5z'&mE™$22Ve>rZX;QH}g%J6`4+fƦn_iw>Y0wLBNcbszq;nmM\w` ؿH(P%ko8=3~ɵ5;uݣ$Iիo OUTA \[pts^|8K|aMO]شI7::! DQh^c̋ 7j4 lZz'>ceʘkLe6}uS>ןpTUt$]j21E#K1*SXB"+dſɟpBelFǿ^ɸr#S ‚$IWFňI|c.% P0$²-_/[ kLG-Зz~qkvC8V!~?LfYl۽j֊UA|^oBB?,xW,_c-e-qMf2罇AQ LӗN$lwiHaY#FYvj GyQ aRT.Б>6DQq4럦 6MU Zܝm4lG̩wnX wLoƔ$ѾCMхSB$yV,X 8*;;6lbZp*BilԡgaRBm|Mt/[smpNgl;v7mԊ.kx?? '6jso+Jyq}o_1x,9sN|)#9af>?V9p?.o*U _\̹_KB__$$s9W ޓjlUO,]vX87kϙ`vFZs?[\w6c.m4 Pd9rz bgh(8BA@@.wDH)p|'ԜDGv>@trҗxsy AΉhKL A0hEϽD䊓PO͘ r^Z!ړ]g4}n*V X- DUUUU"cpS*\(R~Qs]6<֎ !qޟɋ=!*Kqa5y LVyNj%_&R$"h[l YXWj*\$`A;x0 @8DY"座:&~ɇ U|tgj9 >ll Hx0ʫsP ( \Φr'"z .2V+UOnJ.;spFt\xɜ.1f4,#j' : 0/|f/d4.gRKUr^TD倈@;ܻZKJEMʼn!Wsl,IJV} b̹}1jo PN@(՚s %Јf*D7#jo2MKnMFb& U}lnYֹIWG\i6M{8DZ#Q2@ӓ1F)tڱ(цh]  X-6FE p y(R͕B %y(v "\jrQ0^,DR[=tw5AJcÜyPQ!"λ>\Amȭ|qs.hWuي:wqWprL;VoO@jm8;M-(PDԌ5CO3B!k<׳>N&>/H (NdT9h Y5IjW5og'(@T>]@ռ pwNNM:Niɖ~ !0 S #L 4f= mC/Z3$)KHY"{5ZF@3@ Pܽeֱ(A~t}RPtV?>Fx@@UZd_ pk!hdA '&%oͤ$ mYx퓞F2:2p "[IUh?DбՔXjt Ϊ[J Qdt -$t!bO9(Gݯ.,.LB}Mӵ#Sݞcڹ.W_teNnΘI)3m;q }& nZ˲ `vII 0@JYRRlٲq%!qTDufuɪ޺sɪF HXh;:}óEpM7%W #DV'pV?#d-DnO nm6!B,52L*TuAS]Qf/R)Ǿ鑅e˖w3rlx*vsW^u6idթ'dQé:<+Sg2o_l(=ݥԱ͎ut~(=L^] W(2ˎq$h7 T[ǀRBRgU?ux2::x8q{F < SU :+Q2鬹S䎑=UUYvHnZս'|;NJJ6ڟv}(; txni +]`iPg [ikw;q))Jv"d2+qN%S?g.bя\(WJtvd"OĨ^}nݻm\ǟ|ɹ7uN]**VZL<`?;cFiRϡR5Xfe8k]),Ys3w˜̎9 g(>ҩF4J:4ڟ(5wg'@4)Jd#wa"{g RU*)ʜEDdn/~JT*c8NIVnO"OtvDZ@ 0gO>o b{ɊDBl9 :<:=$}Cy$4w'4󝊧@zU"7ξM0m%B24Q=1Z @q"@zi=pLYru.zDlO(|"w,+G[W/:ly m!RDz(0"Baz9Ec vÀk5ɓ Մa̘vPRp"j5G4˲LBhmvBqUlM/% ~o8ܐa#l:]_ ә+@ѿkzDӍj."k^*\2ڂܽ& Q_Ţxvzձ{{p1ts\jWj ":`ر!\mU5fFO' @W ³Sc! R(<'X^dI-IFFAJ@@:sEzJQx@Nw>y$aJ Nt=BdUuVߋf%N .:o3efxz #d69 BUQ0:%i@OG45n`oYpShhO:bx24 4fRhC Ft tYsMÀꤝh_ց/ M{O9k >ٳgCapv@u9nh.~rn]HU:5   ɩ|z @"G*WrC`UN,3 ԉs H۫e ζ@D +V,- uWIaY?wuh]pPKG3Nle/`zaGX Jz5'D(sYCA=L(>RRTP}c֛Zd ոlwBB(= LVv4A'IN#hzUJѝoE@=e |H GO'PQf! @hmAFHJXt3̨Z2v^uu:>dql0.]Flu”/~Ӿ},:Z>mRGMܰ4ƍ7h)rGO ]% P7thko*-roJB4>7~Gߤazl:M~6Ww7֔c7%BR0gޗS@e_gFEn?hrV2SDZm m..*\3[8Om(2r\DQ]4ȢC5oMvl|f~%nreEU]aCNH3M2T1S=&~\e6>i{+Boҕ?.[Vn_BH+h'6J;Z+aFwG-?=݆1n!^Ǖ-g˜!U<`zO>0 4v5h; pķ{v@d) 2yCi?Md9ғyh?T-9ێ*w{{d?Yƛ~rQ`"kEoJ0JoK6P]A+*s.t oRݺuڙko!""|٬C9 ! Q;:aW^A6:ր&z;2dzF~O6$͟M!ⵏt$>S |& =}$[,6 Y6nYgmIJs/>>#5DKF_PChڔ#s}Wo馱md׎THl4CzhZܯeC߯7θHF(԰[~I?oyx4ﮔ.Qw(_3[p'?_>SgR2ǏMv ˯]ߴbEqnMk\~= ĖSAe)++t=[˥Bi '-":Up鵗_ϳ'wi䎋\.N5M>>Co G:v{ 'WyM =~a]rK/<~oz&p'/[}1Y? Y8HD)aRJBRC1+*>~ κ֬w$h q4i @{!k#5{sOs?skyJ99rM9КHMָeFCO|{N=)UJnۤ V wgĮC~捩_Uc-H\fCH|^EiapuFs\VbN. 'bx,FWghF.xD/iN-^nsrrrs<$$8B$䯯@?Н?=5jP56 ST?z)G5dX᳝.*NoDSO9:R!qRaL0Tlhd46 Ӑ&hEI(Ħ;շ Z)[َc<7бaۀ~8ĢO\Kd"QRTMt{ ػm-¶B9_fvVv(l[4ͪPp:~ү6_MvdҐC:|34?\m;Bl.mY?kHgɧr2T_x/`qݳGɣvsYrsLtܼc":cV*Xf?./ᆑ8옩Æ$ Ȑz[hs0BuOkO6gݺqhl*(p/vSѻWu0[ף)0ʧ}\}MݤޛQם%ɶWJxi30U7~O8OgFmsOfvi}MK^;0:Pt V/)R%" t6eǞuW]qR'60 2\+ s_1~ܐrqGNR >9 WqgSnfTEyi VUǞv㗮Zڟ&(++(8BWRdO9b)CGݹZ0 ~)qn3/ο_?~;?ׯ_} REYEcN$n)5B:SYFLZsCni μۮ;bax}p8a}ʔ'# YˊJ'OoE!J`%^sWÇMZh_qTV0W^kGL"r]e+Pc!ƗEqw<ē{oLwxu9?oHPP`X'>~vN;|\YE~uג:,KoJ.YXvSN?:ㅟڵOe{W3ke}#;"\fqf= 0j}oyCGr $6-F3+[/O}Oz-ICԆ[\Dkc=|̴N>Hw9)Eˊ{{[_>+'˱l@" WpCF;+nzӐ !mMZk-_vega\uquXeH9dk~*_4magz4 kLDB_y>*+:뼪pem ÈD†a#N̐G<섓N<{B RZkLOl+D!BBO??;^xɮ}n_hDt@E#awT*6>`mEF D"'/[Yݻ|4gz{Wv?=3xl!6d4|H˂_λ󞛾pk s ÕBJѢݽD$zUsg}!ץ&NG3~Vs/lt[Q&>)ֺ2\oPf~ч{˝O{!S>rϑ=586h ML DV?;}/>ר}R WH)l P0+5DZ`J H u̱z OoF3#RX6 P(4o)ĦjfB$.~^/ޫ5'w}kמ p̀3=u~# g}>/rxd=f|p}M=L֧OOc(3v.7^|/lWԡo=vN]J|^?MPkxUUK~eW֮-ΙpWwE}SԦ_nc~8HO=?W}NtحoݻҩS|?cch4vݚ+~Yâ~^I|S'8,+ÕRLkg\yef,X0weem&2ikRu8lݺ4  D҅#aڥ׮gW]_sٚk줣q1O}H0i,,)uΟ6p.ݺyUH5"_\Zǭ&R#k}GWs݂oiCmc+qZDaRЮ[|nv/+T"\9.R9ٹ|}73}> Gl^ RLU]8q5WvAGeiJ/ ~vxx$&#X̖ܶ#R\. FV6Ry.SipéGMuUVYsZi㌏?}N{[*V6L&:+x<^W`O(qZw$fŢѨR>ӦW2"ߠcF" h5ٯRc>dBJ9\P_Fkvk}m߆#@LZeߗ}zߚfXK9#Hko#{NzcAcfM>t {\ZJ1c[Nk aY:mr33Nﳡ͉1ru@޽GL٣$[i hä p5}/+Uc10͚X8ӒIѶ"9qA?_x4qbc1vu'~3kذaqbLBX r¹ ׯw\q_,cl i"7{=ΆuXBz>38{,'@c-wϙһӵtӶ匱 'Qlcۂm0ۢblQYA۫fF9VCkMD/VJ7 ͬ4͊DqJ-[Nek+3vZ2R}Lbӛ7rOPlfjUK?֯Qܻs[~e-k'"* ÆحnV\"" t1 #7˕>%,e8u+1cֺu6+\Fs%"!D^+;her\[~p8w^Drn˵ reͼ,crhcwpX<*mWb۴9}l&B6峍2J%Sr慶;L#H B|YwO^- m(Q-h'"Z)sPAt)'6*h~w>x^0믻D"iuCW{x<.L9#_lN5i"<[f]$!"iJoç9m˯\X88ʕkRTsdlA-W^5bŋ8hڸ#-XGO$7tGEEeD"5kn%2""iQI)7~cO>WyDdbb6t'.JU"(蔽Jd@}C75@MO m|,T;kV/(w:Xw8}b?H xZU6M~귝QUgdonhCDˆ:)0uErNl "$z_d/~D>HCChذqIǚ,W~ח7\鮲`7}o/lժ9CaYG9cƧGyc}+R)'0\Y.hARJv咈x晧K)sZ]KvLR Rf~7ӣZtW^27YlL&?PUU?å^4cD~`L5pLHS)yYt=W\yyӧ$ҽw8Ӯro@c=o>Q 1VZ\jmiIÀ-g;կRv^dJ@CcǛa@`oJ!@7~ӐATKMT;e@@`')R8R=l@r(C BT [fz~}W2otS""rF  4@}=thvSf 99y46oL}Y+)C{G++k;G9gΜCw_|yNNN=h x!mΝ;v8׫g?vJk\ tьk֬~&7~v1>նTJ3~ݻw4i$ΛmΝ-Z'6l=Ez֭G}xk};6 #)%Ν`]vr{=pOnB m@49 ]EhUX!]ɮ *!]PI*] ]% p: f옎|"kHPr5jX,)'';昣~N8~֬{&3>7kz,hh>>{={+viӎY,SwUWU˗o3OCPzH4׬Y3o|r ^{\~e> _|GuVVV]+ފO1 _Fv}n[x/H!ą8gRV*u9dh$ fm~g`Qj PbLN9 ]Šɪ:^P1[#zgd;OQ$Yw/udU:PrYQtP mJ@(\% Z{dUU>dUk:wSbJ )LUDv\U(ju[JUNp?*t34+W U[ PwU;}W}xJռV5@9YIYkvaXT>6ƶ2Dt'/SRneeߤgy&K ~ע<ԓFߞ], {9ڲlxᇦO?<Î9N:9n{o.)|k5k֗\rЁ˫#hM`"=KzW_}o. 1rs=wʉG9ӡC^{m=_zeӦCde}>={t waYYYBHZuo;:78jQwEp-w?c7_Tr/>8^_kF,: '/?C滟~w8SO>i0ݐ'|G<`Y[o5am@~w>Z0 JYeASv@He Q*JFoJmvj<6.IdUE@V%; hy]+⠓ AYh S'uV=#GayM5|f񟅻,E6RbDTӽzeh#ׅogArT?m_#ǥ#ub@x.@ 7h@h(Y0Aé~Y\K.DPbg"w>jd4- 'h Nrt*ZkjiX@D`]w]b--m߱4/++`GR-aΜ9ݺuճKu}|įªJ(/TJ-YK$y晧M%ի׭[D"a۶[s֭[]*#%q}xm;O/o׮шֺ14M/Yc1+9C)տ s~X,veW !<pgPQ^PXL)c… WDRUUr[yn ;;_~~(UV_m뫯s%ݷ^{i{iik Ӹ[f6+L !yg;ndžQ6>n;&V +6{#6D 2XF*@)\YUH5( H@56ڷHٵ\鬭4(=e!ID(jޒA)DY"Z%@ TKSF6d/ HbKUdN"g4C䠑fP)%W9I@A* :]@hZBR8Р[`9V$ɾ}@}g̘zh5&-"rIzI'Mohh?Ē?L8pRRzРA$0 yy… kmۆaذ iHU 1=kpeeesڵk/ߜc"Jqv:pP(ԯ_ݻ2L6~>Z >Z~~,!0dBS x2dPJvq@*[U+imw,$MUQ,BoJn7i"Dmۆ}mX9*Hfd֛Fb*ى?)h7_^e#4!:0tl- δ!uB ilq[ G!qV_:FM$H˱-\qbc"@>}yڣ: F ymuԉҰnt +VVt۩>QSSc'tw%9)={2M377w^C@ēJ&B `ڴ㮸ɓߥk{x/ڵk^{t ynRyƙ؆ƴ(aYzy .]KE:NW"΄ؐ}oЫW~4 @JR;0}¶- 0hР^{uԨ/vcnyB!]r].㬳.оݷߤw"hʦW3@qnt4 mh~oVO#):,n^dC B#GGyIz:W@@HǗH9hed.tNy=h%BLpʟFhwialCf.J5Ǔ2CW3AG j\+ҨСD2oWJHB^M @oWNrqÑU @;@6ٛ;iPU]LKڕ؎}lm}JΝ78y$ 0XVVPPJ9x";1c&IQЮFHDk=u}w=c@, |K&L0mڴ#_~2GD_~o=j駟qIǏ?w~衇eR)+]RTzU.];tpq&Lx!S6S.]j8DE,Oضd2R^r'xQEߞ}9F F=C#㦟>n܄#Oǎ~MZtI>}/d*+U*e%;O9_|aԩq\]]Q{]|͗flchS.U5G3ie@G O'Ѐf.]& A2o~ˀYh* >>!0,8)^UfYe$ ߮]G%D0]YC5QK)]Ae!N]h펗;ka hRfNO߄WPj=(Tcg!@ (Щ YSi'ګo#(pK$y9ek\^б - dDze˖w3ruRZ( pB)a8"2McoMիm;Ry֭+,_B~'NϲRޭM7ǻt8μy-7dG}vG١C"ԛo߸q㋋s"++5E_͚;ei)ŋ}>;%Krz5oziiI__o~{ ,ݻwVEyyyee{l4ْgZ@ 0gO>oCÉe%A㵪 "HQ `8$vC .(paҁTJ"fDޏ3 ˲~`^5Rx<"$w72}LxD2)|>q5>;!DI@<ׁm,1} 1;D Ē8e݆lnp0`@Ӝ b_91Dy󒳮?dzg3{{h/1=YFӽ5a`wP@+7JFOAz5W"a, 0-}e  *~=fonJWޓmz6~nY,:]M-8SRlqq;yo厊p߿߱GEYk MK818MB4N Et:çO?aذh_w +S)'8 @k]v@JC#J&pBX̎m>ɛi p_ߘB1Ik jORQRJ9{ הSy]s]lJ G (4Vx)Mnqc ?d@lrKp"g5RF{Q Vw%xNlUtOL/>o&-_\|?ss}hcr_}w#oi{Fk6ᯎïݟ7~_nBmz`gb O77@9 `&?mSk%<ur 9#$:̈CdPd ^uxG-o~G~so{wNfpefZrdð=FĒQ㙀2ہDvϞݧ0a옑^91ؖA!Ȩ1{Gwo`OF迳]͍A-h کW7_R{ہ! >b#RcoBDǶ E{{=4j :߬i چn"2MJS;*)lmChS1߄Btsw~5*qSZ=w|8jU[1BX4{sr@)6!L&o>LΑzc-(0N^}/=~k?8vA޻vd}~: cED\piY>3no&O-{Jwm%{ { rlm "/_|9=.nN+aU4ż^o]~օZh2oN8eȾu~2M[iti{o<ї|_n q 8 !CSOj6"ıGJ#u}āE+u ({wƌXUIFz] |c<"\fz7`]z7{{e)64֦%@D62 QD}}7GaH+cBhb[r.gqIB"VM Foм!a,{{.>s{ػ.Q`h|Xk'˲Z![}5wЩNVRc0x,~攣{އ/̫d |޽v imlp JVZtG$"fZCmz/>l׹ۯATRĉexrmL@#؎&=YqS;Wx?,_n] 4k]:䙦rDW_vlJRk֮Ela{ G@֚'X@@5Q[Yt"ҐJpaqQGL[Ujy_}?]ߚT<m.hn6$ uOC@[Ym91܆/m߱qɏC!RO@m_mccӝ@u: oM>'njkbh<EcV%4Ec-{I>{}ܼ\/z0 x2˅ZDZ+D~v:X JHRHP4P]t%D!Z@i;|Z=0 aKFch2L&R-<"2Mcѣ/Y½kDҐ^~ 6 'JH;\dl(\~ V4bD2H 1j+.of"< 逝pdR kŔR K-z/g/7u5ue -ۧFDZk)eK7@`V08Ѐ;u5D"2M W !հhsVR_@k?( ]u"xE wLnXRmkϿ'3uv*n kx5:rVֈH)U]o 1q׫=DBWU5.ɅkK!RJL +77G9o?A#+rrX2BA qqg?rK=rDQ]m# ) w[0Lְ|sw`BǶ[ѻ`wʼn׍?FtvK@aڶ^_a[skf~EavQ[ m÷6dⰱO܄ Մ _vBzUn ﴓ_ׯµ !i#x;v?£SVj-<V^/|eLl馵}RJ"Z>KwSo=;i91W PxGsWx*+pmM:y%:d3BF̥. r +[=wb"lhM\>u|&9AC g9_ͼK=׶t*/OM/v{3zt]wp<6) p՟1dҖuZ#t_: \nӼ[cDZG^BWbvk i**RM}/=_j݅DJO>Zi֗[g}ܑSsTti~K+5w+qk/}Ҿ4;8tW;3OB!EzM+ʼne#S޵^ 3 \~*W]zEis{1)m۞~豗]pݨ=vM⬯gfeeʼneu Wz<+RRp4чtYzӪۊH[leBjvY~@!DF;ݲzHMfA">tpZ]Μ-C#ҽ%9v l[oglqٴ, kmPbU*^GohZ Uׁ65ugMuhdtw;gscк+}-+hбcD4Q_[gFGD&++ @ }Kzסd6 /W]Ge _躏7o@}?*_#.B"uJ%VW8@@ꕫڗ&lɧT\ռ:ipʟs?U%Wyc  M 6}OB7|HSM@ PU_CW JTkM7_HY sǂpAMn 6E蔪yT rU{@ˢh|:oT:3vz≦n_nۆ$Q]Uer87Nz64zQKVZ\bUҎmG۪ r TTUJv44IvKPf&I!)"@҃h:]F@UdU+  ? %jЉ鄮B 9{+%/{\2zssq].O4BejH;̗`lc_iu\@DRJ)%hMY-IUoq>.T¡P %d_"tKV*tw LE!iCW@/hNHb-Ad%ub% CGע' o%"k7 9남5ݦtGQ:8̀Aǣ@QpK?F6C3%!: hMNHTY{PtS_t YdP!Itm·,<tNt[LSYt, m"OG9/@[Ps)0MW;Ϛ/íËT][5" JP3t;Ù@РZ~O\U&%n\LYBq@AUD6ռe?Е! hnkDr48>áC9<7?ч9y<5@ /YOy'=\߿~~Wm80޻ҽD#-;v4H F%AήX*ub)BW (YwQr :P|S$cr/t)| (<A8#t+iiP'*tLk i[ҌƢdckemZtȖ|]Ȫ dWa`7 EVص  M h T$sFҩ t %/2,MEO''NK"x (}+Y0e"0 *& "`'ɹ~Yh?pH/BбJ- #S"AdEWQvϔXnt:(;X?DANDhv։e0#RϞG% :B/?±m@5k/b4#mX 9V.[a(2 1O{*@*0ɪ  ?B41@U$̲Å$JС9(=L;D0U/iiT'"o7vEl*gCڻdDV؀ңB,[lܾߘnVv㴾հUP**>أ:e7!ux̗N:_@OgYF)8k5:A:{xPFN`U& ΪeW]/t[t7oz]ehp< =D(p!u: M)Yrڏ(Unv8FG;ϛ;]N'oU?C'sҠڻv'@UDF造ߢJ5T֑NOur8PS@U d!8 8n枼B)gMCF7@`6J_Ց={{wvܼJ'w?fϛݞm}ϋXPfyɮXCQ^k!?OeYi 4r@ugHڻvǢkn7]PazdM'g}"kלּ} h؏hdW?|Y4NZJ,Ň .=GKNZp:̙5_J?IYvt^/lIJAϥnDM&mH7dڻWHGP @rDpz:# (4 HJGW 4ХoFG " (UG"Oe]lM/PP' J/j&~\m{ݥ"{(pK.#$XO AHZ P Bͧ˖en[a[vURk=m&Rx ((=(3pvH (YCtky~BDH<'=1i]?Cmv!%!²lߧ>'6@k_W˔ڲ6~'G# 8BW!$։`g t| zv#,^Y p (RE@AκtFx @'ktlQzb㨨@u$ɮTPmF "@2oz(5<7n~)k ^1I -XvRBmmBv!O Ci:fF ðm{{wc}Y錯g:NF]>Y t9@"0q!h\-sz6C4@%VD:N邆F) C?i۱dX$"0L'cΤqbc]?yeH[G%GtWKl4L=)J&F*a+(PR| wEo;Y8)RhSjț|B_iD_G~= K&rFNtq*rȪFWQS-t狜e*#(:RR 8kԁ>*$".NN·+AZRY0j|=//h Y }%srֿH:.|=D " ֬Y x x1!N]TVȬ @zE<]G$U>%Q[UHR _"K'Wcg&%;zddg#dנT'VȼNxpͤڐZ\k?4dB3a5(,.[y8)vVPS]|=@ y18d0=a_ d5- ,: (c]JCd$ \@rB1du4:#IEEp9@Gv- 4ȼ~M'/"RZQr$0w,5]ŲPh2 F d$0@FiE(>B@`;VGPj-ۣ9 ۽qAH 0NoË,܂H\ǟ/,) M1:w**j -^6j}="@H@АEY@ܑ* *Jlpbq݉+5:IyDpw4 = IrDC# (]f$03-}/?dgsB-:m pbcAH%{,\xQG"[pn׸< nnMz"Y6lM>yEΠl+ hɂ148 \z8RoQ+HYwN? ~ F@66xWָ%:E`fc`ң"{w7K)g Ϧܷ fϙoglkAT2ٹ[Woгo<>iiECE4]x:z(D`),`Pd @)4`!MD(eH\"2 j}wzyeK7j3 <_~]̜gK.TtGaõb~%Vפ8 nToUEPthtO# N]ɪkFoLI uӝ5}Jnu0jqҠX.@)P`OsD R^7J+}{|9k 3!q=t@꺏U gC"}@kz~67״!p Z$62=z߱~e!/8)4apYxӴU76]o ӏ ydEިZѣl`nr^SU_ꮚ3h}>)8lp<C c[ I~(d-Ye(AdDY87´>\PQ#cVuu'69Q+2U~rWmuFS*rA.CjʐkBo}Zk^z.,՝2!">G׆+/Xָ5IO2)E`zY=XJʼne DLRK;g 7έҍ#[ޟ۳϶LӬV}ơG"کTi^!>ԃ>@[f 0B!x;lԲ )N>_{r̖ҎM)rrL2vh(D'u[renµ\ۨJl|wP,yK- BH4pIjц21Hd]{w@)-ۊAyύǝv_u lXf!" tᕗvu'Z>q}OniG[y*c[]z|_w,]ZpeqǞo}ؓO׵ʼne)e(Ұ~_>?V3fyE/kMa:-Bp84zϱ#Ǎҳrpe "r u<T+2"FU䦻o}'ygV^k_&PJig3t!M66R6 ~Ӈ v6q|8v;Щu|X|.\u۵N8=ӲT;-m}]~59K4BV=ƶDL%ڕۧxkrszA`VI'^~ik[qYfFuN1nA[`{6mg|Oegؼc! 6\sia}^yۿp4x0&)e|wc%%.pmC!JK]O>}z{y7|kuͬZk#Oao ؓ/>t֕RThR֔Îs^SN="\qJY)+{oMزuK{♇\m 4pNfeFk,*/XÏ͚ٷ}S7w\ySO&c[Rk](-+W^Ⅷ}{o.w8`;w֮4;;ȅ뷴ۆkY?_h^m>pᤃx]] .NlAD0Rd_rο-ٗsf~m1 푆| 1Jڎ( /m@7le-±PTE!pcE *ٹ\s.8f}>s_}tp\1e,J¼޻9Ӈ9rlpPuZ6^8 °m:YBڻW:뼘67_. b1ƈ|⢬@XT*\ RJelI6vU(;k3N;;c k7q+,]^oaqqq@<q+Vq '6ֺ!4 1!E [oD@>QiЎc۶NOcӟd<8)|~{Jw WA7쀅k#Ki^Sf(h3{K2@ C9N¶q.NlMBDP`.\S*c1X‰1c,qbc1tc12'6ȑzIDATcLlj1c,qbc1tc12'6cLlj1c,qbc1tc12'6cLlj1c,qbc1tc12'6cLlj1c,qbc1tc12'6cLlj1c,qbc1tc12'6cLlj1c,qbc1tc12'6cLlj1c,qbc1tc12'6cLlj1c,qbc1tc12'6cLlj1c,qbc1tc12'6cLlj1c,qbc1tc12'6cLlj1c,qbc1tc12'6cLlj1c,qbc1tc12'6cLlj1c,qbc1tc12'6cLlj1c,qbc1tc12'6cLlj1c,qbc1tc12'6cLlj1c,qbc1tc12'6cLlj1c,qbc1tc@@Yc'e-ֈB j12iO>R JNX "Iȿ%6Dܜp(%vBJmZlwκ5¡Paa!4UFj" RV 'OV2e&lZ~/R`|}h"QXK# !Ċ؊7i⤬,RDԯ_]{7u3xpc4RP<3W:(ۦO{y|^g (0Oo?z-BP1GoifT2u '^}OԴ"FSOaez~?lT~%  91ho׉ {:kJ)!ğD1""MBfF󗃡Ė Ec6 @{f$6c1"x1c,qbc1tc12'6cLlj1c,qbc1tc12'6cLyp!&tt%tEXtdate:create2025-08-19T02:33:30+00:00o&%tEXtdate:modify2025-08-19T02:33:30+00:002<(tEXtdate:timestamp2025-08-19T02:33:40+00:00IENDB`ukui-quick/framework/doc/images/framework.png0000664000175000017500000023416315153755732020327 0ustar fengfengPNG  IHDR\ cHRMz&u0`:pQ<bKGDtIME3gIDATxuUE9,- "b`wwwbw؅bK7l<13? υ]6_go;{||)5K###################n-TM7jX5'uRJ)⼚>ΓR1Vqus !KR9#|)Dk~S*<>b$@~ēև_,mIǑQI؉3٥#;d6IbR+@~lRS,PPJ1"BOS1HEf4j6kveul[I1.Qts-G4$)"F$ڲ9>ӧmkzy9aTĤV%sl)+edR)AX h[NҮJ/KI=Hs6{׫~T,_ul QGrf?cDp[%Igu;=e:7E"f#)4f"A +WVfJfN|ZUMKzhĪ&$)R)9F"%v;U^W1(HHP!{LJ7J)DT% jFU;I !4M4-[6eʔM6Zj{nUUasRpؑN,5N)88 Go=iGR+LUa),§1Aw!/1c͛H$[ޡ[ )>>i0F)Ή3b6e2yN$+ϑ'Ɖ_ÈJ2˴Ξ6' Ʋ, ]1%{Q)ex k7nٴ{nK uVH1`Rr5M0aw޹px6mӨǝS5/[lfGˢYժm'K&=@*erYHضd"8wJF:ufB<|#>g̜ >m\y^מ]ɔR2d~D<ٻ_{nu÷}D?xϖV g!;+6lx1 2[n999`jV^qG?Ñpvnv^@0`2@Ż3"Ed{c5jҨ~xswLR1!DZ(2 ^ؓimٴeۖm7(~G6mk )> k~iړraÆǏ߿?9*6SJ)C7~g;ta%E%;H8"#i[;zR~gc_}/ *Rq6mجP"pks]פȜ}XV._ԭC-Ty|z1zuY{W_}ڵk7lжmMt&Imm٦iJ!anhаܱ)X8Q7+Ox2|ƯQ7?{/dƼYc_}sNG:V9q0um}޶eq׬/l77\.?-T,L&XnT#ŋof"JOO裏 iFMV(+-7R\Tgd* 7~|B)x {6ԶCSg-s'z9M6i{K%QO@ωEc;'صgLwJNZ2p職~=xÉDA*/:YH>|gq״y㥋]M#lrϩIR$!Rf2L(JHb$@uqc_|8]{DR DHqΓ?Om^i&>4RT2RJ5o5Ec_{oެDz8GI'J]{u9Swj״Y?P(خS[$c|ɂ%RQyU״x,~h}{$3(xCULq&) ET P-Rno: ¶DDs'7/RX<гiҒ2ꆁ$%KeY xw\ODzL)҈RHHE/hv5iK.$5fO-I{Mrc\(37OQ1ŷmtMX HjTVV6g":;>YӍ-YaO|—{ !ãxN=Atya._϶e8"Z0giܴFږ[m5kҬqzFږ[MRHihm;d<?3 %%mTeDĈI Hje˖T*EDDz.Ƙ2-=r6nH9ACdNIK.=zغm+=lߩ݀d2b)RJ 6E=ĴMv6D7xƈO9ww$E\RHϓH$l}É)4dQLF5H P-R:Ŷm.9 r񄻞L=' M#a!D*evܞsV-Wr1ԭix1Ec1P89wg?/cLJ)LuDdc%^zQ2’H Pʧ@+_mۍ|DK&_q;Ҵ!L%S=%twM3ZEfK6V1ҀH}Ϸ"@A                              t`_SDJIj1b1bU{XF= "Թ1)ZIRYK>b$R*&sW/RC2P;1bYZf2RvJzǛ`tsP1n%GqFA$whCo~۪`/!F'(|?u"&]6>ݯHTjEĉxdt~CMb{: o~Ӫ`!FR)]` ]?-tƵn_Rœ׏IͲn5a%9tuq~F* P1"fx܎%;EbP{oH-?en;٩PU#@@4DU_d”J͔Rq;ѧqV}zxIF0ݑNeJFU}0T S PϹ-JIS͛MJ4j9ƘR*`Ҽ鶰V3VC>FM#vnXc9>فZ"L#o~/`o!F@S$>b$ÝGTӭ=PRy()~;;SJ*٧9b̭INW=46P]RJvql0ƄԌ1utay cR@PJa{!>^0,Kz:Rf*HL3ɰ!Hy}~ιJJ{z`0t%Wb)MݳOL'4oRJchX u-cx:(tۯ}{J)gܲ-Dx}:Cmd2IDJ`0r)IŢ;?N:#3SnJaDGc93)-fff97s̙ ;vO?ޫD,@Cc\baMKK={چkhmہ@s/iN:uW~-[IR)5so֯9tQ#IT-)=I&KOضmoZeG۽GDZgmy4Xzo6ɓ>I&vȡGضup/=}ͣ|>ϴߧp!R~٤44cwqmw<|Cm;Zahrprݯ$"]ן}zX<޻[Ȑ/!FB׵ ֭׹]1̞~͵wBo| {c;_vS JKKy}~)%eLӴ|%׶qm۶|YNNH蓉,^н~TQ),~==7~ۃ z}y͚\xU4Z~[jӽGo'ϚAC,KH)cK)D\+)`ߡb,B)Y>[UJ9KgbtC'~w~9JKK+N>B~Y?>=5JK4hȸǼ5Cuw~)R*##λIOOsUq啮E.䌤`Ǎ{ՁsL={nmۖqڭW$mڴaҗoٲOoѢuMھ}اz1NTJ]z;;wǾd񂜜7to~6}ސRշP4L3ˏ ~y%!j y*hݺd|iٚnyd2AD>?=#kJ8Y U+J%U2A` -zQ3~7m0wysgڎ,o@FF֎/t;הRc}1+X00Vnup cܶ۷rxN-ڶmKwb*1bR@ #gS4J5dgMo]tɵroy4M)ן; /"G?m*✔Ra|-7k)R}1`pHJٸIU+ >f]֮]nܼO:3ae}E5mB{l JnFm;w:d8cƼvQ?J%@*| /3bÑ.'FM:#:hpH%=ϖ^zeKYX-+㜤G"iq9ŢJ-[6uѧT$qR)kX%_`f_?eGu{iBkK)%TJVP8NFF}S\yyR$#Tj˜d2yđǽڇZ=x{Yk۶m-[|n\۶cpM8礔[5J*%9J;14~_>H?a; sW,~^}6}qzP88Z5MyR^_$¼^?c, h4Vӿ'@5Gp vin"F"mG*Vj GRD$LOwy7scˢ"ٶl0e&fXGwiFvCSO9ƍg<O&RˌQ=?ӎiԨYnU$2q|o玔,;J6km`A RP(ݷ3>6\rxcX>9M);}߽7}#+4mժzi% M+Yxi?) w뾟a=rR` iW_(} 6y[IOpTQy/_J*z/[#z}M7^oM4B8y tԭE~JJ3p3z= +ÇlӶâs@ZZ"Jm23n33&LxwUnURZ)"wr;+;Ox'hդI^0gִ>}P Xb)AnCA/G#vHIxUKi\۾}ݲusݷnٴe6mOuʲz~y8ţi:tŢ;2 mټki&~^ӦcSOܯZ,2N5k4!C6ee4koNVfv(ylz7r_nn۶vo|# ;qɒ|yVVG7Ӄz[jxRBʴ֭kLοJ3uύ@pav|>ߜZla[Z7#vH)ݯ4R*&8DJ)ucO>+AC]rNpQ㦖ؖuƙv-HD4M;T-az O&ڴHc(ix5n)q΄c7?Ũ<IIu}RH>ADR 8c;f"$̴>}6)RdFf֭_TTJ%áH J$⚦p=h뚮~Eadffb1!DaaIO>iBd"qCpm~MUJdҲX4f#m  MKK'? !e"tdYY8]41!D2 D>s- OI2RE@#v)UYdnƀ$T5FF6 v #dcP!FTTɏjDJ%#%kIP0NZPyY`RvM7#I~U8Imz`GhG["$S* ƗIH1%U|<'HjIS6~|)җDIB b$}C"rFHHı6#XPKUw,H"R4qXNz8IJ"dT*Y"22I9 uu('I{I ȐPm)IZLRNˡnVq(Y_W1 BB  P86sP)RJ!^pMMbRj:8JPoI)^_04qӲ,oeٶzI2Ƃ czu?#6x?nRJ9_#DBMxأ,,ܦDJkn3Ƥ/?VJqPr=|̩yy,s,Xz1;TuTJߤ2 ϦoT*i;U]mٴYC;+%[4=ΧPRJM4MsbˉHx<Kk>Wu+0aR+7RJ?tɂs.k4,KHR) Ӓd,D4MO$sc0@ zne+qM"P4c`RD"~>dp,pCD`(f2ضF]}>qq!<IO&RJ9~!D<z}Ds';-O@BzH7{Uv^tu4sC],tǿL&# /WmZ~c?3ƽ7fYW]s[>}{L)umWB[n{4ͷ|qڴKح~ݤ|} weff}X,ڸqwǾ}C9‹aiH$9:+^ʂ_|ѡwBy^uHJGZj꾧4ivߨϧmڴaߔTN[lz']}yW;wq% n?aO*t⸧a^t#L&ɶ~m0 R93M3gee+Ei8giDW=y9^t^aa;YמouMu/qqJQa>|gQVO??`YYyώ~-d_t˖.j ];s޴Y{F=yO?`޼~_| (}ϝq>ߴc=3FZmU'TZ1!M㺮RɊKJk֬,+-֭wico~0nLҴ ;qf(""&\4cxfl6 O6lbmn,s]RR[UJVH@MBFJi<˖-ر]YYm߶=7i< -\8ȣ磙3kܸY0 wF8cD&z Cs#RQQL)U1ѝ8lڵ8Q'O$D HDsCix_fBo4MbGu|>^zjU/?;_M$7'py_\\Ogs I)MӔR0FD̽IIY%%ES|qd"0Y3s%+#"b$cY&)bDc[nَ-HЁm޼9g֮|~GaTbTJidɂ?fhw~1凯pTj F#rg{So+yKJBH82εt%GmsL&.j"J%J{F=YXͶ파LE4mӯk殙tιJqE~#ڑ/iڎV+ EP?vJ ȔR1cb4ZjFFf8ntbFn/*, D$,1%(++Y GiFV!jt]w/2l۶,+ 3q8㜈*>{E6m[F F@cLB!vV[%""w\xR|~vm˳_[ x=GـOc+FwfM߅ t%[rN_=U5Z#######n@RQ1S@};P;!FT#s2<p'TP^?^ 1(EΆhYͩ{@<%蝽Tb$@Pdx%~V5e%:Y93çE2=!{;{{j=H™6)rs F_Qٖ,ܐܼ*6 f<(ױϟD#Rv.e4H rljnɜ7͟5=״C6ۋީ;PW F@Ĉ?9J3̸<0ˠ\3)$U#"J_o.YԸMxS zJTS@ [5{3%ձ3|Ι'|!=kcK%8J]ӗ^X1ځ!c*Tu&pG0itv1-ҠS%Usݫ{؛RXRpؐibRQvJ0w+wNT27j2c[fRQʰVH)gl(Q*ez M: 'UIkmީ";P!FRI߶ܵ ]9)7ף~H(QM qELZ=ބ䬮͠AJ:UN;U?1'T8`ޘm[EijEVlmZ/>MleA)J)Gq 1࿁1G:'[e_jtچY²;ݟ>!';l?K)%4Ey$po4MJ{<MSdU5IJI8jywgwWl^BO`DRJ e%ۣEdg* d2:6i׶a-:*:) |xܭ1Rҝxq￙r,3oW_yV6_QU#e=H$/mժY_q67 ?AP#scX!ޢ1fV~~K^ثW]xR1.YЬY~$֨Q{G=֦M40 #Ei;k*εP(LM3:|D1 !xu])9\%5DZsrycÎ=|VUNFFck|)~kٶ?/,Lj3cB`0Ԭy˥K,^Xx~,H[YY ^vCMuݘ7os),ޡcMӃ8^4S?t۶p !_{u7_nV]v{=.Y<5gqj:P۶'Ovl?w:t #M}} 8KZlǢev$޾C )P0 PH)}~mV.6lX[\߰~mY]Fx~c\u^F=̀/\8WS?<{MX, wsW;<>!>E~Fݥs!d2I`2UI `]{h cYֆk5kaN+=H1rj߾ƍGΛ;a&u͙3ñm۶kٴH`0W6gڶpʩÇ 9:z! ᜷ncx))e7hPg1cJYw?VNy[n4''(--þP%#& h5u۶ڵTZtOOKVB`2bJJC9 -p *1:xhn=cѲK.>##,Ѫu;TqD %iָ)EG)#xP(BDD_"麡ƵnWG%,DV{F RznĩgNX<0 P0SӥK]z=D<--o[ll߾eDx[jcTzoڭSYp?Gsids^vō| z~F, u q2O?N8㮾ɓ?5`(԰Q_|y:wcLq_zsGxҙw^taǎ>]۲cucit!LTE PpT}%<L&Ny 'mD԰Q'~]4qRwʐ!G㱬\X,|6mwz6m;FYw˛c^8pW|ƙ%P(GN~6ϿN,z|e^}ͭRc͐w>MǾ8gqٶ { 1u~PJqηm7QΝkQUC@W^/&Rq(*鹃~B>l۶mDt!80 SJy?O_+p^ c/tE9țoRRJBq(L3mRJǽMTHID޴rjWYsE~E"~%qc>OD{lΝP?`BL&c@/d2rlg&I!3?tC݄>ٺu[-iTϊa*x^uΝ"( D2 pouSߦ93ADض%}l6 c޼y_=y;sZ!Fn0}ϻ(3#ۯ>-D$k飉{qkZ*,>4 JנK"mv9'bDɧ5md1GpGܼyI'dYq;vt_n@@a7|ګ4p>aGEV]1bI!-FnW?kNy+%+~M/>LV-,Yھ}ݚܪ?ѪUJW\q… ciii7x-ȐP`T13sk9--dN8er]M6y CB1tvn@,׬]7BM^zu"7~{ч >dy|Ϩq@6m3337um۶:k…@@)uǺҲ,dH0 U9BE6lذa=TӦMLӼ˗-[ym$c؊=͓flۦk<嗩۷ed vYg~UϿLI;0wFP&y#Iw?ӧ_s5.3f˖-/RӦMa4c'_'m=딑'fv1ֱC6[rpo^!@,k/vڅU޹0aQIAcǎ}7ӉhҤIݺu{w0 բKND^~~z]D3 W_xO7eUeZqHs߿s.Klx]-Z˯So&M:!z)'c7mkSʃYg5hР .믿...>ӧN:zhι;Z}P5pj nZs/ܹ'zRҲd20 0*^S9o=6p@IIik|{aa4#x<#NDEO?$TSZ+si޼W_}O{^"7l0wjkί؇0 ;/C?7I C+(Xyֹ|҈yo+W=gc^ڥM:gڥ[洴l[cҌt!E<vQݺuHO/,*޸8-Es"z8orءehܝoV'+r'x?믿'R<#yJ)Ƙb۶~/--B C߼eq'Zr-nҸQΝ׫gU|Ťƍ8B麾m۶M6wYDg|QQ񩧜ɧ3Gުe>X4O;䅋Ϟ=wɲeBq' 9P!eIIiSp8mݺujժzjذa|01vp3a&M,_vmX~d2ifߟwQJeiݪ{.ݻum׶M.XjbtE\uťeNDx<}ug1{Ǟxk8m>cSO)u-&5lؽ[゚nz?`YVZ$v}JaYV^^o1p@"z饗>`l1v E_שwm0Fm{<f߰Ef[]Rzǎq_B?,BϪо#h֥Kvֵs-Z4_xXxiǎdFf:/BtA~Im۶L P#awΪi=_ƛGuxü-zȠP(X^fnq¢3gTi8 !7n;crssi*٣MHO,[}5ǟLΦ{GN41H=?`ι+/k׮-[~s/~-[yxP4]*$qn۶lي+ Λ?{5kmX1oq;s_9znudl޼ٟ+ ͐##wS M׋֮[/kЀVގIMƆ 2-@B?[ fgg\~?˯eeeQqImٝ;u|GZRj;7PJYID͟KX~}Iq2--ҦMo߮QG]j+(X9o7@ !22 Vz=$)"Θ;cT>P&4wdO53WΝڴiEDFGe~ 1H؅;׭k&Ïg̚jj!Ef̔Rj89qݻm=?uڹKN]:wjW>+uѢ%YYo1vdJ)D+'s&%csT ;'5k⒏?Im۴}qx3b$MtB)UQJ[eMӮ g7ѵn)ߔOX-m;^1Za#O<׈HHL'}J4cee;j:'5i3pF#H$rϝá?b$ιRJiܝ,e*sccn^W)R%s ڽ3ߺUݎӫWOѥs'"✕78|HNNv۶ObݡN=Inء΃ CT1Hkaی?5wXrUsrss9 "m{J5hũXʹ;uܩ#>a485_Ez1{QnBp èxVaAQJb]F+DĽqHj cl]%+wc7<z2(oAEJ)A!Ob$8~In  IC5P!FKA2Ke3Ht)"ӀIO#Bz1P ):Fz1"P|QxKI=H"$SrC,hMZvɔJVۿ<~4o~iz 1i>}2EP=ov-yL!C`ކ,Q}RPYlH#?L#WېRh?8&is/Q,g1/OC= 1N"{'"at) #"ۘWX 1N܋7%z1R!## fF@}Ԏ!ƪ^JҴj-H?RRJ]׽^R=dR i𤔌qg7V o F@=u# Ge˖-cHD=^JP(Ofm/ TS Roٲ) rαj&@}2 g1W9:Ϲ+%")%1[]RJιR)wo\+,ܶpܾ}hFD{RJ>aCNKϰ-1BbtGJ+^RJqΥJn3cݹ_|6ן-3L2.s cP#sW*,˼[hp܏&-,ּET2iF$42MH#"9z}T2 zeD".>{ʗ^xC0 =)t]F8X‹=KCc>O)Hc ˲L71J)}> `HIxqƘnf*IcX0<[l:A]hUWݤi38WcH󤔑Hx6o0os~혩b_|>a]o&bXt=NYڶA[tO^_@qqѤ/?޴iC68`wxmu|˗-CY9cy#L`~~2( -[h_E"ik;Nm[lԥJ&>߈18vFF3ϾA8~ȑTM#4=HNrQG޾H׍X,SJyt%n94} }}~V\_{eŗ^wW|5yM6g~`QԸI@ ;w7'Þɓ??Vvvn2_~ͻc_{5ǿҋOկdܸ7zΝ;4eΛc^ܥion ,9ihR2"Rx2$4H۔R-[66mB8ĹQt _x`ȷds>Sgy״p8cBs_|tg?D">1>"e4펇zꗛ>w];sZgiNDR[xO޿6n:!7/1m ?qϨ'?btUWyWXҡcwM&EcR24͈FI'iLJIPmJ#F< /ι|[wG~էC?f؉IUXԝB(%mjذI^]Ξ%%E'Ys2TN?,3+EVEE!2围)S~=kI-]TV&8+4OgnH$8G="K`R+,F@x  rN; *)cx1MsMJjp_R)sH%1wH!D<w묖݈\bi.=BƸ""bc')Ƙܪ֭~6!c̲[J)Tڶw8>c_z{OOόŢP#nc9u:K pGP(i<Y\\h!899 ^;pᐈ;!1B'`cԴi 7d#bnΎH)8ѴY_|7I)u`iRq"_Mhk:?lQe%Y,gy^ziIK3 _~h׮]Vƽ7sָq+AC<Jqˣ s%1%2Ƴ҄K/ի_A?m24M)76r"IPrι%t[o4I\+'V_) K.vW\L<^ߍ[3:sL&:u~==?|7yVkVtUJƻos:evM~pCDx]ٶgӣG\y-[:zgFK-*..xn pQ+TDcix迺5P P P P P P P P zM7SfafTRJ Uyn3ToFJyi5JHubR9kHB{BJJ H꣈)UYdnƀdfddL1ITٰ<-4k9ucH3rñdM7C&OMZ1R8m}'-< Y,s0)jnɢlMYI$ƙ"s^@= P$q*6AJR# #_ӭ'R_6L@ҬcJK/9iR*y0iijack%Em ͫb?`Ɲ΃rKbv+@r_c ,p~,$*QmzGmS/ZN͐[|YY 2|Em IA-W-Svئ` P)IEߐ,1q1Ʋ]6c឵*CR+fqy`nAfRXIȬ4FDٳ\0q02$@ZF"IOc4IZL`'E"/UEddrjlQΙ'|!=kcK%H?{B)2"k 7&Q;1jMo ' :H)^32dHI҂gH$j #ȱ!%1г#ǯÆpa@ղOIzc"nگ|;UܳZb$@] Yub$!l[ǕRRb[THC$OiZuTR !ERRƽX/UӴ| U* Pӯ 5TjR4<_b{|47sc9WaSJAOh4.Ė G)en?j;OK % ȶ,(%+>^]P}[n BJI?`޼`4SJ麮|ۭzՓs^1})>~8*?Rs8ȓS@^fٳ{_w7^mKuIVax֯_фfq~eesx"h»i.Z4ۏ{\'N4Q7ю/ vIv)p*CJ̞6B۔RR9G'z}J֤iI)w=WRJxMR`0ҋOfZZ Rr99pawvlRRu!zgJ)Bw~yW:i,7_kh(T9>+ig̘zuΙ3놱l'e!CmqlƘەv;+*ERJM<}麮Ԏ7[8|ƽǛM)z7opM=(2v&mW\ƛ[j-_vƍv^_qq!Z$-=HH)ɨ`EL3%V\zw6 CBx8,Si-k"H0Mc<]"?xM3再P(lX,i\K}z"WVV3 1nf۶=yԷ߀+/?k={M$; TƘm[m22 V,ѣثW]xaK,h,?IȺwcɤe&RpFFΨ8~qq!iT ±X4LfgXeYrF o6"R 3ه[䷎E٫b$@y}˗//FT~~m[8 5n,{[7K, b} kc>Cc~h^pUJQ޼dɂvG7ℑ?'߰~M,;p!]tnv2-[6~ᣎ:ɟΜ[([L&ДzHZ%\ץk"Zh~zkn?hm[M6g!M< cL 5ot"à’KpϏŢi+ 5k2==`nhܸ|n#;tF rf>ןnvN;#ptF1VZ][֭w*d3,J%dr왯Îj߾i'۶jr!hÆEۏ: njΚ5+۵i~x<W\~U5kt 8x9fKO<7ݳbŲ`0ܷ߁n~\y'֯_w|OyvN5m޼sS3N4 C=vgCn 1<ҋO=~iߗkٲw>bϟ}С8׬Y ']>E/\2EoO;|y 4 C;z:RJ.+ _I'}MR׮w'ܢDnQ76dž ?X]7,B&PsǑlذIv"iishذɶm[ڵlZz0y zwy w3cp`5 >db?N̳.n۾FΝ3{+V0a#0dh?Ӣ}oܸnAlf#F Y.>`;|ď'k:B۔"gsvJ mݦ}8q on.';wm+I‰c^/ !Q<±mLt.~kFWZ޴ieY1)R)%9Yee" GѲ-7.X0 ql[7UV%SɎz5qUm[eΥzŕ7yh)ڞPq P1Rd׮=sr;G}p5-:tqd" {nҺu{&59c#T2F;Je*!3<G͛߼WC!2 g1GTU1'---Q;oK!ں%eYsN/~\Ƙ8cjńpBpNbVvJKnzi~iժ](L&Sc\Iix('E8@ 1<̝=}Cv]rYeeV)z#lN)I[ƅH)(/sϻ\' O>qs/΍cȐ fanM0xuwNɍ7\`ŋ?mtYqqGȺW.9s^ٯ߀=&JDzLw81lqH̬t͛7e0b?Nf-/<_LR "Lӝ2MSJ eB8Ag}o<#/[3}hLיmYc6MUӴ;]xM|ի =TJPر맟|۠affv*lԸ/;t"T2M)6ӧ۷}:J)L4_L&8Zf1k/|6m2/Ǿ3<O&oٲgff23^/J68x|=W9M=cO<,i{7:iZ^^#]ݪ+o:Ƽܿ?m i}2 MZj Tm٪Fso^4Mkؘ#(/7kL3 =æՊ12.]zڭI$ii} زec-$"[}>,[o7p]y9Bx<wΞ5-LpyˮSOψ91nY_p\p GyWؖ4߲e!_!÷;ݘ{ɇ~8+w;Ak1Fɔ}޵I%y_B_RDI+udR]ߝi$rōD[?BZvnJI)W\\'IcnߦzffiܲRET ^4),3|]7^oYYc[9yeRIL1n4-33K)ElX*P;_7arqGݟ˻5ie)"2 C[-]5h.$6o0T*y^/FZfRJcŧ19*hӮo*^_1v^"cL)i=\%?ˊ)/rΕRK.e{q /I)wݺUJ!(,3?nj{.]qURIBwlӶbTm7MU]/byz5/aw+cHI%ŋ,^416d谎J%)O j)H̭۱c=ze9d@ PeKܰ8VMq&g^B]"h\R9(d]s㻖1Q /Oŝ cBS0F4#y01 T HZC,y[֔մ=`=83|Z$ӾN P#jΜI)<4`4R(W(blKnHn^3tXneDU/$^Bynܺ6>͚κ lжOӰib0[[2MlM5MQ (E෨Ԯ;ts$HZA)bFvk&ČJs&l/+g`fQ6!KYIkr֎J1Ja#jJ5ͱq$=I5}ҩYAIe_<WJ?H1J$JmYQJV|YpzYM_IR)'J0$o1F-KÚJslr,/ç6@}2 k?cی1ε?zsO$bMx4Eqx1I _NoMDs\9+^ϔRB8 c_}k/uϭS+#^: P)4MFK~4M3fL ̙uXl2Tvv6h86cM"Ź;')ERJMgɖR2u]WJW?77#i!"J$7^O?~zu\|Y?ϤZ Z3Ƙm[m22 V,ѣثW]xaK,h,?IȺwcɤe&Rp$(\Ņs?&@!c, bd2aٖeYʕ5lR)i]qSNEg1O>TC#3Ƙ" 5krEA%,83/E32V,kּezz`%/<иqS]7͛G wE`083=6}/>?==ݶ>}vyB^ן[թc׫{ϭK߰~YgڷA~~׮l**c!@I۶jr!hÆEۏ: njΚ5+۵i~x<W\~U5kt 8h…sE~嗞ǯxogŊe`oux?/QO_摇 -[4.]{8J%M3}֗_|~˖i:ؼA#ǡ;oܸq伹36lҮ]HZڜ93޶mKM4]C1A}5am:|ȐcѨb?N3.l۾m^݆72wy5&5꩓N>o<4zY7piG?}?gd~! P1-j׾eׯ)(X:7lv҅tgheۊI)E+ waR/Ƕmƙee^]RTPx`c{s„D$'pl*-T*eF4;xE_xqyO?{onv, !yHBGifZ˕XNMD>IVǫVkT[ vG(kޢ CGNܯ]ؾ1u4ꭗ7n#P*lǞx]yo .덷Bu]瞇xcHwtvKOFcEfeHXK& y!C)#;Mo.+b$1!BC"8=FO/:JꯏO %rVkTcd0u}"ku󎶽%3݊PaD!PB%q\(i'RBEJ;[^ڛ`3uT䉷-}᣻{ȬשcB"T`#B!.kD"zdRUo-2)ŵF L0 jJJ?>;XEAB!.kfPas1CGDLs8j4`D#*0!esUBRUQPmT81c$B!t!笫I|*!JB2BSI%")9B]X#B'PR\1G4dPlBp! ΅ꐏ s o*aWBX2f#<BZB'Pl\=]+3R5| 0B p"%4!D:qJu@ 3!4_)BYnO83 דJ(G[ט,aHBJ/U[Q®U+)LLv:_WDiЭW3͝~!/ ( !'bQc!$I(E貃N!eLTLI{fXA)Ȫka7M(U%X eTtnz*iӺo6xk >0b opءo^dr*zsn$֬ԝ !N1Zɐb}!8HT@ #p|BSf,>>@~w;77W0L"tB].Pc䪞)PG&'lSUp'vqs&'!bEK޽{Gɲ,o߶i߶nPnΝ[O8:uQnG js:]=r!l6etSJMԴ\QA)DF4uW_$%kC +'-ըY7 4.ļ~tM6ΪRFV520é]vB!ty!Lk4ĩZOZĴ2$:1QR= Y{v︳WݻBAӄUP&Ǐ'NSR!M8o.0Mӵ=zGRRvا۶uڳ{#{-i) |:%-8x }3mFB  3f_&B򁽑!!{G"!hx鮱"(T\mѷs9ݻ9&MZMpl]>ر]n@(eYpv+_~1sw_4yN L8`֍0fN8Ev#|4ᙁfP(w.V=;Qc&5_$П0F"BE Eh3` U $7(]\UԬbFVfP=@@nnRJMӴ̐]0F"B>.cXS!$z $4"o`)?6?x͚DV 0C"B!tŰ`dYBHB+ ZB D!B!T#B!BHB!B1!B!P`D!B!T#B!BHB!B1!B!P`D!B!T#B!BHB!B1!B!P`D!B!T#B!BHB!B1!B!P`D!B!T#B!BHB!B1!B!P`D!B!T#B!BHB!.Q @RBr!t6VmB"B+ o 9 I "0s:(> %U "B0H|GCÑH@-D*!.*ҿ9A)B c$B!"D?~l].@-FD\kFh5|eGqaD!wuZP6v\DvB$HbWpT z#BC((Hrbdf"ă"@0CHB B {ABp y`5 #B!&+e%t BxֈBڅ!B! c$B!BB!B0F"B!*!B! c$Bn)Е@]z{`D"[_`D AHM`!D]L"ྑ]Dn[$Ƙй$'qD`=)z+^B]X8( pV%íhc$Bdpzz:Lpo%DƷV|M+!2-"m.NA芁1ŠEe?B1BGۘwHp8+ŏ] Da!_,#[1̐ʂ1V\yڵ+V0MSe!ΓD&LRNX ^mB]5A'H^@%v]I0F"tQBرcǵkn߾}ƍ5KTحC()֋xf02 vBWD * D#X^ǎ;9s>nܸSH.{@qUa0 I P <!ѕc$B$IBwaҥӦM{ᇛ5kfRvBFk b@!.0Ԁ@7"n &Tp./Boaì7Ȳlع%s?"@(!.Rp93 H."kVZvqm,ˆavFH&7#FBW(6Y*D8cĉv~ǖ-[Ν;hѢBԚE+"ta.SuNH^wpԍ7T)o6F=vϺ㫳"Y;}3K+ a=t8+-+5iUI0CB1KJNs…1cBʕ+W\v˔)c{=`ι5!tabr˿D]"ЕLMw@#ZQ!%"I vk , B۷o * 0#ta9%ꗫȮ`V0.v!.3W=J fHT0F"tPJeuʔ)'=~_N8w*U.E@ bzlR3LqHTRddL34 x H.)B,~=zѣO18+B#膮!BWf2`DXYQ9BHD$ۅUBW(<| H MB!Е!tY B!#B!BHB!B1!B!P`D!B!TR+B o!@!BW]RyEbtD!BW(]"BƘ,֮]nݺC(QD!BW] 1IdY޾}  !}1F"B+H.:4eY/G}%Ir:^rvB!:_# r7o߾ݺe˖]w]֭+TP+'#B+H."+C._{Pf^{G4B!#],ւ:{ڵk ޽{O !BBB87cW]`Vǣ#=,YNoV{*PVB+ @H.0ɓ(Ja!B] ?F"tY1s'B]g?9SC!F IR:7rk BgDޣ#Uc$BO YG,9!D$XABmQ9"%:M C'\nEBf^ڴqVt$XkADg]B( !Tա( !#B44ȏ`D!9x?h >wn$~k>QBMZߦ(?ٽ(jnnN*].ɓE|ʥJ1MsK'v\bJEkۮ$INkժ~qѐ0 1-n]FfZӷlXnwMtO1Re[jqKߗ-ݿwޟ6aZy<>EK[zղ/>( O.}G;p(ظin clY/Q0nۺ)3+J֮LHL*_tϽONNn!tUB 1~yc~4MK8qvږY~Op>s !}{7k׮jo_|Lbcf"^}% SI$msv}mf;c_eeАዾk2sƬ9T_zеX,z22RyL.^|  j-SZ-(]\ReB*^o_~}8YVv~ǝCаa5ꦧt<)){ԭ8U+Z7 =|NBAIdY+T2rrOtI8|'zIUUmн{_V7u]F`D!ЅdK/ܻg/VZ#5(ܜZ3>}?zڕNo'IRB4M+Sďf*zBV%K_ZӵM[:taWɓ?u nORR'UT5diB-eY=th_Fz _BLӴ/|/E#B@tt-~:..>nV;z^U2 N_}y-[͟;} 6e_}$0F"B肱4_wD"b߾݆G.^ ciذnY^tY-۷o69r{ר9gQsιaǎzœki(X0HODN@!$@T]׫߸k[רM)pہ@t ٻwDB>vG"v#B!tBLxgeg }gD.D4wwlv-:,InC=^=z}4ݙ3PB4]\.wFz\wdeeC4-YwaI Z Ez}u(銋g ]wiG#]׭ ) bQI!tB rCc*!kаY&-1; I)D 6ߠ)!:u5MSL6MHd͐عkn70 C$Bv[{^{!iHR p: *g@X^r9aD] 0F"B!$iXR h4m֍R`Yi!DӢBPJáPHe3z{֣[pMLO2; ɲ,BMBd;&`=e!BZy-Pt+!ݕ_:3 ~<%Y:fEχrB!B"!B! c$B!BB!B0F"B!*!B!t% e!5E ȃB! NHD"͈&Nh!B%@A= S&xF"t#\p(ƭotS.%`$B!A=vϺ㫳"Y;}3RH't#q튭y军o[IWz&Xa7 !B;JDmK~9oQCE(%3c$BW?JhT5,_}VoyI;ҷW|]!B5@zp5GWf[VjҪjV]+1yGNMIBЙRmMFHsN ijo9##EpVB˞pٝ U*/g0gEZ0F"t 1=VHre dF"B]$*)l2( #BtC )B!:draDC!8!BqHKB!BJ1!B!P`D!B!T#B!BHB!B1!B!P`D!B!T#B!BHB!B1!B!P`D!B!T#B!BHB!B vB#,\vCйJ,ް?W W"]' Ͽ^ =WvBs/Yt{#u7IN.N8ei͞7+=z;P p8^0ʐ^]ap#[6|ꇁܜ9r{umBpB@0S655}{#BB(Dtk2KmڼY,IKZj#GONM7tW2!BYSgX!_z^1T[]M!ҜgxqM5!rrڵ;];xٵ (YSf$I#FM[nLIp"u5{1k6v1!."!,˹u@jw욕TOn.s$eeoЬY5sniݟE_׬ۡc7,zR%);+~sUTႹ7muh!+3ja7ڥJRvvnƭ6jj/[ov8lBȊefSq" $IYMZխx+oږؔ0G7YFLbj6 &A䊕@$”f/_YUU>aSRMS\Jjé?Nٔ*Uk@vVaiZT([`/|B^z8qD,;'R`9B !ǧe|r'ep:]`fVF)pVE/lSMB3P0jB'WTz}@N+>(!L*Q+!W1vUBv;<H)\W`m=W0E*jh[BszU;x Xh4jI !YdYuu !  @%֬Q9/2cL͛7XڴiStiB!2#g%R[?/#Ŋ۟=? 4 A37G4;ooܨ6UvwM*V4LI8gR9t]_â3-O\L @.K>eYӴJ%4%)=####\2MBV%d #4i6n5 #Z_~9HB!t5yMB߰11ΙSڵ?(=y!i(^bB+FfLta_19eo@d6_quGF 8P(؀C=Ot: ìYZ8NHLpB86Ϛ=W$Y e Pǿ1rW,[u.\rSU|}wv_d4E1b? ]t֭3$B!`Y4燼y];ʒ ?BdIfg>أ{7/1mxp…p\oŔjF(5M3!!~‡xkkҥK.]*ћ7kzaynl߶Mmx.3HCfE1Mb14uB8c,i.S 1(w}K/;v,\…B!d0F^!BŊ}~9֌>+yL5wt]x:qQlo4=" ɊleC̲r9\e2f5_3u]gMb i:cf{^0"h4ٳ7\֣@(D"eQJeY2eJ>}'NXzu4eb!BjC2))-)~4Y>}7lbDi;e4!zϫ^$d*я'5~y&ȸ0'7|,YY[nKJJʿ,BUDQTr0DvWUoҥ-SO=u׀E!B1Za9ݖhtJLLMƾ7qޮmkYӻ:.}ŧԹSի :11dz/6Uu8={nAv_ٕRjF-{W{聟mz3oIխG%)w;zy|k2a%EQ(Ȼ'!@%!n*VbڴiO=TNN!hѢ&M޽;#B!t5y a~>SN޻cUTԡߗ[+ZVD"UԬ۠sP]+VТYS0xEYzMJʾ7LƬm[[FDQ`ÑP(:hy|1}5LkDQ'Nv.p6oJ1K>xիGQѭfCp4$tdZ٤O? lEկ__4EQ0C"Bk9ڷmSF9_Ϳ޻jѼ1l߱n֮[m߳7;o;RDZ4k:b+Wo9XzgG,ZiX.U$T\p؛o+[NϨIlj3ιT"LM={naX=K1O?/}wDUUB]:U\|1=<z!Mӆ b=n޽ǎ۹sg83 !B]# adee'&%yi/wSJehԨ̕ԭSliMݻAgRJbsnuM#@bE5M#t:bgvq!GfVV(JLHuݦt<Mu\~vBj-]kР5k 2f=z?8o޼raDtX/ yӳ/-\!6}M@(ʩr7oڸBrIIjظq5l߱l3^܀D={S}nnJG{^E̙[x10!q׳~&]Bña㦄n G~3v3 !׫w{85EǼ'N5I| $I eEa& "˲ѣvڷo}mڴYf-jРnEӎssƏ:RB!8U0%3Vbs`JiX?(V ByIͭ@Bp7wB<ԎDBXͰ<ǡÚ@lu'Ǘ~8co3Bvw﷞3j뇩DsY~V{n]jp֯p߆S8B:+\^8I3ϝ.kRP,f.8"Hgo[j[/n_wcjD˖b2f /ue="muDc5!''W4Zj]ֳ?p`reFc}5Wl~zrssvfϙl^8#`}!p݌y褔F =Һ}/ˬv,÷uJzƹ#$}{ݙsG8cϘ9{(%I%۶mu|pƌ7tӦMqt>FBh(%f柧byH$[7Z PS9P'ݞh4\6UU\(\nO4᜻\nMYy~:P r1D"$y] MHl6u2? ai!b1lBfSeKQY3׼si3EQaSe7? afxj^ PHw:Oݧ`&D"aιp:jF"SM .jͨw8Y}kf+ F~!4_ 4.\ ɞJipXu0*\$bN0 D6T Z8ιŜNW_CwRNv!D$EQ%+`μBcLQIt]7M3/qeYCv]uO(f;fE1EO0vI%LӠ)OÀ=GUNjBfc[Tx1*g33"hbB<&$m&MZhDJ,o.;+T(8Ͽ4uL9vD(5F F"J t?n?-pKujϿ1nD9!sޔ B!JBƙ(oҸ9 bM}\jM$[5?ιزe"+㚙-i>}z\\܄ ;Ϝ90ʎ0BYVl6 vӟI0rq~>X8H4 W9Wͮ(uJzp8lv;3M0>QU۾mݦ9N Ho5kN}+ɘi[# :B֦mRшr{ZlX(8i֪]e atq >j C B9w+W{;TP߿'Dv0fGz44--9ܜ@ }){wu|c[ rW_xa۶mTK߿˲l;8h !<3VuԘ32rxBdd|gŊP hZnwtmN]tr;oK $YVYGlӦSc5 *aO?`rM@$QT!37uD]Ȇ kkѤwJ.˭wHt1yFӲ>Wۿk[u:Mw{|m[7VVkOKmj !θDrpΧ|1:=-G]F(%teVP9n޴Ve_hf8Ν;XR*.TӅӴϣ: *x?~ߦg7J99 ؏N8 ܓOFO ZH17GmڬU͎;\tW=ֻ6mXaFBB…_O0f v."X?z޽8g|^_ dYap{in&4mǏȒ,@&M[/^2ϜY(Z5pE\0cW!5BDp}>񣳳3_^0K%Bpݹs/>襗Gٳhђwmyvu/_k0F^嬏#Gb`-xSNjU*\Nܽݻ1p.>x?5::޳nvkZd d-[4?cU뺞h˅p8mؾ}gUIZlOOp=O$)B3MtudBN)x/$?~˖-˗/ꫯvQzuD`eȴ){w̴>uM۶uknE3{<^M zǏ:+ڬ|oqGNv7w CMzQMNucruU3ڪuG Dp~F'm9%)=-Qҥ9|p/y@@,Gr h=;#,ˆiceŊUK(qfUd냦~_ G4-̌ޟ kZzY? 1FLKO{}oVfSz*ñeU(r_ XFͺ9YvmWE驍5w%*k׮\A/iKhqnm(;u]xޭ=;gNܜo~5h۷m4q_HOOxv s=ptyC].sSCFhfBBܺu>n"Ň6xTզiuY3,vO?-nѲ]$:=HXP*EcN糙$0~!..!olf]Y,BV^P|ϧM4xxsл[vwƾ^tzpy_ϟٱs7<1Owj*Viu{[6Y7~5w_~A$kY^xn`͝;c$:7ιi_jلmL"r-wھw.D)-_o қTx ~-#=nwf̈~qG3ܧ~0;;GDbQJ}z:u*D1 ę-o +_ҫ8kŒ4M5hдyi viǎO?nRzLEQ98yt۳hW,{<` @(xšt\Y([O{~B͛dgg%$$+Vb/T^}m¹\?,?qؘ?ڰ~ 'N.f+_rjO-UL.7mݺaS}yMZakdF eժ&}4;33'},~%$_l5<|3 עeX۶]uˎ7Gh͸s=HBvM+u: V㧟NJҋ/l6 rλtUٳ&%ԩQcT}CP 3#"pw֭8gK~נAغM%| 5D49p 0m61$J˖h؇Pty.wmظZZ]Z$)1cw?eaC!z<m[n-7G+~5 "L zom>.3M$1Y#؞={I)sƘdt:{#q4?ƹ 1 ιjժlٲܼyEz t5! CR{XT4]׼^55##v65P$\|BBK엔h18nի~gyy󥥦8䉴>2..!>>q‡=?ZEЩc^ML,bZ,tؾY3b?o4ht/[phZLN) ) JH\VVoROD"zYFpQJ$E"Y3tv6m9;thܣ$Isݑԏذ~uz_{C۱csnNvND4`Wp?x5{Z8&MZ*41OKM(!@|=s?*D`0V^#UrTQ^ Gת]COD7fͺ J fk\NW|Bbb%8^=$rcGr/&lڴ6-䫯<{ȡCo8l]}͜X,ÒoivNv:|h$I˖k<䍤"E֯co?}8iƉ㡽{w:Z_~>>BM9?~压4 .D>>3áL~sЇ>xƍñX1n3[`0 gx<{4BUb#F~IԒ$1$@ݲ}T!?xѼ_~Yҿp8YzҥY$BUU|=xD"-nƔ>lܸE}_}9/Ykoinҙ-8,{}^.B4M[݂O&_fwpB@\\6͗ﻷ{GnӶфjա|yXC:uZxŢ@OgvEb%~G43MMB HDfϜ"ɲ!?vXUUǻk =r}TЗ^Um]~~HH/t:]Ǽ A03 coۿ'!!祿>|`dzӕ*UG3EѴ؅ plMrs yŋsr!E ~E߇r4B$EE}_ZUWǥ {0V\m,Vu3ڻw_vNN*oy. aVF_ 3vxuTu@Joߒ~N݆֩i!eɥ펯n3cG3))N?ZTYYs5vι ?ٟۜO%KnWS $gd)RG&$$9r訷^y- Ϝٝw=0jO?@Æz}_VfAlϦ'L:)PB ʔsP(beYNHHb !+#5MQXNND%J}} ۶m +^R4EU+VRt9EU ̌*o~wΟK-@0`{7|'ppMI1g-ivV;jfө(oD 5 4Kp$٠j6[ƍ[)RLUս{w)Rl[l[jZмE;?\0 BgMi=/ dYZw4o6rRRvBA]uiRIk׮VV6"RUQl+Vo޼~k;.Bi,ŢV_c:z|׮m̬*+MppW$˒iP7nQJw>׳F=I=MHH7]w|c8t;/W.Yj;~s?ԺHq1  NjU_ze,RjS^l *coi0ÇkץYv;8 'I!0t4Oٳj՚~es|ޱS{v<5c (6kfԉIIŚ6k sss[nU^6GBBR0lŊ~W\pyknU^ٳVXjJi'NmԨEbw֬Y7';+ /!it}>;wlIO;y y፷Ǽ:Z;rj~q ]f[ Hܪu]Μـ^4fdm޴677'zD?VtvB@IE^aСNsvg{9zk[Rd%''Z%KGaEQ*UVTYk:R4ɡ:w?۵-?ꭗ[lqp8iO~U뎄ǎT^z*]I}>u̹dOty5FsΗ%.'![b q8/E7Ak}o.zy0O\<4nԠfy!Iҁ۴nG16y.k_ǎըQmOO(o;!=H?~ F عkҳ9XkӦl߱#ob+ 0RK~ShO?o~Tadfdu5O>~&;;ZZBrkI6oѮeݻ7f԰"*33ZZMCkiq.VfZÇtt_Rj));CeVxm$? )[aɲߞ|nu+E_}Eǎ]u]{er/?x+W;]8[$n׾˷ ވ#1Mxt]^ 욦խۨnF^(ةqi ,Ƙ9Z/geetuǎ-,gee,zɓ{}nQodfϞS׮lGܶmw8e\RB F][n{O8ZL~gώ` V\6o^}[M,Y:!H k )TDtu=fgΟ?Kvlܨq ׷|,kC߬^kN#+ ୶86m;SJx~3y>?(-5zY8g>_ƏݨQK|FU6nܰv׮mɥ~}wp:]GC;vlNTOty6tHV ? ;cMg`q]w4iԠXѢp]w:NY~_\\\Ms>UT)Fn;JVd^o޼uknnZ՚ SzՊ[?d 8z10R68t xAæV-{ʖu\X;?oHDãGRH1YV6_ ŢѨ,{lѢunwegjBahޢm[z=45jn kt֌y?](E#J9ٔchTu4U $ ))Cf y(w8 'ucntտ[X].З lvf$'%Q5o̭eG:Irsw ]{|cnwX{1f5j7wzղH$~o3''nw7G q]5j r u6P8$cqqrofz29v$YoTro 6*\u+V&I2YQ8%1?!Di`;_FV!RӾNRiϞPI>\\|4}ٗXT$L $1ujnιs\i2 R rMׇ<{s XqΝNWu֬^nw:GdYX>lI.]VW3+/_|r:r~ˊWP~Hq5yreyz@SP/ZTr-Zsg7o`}֞Q]R tB ++CUm&f,vC 5wTpަMP(dáH8BmfMT:=YVvG(iO r7ϙ=ms?sayۦmW3Ƽ©)%37VY rsdYd 4]KHHZz.;oYX4M0 nwHOd#ᰬwTjNa9fu޼/G׼yӓ'Ncf4 ,ám͘%ŴR[PN/9ЩMV쎾\{~}wlܤ7 +V$*PL0ά|kFG'LSQ՝;ݻGt]BkԎD4dzgώĤ?~/!>N݆$짭[7tcnGUs\NsNIK;)ip8  &3p8i1ȋ#p_!$+Vw?}oOB'ML,( kMX,ovDӻ_vv$Ivβm?~|[ێٲݯQ>_g_'u.\v3f-9pTg UU%iS@ `mlwl2eJ,;wn;pބ"gmعstۣ(]u9nZ sameE RODɥC}{msssNJE3 2zZ<+:TZc3S+T K9SB$̸afee4-NN/Seǎ-9M2MTXUVos~j59sghjZK,QNJBah.@!T|8;lv>ǚ&ƹH^p8neʔޚŢZuTژѯp!!Zs:]㥒{<0tsq\.?';RT($J.ڼm=r}EݱvJ %X䁀^rAn9~֮4LQU?Κ@jIYWě=oEc}5G>>>>a}X&@!q۩(%IpoUbf\\BZeH86@72eyo4Ţ[w,_&r)Χp,˒<'#Ǔ݊a@43 CӴ"Elݲ\ Y8ͱ~*À`0\s @@ɓǽ^_8r`BBU99c\PL&If.11>##kϋO6Ms?20.>> [ !.|pի8++4LI-;tof+GѰVr&..[$+}I:.ce.?!'%ͮh4r׭[¯n>YQLÈOԴuTU-U!дY'AfFhVױ(,[577ĉHDbyrsέ{m;[$^屶|~g 81J%ςgeee֪U_hԸE5"MbSAE^pʲ==7?c//s>BѪl*@D;ܱS\ zPŋ;33s,[S MR9/jeB4[u֠mkXYWy$4ᄉ~gTkV/Q$I'Nۿow˖9UJٻsٚq]׬ hеh7nYBd}P(ȅ({w+^ERNvF#pc1Bi\\֭]٦M' >?4ʥpz|>C0hqp(/ɸp8БU|͗>гtɥ5ʹ M8kkZir&3 @UmiJ0,QTN-[E/m9(zoG

e+www|J)S'0cW^>j3~'d͘9ժ]b\s+~c1_܆ÇmMiI|.?~‚v˰uO8S3 ?e%D<UU5?ʕ`݌zL}<5MRuܱd-**ӟ`5Z"~r^wy{WT߷,g>sV./(,igf, uwuݧ?kTL NϜ>5~dw_(ڶ}@#iq:b.9陎0J=^ߴ9nGŕ+o_-(SN[S>\˖= z3f{'u]O^6rS `势4nY֩#oұ$\lLjkk {LӐ$:|];7F,7׿ٺuvwu,Xx,M翾`߾>XiYŲ2e:L38ś- HAxѧG%9b%-s 1jڿƌԴ(JO%0GgѝKѴ%噙Y۷ma1DKJ^OOiߴ,+ 6~X,cX([zC|m {{/G#a+Iճےey]eN\뚢#F.Y;ffY֖i_׭[eIw1+xٯ2PJy)`, IR't=-Y`NvV4wG ϚfӇ&I2;rd &ۂ¢!ok8|s}ÐL_lۖ$q4-|A$\Ζ"ǟy`Q 0qmegyuz D|3g-mR.K%Aͮ%KUUM5MȔ3z Skkk󾽻&=>_QT455um3Bzֲ?eYR={D\M'>{}_n8Niiyamw n[prr?w-2MI)Xt5o%-K_zMWb꺞^(@ -gŶmSJ }+G**F!%8CY5M/XdY4 8e>կ͜9rj(s˖=fשTTLʶmW;8^ٶqÚy۶U^^uJKfTյuˆ@ D0a82qҴ̌l۱y33~5Mrs ~BcY=Hc'O6+(e@HZ0/lݱ {Ҋ">χ{я<4_hr? C`.`i,Y ՞PhHAA~_G~m[ӭgϔ;E?1 K8ß6$3m[|j }E!˲ Z %>ZY(i Nk|ۿ~7y!|cTe#1.c(I|Bni;pXDGjC1].( J (u~!@)*|V[ A"aZfYf8%~^,D+ΟW!ض?^ 㴄v䒡s ʲD)K;"J"GdW1D'@@κ%D,Jyr9w'G&_]%UU5-x`ⱘ$I1`?M6eKuS xyMqI<(B,H}oP1I¡̲;==z NeIsl78˲,G08BH %2% `/8BB@UU<1IeYu%1EeYPp 훱XB8(uRQ^ Np􍇶ٖRr_:8>RF^e^@(co5IoT1׫E)!n۶(i)` LQT^;|B^o<WU$'(> }#A-]}>(B<[z?nDo_?~78t~⎶K^Z6(Bhx_*8!qlaSA xl3%I6 |!"cL0?;ow$XXz離& ~fR6(|x<1F _dH&FS/'Գ«RGm̲LJݒ}-/! mYeƷ@"OD$p۶,1  Z B_8fUʹ'fz8}-cuw/~ e XŋHʃs |ڭԭR'W-u|;y'HBH<E/2?.I yϹ/h8櫧K~GyhyvpD"?uDK].ߩ!d|<9/Q ^usm֬/^Dv =1q߼bWo-~_ٞ 4-t$.E"ᖦp;F"Eii`zFZ0O=+eap/7J$IEYeE`z?@pD!tmkR&$Ib Mwv9}̙SgNhmijkmjoo 0M^T$UU¢#F6rGi^8e:S\!~ˀ8Fឆ3'O:^_t֖ƞn]- / :lQF/LO2nIGB0KHۇeEUU/\S(ԩmY}+2d舒#KJJ+GA.78 p hWښZ[B.]Kض=:+!#J*F.9,iPA`BaA5rQdYVUDbpcG?tcGԟ:<$˲$.vedf(p4 aضm۶c Nm{v>$ee疍W^1b¸E.lacc=ym (Jr$ b1=;z=G?rh_cXԺpᡨ($!9>cöl۶Lò,:N"O$-͍vnw|KOZ5~ʄӊݲYSJd ,K ='W{聣Gԟ<:Yie܎ rzopXS z8}jо3gNƢ GQEQUQ RL˺O:ib[i2nXF"dIYqeˆh$|̩kؿwc.q=V$ϗWWW[zӃ7+7Q%'#3]CݡD,z֖DBUDB3MRvTU1qsz9yECrssr >qp]e=u Eq \.v`]]ښÇB9->/;7hhQ^A^^a~~aAn~/>2?8 dpm-MgO7F7|Qe>ta-TUUTU`3`Rqf<0`B$DM0uSZ[kɣ'O;w{ԟ5sú֭Tel,Z|IX[@﹬C ˊpOϾn^w}{w_G;lᣇ=2'/Wu.KO:ԡ2&Pr\@HFV "EEL0Ϝ:}޶衣h4YъpSκG[WP%4nշdp. ݵuڝ5\f1%eeCG>xp˕%88MbXS'?'?ZrUϹ[_Pn058E{zl޴v׶C NŌcF1|d٨Q2TUu\*(Plԡ:e_p <.H N@At=܉&Mq߲[%nnXF"еn-Ê1&Iۭ.Iƺښ6޵#>-3n쨲U#JFg{|7--fٶ88X,J#P̏ K1jdiY F$n<ݸ{mnߴiu5u5 w,{s/6ܯ*|BnSeUGhmn\bڱ#T5lIVWLW:4LmٶM)Qd K q2`"*'UM2񧟊$"-5mYy˺ uM۸~ ZC<5w$/3M]۶m]}SR[[Y\\.yS'MV n׬߶ak3[bK>OϚ3rm\sΚM[6kSQK!:rĩK+TM]V\>DL6ۡbH2KHMAR{2܆붬F֭`.Y>&!tեt({]rV@AeDٴqk > 3;b¸'8.J `X+R |}P=eᥩi1ATU--/_9lZaGu}۷_ilp+V0qֽQJ{)jZ~5۶۲=EQ2|)Μ6iaT 4 EC1dyr&mۖe%hDY,Z\2O?^yXު7~;o_hoKɿ>& Qǡ9}C]{8|Ԉӧ{ܹܺ?VN?:ҋnRXF"UaG %RM"^MȻH(nlZm揶o]yӚGiSgNXUP- G|M3^]qxzSQ/8cՈ@<E-_;ԬX!I(Woe^Vgл (J4Q۶E2$9(JgiinؼiGk>پ!zUS&L=}U`@EiXū=m #`ݳ{w5sǖ4K,䝛&›@那T$Uu=lٴ@Īi,W6nL0-(tMKD {.o;Mk?%{ 2)y܌1*V]Y [[pq+g̝9cidphީ{Җ?]jښ]W?DTեmإֆe$B]]m[aoo=GtlH&KR;% Ď=Pu][wԐ!ÇM3w̮.,.68۶ݭw! \3DBxM8N,eɲ`E?6{edk唻1BmyW_yefdk S5qf2!SJ$I.[VXT;zd-6pO]MlzffKNU=t054t#hɯE ^96>g_j훯ʵ|O?~1+7[O輹&ӐdIQشq/^yeedq0tc}#yp"D"Go޸f{jR;gfTϞ>9Ӈ=of"3Y38۶ypyy_sOX/^߸f=;?5N r5#tsXdɊ+ƍk.Y9'|Nƀt3ep%a7_FtARߒIa:۶ AHRFADQ%IQTEDloSf=:QQԒYgZ0{"Y^WFȶ [:VR^:kl0.s#_{514Ͷ,H1NA$Id%[6ݼq́e^,{s+$l2MǶp^: >cKM]MQ/Yh湧@seÇdZB,#8J)A :2c _y<'S@Ff+DI 09#IuٴmκcFϽsnpNp2_oHp_µ}7 2۷{ӆ[7=tp"˝`άg̛YY/ǏZ^/ VV6nGU267@QTr)uo2]}-A"\ 0"&n-mm?vcX_MEQssΜvǬi 1,͓fmZ;Y]_(U~[߲~s,MYZ1nQecʫ(vCyCyLN""J麞HĚZ[[N߷==~O˫*fΟ5yc+˃ eZ|(_ӹ};D#_RHp t0kinllokmnl߿o{BX9c̹w/Ow 6-v# x';nݰ%*)S9vl^:8'(i%LÈFíMgZZNL]uoSu>ZުwU֫,9y+VW^U_TS3 ]}'˲3qx\Ip@2eYa}ݱm;;;ښδ474vtjfo=;әnbot2 (2I\nj܌6Uv}Ht.EQ !$*'!2m6Sת>/BJK*]VQVV1dl騲Q>߯)Peٖ(흠vԷ&Q].nGmmͮښ] uRۥS(BQUEG!cf(Y:j܄ʱ&V(H xD a|J1b"(.#y4;TnqtS[kiaYye/'ssss rs ss>i9c6u EQ%E"z4>~ء}4nh8Ь%nnBq{<@pHQ~q~aqQ~aAᐢ!E`Z@4Xb|lAhH8rq0l =- H"DW؜ üAp]¼!Ei'@uqH\7!5Lu>ߠ)3RKtML uwtCn-%Pw,PO',Jc;ieY`в,_ض/+ZLOr$ ; &YSL `L`FpѴfF8nomuuZ2 öζp'-=- :\@N~.!(J!ٙr܂`̶-qhOrJL2g DIPpm! ƐwE1 = uw_8` ?8]4(#+SunE\`mS۶mqBPrn 2{B7>/<ٱ۲, +P"D@ a$eJmqFa'4]K]'l˴4 (S|E"   &C6?yww͢wjSrMRӂ%88qx"Fc!t9)u([Q!|Ch]]`B!n!XF"4AnXr%`B!n!XF"4xkdIIIEE,_C$$B!ЭH!qx?)ض}7 !BvAn2~3f G?ڱc,˖e k~B! 3b $ C=:X 0] +K!B5gpH Q)?? ;~,˔Rq`$!n\TG3*:A!Bq-.-WӃnl~wݲ\;=?կ8""%QL!c@XWXf޽{u]gܹHk2ðCa eB!B%K}x5!bOOOkkkWWWeehWuƀA>" B[ OXF"tQJ)4h$3G"B\ũ3D79y!B˂e$B!B<B!B!2!B!Ѐ`B!BhD!B!4XF"B!,#B!B !B!HB!Be$B!B2!B!`B!BhD!B!4XF"B!,#B!B !B!HB!Be$B!B2!B!`B!Bh 0]%tEXtdate:create2025-08-19T01:30:43+00:00%tEXtdate:modify2025-08-19T01:30:43+00:00Zu(tEXtdate:timestamp2025-08-19T01:30:51+00:00jIENDB`ukui-quick/framework/doc/images/uml.png0000664000175000017500000021142515153755732017123 0ustar fengfengPNG  IHDRPYj cHRMz&u0`:pQ<bKGDtIME$}/fIDATxu|swI mKKqwwݝ!66`Ȁ1dl Ckqi mѺē{~s,[]իqqq.B Y݃4iһwooo, "X֥K]vjBA1,-ZN:YD0 >[RBd_ӧ===GUv #oݸqc…ϟ'[SrܸqgزeK%(/ q X,~^: ,,ٙ3}>><888--mϞ=M4IIIC 8D3F)$Iƚ5k-]o߾(g"I">O,<ɴU֫WO)JG O=<sNȲlEgϞtCGXRv]Jː}l$I(I۶X򏤤d}||V#<"BdF(a A!^ߵ{SF>|z]T*5 :e+}7 }R8R䤲'''JF„R8{Bܹ\]]>TRZvusS(vٗȟ\2 T$I8Rvl.X@JV[wx[v͈ȇ.^rqv_JZ*`0Pӕ/WVU$$IϛLR{: ;BTR=zg߁ܹsM8._>^\xP` z?rP*w3nb\\IӄHK I!V`ZGbu/3u!!BVk`@aoowߺ}ٙ8RT*:䥉JӉ[Zyƛ7dU]feТ&8J,B) DR(T(9Z,䥂xyz.]p-ڶ9{°_t:[CJQT=__9Wby}F #I<C(BX:NV8O3* o//xyY0 Pv5 ,ЬI/^Lvꅋ,ˑǻjJE !@x*URnݹS@&*Y"H_Nj6!x( T,?{@T* r'<|ǪVCC?~ y(*1)\M7zYTTFfr -B++~_^.hZ??m[WXj :j&''ۿjJhNeJ<|455Mh%JB!t J,yۻG<A0Lh=bQPF6߼y^z~:^A0B^fǎۻb-Ztlk ^^^zcΚ;vjU*[,@ [6Pl޼yVkV-Aضcbؾ(I'N8|MtiG/BdZ 'MN2%55͂ބM'V%IZNNN@TJ6REb͏: BZF͹R,M& j,<[]W^nK, +(R*+JllVVF#IyADf(*JBɉp{ EaVy75hȈ[6ԬQ]B@eFs2e߼yM>uEXB(آj]N=RY`VS0`ۄ2:=&;  ۻwn -=rXYaLl?;^u:e؟Oܩ!r L!!r L!!r L!!r L!!r L!!r L!!r L!!zx;>4L!!,˶dHqSB(zSe8ؼ<㌫} 8;B(zm`ѣFDDj4ڵk͍R6)JmRJ)!kƏu߻emfli_R++ ʎfsLL!Rbv쒔4jܹ{_ٳv#8<= WMJim?qmg*m\Fxu϶RΈ-T*۞_[ڿ7'ElBP.ᑃX,`w}d;h47o\۬in]<~d} zy-vzB$I7nNKӲ+z|B•8ivl66wFGd08I߸ <Xn`rp)));w.Rl˶rko .V҃+Y>*:R:|ؼ֭D)=pWN'OQJEIr/9 {޽{V+yg".87LFܭ״f=~dElq5+~7y]Uk9z\rr l߱gɩYƞҴ4m [hoV٭TFvE> ;wnD/OOI镚z-yaa 4MJY@~qB@F?L0X=+޼u?~ѡC{\-dvqqE9R2+[I9'egѱ}&&$/B<yq\BU l^puqf Bȁ_RX@IKKx^ʕ*>y0X-YpEQJ/vfʖ)Ud%)"A...Q1EJ*Qd"NNN$ iҤiӦM8-@3&)Zn[HfL|.a婗I)1sǢR޴זM[$(;`{߼yS|W`P6N6eD$r۔ q]l趽౭7gnٛisۏ ˙i\FGL8"˲,mmGl@ejyB"U_Befpc{GۃWzm&6ՂiTMT*}Qnv7B!SB!SB!SB!SB!SB!SB!SB!SB!SB!SB!SB!SB!SB!SB!SB!SB!SB!SB!SB!SB!SB!SBeY)-T]w.y:S)!^B)d9R*I08#eYYeJqJ RI"rvRv:\"l굗Rx!-`Q^  o9"ǑWq!ID÷ϔ<Ȳ̽|vﰫ'-N'Y{")!Mz)eJDD>d:>"Zrr'O^-@=uמ}3ժnˎ…> B`6ccgJeɓ<:k2Ǻ~YjʖaKl/2A׮^^RE+سVUP%%+Wc{`-:."⡻{RhZhEj4|Сvy8 p/;ureK<}bɲl0Tl+YM&Ff3Ͳ,WO޺u^^ФqÄDRI)eG|닅y>(JRxQp!gOt:@)Ulil ˲2BFCyAL&ݻgX,BBxvrw P0 v^$R 5`Z5M…nQx(>FVq칇qvvlѬy !:zsQV'N͛N-_$.VJ2<;ѣ[{~l\\`ȯ8B̟C1M} mɂ}*Ub`݅ ժY}@>C 6d WKEe4!1Q'&&Iy>>>yƧϜcm {UVѲ x/y;ܲu{$K:^%K=vU_x&|?-X JJel6M-2CNMM;~t*T(7jx(U' ׮߰X,NwVP8yEE RyuMekБzЁ]'hղY}cJJNnִxӿ_3gհ0WbE䱃,oZ977W\qtsucGu xzzh$<<}Zy^Vʲ\fݻԴ"E""t:2 Ҵ]tjӺ%htR%h̗ϿiF{Y<=f͘j3` B($IV/_Zr5l)W.uR^#"mEggMjUN9fBnݾ`ђ_Ν+1)IP1(q&T) GTZIuZ5EDDNfb&cm={.c!DdHLL*Y&\~~@c]{ӧ -P($Ib=?`W]d(TrnQ(QA(UdTt   Fgg q FHVX+ }s1Z#JJ%I]E yvER>ZȒR) ,a Ae/컵J%SQYgL8$]6#nj ~gn4ᣨzulvƴ]Z}-|ujzt*Yܹ Apsw+Q<@ADgQFϛJ,vS+s">]&4tںV;9{BV#J4j5XEXjcR[qד>sn˔.u?ۖHH|S&q,lAe/UpuqbOBB FcP`!ԣ* NNƍ䔔mmx}oF _zm_թMa-5%Ky>ElАR%K*Y"((%ԬQkW^X,Q|מiիUASSSYacty֥EKU* ǝ>s.55f,?pJeZlW \~.(Mպso&/SKL,el}| ujռv?oҥJ,^D\ضB+>oX^0$ȚU3VkrJ 4 !1$IJMIXPT_8p޽{~:@e:Z,֤dIJo0)c5lPo?=wa-~t:=ozܘACG^l3gӿoo[wS~dWv}+U(J^v2KzɎ15j7٣kܹ=~-ֆ1l1r+ԡHbg15ohѐK~[|5_,4t ܸyk=fMm~R2ZRRSV+uݵ59b(CGӳDbF\O2e@O:u:uԨQþMGFbZy[  (^=V|YywテEuЮv~~9(:z*>Vڵkfյz*IRT c{۔_:p%KeTB>r+VQyw\L麵k@Μ9[lV4$R|ttL`VSvK)9]637oΓ;Osgʲ\R{~f:fϘ^*<_V be4sQby.$:]zuU|'NɝkjJBϟbrVx%%5Bry -?\$fjd2\X#`l /4h; ǀU:}I&M6mĉlmv{̴oMxSgI?'MZ_r>o];G-ݻ|MABBk?:مvò{8vZǙvm3ؖ"TúDzlwyu{7^a`S~}zSmo4cۭlKd`qW_=y~!!7Կ2qA7atL|u.{i۞3i٭mM3R~[3S`ڗb A!c` A!c` A!c` A!c` A!c` A!cH]Pvfpt)Tf}b A} g@蝱 ]^{w)!3Le+,k4M A">g(9sΝ1e {"B1.!9cM0cƌEwZdnܸQ\9;,D}{"ܲpoBB?vxժP(]>U,YGSB!BB!BB!BB!BB!BB!BB!BB%Ҭo%;R&m{sTJ[<\G,9Yҷz L!!7J)!$Kn7c!]AeYrIqm'o:V/[ʐ%?%vUbY$I?'ɲAȒ·z;L!lk<^o=wNILL\oI q} f< Z7j԰-!D<~ `0N>S+6{yRp]j°5#f8ޚr(W~&!}jDmP6]<:}bk^!l+ >ٿ1רp`8/!d_7lM,立;c[-M#"b7aM4i1b FctL̫U#$@|BBͺ~[e'bkɟ?ߑc'N>ڄa՜y .\dBzS=0 #8ՅJ#/QE+_lYM@/𾾾 )9?ipǚf6oپxH$OV\rcE& HvEZ?g{`˶aؐ?͙e;Gjf4f#ksa߲}G]#,_Gn]:jx/^ ŽrْmQ[a2""BXRlEkym_oJ Wkڷ@0 v 4toT(?8VwjسgǙ曷n߻2jdrG)]sJe .(/[b;_Zvx aoGDT(Z4k7/+`9Nݼu;!1%C/RRSg'Ow*Yv?!QQ®[VYt:,ZY,^o4M+U'Oٻݺtjެew'''3IZ%$q\LH7kP۹^g7ٵܹwYwOHLdޛb8;kןս˗ڳ[/\?͙ٺU BHrJʀA]]\?xRj׮9\~~>^®ׯ['55mc{[,j#ښI6~6Ee?4yn))ʕYӏ,9L_~#M7w4PvAPJz}bb$IqIIiiik}% =6fS|h-ܶcRѨgLо'|Ukص{yN=eJo@g4[jq%N;v_+6A)uuqqCwڿ (tzQ% ]:Sd9?7ԯWБO)V,Vh`Gٳ#8qʝ8uUܾsd2:}RSS^Vbs~\bj?vfB:O7۷l;ps͞1M$ !u7͚1s ԛ9{:}f|B;vlXb:kΟ;ȑ֭;7{^{4mPqTZh4RJ}] p$Z…xoӪERR2׫Wqqpȱ:k*ǰɓ#N0[J>}&&%%?yT囷n:pmJY\$/d$sώ-6hv juuu8o&T* ! *x0ntc))njьy&E v췓&BL&Ӏ}ʕ-#b*5mcXjMA)˙sάvx߁CgMfU,=Pv!IZ^Z}jSV\R] (*Zl[CpD? )(*煿GD;|}}<=Y+@jj]J"IZ]g ! g2YHBтEKm2z=ЌKۣb2VX3_ivolJAVI-VQzH w/zˎw/] tqq*X$IZR*nݾ)m;v9ogV6q$;;;{z g\)l`)<˒ *V8q԰!`׮ Iy^^OQ|}}_sG%A %x(D B ,vr :P! ŋ>j,FN@FM3_I)R /\\bE6>q'5c1[Fʔ)v^/T`rq{TXmrRd^A` -sۦev;l JeY@(q@@eQTZ4֨C6hڲMJi2.Yu0qqyKq13>bIxTJ[h4$$IbIh2N]PO6EGskLzbVVeh`]>y>s\BbYsx'-S 5-M~! dk͛7B|1QO^egɃ/$q=5u7URIŕ-]Ry\4$TJ `S;w/\\Li(^bV)+{w#d2Y,VW7We*IB!(2*N.9..>W8chj58|SRcO?pHP)T4 {yzu@F߰9 Ae/,Yjy12*ElkT:rؐfv[._p5V eU\gϝoڤTZFj:tn]:839sT_]:6[6|u'ov¶>򫀀C2M&Sæ.YX$(-?/\r;z0֍_Mw~M'Zݻw/$$$88͛ ˅BBk?:3@M.`/.'TnBb[~tnY-{l-?m>e6mCgN^,˒ݒWOv \glAPHdkbe|Lų?iEQzSgظ tlŋػw2vwל퟽,8~/Fkbߍx O`zI/W{is$]iݍj ؍_' l3eWi!t:o:bf=3}ۋDxe~n]{שUciii fK o/{}H3}PBB3JihhzVcѸQ ED>e@|lu777777l+` A!X#lG `KXstW,߄V#A1|$ԓ` A}'&^>-?fEY22B36/Ybb"`A(+&L M">O,s˗.^ #"ܸq *Ʊ >O,ԨQ6lV(GVJ$I;ԯ_^7 NE}$Iy~f ܳgO`` 'gϞV  svv~ӘBB-iZԩsi//qիWPBliGO!$666,,l޼ygϞUVe)}w1NKKҥˮ]BZ2{&gjժ:udIL!Ϟz۶m֭vڋ/#Q"˲{"E6mڻwooo﬊ )!lZ#\85xh4qF>DQ8g h9Qy*~ʔ)>//pt肠qŋ˲\jUQ]#pPvSTTTllǎ!vu_!OP(I1;BXKe7BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB!BB9/>0 r0 r$Y޽{`4]"9B9,ŕ*UaÆ:E BB9 &oBB9(WBB9q(.X2‡ $uСcǎ$aA!0$<߱cǍ7*J{֭9rA4. BaXȂ raXlhg !ȑcЩS'lVE!~J)qcƌ߿``$IXDb A!2G"D~XBYJ`;J'ޱ!*JP@tpˈ)! ?_:i4?+q7oފ+vҥ^zqG_z/,_~iii ROW Hd6ُ}]d Jzw_@FSX1///V7k2>|w[,vmڴ SB}XǏ+_O>DQ~!迤T*ѣ_|EJJʢEl1B}3fhԨѾ} (G?Ȳ,IҥKh۷o+FSB N)XK6o_t[!Xj-^xLL̹s*UwS B9)BbŰ9!DeFSLeSB;YFRdQ]ɲ,7 G_B`AǺGci!>ry@|*>1 B10 B10 B1p\;Bɲ ӅRfIN0:i~)!NhXFɔYJ%bZpJb?| /M>sqqV;Q $Z,r.(` Be <.KBBs&TRGDvS$IR(Ĥ>**!M DQܼuǺ5+9<,["gϞ_HquFdI$IeYdRj[/_yZBeRR):O?/jz ϵf ퟭlwF,SȸyJVz)sV$I)y/\|]Reۉ,ٝ`qEIΰE!>g,S773g+Æq֮=\]\],ÇYv-RͤjڤbyRbFѸ{Jȸk:8XVB)U(* dY6,9;;߾s_s)_j!11π͛7O|n9z섿5i m~"lvqqk%IR(:2vē*Xж(L&I\]]W--1)ZA띜T~ȶ-ȶJB0 *R(flyBBsFE1OŮ߸7on r,&qТe˖67n{MB@|~~~$]xp ,Y"HdZ>nn&;qqq~q%wzuj+** Yyi|XfȇQN)WLТf "?L!!$I*TSO<. -Q7;wQj-XtEKX{ΝF)SWC{zz,"$%'63FY0n c85|G 2c~}z0y5yrז,__Εk (B/j0G>}Vejת1y$&&4hHǶx\Z5w'"򡛛3 FMkju.=/\Lb,Q)))C [AV?bv+ Ulݲy===m9{ンwoZR%KF 5!djFc+b2=!kaUT.Q׀ҧO*XS(.9s}qT6u={v{OIIÆѭKrrʸ SRRV,}+nꌈ=ի[;6.QXZj7SR * !fYRJ|wvml۲mvad^|%99eʥ_niVըbB }.YBΜ9&M{ǏIHLurq;MVuP*F㎝{=bnm4y6o5|șGF aMm!s\rŵVWZѿOOׅHj}^9$I8V !/ao-[j'O>~ݭ@|qCqp!Խk]w]?|HP5kTS(ՙo/);v lݲk& \}z+!M֬ie-nQ_;f3 Ύ>G#ܹs*T˓o j psu}K\2IIfϞ= ( gs``aIEQX&T\׮ߘ+eYΝ+ETQXV'jUT.]p񒔔T_B\MB([Դ4BVj$$$Z,Q[$R4L&8L@RRZ&f۹sܹsϞ=ʆv:zhFA}$DI/dCWW9r\D!O*Uh4O*b Fx^pRl65mf'N]xMiڤ!x{yY,J++99ET@P*RDŀ…<"#޸yjYcX8qFIKKKMK݃M!{P(\]=ruj͘| @R(QO!d2=x <\B,X@Μ9>p'"q/PYv6o0î8kz=/]^2K) 9@U*ܸqͭR iiڴ9}ÃpŋXYEQ̗/ofMywfN2)HБX=|eJmwui==([tѢ:^NxX;S$L'NL0ڵk-[<{kF$IBȟ/gϟQڽPhϞΚ” CZjmlݶc/\%&֤T)A1kOM&sڵmZtȠ~~]NÒ%?˙s֍>>+^zͶ; 0x`#Fl߹I/<==-VKF+U0q~ ՜~~븯F899kOZIiiZYy\Mg̚U#yh<@hGe_S'i7 ! el6gzVV<&{Is=Aͳ9 z c r " GaSOesZXRC)§|ChcF*~7DbG [ǟVrѐ`$I,C{,{ʕ31|,!$8*XO=˓' K(Q[wԮ "ٹP0umۼ~))#G۹klrpSբ(=|hnqvv6:--mi{v߲uOww_԰A]{GD,so\q7"xxxjvyHT:lHVC$%<{".>>2a=<ܯ]e)**ݽP,+JJڡXn݆B1Ḱ.]~I_jMZjټiTTqva͞[=~7II'-QxJJj((0bpF,!a=B>;Xa m /boݺ#b qV\\BtL^//OIY?SN*ՐAhrؐeʔ2O<=~Axx ժoEܹ >!"aTBB{z<~P(ʗ+[4$܅KnIKVXAV[)8d2=~ýpBl62%H 5W=tey?j^xn_V8RV; Z-#:ZVA7 B/0Bj-P ǽ{JeТhHcVP/&3N(󮮮O>uJyRA)e(m۴^-[ع[4ǙʖwV۫G׶mZ%&&99HPP(< *}sB賅)B$Irws . "$I /zyy޼}h4$8N:u!ԢMiV b?qwf <6mujۻѣUޠq+ 2j<_REATh47!>-X1jҨ$InʖX,f`>/_)?*7oRjU[nCVۼI\rn߹?o(Ybu!g6n^dam2lRMMMkݪE-`0|Ѧe6SbBSDž8^ߠ~ݦMdb#ztܫG70lV>=-VRZNZ52U($5lPjjBP`)fΙ8'O,˝رC;IrҁMxJA7$!>L!B`4gmh4ݰ@N4LJ)"k5'H'O֬]ҴqCQ9jyGlRNs @XB1Wk#2-hu1Yp޼ WSEw7Z52 8(Hox#x9R?HeY~!8l6+[˵:(N*("B?&2½.ZLքm),$B2j1lD/E3B`0MPt:=xBTOq곔+W_qyYUy{H~Ln...le?""Тɒ D D#$*:#[7+_,n">>^R@LcNR9;;[VQjuLcPPeY6..&ܹrºܾ}-XU }0 PVېe]$E FcC_흐DG|=yJԮ-M ."˲!dk'}~6_aNW\q?thǙjkԻg;ݽwБYqZvyľFJuARSVVlAD!{# FNn1[t:$ɉ )o޸y{׎ڳoБ 9%e5Wc)DY=<<&3n7.^<_@:Ukݹx~/?s[$|DÍ7ϛ3hHp>NRM&Ӿ!ʙ3Ǽgʲo,lADplo=:ֳWڼcn udt`X'O?phܙ@g(^ޭk{vcFNꋎ5q{Յ8//Ow77NNNfL͝OTBxA AƌQ4$ڷo7[-7e͚DD?o*gYS՚'w.ww+`(e/ljaT)JB%(Uc';w͛'`]p&pP(^;F+W[yBzGBFL_Y_R(/,,bYzAEY ݹmSh{Я:8%I$"6$ Ķ[ 4bz nۉpjq`FR[о|2Yb!r%<(YxW ^# P4 j:ϟMΝ{hքrssyA煐ðީfΜ9O손9M12iiZ=x{{sWZǞ?y.vFͺ8_^2'O4l֦v}ZhŊ|4 lղhSRj6@dSVkrrm 4/^ )^,ѯ(eM TP|>>"[nٗ999驖-yyyƎڷsխ]@~mբYP` dL#&˴A'\l垽 ! :dF#rfLuM4 -کc{ Nۇ//Ͼ{eO۳{WVR\CnݾS 0yA>kD^7`5Z={nZP%˲B۷eV\٣Grŋcq}:dku֤YukVϟ-Yߗq{P.]|derK$B~wIh.kR*˔ݔ~wpb.kaJ jhVQ/\?>Jillm;ڴj;}0uDBϒ6*!6nx=ojdYyi?Z~K\||ǎ 8L}0BBqƍ4_7#Y Pm1BAlIBXWg BȱB9zPJ#n~:3gϏ3~ȯڲM$YEQ|u}Ĥ"-_/ǑfvSlG8!PvaS$IoJsrȸK̪1dYx#2(vimO ;vJ3ewJpRCD YYRlӪE``dT(l}f^)6B!և®3f-ЮmI2I8RqO,˲, < 2z{5_]xɼgkZݥ+W>ݻ/[w'O=_m ,kVRYREnW:G$YEݝեN{i?6:v͟8t*+3v?L2tM7jPѣ,Я_^ iv?qJ)%K?vS!CAiR N9h@sg 1;11nڝ{ Gܽ9vd /X|;w >b4kr ?$L&L&I@e(IsHl2X̹s Cڵ5iDejZ_B6(ܛ&BއjDBB3 *3'{<{洱'u[v_IV5:&FղXiݲbYbUkmX?oJi۴j٥{… ݵ'(0E)4 [R4$UxmۺW544MǩTLnԠ}Zz,!; o‘}|Ka| (h4MZZmad=((m[!--M׳)JlySSҴZہt:M>AV,vhY- [].GE16.N'UR|G ܯ_?Xr%4[}{ZNټpE,Co* ?y>{nguքRhX%?Rxu[v77WW̎%H&!D!~!DP@\|s|__٤;"b/]2}e؏F):qh=7= )3fa7bG_2uᕖG֬wPBHrr'O BG`[C`l^d%]gnnOQJs5?黩3hY4g& BElrrkȶ=rJ)*ۿz֑ }ߖNF#ߗC)]a;w[GR}lUTsbE!^e*Kڳw 2؇w[ޚozA1/o?vTf/^L)mڤQn0B`:Q.. 8yJ\\,K_4wV&`ƿΟXxæIׯY3gO8dGQnnVeoCBҴ ٙ/ |K,j(/?p.^7wL .*Q8{n֬ .a_Q5kҴnkV- (''Ugq& lB(+Q>gZfJ؏x3ql֐bl2`ΪVM[@el :mګGڵjBEQyXq^Bϟ;~(Q㸄b!J(UD@A(J/7o Ğ$gN p<ϳ`n&";Y>Qvu!!Ih߰IK?ҧHNI1M0hBVO>vS|:s"AZ6TŠUk<}g~VZ^&sJj*P tz Eݧ_ʕo _vjߦUcG <|ʖ>s~eYNIIX,Plzujw뛉 F;u:Qצ[ {5kH„$I),~dȨG1iiqhѢ,]buj֨BV0 Zhz@ͼ\nٽal3ek3ӊm"ȸ{K )MVn8>J6@Nۍ]Vc[c+mFTMd )m2 c1"##B}+2UB~Cgܒ=ɧ|i']l{{iT6[12Ma+^[6c ɶ.!B)!B)!B)!B)!B)!B)!B)!B)!B)!B)!B)!B)!B)!B)!B)!B)!B)!B)ɲL)} o!B$Ieݶ//$)LpGyK wE!>5K!r,0x #I2$"!s9B}>l yK˜?\2 !b$Bvu|jCc{̚?26}Mnm#ӱ؆Su ?z԰ߗZ~;&\1#7/gΔT1kβn߹7vd`ft;w1LNIIeu؉C]|#G:O; m7w޻ePJ2KٽnZ~3"ӧOXr#GjZώէ.[)͛p칯H- 1ckްi󆍛poBHBBgc[M_Y-_[-X;˸(.]`AE@[; A f.; >޹=qϜ{ǎ>s3gEKר@ EuXzSgOB $$&)J>̟hpZ1سZn~Jy ܺ}ց삥ׅPum;vݘ*jAJ~V`D6yٶ}W~> VVaPy˗{/~:8mDXhDX(>3jxV=K4mggv2y7ou;v>rpOhH0lǶ`˦5kʲu/SA N9mذ'+W {isss@,K$KX@Moݹ %K̬]a1D"FYdٳ322h?۷o,XUN gII`lddmmU>s=VTZbQbRҌY݆ !< a\6ws|k6!0‹x <3 d+-4Jf3غ4UkGض'N?}2o׺RX(WP0 79JCG6vs-;g8x@M &j\5;oo| $2#dme5y Odfe/_PX]1ۢ:գ[wB#+kӪ}ֶ66t"߈Ϟعkw3b16aYœfheE"V{1 ҨX,~F]z@QFncm G8;7\!;[zujk9Q8366J2v/_h*$Z{1c<6m:a„9sOg 9 ֵ0'`kkv̹ժ`'\u-#a2Ƿo߹(ammӽIHP(YFFF& M7 999%,-,񏢨}lllT) j/c_b_o{=PqjKB/^Ʋ,- R޿Ovq)p 11N냧cq71mfff@X̲,E\Elg'[[Zmjj&_ IYYY~F --MVKR-66NQxK$C">Z.KQ~cD++nLRĄiZ*.^\RNNNN`kkng ߂"qUE8=Wٺ}MkM,9i>sܰc[l>z0Lޡsw\EQ!ᇟS E #H0L~bB9U\xxhzF0 醏s5;[BxȈCG*0$eǎR/\TC/cJ<}!uNwoen.0}pSgD7i-WWn֯_y0bEFFT*VU*iӦip,Ç4++ᙙ BHMAVwoe,}߁C,Zа+ .^ҢuM[PU"6nE,Bs$$"&L}e29Bhqe+"-Yq]УO# +vƋ RS? ZzBHPmФG:g߁{UkE=x?}r2Bhæ-Qq?L:ç'OBW(UjE~׬݀zEPHԹ BAQ.@*̙!֧TGkZV֨{ɺ \r !r#[s^F)_tвVzuއe !tG77( 8_c'NnBH./Xѣ'{1z]v!Q5xE]TO߀u6#t,<7nDgƳ}?4Z-vFi Aj5VL2kYҰ{ 7W^(`@T~0{qZFX |kVP\vnXvma+Uh԰~tkW/ qJnPBHV wg sgdbs/N2}RGJ=~|(N'1q~{xի=w[&&EYYYa!a! }8,---6m6t-_r ֥Tj^^?SSSl&9o֤,V|_D"t0-U U+Wc>c#t:s3޻oA`anIJe˕:theO:=dP7sff'V-_bgkq\.o|ݨXkhanq<6=8.''TIK@ǏǏZfkq[K1<ddl5iԠ~]SOA%T*D ,rfj|OumiV(?w'M/cc6U$[;~J,iYB(3+K*5U lRpdHpׯS<>'GVB+Wq!!1IcL%'y|Rt7nY[[}L/^ ] 7Gӱ,KbG+VMK씝 qt:qX,~&ao222Bo`E R;izƵmݲyͳ%1v&9fىQ կWGǍBfffKS*,YZZ2tQ# VZYZbK(a<^9RSD,n׶~4jXnx Jts-~p.|ի[vbB[NH*رuCNL, ۨQ:u52{ݻw… Sϰ=ې§ D"QT*>uAj *"| ]\;]3E"Բy:kegg34#@oIaf̞w᥋GF]qm.!YbH@zzF6ʗZzsy5dؘN]{Q`LmٶSMmnW``iByE'GlPW232|}u1 #m۷k=|K<~}+fK,^8W."m)@4Ma?-W6X"G-W6 Z}J(2{ JMQĥX1ܙOC>.?k/cq,0"s᣶vqܙffPoݺ]֯^aoo7`p^˾CdpC>o2,^ Eq,@8XT6,] b ~h񁑑6CVh IoY ôTA UX"7 ˲FFFÇoӦʹiVZ5x`_[m  /E!cbb\#0 iؠn6o\ "qgdd_)$ӻbX0{="2" rrr<0Ŋ=wb:ݻbE{1Ξ6i"H,Hwo]ql2==C$YYZ"d%\mP\٨;P*s[li pq)fiiq I 3+R('N9~t^ʗݶc{O:V*9cD qPlT>+H̿3?V wS;0hƦi;es/MmJ3SϿPa&֋eY"a ..'+9+Zʕ+;v(N%VA (*RN24ˑ4j Ϛ1z*ӱ999m0i˜FM[=y޽ɤ c-,̃͞`يnn֎ ]x/;RAknn\&܅Kur>^TjbR62sO>kPNt66AV~>g]|YԯkccPrsN='q;BBtT*`:!1tilk\,ٵn!073j+ǚlFcna^|9(v'OhѴfjN-ǽq_xEWVY_6*ѱA:4M:}v5kT(*L{17o/U͵xVvvDxE?[JUsP`q>^Q.^}\^;.=}M˗+q7o*׳{WSSӨp+/kiiyĤΝGFFpsss-^؉޽ڹCDxKb>K8{/EׯWuP`|Яy~N( O.;99@jj91$`Ϟ=׬YqN:Yf˩)))eʔ_e4po/ig ]~e?|͖e>[8qmJ ?f$rxV+l%"<_ yjͿy /;0.Yurr*S 2pkMCd۶0#,,1gn>F;@ yFB"8'@?,N|?V)Z'd+TjuWqq))U(؆i cXmzâ*<^s;V ׼yOO*B i|Ϳj@M"D}73k8g8cNH D#f Fg/3_N^7m Ztap|ӔLQFz f>m/i*ߔ{2[ D !~ mwFؔc92YbBuE_H!@(|7 C $D !O`#U?BAf'+d@ H!@  "@(B p R@ H!@  "@(B p R@ H!@  "@(B p R@ K @%]@ M4MS!rF<0 D\Zm䆵"V)@ 3xX=~┣CHpiD P*^3_^jV;kB\Vƍ^xKX=#d 򼒠[߸y?U*.>l8ТYCܽ_e>B px/1ml`N99ڪL\¾PBӅ\x9++qha(j~8aD/0phr_ I$U|)gú]qKQ*?~]g̜=ݾ*F2;~rٲnj8Npb#Oը^0+V0hx^:uh=,υ_v,K[.7mm|}=~RR.;#_WYJJ?qssݼa ˲X8jʥ }nnwth׺k+0nT˗Jϟ:yBrQq, (9eYrzݠ~Td8B0Ll&_te!Фqt̃-v\BVv\8t:VhhV._Ԯc5 zeiRfLqrr\x/\Rݘ]:ԡؔ J~7Ŵ/Ȱ, Ҭe;c3ۘެ7++ͳdxTE|p8Bq8׮wa4ް8!!ԣw^}1۰V\bS@D|, ϰcl~9kGKزmk\.slk֯]g87o}Žϟwoڲm,${/fŪ;wɑ! ŕ ajXuiB!~y޺}G! a|$;~7Ϗ҅`}=7kb8Mvvv:~t@|ĩ )R8`WKT* O>0F4MaՂVͷ(QJFFjMAh4"(V=X[c*2p)M[}`oV$$:99޽wr&!RаA݆ ~"ahPbRD4檤RAU $h4bX^AݽFoƱK1eSmZ{---Ӱh90o߽KIIǂEc\"hgp8WKIEϩOO޽SSZժ 8;qv3[yƦ}rySD)\G8[jl!Th?E($LHp!3SȨQtO:ywtpЮaTHߊ;#;[[ϞR(uԺs\.777.TZ9-@^ty r >6o[fZnު[񹳦zdNgLqw/G,]!T6*21mbEa.ص99͛ISo޺#51iֲmzvӵ6ɉ 6yT*;w֬Q !2J!Ϟpd/澳KW+WR^IF'N>qsخ{W@QP\ D"~)?~LH$6=wVrFF3?~ լqt=RS? 1Z&/Xt-F-_㸈3ηiB"ds<}R&##Grz.=թnR~~]zQTժV(c6d e2cacOvѧazkW//#c##@:sýy7kDZ5kXXٽkDX(p13M6uc',[*zء#xJ.+U^S}װŎ]{ ֦U[[[FөkOs3{wmK*V(]t/9|𲕫;ؤqX,\BBrV޽;+W{f&MקK5m~fjnnE85<,^NϙyYsdʩgubwXQ$W(N>Wz5ipԘ LLLG7J!ezvg]Y`}gYp)6cN1 -ba;:aM!X/[0ylFZ-+9 >1 c ޗ'7[s:ࡣZw`xTj3Ck ص!4orgܽ BRZN3gScPV7k=pæ 8Nʵ=`};!Ժ][ccOT] 6o^b5* 8bڌ9B62mB{%spxmpw%_qsmPVZF>k.BC\|U.=B*:Lı'BpC>EUR(lNE} \~>.[ڮ{?s-Y qMa,#ѻɣG Fcdd+A߶o(gϞ+ZWeYxʙs?s+KK?,_6 fRRJ:rTվ{>,4'qq |(ɫV:'`@}޼IʤǏTCGjccWq!RR( ͜3_&ǿ~S^R qܽ?y+H߽{_F"BϞpx## <}㸨ֲ~H$ݲuǽJejjZL*yJ%4m;v]qK&ƾ 2#C !_ ylmm]]] ӑEjSSy MMA?S!IJ:Kkj=`Sgtl߶GΏ<7pNRPNץ[޽/5t\*_0'NpR^ݣCS\X )}Ǯnps~~._}]~PMVK ŋk۪s=u9wᒗGW--,Kj|}\])=34-+W`*)H4j5Ų,EQ^^vv!@a ۙb9f֜׻gu?aV KQPxֶCעE{t]V}N C<Ϸ5G&߯W٨ȡFO ߍ/`jфU kѺyXB*qv.\sv1"A3oa{ >  Hdfjիb=V\rا4M^"(!1iێ]ǏSS0[ցýLMvSTI$2"k jBL\.h~262< ׊G %$$$ĸ[N Tߑc' MҼi;(_.j pss(*2"JMgbR\~Ӗk˕l> >:.!)PD<-[wh4HB<Ǚ;^=M$] BA~5u@<5X\tժV 8M[/cmؤP(>Jlmo߽Vo߹7{BNjm -=wbFF59fddV:sr|æ-G|${yzܼu;99%++œKj4GعH"AV'NݲcϾ28Jݼua8e9F$IfMMr-X|MH 2;E4jR&(33ߤIE233wH$"2L&34VU4n[YZޥs^'NV(7o޽w?"N\Kz.##S$_xI՞jYiz<}J:M49mҴ[erezzm,v|HKstp0l ^\ld\| sxx:c[ko/7fZ{Tڴqt>=MںbrC`9xR~qrtܳsqlk$17jH$IL:c3O[%޽s 0:x)i6JE7޽[;wcjԪ_V% Θ>yԙGޭӍp̜6bԸVm:ZZZlѴK玦RShټ靻1Zupph۪EnpWL;r*6[N}{:by&z%oǷ@7tL8y>33 `fT*Y@:nЧJRXcvlZOi4l!gQܗea)J\.:Rv eVVL&KMW:xނB&2\Z:` ^Sx Qvlv6Ei̬?;0L&.򖜜\pDfeg :B~6~7Ϗo9@pnc`IaZpK  q<8B;Kp'V+Q3TѻO*|uSSs^zF\AQ4^Eӱ"cnnfp|`ddw~,%bq(Ҡb W7_l)bXsnaag.X,Ź ox]Qh0@Bt}ް| " V /7( ~]QJo.^Ҡq e;k԰>BH,v-`= kرB`d||ÅE=@ n2߰>WoI7K7\cFU_!dgk{h\}gkk3x`E߬owiMe@ ~2' "bZ{@o] 3e*,[(ϛ7 oхttJ @Ξ=ۻw︸88uԪU<<<.m`C mB ?0T5jٲew9{l``YghL!")@`/u>{l^ ;fg>l2oooVKf~r[ Rc a޿?ykH$΅p MgΜ -^8xK4"?-x3;wc'"?,m$%%:tD"KØ\.iaW𷰱",B ^8bĈK^#`E˲3gάSA(Q. :¯ B օh4cc%K4l4(*z+W,"p ގի?|/RY,-/X7B;%wE1 g^.]ݣGŋoذ=o{I߯YK |;G\<"HP<~ȳC ~7BӴRСT؍" D"}6W"ay= 09h?ʩ<#JnWN}x!B֭[c@=''ǖ"@l ~W;x̝_x,2 8_od¿;H!E=]G(H bu..ł u qp SBg☆&,̚3^ZlBq4M_x̩X {J"|Wpʠ>ɐ6qAwye%ɮt(jt~o8:33k-7zZ^pO&<E8yҕvڷq/^eo؜RrŪ+D;w݋yR6lPnmJen j֨&^cC3+VmެI7/^֭]hd;vIJJiצnV=u\zuN9wOO6Z27WP:8v Mv]^y{ymH:>}[wF}rUq)wΩ]F 㸳.T\1N:;;m2==cæ-Rt˶EiXMzFƑ'^srrl߶\ؿ'N/_6>ޞXp|ک3gMfMy{{aÝK:;;9ۯ]QR5kҨdI?2-#\H$`a###Q 7 -[4eyFKW+Ǝܫπ7nj5ZeICN3~D,unt)p>uν^h1ƿ~#܋ 6oޫofVVzzzvlo$$5={.]2fDlڒv{0JW^ݤen rssnjإ[ d#G0y*Qâ*@o)EtVvvmiaai dfe\2}񓗯Xჩt3fܯY/(@Νk> -ʚ3oa 3+'el샇8iѪégi޸yk@V߹{/=##11Y9c/^EM9k><'&Ӡɱ'{?xWqqg_l֪}JJ*V˂<&ɤRT*dB ;ѣlܸ! Rg_w[xæFwsKLJz <}޾Ù|x`RЎ]-ll݁3%BΜ=:|_̬,|uN&NAm۱׹px *#޾}[XJM\]PUy ԭf8\D4!$ɽ'LO߸/07WڰqKCqrl8dDJՅ"U>pp@O߀&-SgΩ WRsނrru>ro߾çT*UWeY7ϒ.^Fq|-T6nފ|C[S /.Z1H(v!~>6ǎ Ü=!7772" g>]]ǚKѧKa)(Ce/zPfcG񓧦Na+@A(>˲}r)M<(uK&޽c% NHL211666VT}e&MZXb+מ:sia?y:dبں+x\/ФQ p 992LYOXZX`TJejʇ`t>|l+*T!UjTjz톦؂yov-i8z rBjb"׬Фq4tCJ.>it\1!<  (E eL.Áqq]\rʵ7o$F&%^,@&^;~( ,\-Bbm[ @Qm7t{oټirrJ>KD>j8z yϱ!~*W0aܨ/%J?1u8K(9?4mh߁M[ =bhV VvtGfXVZK5lhXBƌuVzWU\qЀ4DzH/`H<a!ܱ}f-[wtZHҖ-͜3K-[4SVŲPpiS& <|Ͼ99׭^.510ǑӾ }M r#℟ۃU={\fƍ;uwEE/@!~$$\rMcC˔ S䔷ޅݸy5ի / ]62dI?ȗjLJ=|ujc'NyPzUo/Ϙbʛ7 ϞlެIjĤaBN>XB̬g_DFD/^&'TZ9_w3}xMvvNppPʕ555EݾsץX"E@̽a@SS)NT:j:;;ǿ>s|g ={ܥXQ\֪Y]\cWq_0U5 M?{J͉y`ee^'~7Bϯ. SՕ0y9>?/*٪ Oɿ |~',or_۱ #0a8<^"ȗ0_/V~pTX O!t>Ê IlmWEQ>2kY G3L҆P[л׷b:_*͗:Yp垯c ց/ R@ _/8Y0a$ R_0V,-0Wx=׻>߰]RW!#H]e7V+/l4ޯPqq?jZS0h77W9kӱi)O:y >f?T.{nORW]Ǯ?({&NCnۡkǏBA6lUQ{c:EQv¤i=0xxjSR_IPTe 04EQ/_{wt*(ʤ}UQNƓ1 H$Wq:tmܬUF;wբus.4!-mu4^5)zy]5Z킅K[:!1 477j,+Z4<,o1`ժ=w",\l=UQTZӫπWqq.\jҼ́(zF^6nQR)fۀ:s(L6xبG :j54!i>{|zG7kݮs8,4L>+V /wvDs.*O޳ȱׂdffv7> MuZa߸20EQZ9z\u\tYG ȏzD"`?:߿O8xxfW\ZrHHHlڢK5n4hoVDQTZlW^/_ښy]<Ah[X+thzБ7oEߋߧ`en3K6bLFfm&D SR.:-ZD՝9{br+.lݪňb<\~̹iSg6lPo֌)w[qŪ,2e?:띻1mR"wƸg''{{{<=˗³$U*ɒx&b˶ƌo߮---_ê Zݹk/;;ChѼj֨wCe2p.[JeTعcn7oݶ X,1XAӴVmѺCBBEnju# 1^>y؎N>&&)קRpggsg3rͺ;,]zwqc'`, "k l֤Ѻ5+-ҡSwJ ?^:XbxB t,+T*զ-c?P())y[G"ĤVڲ O=W(l߱k>9XM?~\HaGO89/;\P4 dq @ |_ ylmm]]] ׯ[g՚u 5>ub5ZCpahwɤH$?zzzص.Q]E"a;`ZF<4&b('GֲmGk+^=nݶFd8:KExgY(_o sʗq>؀*%%V!A{y뎙^{ʵTR%u/r 4//6[tңWngϝ/רa}X"0_RS4+雑Rhl"Q^Ij1@.8G_ЯwŊG{ !_xy==}4Z EQKc]apxT*T*lA@o̼x銓C^= $121 !oK!%ZFN`mEDxhDxUkgYоmnn2mV2OӌT#a ;pihaMK`_ڿԍwޣi+4MQhDQ4211?~iaEO[6Dʗ  1?6?WX[r2H,cϾ~XgzMBQnmθ"Eu:e쫀X2ֵꤤH];w_K^zSv:T0 6Oe,,̗Z3c$t{{ҋ, W223f[4`C//4--o޺sGo h_' dddZ[[牡w|B%Jy{{>z5kTçK]v$S@_7i(Z۷>~LHGd.!d@(~ś| KI]^F5rl IIZe+bE^W^;~rzzƻ7lڒ ʒ^Ç bcoֲ`@wL5W&9w~}{FձyVZxLL%b'Xw|QzFƢ%˯ݸkHR$ϫj*,ht:UmZx%2qj5d 0tR[FE{{{;?v +V(wnLWxNt:VKܵ}i3?D!K(舏i.kandS<|)ԻpRNl[4oڳ{sss066*U$>07++\f[ko/a`N)Ĥu85o?_##EaێQIIQQ8fptt8mVv̬Iݻu¾+)RB>s|%/{ǖq6kN,;::L?}q3fwiԮYzՃv͘5U xzzL0(s3;;^tSV4yB.ń+Nydĩs.WV>^'$T>^66@3LI?_J5V jXtX"\Iw.:驩 o<*A-[w.yT;L4z"g.ONNN`bbmC׮٩]VEぇl#C@eggv]S!0  "+T*FYV#FQ5yraT zFq" ¾>EVZVTq}0SgUΜ>{.u]RJ%B`ff zϳ.߸eZa2r:{vؿD.Wpa 仲v짥Ϛ a=XݗԒ`b{*_:ı<?LHgt0,/c@aYtfc!}ׇF. 9|fjگ-O(PZ5F3?ÿx/R衽/\moo7| GG( zoO{nڤQCs>O 0EYYYam_aׅchSO~&}/j֨&,@f.~h%J(t:0Xu!`[/mesBBɈ0e[ P`@b8aZu!|VC H _[@ B@ D !~VA!S l! eh8K Po/@ bbbh^zmK < S(Ry@$\D"QDDٳvB;k]c[[۠ ޗ곧XٯT?Jdr?y3v.]zaXl=S%H233{:uv ? |_n,ݗ!tn/—66St _x2["r|0 qǏf͚M6-==a hN8Q|'OL4 "w峃qAͱpɱ 7?|X؟QpG"pR?|xWe(̬ISf;to۰d;gʔ)K.?~ (ZŋT>pqĵI-9ҩ= C޻?%%u`>'c_ oٗ.]y.Y`ɠ}J<X]*>(Xɼ"Zty7BIIommm̾RP灢qB ? =:=T" YB9eRbYWil!^æ/]!${nmes.˲sT*MvokTIR%p4Mgǿvrt,ZTTe!T*/\lll_$BH&'+ `bb"<!Baanhb1C(K TsssJ4bqTTđ'p/ %5û eYNDz0 ,VkLsssR)<~T*z9ãGOLL==SUG<6IDATo^xŋvB?(<<|KyF]|504EQ_TBꇴTo/O=w;ف'zt]G{'''R04ys.tw/MR407{F׮:|D&U,_nw= 7Wշ``E![Y<˲"hμ7oE;ph9N?7ox lŚJBlڢM쫸v=0q+ʥku:Xg]8{|57nvq)v/Bܰvkq޻ٚUˊ%V'N>eێysf8;9IϘ{h&L>cּ3N9]Vڵjuxjf zbBt훖,'12Zx~)){ڥÅ3O;t͵7/_P,\SeT*geeVjf؉# >~dM"b;88;m[޻TjgV++&?׺eʕ+>{>++["B@Dz.^*UԱCΝdfѸ\:ꥳ%MKKKK1_;~*3#2W,i_8{brᡡ;wD̲+jT Jeg;o{w;quѹ[uk_8{⹓$,[(| ֬`oo26g׮P5S&+(}}c_W-5.V)Xb@ C3 psժ5xX,nެ DX?{"$ KvYٶiSݻvk+K*;ah ^/ `cc0Ԕi33S666@Ӵ%C3&&X8U'۵mVڹ6K 2!!޽W.1"߷Wvsrr,--MMMu,+ˑY[Yֹih''G\(^Gccmc6{y~W]:TVRIxwqC߳وau:Yv<z)d_K$R~~8Pӱ:a.UJ"?ynZ>JLz[ڿddfžkШNESqcټXnCG;~YF}z077cY88LMix4qo׶Յ8;M筹8a''G###LeFޫr4Mk4sU*KKKx㔹 bn,w߁R%>!Vo%Nvv,ljL8B  H$3(| J1 Ke>zewVP >VR(ccc:-,ˈDH3tq)֦Uvѣ' eK?wVvv6^su9#138gr;!BC/;qԙg߸y] Ů040X X(hټ-32.^RliW,0LffF̘2fhN";:D"8N$jlUǍzqyEsrd nn͑pFv6"ÏH!=Axc-?v~M8su-lꤷov R<~┳eXhB(#3j]w&&R)ꬬlgg'^( ͩWu-_mffV6*BVw׾CClTĎ]-, &"H!=(C)viffc'N WPn)ŊrU*\&&&8y˲~ӱ@͵nݪU>lccs/n<<,͵Z4kRTɢE M[=RXЯJGغe3nwrq)a!֮+W6ύA/.ٲmkqggȈph߶UݪVT&(/516pիV{/f֯GGzws!%K]xK 77WcXvIS_9_6 ;[[{; thG*֭Sk¤F 566Ƴ?'Hvޛq*W+W=z0kkg7WjJrm O(JΛ7 w,>k؈?)ԦuKD 66rzaxS"# " *V,GFǴtBLU*CdDsnܼekc3w4[[ NHL:{BѢEJ[XXlܴIh.N9s/i&tH$pquj{Ǐg͘RJe,ODy{{=~2ujaNJUk\h ꙙ>inݾw޽2x}垽ƎU5l(^e=V+.,Q8ԣ(8ugkԨfiiΌ nѣGݻרQ_@*liKSy/rMVإa_鐵7}6 4Cҿ̄8PNݷ}YgWAIIoB!4}K&'T#99pN۸y[ju #ԋn:g‚EB$Sn@ ~YM890\`&!ݗc$?ϣ={4]B9N"Qa]@(ן83S/ٽ˞}@$ ONprn׫Rylj %]rܟ&Nq{ڰiKv:MBg͙?p@U `YWUEzİ >CB([>y~-ݻzߨi+77WiƦed`_ٰh!aLÝA٨'` aHܺ}wE 107^i eV- Abx,={߱&Өa}g٨H?_}ܰB-~?[:\ϯgw Lp}u ?{r9سg5klܸSNB'ԕ#—`cc݁@ -S9yS?(<⁹P:w R@  fRoտUP[!B@&Bw]®߫maW@ ~ah666j]?222n!R@ {R'˲=zZt;w+|y¿`zwYjUE"˲-pa$Ɏ;BBBW"+u _W}t999'N${b +W4h ''gٲe}%)@ =E{-[@zz`ccCޮx#G̙3GӵjjΝ]]·;wK&H$[xW{+WdWF@VLRRڵkϜ9P(~~@Xb۷^:dsB; #T*Y,CAQD"otS['R@ |?*x?5D ! y _HIC P5W@ R@ H!@  "@(B p R@ H!@  "@(B p R@ H!@  "@(B p R@ H!@  "@(B p R@ H!@  Mx7 %tEXtdate:create2025-08-19T02:35:54+00:00Q{S%tEXtdate:modify2025-08-19T02:35:54+00:00 (tEXtdate:timestamp2025-08-19T02:36:00+00:00ƁsDIENDB`ukui-quick/framework/doc/ukui-quick-framework.qdoc0000664000175000017500000000530715153755732021305 0ustar fengfeng/*! \module UkuiQuickFramework \title Ukui Quick Framework C++ Classes \brief */ /*! \qmlmodule UkuiQuickFramework \title Ukui Quick Framework QML Types \brief */ /*! \page ukui-quick-framework.html \title Ukui Quick Framework \section1 简介 ukui-quick作为一个基于Qt Quick的UI开发功能集合,其中提供了一个基于插件模式的UI开发框架,目的在于实现对桌面通用插件的定义,加载以及统一配置管理等功能, 此框架的特点包括: \list \li 可实现桌面组件一定程度上的功能解耦与功能复用 \li 插件和宿主应用(Host)之间不会强制有二进制依赖关系,插件的开发相对更加独立,不易产生版本迭代时插件不兼容的情况. \li 插件开发门槛低,无需了解宿主应用全部功能即可开发插件. \li 通用性,可以实现不同桌面组件之间无缝共享插件 \li 统一的配置管理模块,便于应用和插件的统一配置管理 \section1 基本概念 插件框架整体由4个主要部分组成: Widget(插件),Container(容器),Island(窗口) ,Config(配置). \c Widget 表示一个桌面插件, \c Container 为可用于放置插件的容器, \c Island 为基于QQuickWindow的通用窗口,继承了加载Container的功能, \c Config 为统一的配置接口,提供了上述3个结构共用的配置管理功能. 基于此框架开发的一个应用的典型结构如下图所示: \image framework.png {Ukui Quick Framework} 此应用包含两个窗口,其中窗口2为插件框架中的Island,而 \c Island 中包含了一个主 \c {Container(mainView)} 作为 \c UI 的根节点,根节点与其所有子节点共同组成一个树形结构. 通过统一配置管理接口,应用 \c {(Island)}可以生成属于自己的本地配置, 也可以访问属于其插件的全局配置. \section1 设计模式 整体插件框架分为两部分,一部分是负责保存和处理信息的后端 (Widget/Container/Island) ,另一部分就是负责显示的前端 (WidgetQuickItem/WidgetContainerItem/IslandView): \image model_view.png {Ukui Quick Framework Model View} 框架类似Composite模式: \c {Widget}:定义了插件和插件容器的公共接口,并实现了相关的默认行为,同时也作为插件 c\ {(leaf&Component)}. \c {Container}:继承自 \c {Widget},作为 \c UI 结构中的分支节点对象\c {(Composite)},负责管理所有子widget的生命周期. \c {client} 表示宿主应用,\c {client} 可以选择直接使用封装好的 \c {Island} 窗口作为应用窗口,也可以选择直接初始化 \c {Container} 作为插件加载根节点. \image uml.png {Ukui Quick Framework UML} */ukui-quick/framework/view/0000775000175000017500000000000015153755732014553 5ustar fengfengukui-quick/framework/view/island-view.h0000664000175000017500000000564615153755732017161 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_ISLAND_VIEW_H #define UKUI_QUICK_ISLAND_VIEW_H #include #include #include #include "widget-container-item.h" #include "island.h" namespace UkuiQuick { class IslandViewPrivate; /** * 1.用于加载ContainerItem的窗口 * * 封装加载逻辑,简化加载流程 */ class IslandView : public QQuickWindow { Q_OBJECT public: IslandView() = delete; explicit IslandView(const QString &id, const QString &app); ~IslandView() override; /** * 在搜索路径中自动寻找该插件并加载,现已实现 */ bool loadMainView(const QString &id); bool loadMainView(const QString &id, int instanceId); /** * 在搜索路径中自动寻找该插件并加载,不加载UI,配合bool loadMainViewItem()使用 */ bool loadMainViewWithoutItem(const QString &id); bool loadMainViewWithoutItem(const QString &id, int instanceId); /** * 加载当前mainview的ui */ bool loadMainViewItem(); /** * 设置一个Container作为根View,一般用作异常情况调用 * @param view */ void setMainView(WidgetContainer *view); WidgetContainer *mainView() const; /** * 已废弃接口,使用 @loadMainView(const QString &id); 自动加载。 * @note: 若需要分离Container与island层,可以从此处加载特定的island代码,并将Container嵌入到该对象。 * * 加载Container的ui * 不同的Container往往UI也不相同,需要按情况编写并加载 * @param source ui文件的地址 */ [[deprecated]] void setSource(const QUrl &source); /** * mainView UI组件的上下文 * 目前的情况是,islandView拥有一个上下文,用于开发时向ui提供一些数据 * Container也有一个上下文,他们两个之间的数据并不互通,为了能让这两个上下文互通,现在islandView的上下文会作为Container的父级存在。 */ QQmlContext *rootContext() const; WidgetContainerItem *rootItem() const; Config *config() const; Island *island() const; private: IslandViewPrivate *d {nullptr}; }; } // UkuiQuick #endif //UKUI_QUICK_ISLAND_VIEW_H ukui-quick/framework/view/island-view.cpp0000664000175000017500000001321015153755732017476 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "island-view.h" #include "shared-engine-component.h" #include "island.h" namespace UkuiQuick { class IslandViewPrivate { public: explicit IslandViewPrivate(IslandView *q); Island *island {nullptr}; QQmlContext *rootContext {nullptr}; WidgetContainerItem *containerItem {nullptr}; IslandView *q = nullptr; void onContentItemSizeChanged(); bool onMainViewChanged(); }; IslandViewPrivate::IslandViewPrivate(IslandView* q) : q(q) { } /** * 主视图的尺寸始终与窗口尺寸保持一致 */ void IslandViewPrivate::onContentItemSizeChanged() { if (containerItem) { containerItem->setSize(q->contentItem()->size()); } } bool IslandViewPrivate::onMainViewChanged() { auto cont = island->mainView(); if (!cont) { return false; } auto contItem = qobject_cast(WidgetQuickItem::loadWidgetItem(cont, rootContext)); if (!contItem) { qWarning() << "The main item must be WidgetContainerItem."; return false; } containerItem = contItem; containerItem->setParentItem(q->contentItem()); onContentItemSizeChanged(); return true; } /*! \class UkuiQuick::IslandView \inmodule UkuiQuickFramework \inherits QQuickWindow \inheaderfile island-view.h \brief 用于加载 \c ContainerItem 的窗口. */ IslandView::IslandView(const QString &id, const QString &app) : QQuickWindow(), d(new IslandViewPrivate(this)) { // TODO: 信号监听 d->island = new Island(id, app, this); d->rootContext = new QQmlContext(SharedEngineComponent::sharedEngine(), this); d->rootContext->setContextProperty("island", this); // 默认将mainView的大小调整为窗口大小 QObject::connect(contentItem(), &QQuickItem::widthChanged, this, [this]() { d->onContentItemSizeChanged(); }); QObject::connect(contentItem(), &QQuickItem::heightChanged, this, [this]() { d->onContentItemSizeChanged(); }); // 当island指定的主View切换时,同步加载对应的主qml文件 connect(d->island, &Island::mainViewChanged, this, [this] { d->onMainViewChanged(); }); } IslandView::~IslandView() { if (d) { delete d; d = nullptr; } } /*! \brief 加载Container的ui. \deprecated 使用 \l {loadMainView(const QString &id)} 自动加载. \note 若需要分离 \c Container 与 \c island 层,可以从此处加载特定的 \c island 代码,并将 \c Container 嵌入到该对象. */ void IslandView::setSource(const QUrl &source) { Q_UNUSED(source) } /*! \brief mainView UI组件的上下文 目前的情况是 \c IslandView 拥有一个上下文,用于开发时向 \c ui 提供一些数据, \c Container 也有一个上下文,他们两个之间的数据并不互通,为了能让这两个上下文互通,现在 \c IslandView 的上下文会作为 \c Container 的父级存在. */ QQmlContext *IslandView::rootContext() const { return d->rootContext; } /*! \brief 主视图的根容器项. */ WidgetContainerItem *IslandView::rootItem() const { return d->containerItem; } /*! \brief 通过插件 \c id 加载对应的主视图到当前 \c IslandView 中. \sa Island::loadMainView */ bool IslandView::loadMainView(const QString &id) { return d->island->loadMainView(id); } /*! \brief 通过插件 \c id 和 \c instanceId 加载对应的主视图到当前 \c IslandView 中. \sa Island::loadMainView */ bool IslandView::loadMainView(const QString& id, int instanceId) { return d->island->loadMainView(id, instanceId); } /*! \brief 在搜索路径中自动寻找该插件并加载,不加载 \c UI,配合 \c {loadMainViewItem()} 使用. \sa Island::loadMainView */ bool IslandView::loadMainViewWithoutItem(const QString &id) { return d->island->loadMainView(id, false); } /*! \brief 在搜索路径中自动寻找该插件并加载,不加载 \c UI,配合 \c {loadMainViewItem()} 使用. \sa Island::loadMainView */ bool IslandView::loadMainViewWithoutItem(const QString& id, int instanceId) { return d->island->loadMainView(id, instanceId, false); } /*! \brief 加载当前 \c mainview 的 \c ui . */ bool IslandView::loadMainViewItem() { return d->onMainViewChanged(); } /*! \brief 设置一个 \c Container 作为根 \c View ,一般用作异常情况调用. \sa Island::setMainView */ void IslandView::setMainView(WidgetContainer *container) { d->island->setMainView(container); } /*! \brief 获取当前 \c mainview . \sa Island::mainView */ WidgetContainer *IslandView::mainView() const { return d->island->mainView(); } /*! \brief 获取当前 \c IslandView 的配置. \sa Island::config */ Config *IslandView::config() const { return d->island->config(); } /*! \brief 获取当前 \c IslandView 关联的 \c Island 对象. \sa Island::island */ Island *IslandView::island() const { return d->island; } } // UkuiQuick ukui-quick/framework/widget/0000775000175000017500000000000015153756415015063 5ustar fengfengukui-quick/framework/widget/widget.cpp0000664000175000017500000002444615153755732017065 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "widget.h" #include #include "widget-container.h" #include "config-loader.h" #include "config-property-map.h" #include "widget-interface.h" namespace UkuiQuick { class WidgetPrivate { public: explicit WidgetPrivate(const WidgetMetadata& m); int instanceId {-1}; QString icon; QString name; QString tooltip; QString version; QString uiError; WidgetContent content; WidgetMetadata metaData; // actions, 用于显示当前Widget的某些操作 QList actions; //附加action,用于Widget给container的action list或者container给widget的action list附加action //例如,widget可以通过widgetInterface插件形式给container的action列表中附加action,container给他的某些字widget附加一个和 //container相关的action QList appendActions; bool active {false}; bool visible {true}; ConfigPropertyMap *qmlLocalConfig {nullptr}; ConfigPropertyMap *qmlGlobalConfig {nullptr}; Config *config {nullptr}; WidgetContainer *container {nullptr}; Types::Orientation orientation {Types::Horizontal}; QScopedPointer plugin; double ratio {1.0}; }; WidgetPrivate::WidgetPrivate(const WidgetMetadata &m) : metaData(m), content(m), icon(m.icon()), name(m.name()), tooltip(m.tooltip()), version(m.version()) { } /*! \class UkuiQuick::Widget \inmodule UkuiQuickFramework \inheaderfile widget.h \brief 一个 \c "Widget" 组件 widget包含元数据和内容(\c Content)数据,存放插件的基础数据属性. widget通过元数据进行初始化,被加载到某个容器后,容器赋予其更多新属性. */ Widget::Widget(const WidgetMetadata& metaData, QObject *parent) : QObject(parent), d(new WidgetPrivate(metaData)) { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); if(d->content.pluginPreload()) { loadPlugin(); } } Widget::~Widget() { if (d) { delete d; d = nullptr; } } Types::WidgetType Widget::type() const { return UkuiQuick::Types::Widget; } /*! \property 插件元数据的 \c id \sa WidgetMetadata::id() */ QString Widget::id() const { return d->metaData.id(); } /*! \property Widget::icon 插件的图标. */ QString Widget::icon() const { return d->icon; } void Widget::setIcon(const QString &icon) { d->icon = icon; Q_EMIT iconChanged(); } /*! \property Widget::name 插件的名称. */ QString Widget::name() const { return d->name; } void Widget::setName(const QString &name) { d->name = name; Q_EMIT nameChanged(); } /*! \property Widget::tooltip 插件的提示信息. */ QString Widget::tooltip() const { return d->tooltip; } void Widget::setTooltip(const QString &tooltip) { d->tooltip = tooltip; Q_EMIT tooltipChanged(); } /*! \property Widget::version 插件的版本. */ QString Widget::version() const { return d->version; } void Widget::setVersion(const QString &version) { d->version = version; Q_EMIT versionChanged(); } /*! \property Widget::website 插件的地址. \sa WidgetMetadata::website() */ QString Widget::website() const { return d->metaData.website(); } /*! \property Widget::bugReport 插件的bug提交地址. \sa WidgetMetadata::bugReport() */ QString Widget::bugReport() const { return d->metaData.bugReport(); } /*! \property Widget::description 插件的描述. \sa WidgetMetadata::description() */ QString Widget::description() const { return d->metaData.description(); } /*! \property Widget::authors 插件的作者列表. \sa WidgetMetadata::authors() */ QVariantList Widget::authors() const { return d->metaData.authors();; } /*! \fn const WidgetMetadata &Widget::metadata() const 插件的元数据. \sa WidgetMetadata */ const WidgetMetadata &Widget::metadata() const { return d->metaData; } const WidgetContent &Widget::content() const { return d->content; } void Widget::setUiError(const QString &error) { d->uiError = error; } bool Widget::hasUiError() const { return !d->uiError.isEmpty(); } QString Widget::uiError() const { return d->uiError; } /*! \property Widget::actions 获取插件的所有 \l QAction 列表. */ QList Widget::actions() const { return d->actions + d->appendActions; } void Widget::setActions(const QList& actions) { d->actions = actions; } void Widget::setAppendActions(QList actions) { d->appendActions = std::move(actions); } QQmlListProperty Widget::qmlActions() { return {this, &d->actions}; } /*! \property Widget::instanceId 插件的实例 \c id,在某个应用中是唯一的,用于确认 \c widget 身份. */ int Widget::instanceId() const { return d->instanceId; } void Widget::setInstanceId(const int instanceId) { if (instanceId == d->instanceId) { return; } d->instanceId = instanceId; } /*! \property Widget::active 该属性表示插件激活状态, 将 \c Widget 置为激活状态,应用可能会根据这个状态设置一些UI交互,比如任务栏不会自动隐藏. */ bool Widget::active() const { return d->active; } void Widget::setActive(bool active) { if (d->active == active) { return; } d->active = active; Q_EMIT activeChanged(); } /*! \property Widget::orientation 该属性表示插件的布局方向 */ Types::Orientation Widget::orientation() const { return d->orientation; } void Widget::setOrientation(Types::Orientation o) { if (d->orientation == o) { return; } d->orientation = o; Q_EMIT orientationChanged(); } /*! \property Widget::config 插件的局部配置接口,该配置仅在当前Container内有效. */ ConfigPropertyMap *Widget::qmlLocalConfig() const { if (!d->qmlLocalConfig) { Config *config = Widget::config(); if (config) { d->qmlLocalConfig = new ConfigPropertyMap(config, (QObject *) this); } } return d->qmlLocalConfig; } /*! \property Widget::config Widget的独立配置接口,可由ConfigPolicy控制,可在多个Container间共享,该配置仅在当前Container内有效. */ ConfigPropertyMap *Widget::qmlGlobalConfig() const { if (content().configPolicy() == WidgetContent::LocalOnly) { return qmlLocalConfig(); } if (!d->qmlGlobalConfig) { Config *config = globalConfig(); if (config) { d->qmlGlobalConfig = new ConfigPropertyMap(config, (QObject *) this); } } return d->qmlGlobalConfig; } Config *Widget::config() const { if (!d->config) { if (d->container) { d->config = d->container->config()->child(QStringLiteral("widgets"), d->instanceId); } } return d->config; } void Widget::setConfig(Config *config) { if (d->config == config) { return; } d->config = config; } Config *Widget::globalConfig() const { WidgetContent::ConfigPolicy policy = content().configPolicy(); if (policy == WidgetContent::LocalOnly) { return config(); } return ConfigLoader::getConfig(d->metaData.id(), ConfigLoader::Global); } /*! \property Widget::container 该属性表示插件所属的容器对象. */ WidgetContainer *Widget::container() const { return d->container; } /*! \property Widget::showIn 插件的显示位置. \sa WidgetMetadata::showIn() */ WidgetMetadata::Hosts Widget::showIn() const { return d->metaData.showIn(); } void Widget::setContainer(WidgetContainer *container) { WidgetContainer *old = d->container; if (old) { old->disconnect(this, nullptr); } d->container = container; Q_EMIT containerChanged(old ? old->id() : "", d->container ? d->container->id() : ""); if (d->container) { setOrientation(d->container->orientation()); connect(d->container, &WidgetContainer::orientationChanged, this, [this] { setOrientation(d->container->orientation()); }); } } /*! \property Widget::ratio 该属性表示插件的缩放系数,一般在 \c container 里控制,\c widget 根据需要读取,例如根据缩放系数进行布局调整等. */ double Widget::ratio() const { return d->ratio; } void Widget::setRatio(double ratio) { if(d->ratio != ratio) { d->ratio = ratio; Q_EMIT ratioChanged(); } } void Widget::loadPlugin() const { if(d->plugin.isNull() && !d->content.plugin().isNull()) { QPluginLoader pluginLoader(d->content.plugin()); if(d->content.pluginVersion() == WIDGET_INTERFACE_VERSION) { d->plugin.reset(dynamic_cast(pluginLoader.instance())); if(d->plugin.isNull()) { qWarning() << "Fail to load widget plugin:" << id() << d->content.plugin(); } } else { qWarning() << "Plugin version unmatched:" << id() << d->content.plugin(); } } } /*! \property Widget::visible 插件的可见状态. */ bool Widget::visible() const { return false; } void Widget::setVisible(bool visible) { if (d->visible == visible) { return; } d->visible = visible; Q_EMIT visibleChanged(); } /*! \property Widget::plugin 该属性表示 \c widget 中加载的以动态库形式安装的插件. */ WidgetInterface* Widget::plugin() const { loadPlugin(); return d->plugin.data(); } } // UkuiQuick ukui-quick/framework/widget/island.h0000664000175000017500000000616115153755732016513 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_ISLAND_H #define UKUI_QUICK_ISLAND_H #include #include #include "config.h" #include "widget-container.h" namespace UkuiQuick { class IslandPrivate; /** * @class Island 岛,存放着一堆'view'. * 一般情况下,island表示一个应用程序,只为这个应用加载配置文件,并设置基本的组信息,不负责其他业务逻辑, * 配置文件默认分组为:"_views" * * 一个island有多个'View'。 * 但是只能有一个view处于实例化状态,也就是只能有一个顶级View. * */ class Island : public QObject { Q_OBJECT public: /** * 构造方法 * @param id island的id,任意字符串 * @param app 当前应用名称 */ explicit Island(const QString &id, const QString &app, QObject *parent = nullptr); Island() = delete; ~Island() override; QString id() const; QString app() const; Config *config() const; /** * 加载配置文件中的一个view,默认加载 * @param id view的ID * @param emitChangeSignal 加载成功后是否发送信号(自动加载ui) * @return 加载成功返回true,否则返回false */ bool loadMainView(const QString &id, bool emitChangeSignal = true); /** * 加载配置文件中的一个view * @param id view的ID * @param instanceId view的instanceId,用于区分多个相同id的view * @param emitChangeSignal 加载成功后是否发送信号(自动加载ui) * @return 加载成功返回true,否则返回false */ bool loadMainView(const QString &id, int instanceId, bool emitChangeSignal = true); /** * 主view * 使用前应该进行判断 * @return */ WidgetContainer *mainView() const; /** * 手动设置一个主view,不用loader加载 * @param view */ void setMainView(WidgetContainer *view, bool emitChangeSignal = true); /** * 内部view的id列表 * @return 全部view的id */ QStringList views() const; /** * view的数量 * @return */ int viewCount() const; /** * 默认分组的组名 * @return */ static QString viewGroupName(); /** * 默认分组的键名 * @return */ static QString viewGroupKey(); Q_SIGNALS: void mainViewChanged(); private: IslandPrivate *d {nullptr}; }; } // UkuiQuick #endif //UKUI_QUICK_ISLAND_H ukui-quick/framework/widget/widget-metadata.h0000664000175000017500000000576415153755732020312 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_WIDGET_METADATA_H #define UKUI_QUICK_WIDGET_METADATA_H #include #include #include #include #include #include "types.h" namespace UkuiQuick { class WidgetMetadataPrivate; class WidgetMetadata { public: enum Host { Undefined = 0x00000000, /** *@brief 用于panel类型container,如ukui-panel */ Panel = 0x00000001, /** *@brief 用于sidebar类型container,如ukui-sidebar中的通知中心等从屏幕侧边划入的面板 */ SideBar = 0x00000002, /** *@brief 用于desktop类型container */ Desktop = 0x00000004, /** *@brief 可以加载到taskManager的图标上 */ TaskManager = 0x00000008, /** *@brief 用于加载到快捷操作面板上,作为对应快捷操作的二级菜单 */ Shortcut = 0x00000010, /** *@brief 用于所有类型container */ All = Panel | SideBar | Desktop | TaskManager | Shortcut, /** * @brief 桌面壁纸插件 */ Wallpaper, /** * @brief 桌面布局插件 */ DesktopView }; Q_DECLARE_FLAGS(Hosts, Host); WidgetMetadata(); explicit WidgetMetadata(const QString &root); WidgetMetadata(const WidgetMetadata &other); WidgetMetadata &operator=(const WidgetMetadata &other); bool operator==(const WidgetMetadata &other) const; ~WidgetMetadata(); bool isValid() const; const QDir &root() const; QString id() const; QString icon() const; QString name() const; QString tooltip() const; QString version() const; QString website() const; QString bugReport() const; QString description() const; QVariantList authors() const; QVariantMap contents() const; Hosts showIn() const; Types::WidgetType widgetType() const; QStringList applicationId() const; QString thumbnail() const; private: QSharedDataPointer d; }; } // UkuiQuick Q_DECLARE_OPERATORS_FOR_FLAGS(UkuiQuick::WidgetMetadata::Hosts) Q_DECLARE_TYPEINFO(UkuiQuick::WidgetMetadata, Q_MOVABLE_TYPE); #endif //UKUI_QUICK_WIDGET_METADATA_H ukui-quick/framework/widget/widget-container.h0000664000175000017500000000711615153755732020505 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_WIDGET_CONTAINER_H #define UKUI_QUICK_WIDGET_CONTAINER_H #include #include #include #include #include #include #include "types.h" #include "margin.h" #include "widget.h" #include "widget-loader.h" namespace UkuiQuick { class Island; class Widget; class WidgetContainerPrivate; class WidgetContainer : public Widget { Q_OBJECT Q_PROPERTY(QString appid READ appid CONSTANT FINAL) Q_PROPERTY(UkuiQuick::Margin* margin READ margin CONSTANT FINAL) Q_PROPERTY(UkuiQuick::Margin* padding READ padding CONSTANT FINAL) Q_PROPERTY(int radius READ radius NOTIFY radiusChanged FINAL) Q_PROPERTY(QRect geometry READ geometry NOTIFY geometryChanged FINAL) Q_PROPERTY(UkuiQuick::Types::Pos position READ position NOTIFY positionChanged FINAL) Q_PROPERTY(QScreen* screen READ screen NOTIFY screenChanged FINAL) Q_PROPERTY(WidgetMetadata::Host host READ host WRITE setHost NOTIFY hostChanged) public: explicit WidgetContainer(QObject *parent = nullptr); explicit WidgetContainer(const WidgetMetadata &metaData, QObject *parent = nullptr); ~WidgetContainer(); Types::WidgetType type() const override; // prop QString appid() const; QRect geometry() const; void setGeometry(QRect geometry); int radius() const; void setRadius(int radius); Types::Pos position() const; void setPosition(Types::Pos position); Margin* margin() const; Margin* padding() const; // widgets void addWidget(Widget *widget); void addWidget(const QString &id, int instanceId); QVector widgets() const; Widget *widget(int instanceId) const; // loader static WidgetLoader &widgetLoader(); void removeWidget(QString id); void removeWidget(int index); void removeWidget(Widget *widget); QScreen* screen() const; void setScreen(QScreen *screen); bool active() const override; [[deprecated]]WidgetMetadata::Host host() const; [[deprecated]]void setHost(WidgetMetadata::Host host); WidgetMetadata::Hosts hosts() const; void setHosts(WidgetMetadata::Hosts hosts); const Island *island() const; void setIsland(Island *island); void setContainer(WidgetContainer* container) override; void setConfig(Config* config) override; void setDisableInstances(const QList& instanceIds); void addDisableInstance(int id); void removeDisableInstance(int id); QList disableInstances(); Q_SIGNALS: void widgetAdded(UkuiQuick::Widget *widget); void widgetRemoved(UkuiQuick::Widget *widget); void geometryChanged(); void screenChanged(); void radiusChanged(); void positionChanged(); [[deprecated]]void hostChanged(); void hostsChanged(); private: WidgetContainerPrivate *d {nullptr}; }; } // UkuiQuick #endif //UKUI_QUICK_WIDGET_CONTAINER_H ukui-quick/framework/widget/widget-metadata.cpp0000664000175000017500000002712115153755732020634 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "widget-metadata.h" #include #include #include #include #include #include #include namespace UkuiQuick { class WidgetMetadataPrivate : public QSharedData { public: WidgetMetadataPrivate() : m_isValid(false){}; WidgetMetadataPrivate(const QString &root); WidgetMetadataPrivate(const WidgetMetadataPrivate &other) : QSharedData(other), m_isValid(other.m_isValid), m_id(other.m_id), m_root(other.m_root), m_object(other.m_object) {}; ~WidgetMetadataPrivate() {}; static QString localeKey(const QString &key); bool m_isValid; QString m_id; QDir m_root; QJsonObject m_object; }; WidgetMetadataPrivate::WidgetMetadataPrivate(const QString& root) { if(root.isEmpty()) { return; } m_root = QDir(root); if (!m_root.entryList(QDir::Files).contains("metadata.json")) { qWarning() << "loadWidgetPackage: can not find metadata.json, path:" << m_root.path(); return; } QFile file(m_root.absoluteFilePath("metadata.json")); if (!file.open(QFile::ReadOnly)) { return; } QTextStream iStream(&file); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) iStream.setCodec("UTF-8"); #else iStream.setEncoding(QStringConverter::Utf8); #endif QJsonParseError parseError {}; QJsonDocument document = QJsonDocument::fromJson(iStream.readAll().toUtf8(), &parseError); file.close(); if (parseError.error != QJsonParseError::NoError) { qWarning() << "metadata.json parse error:" << parseError.errorString(); return; } if (!document.isObject()) { qWarning() << "metadata.json parse error: Is not object."; return; } QJsonObject object = document.object(); QJsonValue id = object.value(QLatin1String("Id")); if (id.type() == QJsonValue::Undefined) { qWarning() << "metadata: Id is undefined."; return; } if ((m_id = id.toString()) != m_root.dirName()) { qWarning() << "metadata: dir not equal id."; return; } m_object = object; m_isValid = true; } QString WidgetMetadataPrivate::localeKey(const QString& key) { return (key + "[" + QLocale::system().name() + "]"); } /*! \class UkuiQuick::WidgetMetadata \inmodule UkuiQuickFramework \inheaderfile widget-metadata.h \brief 插件元数据 WidgetMetadata表示一个插件的原始元数据信息,所谓原始元数据,就是插件在打包时安装的metadata.json文件中提供的信息. 原始元数据在插件框架中非常重要,它用于描述一个插件,包括插件的名称,\c ID,介绍等基础信息,还有插件的类型 \l Host,国际化,\c UI 入口等信息. 实际上,他可以类比为原生linux应用的 \c "desktop" 文件. \l WidgetMetaData在一个 \l Widget 首次加载时加载,并且在同一个插件被多次加载时共享. \c Widget 提供了在运行时访问全部 \c metadata 信息的功能,并可以修改部分信息,如 \c Tooltip文本等(仅在运行时生效,不会直接修改 \c json 文件). \sa UkuiQuick::Widget */ /*! \enum WidgetMetadata::Host 插件适用的容器类型. \value Undefined \value Panel 用于 \c panel 类型 \c container ,如 \c ukui-panel \value SideBar 用于 \c sidebar 类型 \c container ,如 \c ukui-sidebar 中的通知中心等从屏幕侧边划入的面板 \value Desktop 用于 \c desktop 类型 \c container \value TaskManager 可以加载到\c taskManager 的图标上 \value Shortcut 用于加载到快捷操作面板上,作为对应快捷操作的二级菜单 \value All 用于所有类型 \c container \value Wallpaper 桌面壁纸插件 \value DesktopView 桌面布局插件 */ /*! \brief 构造函数 */ WidgetMetadata::WidgetMetadata() : d(new WidgetMetadataPrivate) { } /*! \brief 从指定路径构造插件元数据. \a root 插件的路径,从该路径下的 \c metadata.json 文件加载元数据 */ WidgetMetadata::WidgetMetadata(const QString &root) : d(new WidgetMetadataPrivate(root)) { } /*! \brief 拷贝构造函数. */ WidgetMetadata::WidgetMetadata(const WidgetMetadata& other): d(other.d) { } /*! \brief 获取插件的元数据文件路径. */ const QDir &WidgetMetadata::root() const { return d->m_root; } /*! \brief 拷贝赋值运算符. */ WidgetMetadata& WidgetMetadata::operator=(const WidgetMetadata& other) { d = other.d; return *this; } /*! \brief 比较运算符,通过比较 id 判断是否相同. */ bool WidgetMetadata::operator==(const WidgetMetadata& other) const { return d->m_id == other.d->m_id; } WidgetMetadata::~WidgetMetadata() = default; /*! \brief 判断插件元数据是否有效. */ bool WidgetMetadata::isValid() const { return d->m_isValid; } /*! \brief 获取插件的唯一标识符. */ QString WidgetMetadata::id() const { return d->m_id; } /*! \brief 获取插件的图标. */ QString WidgetMetadata::icon() const { QJsonValue value = d->m_object.value(QLatin1String("Icon")); if (value.type() == QJsonValue::Undefined) { return "application-x-desktop"; } return value.toString(); } /*! \brief 获取插件的本地化名称. 该方法首先尝试获取当前语言环境下的本地化名称,如果未找到本地化名称,则返回默认名称. 如果连默认名称也未定义,则返回插件ID作为名称. */ QString WidgetMetadata::name() const { QJsonValue value = d->m_object.value(WidgetMetadataPrivate::localeKey("Name")); if (value.type() != QJsonValue::Undefined) { return value.toString(); } value = d->m_object.value(QLatin1String("Name")); if (value.type() == QJsonValue::Undefined) { return d->m_id; } return value.toString(); } /*! \brief 获取插件的本地化提示文本. 该方法首先尝试获取当前语言环境下的本地化提示文本,如果未找到本地化提示文本,则返回默认提示文本. 如果连默认提示文本也未定义,则返回插件ID作为提示文本. */ QString WidgetMetadata::tooltip() const { QJsonValue value = d->m_object.value(WidgetMetadataPrivate::localeKey("Tooltip")); if (value.type() != QJsonValue::Undefined) { return value.toString(); } value = d->m_object.value(QLatin1String("Tooltip")); if (value.type() == QJsonValue::Undefined) { return d->m_id; } return value.toString(); } /*! \brief 获取插件的版本号. */ QString WidgetMetadata::version() const { QJsonValue value = d->m_object.value(QLatin1String("Version")); if (value.type() == QJsonValue::Undefined) { return ""; } return value.toString(); } /*! \brief 获取插件的网站地址. */ QString WidgetMetadata::website() const { QJsonValue value = d->m_object.value(QLatin1String("Website")); if (value.type() == QJsonValue::Undefined) { return ""; } return value.toString(); } /*! \brief 获取插件的错误报告地址. 地址用于用户提交插件使用过程中遇到的问题。 */ QString WidgetMetadata::bugReport() const { QJsonValue value = d->m_object.value(QLatin1String("BugReport")); if (value.type() == QJsonValue::Undefined) { return "https://gitee.com/openkylin/ukui-panel/issues"; } return value.toString(); } /*! \brief 获取插件的本地化描述文本. 该方法首先尝试获取当前语言环境下的本地化描述文本,如果未找到本地化描述文本,则返回默认描述文本. 如果连默认描述文本也未定义,则返回插件ID作为描述文本. \sa name() */ QString WidgetMetadata::description() const { QJsonValue value = d->m_object.value(WidgetMetadataPrivate::localeKey("Description")); if (value.type() != QJsonValue::Undefined) { return value.toString(); } value = d->m_object.value(QLatin1String("Description")); if (value.type() == QJsonValue::Undefined) { return d->m_id; } return value.toString(); } /*! \brief 获取插件的作者列表. */ QVariantList WidgetMetadata::authors() const { QJsonValue value = d->m_object.value(QLatin1String("Authors")); if (value.type() == QJsonValue::Undefined || value.type() != QJsonValue::Array) { return {}; } return value.toArray().toVariantList(); } /*! \brief 获取插件的内容配置. */ QVariantMap WidgetMetadata::contents() const { QJsonValue value = d->m_object.value(QLatin1String("Contents")); if (value.type() == QJsonValue::Undefined || value.type() != QJsonValue::Object) { return {}; } return value.toObject().toVariantMap(); } /*! \brief 获取插件适用的容器类型. \sa Hosts */ WidgetMetadata::Hosts WidgetMetadata::showIn() const { QJsonValue value = d->m_object.value(QLatin1String("ShowIn")); if (value.type() == QJsonValue::Undefined || value.type() != QJsonValue::String) { return All; } QStringList hostList = value.toString().split(","); if (hostList.isEmpty()) { return All; } Hosts hosts = Undefined; for (const QString &host : hostList) { if (host == QStringLiteral("Panel")) { hosts |= Panel; } else if (host == QStringLiteral("SideBar")) { hosts |= SideBar; } else if (host == QStringLiteral("Desktop")) { hosts |= Desktop; } else if (host == QStringLiteral("All")) { hosts |= All; } else if (host == QStringLiteral("TaskManager")) { hosts |= TaskManager; } else if (host == QStringLiteral("Shortcut")) { hosts |= Shortcut; } else if (host == QStringLiteral("Wallpaper")) { hosts |= Wallpaper; } else if (host == QStringLiteral("DesktopView")) { hosts |= DesktopView; } } return hosts; } /*! \brief 获取插件的类型. \sa Types::WidgetType */ Types::WidgetType WidgetMetadata::widgetType() const { QJsonValue value = d->m_object.value(QLatin1String("WidgetType")); if (value.toString() == QStringLiteral("Container")) { return Types::Container; } return Types::Widget; } /*! \brief 获取插件的应用程序列表. */ QStringList WidgetMetadata::applicationId() const { const QJsonValue value = d->m_object.value(QLatin1String("ApplicationId")); if (value.type() == QJsonValue::Undefined || value.type() != QJsonValue::Array) { return {}; } QStringList ids; for (auto id : value.toArray()) { ids.append(id.toString()); } return ids; } /*! \brief 获取插件的缩略图. */ QString WidgetMetadata::thumbnail() const { const QJsonValue value = d->m_object.value(QLatin1String("Thumbnail")); if (value.type() == QJsonValue::Undefined ) { return {}; } return value.toString(); } } ukui-quick/framework/widget/widget-interface.h0000664000175000017500000000326115153755732020460 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #ifndef UKUI_QUICK_WIDGETINTERFACE_H #define UKUI_QUICK_WIDGETINTERFACE_H #define WidgetInterface_iid "org.ukui.quick.widgetInterface" #define WIDGET_INTERFACE_VERSION "1.0.0" #include #include namespace UkuiQuick { /*! \class UkuiQuick::WidgetInterface \inmodule UkuiQuickFramework \inheaderfile widget-interface.h \brief 插件接口. WidgetInterface 是一个插件接口定义,插件可以选择实现这个接口以实现某些特性功能,如增加自定义右键菜单项等. 这是一个可选接口,但可以脱离qmlUI单独加载,即可以实现一个不包含UI的插件,仅包含一个动态库的插件. */ class WidgetInterface { public: WidgetInterface()= default; virtual QList actions() { return {}; }; }; } Q_DECLARE_INTERFACE(UkuiQuick::WidgetInterface, WidgetInterface_iid) Q_DECLARE_METATYPE(UkuiQuick::WidgetInterface *); #endif //UKUI_QUICK_WIDGETINTERFACE_H ukui-quick/framework/widget/widget-content.h0000664000175000017500000000353615153755732020177 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_WIDGET_CONTENT_H #define UKUI_QUICK_WIDGET_CONTENT_H #include #include namespace UkuiQuick { class WidgetMetadata; class WidgetContentPrivate; /** * widget包内的文件映射 */ class WidgetContent { public: enum ContentKey { Main, Config, I18n, Plugin, PluginVersion, PluginPreload }; enum ConfigPolicy { Null, LocalOnly }; WidgetContent() = delete; explicit WidgetContent(const WidgetMetadata &metaData); virtual ~WidgetContent(); QString main() const; QString data() const; QString i18n() const; QString config() const; QString plugin() const; QString pluginVersion() const; bool pluginPreload() const; ConfigPolicy configPolicy() const; // Widget的根目录 QString rootPath() const; // 获取文件的绝对路径 QString filePath(const ContentKey &key) const; // 获取文件文件的Url QUrl fileUrl(const ContentKey &key) const; private: WidgetContentPrivate *d {nullptr}; }; } // UkuiQuick #endif //UKUI_QUICK_WIDGET_CONTENT_H ukui-quick/framework/widget/widget.h0000664000175000017500000001352715153755732016530 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_WIDGET_H #define UKUI_QUICK_WIDGET_H #include #include #include #include "widget-metadata.h" #include "widget-content.h" #include "widget-interface.h" #include "config-property-map.h" #include "types.h" #include "config.h" namespace UkuiQuick { class Config; class WidgetPrivate; class WidgetContainer; /** * 一个"Widget"。 * widget包含元数据和内容(Content)数据,存放插件的基础数据属性。 * widget通过元数据进行初始化,被加载到某个容器后,容器赋予其更多新属性。 */ class Widget : public QObject { Q_OBJECT Q_PROPERTY(QString id READ id CONSTANT FINAL) Q_PROPERTY(QString website READ website CONSTANT FINAL) Q_PROPERTY(QString bugReport READ bugReport CONSTANT FINAL) Q_PROPERTY(QString description READ description CONSTANT FINAL) Q_PROPERTY(QVariantList authors READ authors CONSTANT FINAL) Q_PROPERTY(QString icon READ icon WRITE setIcon NOTIFY iconChanged) Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(QString tooltip READ tooltip WRITE setTooltip NOTIFY tooltipChanged) Q_PROPERTY(QString version READ version WRITE setVersion NOTIFY versionChanged) Q_PROPERTY(QQmlListProperty actions READ qmlActions) Q_PROPERTY(int instanceId READ instanceId CONSTANT FINAL) /** * 将Widget置为激活状态,应用可能会根据这个状态设置一些UI交互,比如任务栏不会自动隐藏。 */ Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged FINAL) /** * 控制Widget希望显示的位置 */ Q_PROPERTY(WidgetMetadata::Hosts showIn READ showIn CONSTANT FINAL) Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged FINAL) /** * Widget的局部配置接口,该配置仅在当前Container内有效 */ Q_PROPERTY(UkuiQuick::ConfigPropertyMap *config READ qmlLocalConfig CONSTANT FINAL) /** *Widget的独立配置接口,可由ConfigPolicy控制,可在多个Container间共享 */ Q_PROPERTY(UkuiQuick::ConfigPropertyMap *globalConfig READ qmlGlobalConfig CONSTANT FINAL) Q_PROPERTY(UkuiQuick::WidgetContainer *container READ container NOTIFY containerChanged FINAL) Q_PROPERTY(UkuiQuick::Types::Orientation orientation READ orientation NOTIFY orientationChanged FINAL) /** *widget中加载的以动态库形式安装的插件 */ Q_PROPERTY(const UkuiQuick::WidgetInterface* plugin READ plugin CONSTANT FINAL) /** *缩放系数,一般在container里控制,widget根据需要读取,例如根据缩放系数进行布局调整等 */ Q_PROPERTY(double ratio READ ratio WRITE setRatio NOTIFY ratioChanged FINAL) public: explicit Widget(const WidgetMetadata& metaData, QObject *parent=nullptr); ~Widget() override; QString id() const; QString icon() const; void setIcon(const QString &icon); QString name() const; void setName(const QString &name); QString tooltip() const; void setTooltip(const QString &tooltip); QString version() const; void setVersion(const QString &version); QString website() const; QString bugReport() const; QString description() const; QVariantList authors() const; const WidgetMetadata &metadata() const; const WidgetContent &content() const; // 初始化UI组件时,如果出现错误,会存放数据该属性 void setUiError(const QString &error); QString uiError() const; bool hasUiError() const; QList actions() const; void setActions(const QList& actions); void setAppendActions(QList actions); // qml接口 QQmlListProperty qmlActions(); // widget的唯一id,在某个应用中是唯一的,用于确认widget身份 int instanceId() const; void setInstanceId(int instanceId); virtual bool active() const; virtual void setActive(bool active); virtual Types::WidgetType type() const; Types::Orientation orientation() const; void setOrientation(Types::Orientation o); // qml api ConfigPropertyMap *qmlLocalConfig() const; ConfigPropertyMap *qmlGlobalConfig() const; Config *config() const; virtual void setConfig(Config *config); Config *globalConfig() const; WidgetContainer *container() const; WidgetMetadata::Hosts showIn() const; bool visible() const; void setVisible(bool visible); WidgetInterface *plugin() const; virtual void setContainer(WidgetContainer *container); double ratio() const; virtual void setRatio(double ratio); Q_SIGNALS: void iconChanged(); void nameChanged(); void tooltipChanged(); void versionChanged(); void actionsChanged(); void activeChanged(); void visibleChanged(); void orientationChanged(); void ratioChanged(); // widget生命周期信号 void aboutToDeleted(); void containerChanged(const QString &oldId, const QString &newId); private: void loadPlugin() const; private: WidgetPrivate *d {nullptr}; friend class WidgetContainer; }; } // UkuiQuick #endif //UKUI_QUICK_WIDGET_H ukui-quick/framework/widget/island.cpp0000664000175000017500000001622515153755732017050 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "island.h" #include "config-loader.h" #include #include namespace UkuiQuick { class IslandPrivate { public: explicit IslandPrivate(QString id, QString app); // 当前Island的Id,可以是任意字符串 QString islandId; // island所在的app名称, 一般是进程名称, 如: ukui-panel, ukui-menu等. QString appId; // 当前Island的主配置文件 Config *config {nullptr}; // island当前加载的顶级View, 有且只有一个顶级View WidgetContainer *view {nullptr}; }; IslandPrivate::IslandPrivate(QString id, QString app) : islandId(std::move(id)), appId(std::move(app)) { } /*! \class UkuiQuick::Island \inmodule UkuiQuickFramework \inheaderfile island.h \brief 岛,存放着一堆 \c view. 一般情况下,island表示一个应用程序,只为这个应用加载配置文件,并设置基本的组信息,不负责其他业务逻辑,配置文件默认分组为:\c _views. 一个island有多个 \c View,但是只能有一个处于实例化状态,也就是只能有一个顶级 \c View. */ /*! \brief 构造函数 \a id island的id,任意字符串, \a app 当前应用名称 */ Island::Island(const QString &id, const QString &app, QObject *parent) : QObject(parent), d(new IslandPrivate(id, app)) { // _root auto config = ConfigLoader::getConfig(id, ConfigLoader::Local, app); if (!config) { config = new Config(QVariantMap(), this); qWarning() << "Island: can not load config for:" << id << app << ", use default."; } // 初始配置分组信息 config->addGroupInfo(Island::viewGroupName(), Island::viewGroupKey()); d->config = config; } Island::~Island() { if (d->view) { Q_EMIT d->view->aboutToDeleted(); d->view->deleteLater(); d->view = nullptr; } if (d) { delete d; d = nullptr; } } /*! \brief 获取当前 \c island的配置 */ Config *Island::config() const { return d->config; } /*! \brief 获取当前 \c island的 \c id */ QString Island::id() const { return d->islandId; } /*! \brief 获取当前 \c island的应用名称 */ QString Island::app() const { return d->appId; } /*! \brief 获取当前island的主view,使用前应该进行判断 */ WidgetContainer *Island::mainView() const { return d->view; } /*! \brief 加载配置文件中的一个view,默认加载 \a id \c view的 \c ID ,\a emitChangeSignal 加载成功后是否发送信号(自动加载 \c ui) Returns 加载成功返回 \c true,否则返回 \c false */ bool Island::loadMainView(const QString &id, bool emitChangeSignal) { auto list = d->config->children(viewGroupName()); Config *config = nullptr; for(auto c : list) { if(c->getValue(QStringLiteral("id")) == id) { config = c; } } if (!config) { qWarning() << "Config for:" << id << "not found, generating default one"; QVariantMap data; data.insert(QStringLiteral("id"), id); int instanceId = d->config->children(UkuiQuick::Island::viewGroupName()).count(); data.insert(QStringLiteral("instanceId"), instanceId); data.insert(QStringLiteral("widgets"), QVariantList()); d->config->addChild(viewGroupName(), data); config = d->config->child(viewGroupName(), instanceId); } // TODO: 从一个'widget包'进行加载 auto widget = WidgetContainer::widgetLoader().loadWidget(id); auto container = qobject_cast(widget); if (!container) { qWarning() << "The main view is not 'Container'" << id; delete widget; return false; } container->setInstanceId(config->getValue(QStringLiteral("instanceId")).toInt()); container->setConfig(config); setMainView(container, emitChangeSignal); return true; } /*! \brief 加载配置文件中的一个 \c view. \a id \c view的 \c ID ; \a instanceId \c view的 \c instanceId, 用于区分多个相同 \c id的 \c view ; \a emitChangeSignal 加载成功后是否发送信号(自动加载 \c ui) Returns 加载成功返回 \c true,否则返回 \c false */ bool Island::loadMainView(const QString& id, int instanceId, bool emitChangeSignal) { auto list = d->config->children(viewGroupName()); Config *config = nullptr; for(auto c : list) { if(c->getValue(QStringLiteral("id")) == id && c->getValue(QStringLiteral("instanceId")).toInt() == instanceId) { config = c; } } if (!config) { qWarning() << "Config for:" << id << "not found, generating default one!"; QVariantMap data; data.insert(QStringLiteral("id"), id); data.insert(QStringLiteral("instanceId"), instanceId); data.insert(QStringLiteral("widgets"), QVariantList()); d->config->addChild(viewGroupName(), data); config = d->config->child(viewGroupName(), instanceId); } auto widget = WidgetContainer::widgetLoader().loadWidget(id); auto container = qobject_cast(widget); if (!container) { qWarning() << "The main view is not 'Container'" << id; delete widget; return false; } container->setInstanceId(instanceId); container->setConfig(config); setMainView(container, emitChangeSignal); return true; } /*! \brief 手动设置一个主view,不用loader加载. */ void Island::setMainView(WidgetContainer *view, bool emitChangeSignal) { if (view == d->view) { return; } if (d->view) { Q_EMIT d->view->aboutToDeleted(); d->view->deleteLater(); d->view = nullptr; } d->view = view; if (view) { view->setIsland(this); } if(emitChangeSignal) { Q_EMIT mainViewChanged(); } } /*! \brief 获取内部\c view的 \c 默认分组的组名 */ QString Island::viewGroupName() { return QStringLiteral("_views"); } /*! \brief 获取\c view分组 \c key */ QString Island::viewGroupKey() { return QStringLiteral("instanceId"); } /*! \brief 获取内部\c view的 \c id 列表 */ QStringList Island::views() const { QStringList list; for (const auto &item : d->config->children(Island::viewGroupName())) { list << item->id().toString(); } return list; } /*! \brief 获取内部\c view的数量 */ int Island::viewCount() const { return d->config->numberOfChildren(Island::viewGroupName()); } } // UkuiQuick ukui-quick/framework/widget/widget-loader.h0000664000175000017500000000333215153755732017765 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_WIDGET_LOADER_H #define UKUI_QUICK_WIDGET_LOADER_H #include #include #include "widget-metadata.h" namespace UkuiQuick { class Widget; class WidgetLoaderPrivate; class WidgetLoader : public QObject { Q_OBJECT public: explicit WidgetLoader(QObject *parent = nullptr); ~WidgetLoader() override; Widget *loadWidget(const QString &id); WidgetMetadata loadMetadata(const QString &id); void addWidgetSearchPath(const QString &path); void setShowInFilter(WidgetMetadata::Hosts hosts); static WidgetLoader &globalLoader(); /** * @return 获取支持当前host的并且ApplicationId包含 @param applicationId 的所有插件id */ QStringList widgets(const QString &applicationId = ""); /** * * @return 获取所有已安装插件的元数据列表 */ QList widgetsMetadata(); private: WidgetLoaderPrivate *d {nullptr}; }; } // UkuiQuick #endif //UKUI_QUICK_WIDGET_LOADER_H ukui-quick/framework/widget/widget-loader.cpp0000664000175000017500000001571715153755732020332 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "widget-loader.h" #include "widget.h" #include "widget-container.h" #include #include using namespace UkuiQuick; namespace UkuiQuick { class WidgetLoaderPrivate { public: explicit WidgetLoaderPrivate() { widgetSearchPath << ":/ukui/widgets" << (QDir::homePath() + "/.local/share/ukui/widgets") << "/usr/share/ukui/widgets" << "/opt/system/resource/ukui/ukui/widgets"; } WidgetMetadata findMetadata(const QString &id) const { return m_metadataCache.value(id); } void cacheMetadata(const WidgetMetadata& metaData) { m_metadataCache.insert(metaData.id(), metaData); // if (!m_metadataCache.contains(metaData.id())) { // m_metadataCache.insert(metaData.id(), metaData); // } } public: WidgetMetadata loadMetaData(const QString &id); QStringList widgetSearchPath; WidgetMetadata::Hosts hosts = WidgetMetadata::All; private: QHash m_metadataCache; }; } WidgetMetadata WidgetLoaderPrivate::loadMetaData(const QString &id) { WidgetMetadata metaData = findMetadata(id); if (metaData.isValid()) { return metaData; } for (const auto &path : widgetSearchPath) { QDir dir(path); if (!dir.exists()) { continue; } QStringList subDirList = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); if (subDirList.contains(id)) { qDebug() << "loadMetaData for:" << id; metaData = WidgetMetadata(dir.absoluteFilePath(id)); if (metaData.isValid()) { cacheMetadata(metaData); return metaData; } break; } } qWarning() << "can not find widget:" << id; return {}; } /*! \class UkuiQuick::WidgetLoader \inmodule UkuiQuickFramework \inheaderfile widget-loader.h \brief 插件加载器 插件加载器,WidgetLoader用于根据插件ID加载插件,默认加载路径为: \code { ":/ukui/widgets" "/usr/share/ukui/widgets" "~/.local/share/ukui/widgets" } \endcode 可在运行时临时增加查找目录,还继承了插件种类判断功能,插件元数据中有一个类型 \l (host)字段,可以通过指定插件加载器所在的应用(宿主)类型,可以自动过滤掉不属于当前类型的插件. 例如,在某个应用的插件选择页面只显示可以被加载到当前应用的插件. 插件加载器的扫描路径逻辑可以根据需求进行扩充,例如:不同分类的插件可以区分不同目录等 */ WidgetLoader::WidgetLoader(QObject *parent) : QObject(parent), d(new WidgetLoaderPrivate()) { } /*! \brief 根据插件 \a id 加载并实例化插件对象. */ Widget *WidgetLoader::loadWidget(const QString &id) { WidgetMetadata metaData = loadMetadata(id); if (!metaData.isValid()) { return nullptr; } if (!(d->hosts & metaData.showIn())) { return nullptr; } Widget *widget = nullptr; if (metaData.widgetType() == Types::Container) { widget = new WidgetContainer(metaData); } else { widget = new Widget(metaData); } return widget; } /*! \brief 根据插件 \a id 加载插件元数据. */ WidgetMetadata WidgetLoader::loadMetadata(const QString &id) { return d->loadMetaData(id); } WidgetLoader::~WidgetLoader() { if (d) { delete d; d = nullptr; } } /*! \brief 向插件加载器添加自定义搜索路径. 添加的路径会被插入到搜索路径列表的最前面,具有最高优先级。 */ void WidgetLoader::addWidgetSearchPath(const QString &path) { if(!d->widgetSearchPath.contains(path) && !path.isEmpty()) { // 添加的自定义搜索路径优先级最高 d->widgetSearchPath.prepend(path); } } /*! \brief 获取全局插件加载器实例. */ WidgetLoader &WidgetLoader::globalLoader() { static WidgetLoader loader; return loader; } /*! 获取支持当前 \l host 的并且 \c ApplicationId 包含 \a applicationId 的所有插件 \c id. */ QStringList WidgetLoader::widgets(const QString &applicationId) { if(applicationId.isEmpty()) { return{}; } QStringList widgets; for (const auto &path : d->widgetSearchPath) { QDir dir(path); if (!dir.exists()) { continue; } QStringList subDirList = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); for (const auto &id : subDirList) { WidgetMetadata metaData = d->findMetadata(id); if(!metaData.isValid()) { metaData = WidgetMetadata(dir.absoluteFilePath(id)); if(!metaData.isValid()) { continue; } d->cacheMetadata(metaData); } if (d->hosts & metaData.showIn()) { if(!metaData.applicationId().contains(applicationId)) { continue; } if(!widgets.contains(id)) { widgets.append(id); } } } } return widgets; } /*! \brief 获取所有已安装插件的元数据列表. */ QList WidgetLoader::widgetsMetadata() { QList widgets; for (const auto &path : d->widgetSearchPath) { QDir dir(path); if (!dir.exists()) { continue; } QStringList subDirList = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); for (const auto &id : subDirList) { WidgetMetadata metaData = d->findMetadata(id); if(!metaData.isValid()) { metaData = WidgetMetadata(dir.absoluteFilePath(id)); if(!metaData.isValid()) { continue; } } if (d->hosts & metaData.showIn()) { d->cacheMetadata(metaData); if(!widgets.contains(metaData)) { widgets.append(metaData); } } } } return widgets; } /*! \brief 设置插件加载器的宿主类型过滤条件. */ void WidgetLoader::setShowInFilter(const WidgetMetadata::Hosts hosts) { d->hosts = hosts; } ukui-quick/framework/widget/widget-container.cpp0000664000175000017500000003112315153756415021032 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "widget-container.h" #include #include "widget.h" #include "island.h" namespace UkuiQuick { class WidgetContainerPrivate { public: WidgetContainerPrivate(WidgetContainer *q) : q(q){}; bool containActiveWidgets(); void setDisableInstancesId(const QList& instanceIds, bool removeDumplicated = true); void loadConfig(Config* config); void removeDisableInstances(); // prop int radius {0}; QRect geometry; QScreen *screen {nullptr}; Margin *margin {nullptr}; Margin *padding {nullptr}; Types::Pos position {Types::NoPosition}; // widgets QVector widgets; Island *island {nullptr}; WidgetMetadata::Host host = WidgetMetadata::All; WidgetMetadata::Hosts hosts = WidgetMetadata::All; bool configLoaded = false; QList disableInstances; WidgetContainer *q = nullptr; }; bool WidgetContainerPrivate::containActiveWidgets() { return std::any_of(widgets.constBegin(), widgets.constEnd(), [] (Widget *widget) { return widget->active(); }); } void WidgetContainerPrivate::setDisableInstancesId(const QList& instanceIds, bool removeDumplicated) { //去重 if(removeDumplicated) { QHash uniqueElements; foreach(int element, instanceIds) { uniqueElements[element] = true; } disableInstances.clear(); disableInstances.reserve(uniqueElements.size()); for (auto it = uniqueElements.begin(); it != uniqueElements.end(); ++it) { disableInstances.append(it.key()); } } else { disableInstances = instanceIds; } QJsonArray jsonArray; for (int value : disableInstances) { jsonArray.append(value); } q->config()->setValue(QStringLiteral("disabledInstances"), jsonArray); } void WidgetContainerPrivate::loadConfig(Config* config) { if (config && !configLoaded) { auto list = config->children(QStringLiteral("widgets")); auto array = config->getValue(QStringLiteral("disabledInstances")).toJsonArray(); disableInstances.clear(); for(auto value : array) { disableInstances.append(value.toInt()); } for (const auto widgetConfig: list) { if(disableInstances.contains(widgetConfig->getValue(QStringLiteral("instanceId")).toInt())) { continue; } q->addWidget(widgetConfig->getValue(QStringLiteral("id")).toString(), widgetConfig->getValue(QStringLiteral("instanceId")).toInt()); } configLoaded = true; } } void WidgetContainerPrivate::removeDisableInstances() { for(Widget *w : widgets) { if(disableInstances.contains(w->instanceId())) { q->removeWidget(w); } } } /*! \class UkuiQuick::WidgetContainer \inmodule UkuiQuickFramework \inheaderfile widget-container.h \brief 插件容器. \c WidgetContainer 继承自 Widget,在其基础上增加了一些用于插件容器的属性,如屏幕信息,边距等,实现了插件加载和配置文件自动加载功能. 一般情况下,\c WidgetContainer 用于加载 Widget,但一个 \c WidgetContainer 还可能与 \c Island 有关联(作为 \c island 的主视图). */ /*! \brief 默认构造函数,创建一个空的插件容器. */ WidgetContainer::WidgetContainer(QObject *parent) : WidgetContainer({}, parent) {} /*! \brief 带元数据的构造函数,创建一个指定元数据的插件容器. */ WidgetContainer::WidgetContainer(const WidgetMetadata &metaData, QObject *parent) : Widget(metaData, parent), d(new WidgetContainerPrivate(this)) { d->margin = new Margin(this); d->padding = new Margin(this); qRegisterMetaType(); } WidgetContainer::~WidgetContainer() { QVector widgets = d->widgets; for(auto w : widgets) { removeWidget(w); } if(d) { delete d; d = nullptr; } } /*! \brief 获取容器部件的类型标识. Returns Types::Container,表示这是一个容器类型的部件. \sa Types::WidgetType */ Types::WidgetType WidgetContainer::type() const { return Types::Container; } /*! \property WidgetContainer::appid 容器所属应用 */ QString WidgetContainer::appid() const { if (d->island) { return d->island->app(); } return {}; } /*! \brief 添加一个插件部件 \a widget到容器中. */ void WidgetContainer::addWidget(Widget *widget) { if (!widget || d->widgets.contains(widget) || d->disableInstances.contains(widget->instanceId())) { return; } // 删除重复instanceId的widget auto it = std::find_if(d->widgets.constBegin(), d->widgets.constEnd(), [widget] (const Widget *w) { return w->instanceId() == widget->instanceId(); }); if (it != d->widgets.constEnd()) { removeWidget(*it); } connect(widget, &Widget::activeChanged, this, &Widget::activeChanged); d->widgets.append(widget); if (config() && !config()->child(QStringLiteral("widgets"), widget->instanceId())) { QVariantMap wData; wData.insert(QStringLiteral("id"), widget->id()); wData.insert(QStringLiteral("instanceId"), widget->instanceId()); config()->addChild(QStringLiteral("widgets"), wData); } widget->setContainer(this); Q_EMIT widgetAdded(widget); } /*! \brief 根据插件 \a id 和 \a instanceId 向容器中添加插件部件 \sa addWidget(Widget *widget) */ void WidgetContainer::addWidget(const QString &id, const int instanceId) { if (instanceId < 0 || d->disableInstances.contains(instanceId)) { return; } Widget *widget = WidgetContainer::widgetLoader().loadWidget(id); if (widget) { widget->setInstanceId(instanceId); addWidget(widget); } } /*! \brief 获取全局插件加载器实例 返回WidgetLoader的单例对象,用于加载和管理插件. \sa WidgetLoader::globalLoader() */ WidgetLoader &WidgetContainer::widgetLoader() { return WidgetLoader::globalLoader(); } /*! \brief 根据插件 \a id 从容器中移除部件 \sa removeWidget(int index) */ void WidgetContainer::removeWidget(QString id) { for(Widget *w : d->widgets) { if(w->id() == id) { removeWidget(d->widgets.indexOf(w)); } } } /*! \brief 从容器中移除指定的部件 \a widget. \sa removeWidget(int index) */ void WidgetContainer::removeWidget(Widget *widget) { if (!widget) { return; } removeWidget(d->widgets.indexOf(widget)); } /*! \brief 根据索引从容器中移除部件. \sa removeWidget(QString id), removeWidget(Widget *widget) */ void WidgetContainer::removeWidget(int index) { if (index < 0 || index >= d->widgets.size()) { return; } Widget *widget = d->widgets.takeAt(index); // 通知ContainerItem卸载WidgetItem,Widget即将被删除 Q_EMIT widgetRemoved(widget); // 发送Widget删除信号,这个操作会从全局的缓存中移除widget对应的WidgetItem并delete掉它 Q_EMIT widget->aboutToDeleted(); widget->disconnect(this); widget->deleteLater(); } /*! \property WidgetContainer::geometry 容器位置和大小. */ QRect WidgetContainer::geometry() const { return d->geometry; } void WidgetContainer::setGeometry(QRect geometry) { if (d->geometry == geometry) { return; } d->geometry = geometry; Q_EMIT geometryChanged(); } /*! \property WidgetContainer::margin 容器外边距. */ Margin* WidgetContainer::margin() const { return d->margin; } /*! \property WidgetContainer::padding 容器内边距. */ Margin *WidgetContainer::padding() const { return d->padding; } /*! \property WidgetContainer::screen 容器所在屏幕. */ QScreen *WidgetContainer::screen() const { return d->screen; } void WidgetContainer::setScreen(QScreen *screen) { if(d->screen == screen) { return; } d->screen = screen; Q_EMIT screenChanged(); } /*! \property WidgetContainer::radius 圆角半径. */ int WidgetContainer::radius() const { return d->radius; } void WidgetContainer::setRadius(int radius) { if (d->radius == radius) { return; } d->radius = radius; Q_EMIT radiusChanged(); } /*! \property WidgetContainer::position 容器位置. \sa Types::Pos */ Types::Pos WidgetContainer::position() const { return d->position; } void WidgetContainer::setPosition(Types::Pos position) { if (d->position == position) { return; } d->position = position; Q_EMIT positionChanged(); } /*! \brief 获取容器中的所有部件列表. */ QVector WidgetContainer::widgets() const { return d->widgets; } /*! \brief 根据 \a instanceId 获取指定部件. */ Widget* WidgetContainer::widget(int instanceId) const { for(const auto w : d->widgets) { if(w->instanceId() == instanceId) { return w; } } return nullptr; } /*! \overload */ bool WidgetContainer::active() const { if(d->containActiveWidgets()) { return true; } return Widget::active(); } /*! \deprecated */ WidgetMetadata::Host WidgetContainer::host() const { return d->host; } /*! \deprecated */ void WidgetContainer::setHost(WidgetMetadata::Host host) { if(d->host == host) { return; } d->host = host; widgetLoader().setShowInFilter(d->host); Q_EMIT hostChanged(); } /*! \brief 获取容器支持的类型列表. */ WidgetMetadata::Hosts WidgetContainer::hosts() const { return d->hosts; } /*! \brief 设置容器支持的 \a hosts 类型列表 \sa WidgetMetadata::Host */ void WidgetContainer::setHosts(WidgetMetadata::Hosts hosts) { if(d->hosts == hosts) { return; } d->hosts = hosts; widgetLoader().setShowInFilter(d->hosts); Q_EMIT hostsChanged(); } /*! \brief 获取关联的Island对象 */ const Island *WidgetContainer::island() const { return d->island; } /*! \brief 设置关联的 Island对象 \a island. */ void WidgetContainer::setIsland(Island *island) { if (d->island == island) { return; } d->island = island; auto config = island->config()->child(UkuiQuick::Island::viewGroupName(), instanceId()); if (config) { setConfig(config); d->loadConfig(this->config()); } } void WidgetContainer::setContainer(WidgetContainer* container) { Widget::setContainer(container); setScreen(container->screen()); connect(container, &UkuiQuick::WidgetContainer::screenChanged, this, [&, container](){ setScreen(container->screen()); }); setPosition(container->position()); connect(container, &UkuiQuick::WidgetContainer::positionChanged, this, [&, container](){ setPosition(container->position()); }); setConfig(container->config()->child(QStringLiteral("widgets"), instanceId())); setInstanceId(config()->getValue(QStringLiteral("instanceId")).toInt()); d->loadConfig(config()); } void WidgetContainer::setConfig(Config* config) { Widget::setConfig(config); if(config) { config->addGroupInfo(QStringLiteral("widgets"), QStringLiteral("instanceId")); } } void WidgetContainer::setDisableInstances(const QList& instanceIds) { d->setDisableInstancesId(instanceIds); d->removeDisableInstances(); } void WidgetContainer::addDisableInstance(int id) { if(d->disableInstances.contains(id)) { return; } d->setDisableInstancesId(d->disableInstances << id, false); d->removeDisableInstances(); } void WidgetContainer::removeDisableInstance(int id) { if(!d->disableInstances.contains(id)) { return; } d->disableInstances.removeAll(id); d->setDisableInstancesId(d->disableInstances, false); } QList WidgetContainer::disableInstances() { return d->disableInstances; } } // UkuiQuick ukui-quick/framework/widget/widget-content.cpp0000664000175000017500000001411315153755732020523 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "widget-content.h" #include "widget-metadata.h" #include #include using namespace UkuiQuick; namespace UkuiQuick { class WidgetContentPrivate { friend class WidgetContent; public: explicit WidgetContentPrivate(const WidgetMetadata &m) : contentsRoot(m.root()), contents(m.contents()) { } private: QDir contentsRoot; QVariantMap contents; }; } /*! \class UkuiQuick::WidgetContent \inmodule UkuiQuickFramework \inheaderfile widget-content.h \brief 插件内容. 该类负责管理插件中的各种资源文件路径,包括主界面文件、国际化文件、配置文件等. */ /*! \enum WidgetContent::ContentKey 插件内容类型. \value Main 主界面文件 \value Config 配置文件 \value I18n 国际化文件 \value Plugin 动态库文件 \value PluginVersion 插件版本号 \value PluginPreload 插件是否预加载 */ /*! \enum WidgetContent::ConfigPolicy 配置文件行为. \value Null 为空,将生成全局配置文件 \value LocalOnly 插件的配置文件将记录在宿主应用中 */ WidgetContent::WidgetContent(const WidgetMetadata &metaData) : d(new WidgetContentPrivate(metaData)) { } WidgetContent::~WidgetContent() { if (d) { delete d; d = nullptr; } } /*! \brief 获取插件的主界面文件路径. */ QString WidgetContent::main() const { if (d->contents.contains(QStringLiteral("Main"))) { return d->contents.value("Main").toString(); } return {}; } /*! \brief 获取插件的数据文件路径. */ QString WidgetContent::data() const { if (d->contents.contains(QStringLiteral("Data"))) { return d->contents.value(QStringLiteral("Data")).toString(); } return {}; } /*! \brief 获取插件的国际化文件路径. */ QString WidgetContent::i18n() const { QVariant i18n = d->contents.value(QStringLiteral("I18n")); if (i18n.isValid()) { if (i18n.userType() == QMetaType::QString) { return i18n.toString(); } } return {}; } /*! \brief 配置文件行为, \c "LoclOnly" 表示插件的配置文件将记录在宿主应用中,为空时将生成全局配置文件 */ QString WidgetContent::config() const { QVariant cfg = d->contents.value(QStringLiteral("Config")); if (cfg.isValid()) { if (cfg.userType() == QMetaType::QString) { return cfg.toString(); } } return {}; } /*! \brief 获取插件的动态库文件路径. */ QString WidgetContent::plugin() const { QVariant plugin = d->contents.value(QStringLiteral("Plugin")); if (plugin.isValid()) { if (plugin.userType() == QMetaType::QString) { return plugin.toString(); } } return {}; } /*! \brief 获取插件版本号. */ QString WidgetContent::pluginVersion() const { QVariant pluginVersion = d->contents.value(QStringLiteral("PluginVersion")); if (pluginVersion.isValid()) { if (pluginVersion.userType() == QMetaType::QString) { return pluginVersion.toString(); } } return {}; } /*! \brief 检查插件是否需要预加载. \c true 表示插件在加载时即加载so插件,否则会在调用插件功能时加载 so插件. */ bool WidgetContent::pluginPreload() const { QVariant pluginPreload = d->contents.value(QStringLiteral("PluginPreload")); if (pluginPreload.isValid()) { if (pluginPreload.userType() == QMetaType::Bool) { return pluginPreload.toBool(); } } return false; } /*! \brief 获取插件的配置文件行为. \sa config() */ WidgetContent::ConfigPolicy WidgetContent::configPolicy() const { QString cfg = config(); if (!cfg.isEmpty() && cfg == QStringLiteral("LocalOnly")) { return LocalOnly; } return Null; } /*! \brief 获取插件的根目录路径. 返回插件安装目录的绝对路径,该路径包含了插件的所有资源文件. */ QString WidgetContent::rootPath() const { return d->contentsRoot.absolutePath(); } /*! \brief 根据内容类型 \a key 获取对应的文件路径. \sa ContentKey */ QString WidgetContent::filePath(const WidgetContent::ContentKey &key) const { QString file; switch (key) { case Main: file = main(); break; case I18n: { file = i18n(); break; } case Plugin: { file = plugin(); break; } default: break; } if (file.isEmpty()) { return {}; } return d->contentsRoot.absoluteFilePath(file); } /*! \brief 根据内容类型 \a key 获取对应的文件 \c URL. 特殊处理qrc资源路径,将其转换为QQmlComponent可识别的格式. \sa ContentKey */ QUrl WidgetContent::fileUrl(const WidgetContent::ContentKey &key) const { if (key == Config) { return {}; } QString path = filePath(key); if (path.isEmpty()) { return {}; } // NOTE: 在此处特殊处理qrc文件路径,将qrc格式的path(如:':/'形式)输出为qrc:/格式的Url。 // 目的是让QQmlComponent可以自动识别同目录的其他组件。 if (path.startsWith(QStringLiteral(":/"))) { return QUrl(path.prepend(QStringLiteral("qrc"))); } return QUrl::fromLocalFile(path); } ukui-quick/framework/CMakeLists.txt0000664000175000017500000001031415153756415016337 0ustar fengfengcmake_minimum_required(VERSION 3.16) project(framework) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) # 依赖Widgets是为了使用QAction,在高版本qt中,QAction已挪到Gui模块,不需要依赖Widgets find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Quick Widgets REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Quick Widgets REQUIRED) set(PROJECT_SOURCES widget/island.cpp widget/island.h widget/widget.cpp widget/widget.h widget/widget-loader.cpp widget/widget-loader.h widget/widget-metadata.cpp widget/widget-metadata.h widget/widget-content.cpp widget/widget-content.h widget/widget-container.cpp widget/widget-container.h widget/widget-interface.h widget-ui/widget-item.cpp widget-ui/widget-item.h widget-ui/widget-item-engine.cpp widget-ui/widget-item-engine.h widget-ui/widget-item-context.cpp widget-ui/widget-item-context.h widget-ui/widget-item-attached.cpp widget-ui/widget-item-attached.h widget-ui/widget-container-item.cpp widget-ui/widget-container-item.h config/config.h config/config.cpp config/ini-config.h config/ini-config.cpp config/config-loader.h config/config-loader.cpp config/config-property-map.cpp config/config-property-map.h view/island-view.cpp view/island-view.h ) set(HEADERS widget/island.h widget/widget.h widget/widget-loader.h widget/widget-metadata.h widget/widget-container.h widget/widget-content.h widget/widget-interface.h widget-ui/widget-item.h widget-ui/widget-container-item.h config/config.h config/ini-config.h config/config-loader.h config/config-property-map.h view/island-view.h ) add_library(${PROJECT_NAME} SHARED ${PROJECT_SOURCES}) add_library(${ROOT_PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) include_directories(config) include_directories(widget) include_directories(widget-ui) include_directories(../core) target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::Widgets ukui-quick::core ) include(CMakePackageConfigHelpers) set(CMAKE_CONFIG_INSTALL_DIR "/usr/share/cmake/ukui-quick-framework") set(HEADERS_INSTALL_DIR "/usr/include/ukui-quick/framework") set(PC_INSTALL_DIR "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/pkgconfig") target_include_directories(${PROJECT_NAME} INTERFACE $ ) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/ukui-quick-framework-config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-framework-config.cmake" INSTALL_DESTINATION ${CMAKE_CONFIG_INSTALL_DIR} ) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-framework-config-version.cmake VERSION ${UKUI_QUICK_VERSION} COMPATIBILITY SameMajorVersion ) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/ukui-quick-framework.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-framework.pc" INSTALL_DESTINATION ${PC_INSTALL_DIR} ) set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${UKUI_QUICK_VERSION} SOVERSION ${VERSION_MAJOR} OUTPUT_NAME ${ROOT_PROJECT_NAME}-${PROJECT_NAME} ) install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME} PUBLIC_HEADER DESTINATION ${HEADERS_INSTALL_DIR} LIBRARY DESTINATION /usr/lib/${CMAKE_LIBRARY_ARCHITECTURE} ) install(EXPORT ${PROJECT_NAME} FILE ukui-quick-framework-targets.cmake DESTINATION ${CMAKE_CONFIG_INSTALL_DIR} NAMESPACE ${ROOT_PROJECT_NAME}:: ) install(FILES ${HEADERS} DESTINATION ${HEADERS_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-framework.pc DESTINATION ${PC_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-framework-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-framework-config-version.cmake DESTINATION ${CMAKE_CONFIG_INSTALL_DIR}) if(BUILD_TEST) add_subdirectory(test) endif () ukui-quick/framework/widget-ui/0000775000175000017500000000000015153755732015477 5ustar fengfengukui-quick/framework/widget-ui/widget-item-engine.cpp0000664000175000017500000000565015153755732021673 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "widget-item-engine.h" #include "widget-item-context.h" #include "widget.h" #include "shared-engine-component.h" namespace UkuiQuick { class WidgetItemEnginePrivate { public: explicit WidgetItemEnginePrivate(WidgetItemEngine *itemEngine); ~WidgetItemEnginePrivate() = default; WidgetItemEngine *q {nullptr}; // 每一个widget都有一个自己的上下文 WidgetItemContext *rootContext {nullptr}; SharedEngineComponent *component {nullptr}; }; WidgetItemEnginePrivate::WidgetItemEnginePrivate(WidgetItemEngine *itemEngine) : q(itemEngine) { } WidgetItemEngine::WidgetItemEngine(Widget *widget, QQmlContext *pc, QObject *parent) : QObject(parent), d(new WidgetItemEnginePrivate(this)) { if (pc && pc->engine() == SharedEngineComponent::sharedEngine()) { d->rootContext = new WidgetItemContext(pc, widget, this); } else { d->rootContext = new WidgetItemContext(SharedEngineComponent::sharedEngine(), widget, this); } d->component = new SharedEngineComponent(d->rootContext, this); } WidgetItemEngine::~WidgetItemEngine() { if (d) { delete d; d = nullptr; } } QObject *WidgetItemEngine::mainItem() { return d->component->rootObject(); } void WidgetItemEngine::loadMainItem() { const auto widget = d->rootContext->widget(); const auto mainUrl = widget->content().fileUrl(WidgetContent::Main); if (mainUrl.isEmpty()) { qWarning() << "WidgetItemEngine::loadMainItem: The main file was not found.."; widget->setUiError(QStringLiteral("The main file was not found.")); return; } d->component->setSource(mainUrl); } const QQmlComponent *WidgetItemEngine::component() const { return d->component->component(); } const Widget *WidgetItemEngine::widget() const { return d->rootContext->widget(); } const QQmlEngine *WidgetItemEngine::engine() const { return SharedEngineComponent::sharedEngine(); } WidgetItemContext *WidgetItemEngine::rootContext() const { return d->rootContext; } void WidgetItemEngine::initContextProperty() { if (d->rootContext) { d->rootContext->registerProperty(); // d->rootContext->loadContextData(); } } } // UkuiQuick ukui-quick/framework/widget-ui/widget-item-context.h0000664000175000017500000000301515153755732021550 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_WIDGET_ITEM_CONTEXT_H #define UKUI_QUICK_WIDGET_ITEM_CONTEXT_H #include namespace UkuiQuick { class Widget; class WidgetItemEngine; /** * @class WidgetItemContext * * 每一个WidgetItem的上下文 * 可以为Item挂载自定义的数据 * */ class WidgetItemContext : public QQmlContext { Q_OBJECT friend class WidgetItemEngine; public: explicit WidgetItemContext(QQmlEngine *engine, Widget *widget, QObject *objParent = nullptr); explicit WidgetItemContext(QQmlContext *parent, Widget *widget, QObject *objParent = nullptr); Widget *widget() const; private: void init(); void registerProperty(); void loadContextData(); private: Widget *m_widget {nullptr}; }; } // UkuiQuick #endif //UKUI_QUICK_WIDGET_ITEM_CONTEXT_H ukui-quick/framework/widget-ui/widget-container-item.cpp0000664000175000017500000002506015153755732022405 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "widget-container-item.h" #include "widget.h" #include "widget-ui/widget-item.h" #include "widget-container.h" #include "shared-engine-component.h" #include "widget-item-context.h" #include "widget-interface.h" #include #include #include #include #include #include std::once_flag containerOnceFlag; namespace UkuiQuick { // WidgetContainerItemPrivate class WidgetContainerItemPrivate { public: std::unique_ptr contextMenu; WidgetContainer *container {nullptr}; SharedEngineComponent *component {nullptr}; WidgetItemModel *itemModel {nullptr}; }; /*! \qmltype WidgetContainerItem \instantiates WidgetContainerItem \inqmlmodule UkuiQuickFramework \brief WidgetContainerItem 是容器的主入口,容器负责管理和控制 WidgetItem,并将他们放置在某种布局中. */ // ====== WidgetContainerItem ====== // WidgetContainerItem::WidgetContainerItem(QQuickItem *parent) : WidgetQuickItem(parent), d(new WidgetContainerItemPrivate) { setAcceptedMouseButtons(Qt::AllButtons); d->itemModel = new WidgetItemModel(this); } WidgetContainerItem::~WidgetContainerItem() { if (d) { delete d; d = nullptr; } } WidgetContainer *WidgetContainerItem::container() const { return d->container; } SharedEngineComponent *WidgetContainerItem::component() const { return d->component; } /*! \readonly \qmlproperty WidgetItemModel WidgetContainerItem::widgetItemModel widgetItemModel 是容器中 WidgetItem 的模型. */ UkuiQuick::WidgetItemModel *WidgetContainerItem::widgetItemModel() const { return d->itemModel; } /*! \qmlmethod WidgetItem *WidgetContainerItem::widgetItemForWidget(Widget *widget) const \brief 根据 \c instanceId 获取 WidgetItem. */ WidgetItem *WidgetContainerItem::widgetItemForWidget(Widget *widget) const { if (!widget || widget->type() != Types::Widget) { return nullptr; } WidgetQuickItem *item = WidgetQuickItem::loadWidgetItem(widget); return qobject_cast(item); } /*! \qmlmethod WidgetItem WidgetContainerItem::widgetItem(int instanceId) \brief 根据给定的 \c Widget 对象获取对应的 \c WidgetItem 指针. */ UkuiQuick::WidgetItem* WidgetContainerItem::widgetItem(int instanceId) const { Widget *w = d->container->widget(instanceId); if(w) { return qobject_cast(loadWidgetItem(w)); } return nullptr; } void WidgetContainerItem::classBegin() { WidgetQuickItem::classBegin(); QQmlContext *context = QQmlEngine::contextForObject(this)->parentContext(); if (context) { if (auto c = context->contextProperty("container").value()) { setContainer(c); } } } void WidgetContainerItem::setContainer(WidgetContainer *container) { d->container = container; if (container) { for (const auto &widget : container->widgets()) { onWidgetAdded(widget); } connect(container, &WidgetContainer::widgetAdded, this, &WidgetContainerItem::onWidgetAdded); connect(container, &WidgetContainer::widgetRemoved, this, &WidgetContainerItem::onWidgetRemoved); } } void WidgetContainerItem::onWidgetAdded(Widget *widget) { WidgetQuickItem *widgetItem = WidgetQuickItem::loadWidgetItem(widget); connect(this, &WidgetItem::adjustingLayoutChanged, widgetItem, [&, widgetItem]() { widgetItem->setAdjustingLayout(adjustingLayout()); }); if (d->itemModel->insertWidgetItem(widgetItem)) { Q_EMIT widgetItemAdded(widgetItem); } } void WidgetContainerItem::onWidgetRemoved(Widget *widget) { WidgetQuickItem *widgetItem = WidgetQuickItem::loadWidgetItem(widget); if (d->itemModel->removeWidgetItem(widgetItem)) { Q_EMIT widgetItemRemoved(widgetItem); } } bool WidgetContainerItem::event(QEvent *event) { if (event->type() == QEvent::MouseButtonPress) { auto *mouseEvent = dynamic_cast(event); switch (mouseEvent->button()) { case Qt::MiddleButton: case Qt::LeftButton: { // 执行 Action if (d->contextMenu) { // 关闭菜单 d->contextMenu->close(); } break; } case Qt::RightButton: // 显示菜单 if (showContextMenu(mouseEvent)) { //https://bugreports.qt.io/browse/QTBUG-59044 QTimer::singleShot(0, this, [this]() { if (window() && window()->mouseGrabberItem()) { window()->mouseGrabberItem()->ungrabMouse(); } }); return true; } break; case Qt::BackButton: case Qt::ForwardButton: // 循环 break; default: break; } } return WidgetQuickItem::event(event); } bool WidgetContainerItem::showContextMenu(QMouseEvent *mouseEvent) { if (!container()) { return false; } if (d->contextMenu) { // 连续两次右键关闭右键菜单 if (d->contextMenu->isVisible()) { d->contextMenu->close(); return false; } d->contextMenu->close(); for (const auto &action : d->contextMenu->actions()) { if (action->menu()) { // 断开菜单窗口之间的信号 action->menu()->disconnect(d->contextMenu.get()); } } d->contextMenu->clear(); } else { d->contextMenu.reset(new QMenu); d->contextMenu->setAttribute(Qt::WA_DeleteOnClose, false); d->contextMenu->setToolTipsVisible(true); connect(d->contextMenu.get(), &QMenu::aboutToHide, this, [&](){ d->container->setActive(false); }); } // 1.添加widget的Action QList widgetActions; QPointF pointF = mouseEvent->pos(); for (const auto &widgetItem : std::as_const(d->itemModel->m_widgetItems)) { const Widget *widget = widgetItem->widget(); QRectF widgetRect = widgetItem->mapRectToItem(this, {widgetItem->position(), widgetItem->size()}); if (widget && widgetRect.contains(pointF)) { widgetActions << widget->actions(); break; } } // 2.添加Container的Action,一般代表程序窗口的某些操作 // 插件存在Action那么就不显示Container的菜单 //插件通过WidgetInterface加载的action会被加到最后 if (widgetActions.isEmpty()) { QList widgetPluginActions; for(auto w : d->container->widgets()) { if (w && w->plugin()) { widgetPluginActions.append(w->plugin()->actions()); } } container()->setAppendActions(widgetPluginActions); //如果有父container存在,由父container显示菜单 if(container()->container()) { return false; } d->contextMenu->addActions(container()->actions()); } else { d->contextMenu->addActions(widgetActions); } if (d->contextMenu->isEmpty()) { return false; } // 创建window d->contextMenu->winId(); if (d->contextMenu->windowHandle()) { d->contextMenu->windowHandle()->setTransientParent(window()); } for (const auto &action : d->contextMenu->actions()) { QMenu *menu = action->menu(); if (menu) { menu->setToolTipsVisible(true); connect(menu, &QMenu::aboutToShow, d->contextMenu.get(), [this, menu] { if (menu->windowHandle()) { menu->windowHandle()->setTransientParent(d->contextMenu->windowHandle()); } }); } } d->container->setActive(true); d->contextMenu->popup(mouseEvent->globalPos()); return true; } // ====== WidgetContainerItemAttached ====== // WidgetContainer *WidgetContainerItemAttached::qmlAttachedProperties(QObject *object) { auto containerItem = qobject_cast(object); if (containerItem) { return containerItem->container(); } QQmlContext *context = QQmlEngine::contextForObject(object); while (context) { context = context->parentContext(); if (auto c = qobject_cast(context)) { return qobject_cast(c->widget()); } } return nullptr; } // ====== WidgetItemModel ====== WidgetItemModel::WidgetItemModel(QObject *parent) : QAbstractListModel(parent) { } int WidgetItemModel::rowCount(const QModelIndex &parent) const { return m_widgetItems.size(); } int WidgetItemModel::columnCount(const QModelIndex &parent) const { return 1; } QVariant WidgetItemModel::data(const QModelIndex &index, int role) const { if (!checkIndex(index, CheckIndexOption::IndexIsValid)) { return {}; } if (role == WidgetItemRole) { return QVariant::fromValue(m_widgetItems.at(index.row())); } return {}; } QHash WidgetItemModel::roleNames() const { QHash roles; roles.insert(WidgetItemRole, "widgetItem"); return roles; } bool WidgetItemModel::insertWidgetItem(WidgetQuickItem *item) { if (!item || m_widgetItems.contains(item)) { return false; } beginInsertRows(QModelIndex(), m_widgetItems.size(), m_widgetItems.size()); m_widgetItems.append(item); endInsertRows(); return true; } bool WidgetItemModel::removeWidgetItem(WidgetQuickItem *item) { if (!item || !m_widgetItems.contains(item)) { return false; } int idx = m_widgetItems.indexOf(item); beginRemoveRows(QModelIndex(), idx, idx); m_widgetItems.takeAt(idx); endRemoveRows(); return true; } } // UkuiQuick ukui-quick/framework/widget-ui/widget-item-context.cpp0000664000175000017500000000314415153755732022106 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "widget-item-context.h" #include "widget-item-engine.h" #include "widget.h" namespace UkuiQuick { WidgetItemContext::WidgetItemContext(QQmlEngine *engine, Widget *widget, QObject *objParent) : QQmlContext(engine, objParent), m_widget(widget) { init(); } WidgetItemContext::WidgetItemContext(QQmlContext *parent, Widget *widget, QObject *objParent) : QQmlContext(parent, objParent), m_widget(widget) { init(); } void WidgetItemContext::init() { // init context property setContextProperty(QStringLiteral("widget"), m_widget); if (m_widget->type() == Types::Container) { setContextProperty(QStringLiteral("container"), m_widget); } } void WidgetItemContext::loadContextData() { // TODO: data接口 } void WidgetItemContext::registerProperty() { // } Widget *WidgetItemContext::widget() const { return m_widget; } } // UkuiQuick ukui-quick/framework/widget-ui/widget-item.cpp0000664000175000017500000002115615153755732020427 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "widget-item.h" #include "widget-item-engine.h" #include "widget-item-context.h" #include "widget-item-attached.h" #include "widget.h" #include "widget-container.h" #include "widget-container-item.h" #include #include #include #include #include #include std::once_flag flag; using namespace UkuiQuick; namespace UkuiQuick { class WidgetQuickItemPrivate { public: Widget *widget{nullptr}; WidgetItemEngine *itemEngine{nullptr}; bool adjustingLayout {false}; static QHash s_translators; static QHash s_widgetItems; static void installTranslator(const Widget *widget); static void unInstallTranslator(const QString &widgetId); static WidgetQuickItem *toWidgetItem(Widget *widget, QObject *object); static WidgetQuickItem *toContainerItem(Widget *widget, QObject *object); }; QHash WidgetQuickItemPrivate::s_translators = QHash(); QHash WidgetQuickItemPrivate::s_widgetItems = QHash(); // 加载翻译文件 void WidgetQuickItemPrivate::installTranslator(const Widget *widget) { /** * TODO: 安装多个翻译文件 * 将翻译文件安装到规定的路径下,通过唯一id找到翻译文件并加载,实现为某组件加载多个翻译文件 */ const QString wid = widget->id(); if (!WidgetQuickItemPrivate::s_translators.contains(wid)) { // i18n可以是数组 QString tsFile = widget->content().i18n(); if (!tsFile.isEmpty()) { auto translator = new QTranslator(QCoreApplication::instance()); bool loaded = translator->load(QLocale(), tsFile, QStringLiteral("_"), widget->content().rootPath()); if (loaded) { QCoreApplication::installTranslator(translator); WidgetQuickItemPrivate::s_translators.insert(wid, translator); qDebug() << "Installing translator for:" << wid << QLocale(); } else { qWarning() << "Widget:" << wid << ",Load translation file for" << QLocale::system().name() << "failed!"; delete translator; } } } } void WidgetQuickItemPrivate::unInstallTranslator(const QString &widgetId) { // 卸载翻译 auto it = std::find_if(WidgetQuickItemPrivate::s_widgetItems.constBegin(), WidgetQuickItemPrivate::s_widgetItems.constEnd(), [&widgetId](const WidgetQuickItem *wi) { return wi->widget()->id() == widgetId; }); if (it == WidgetQuickItemPrivate::s_widgetItems.constEnd()) { auto translator = WidgetQuickItemPrivate::s_translators.take(widgetId); if (translator) { QCoreApplication::removeTranslator(translator); delete translator; } } } WidgetQuickItem *WidgetQuickItemPrivate::toWidgetItem(Widget *widget, QObject *object) { auto widgetItem = qobject_cast(object); if (!widgetItem) { // 转换错误,qml的根项不是WidgetItem. qWarning() << "Error: The root item must be WidgetItem."; widget->setUiError("Error: The root item must be WidgetItem."); return nullptr; } widgetItem->setProperty("widget", QVariant::fromValue(widget)); widget->setProperty("widgetItem", QVariant::fromValue(widgetItem)); return widgetItem; } WidgetQuickItem *WidgetQuickItemPrivate::toContainerItem(Widget *widget, QObject *object) { auto containerItem = qobject_cast(object); if (!containerItem) { qWarning() << "Error: The root item must be WidgetContainerItem."; widget->setUiError("Error: The root item must be WidgetContainerItem."); return nullptr; } containerItem->setProperty("container", QVariant::fromValue(widget)); widget->setProperty("containerItem", QVariant::fromValue(containerItem)); return containerItem; } // ====== WidgetQuickItem ====== // WidgetQuickItem::WidgetQuickItem(QQuickItem *parent) : QQuickItem(parent), d(new WidgetQuickItemPrivate) { connect(this, &QQuickItem::windowChanged, this, &WidgetQuickItem::widgetWindowChanged); } WidgetQuickItem::~WidgetQuickItem() { if (d) { delete d; d = nullptr; } } void WidgetQuickItem::registerTypes() { // 注册widget和container const char *uri = "org.ukui.quick.widgets"; const char *containerUri = "org.ukui.quick.container"; qmlRegisterType(uri, 1, 0, "WidgetItem"); qmlRegisterUncreatableType(uri, 1, 0, "Widget", "For widgetItem attached prop."); qmlRegisterType(containerUri, 1, 0, "WidgetContainerItem"); qmlRegisterUncreatableType(containerUri, 1, 0, "WidgetContainer", "For WidgetContainer attached prop."); } WidgetQuickItem *WidgetQuickItem::loadWidgetItem(Widget *widget, QQmlContext *pc) { if (!widget) { return nullptr; } if (WidgetQuickItemPrivate::s_widgetItems.contains(widget)) { return WidgetQuickItemPrivate::s_widgetItems.value(widget); } std::call_once(flag, [] { WidgetQuickItem::registerTypes(); }); // 加载翻译文件 WidgetQuickItemPrivate::installTranslator(widget); auto itemEngine = new WidgetItemEngine(widget, pc); itemEngine->loadMainItem(); const auto qmlComponent = itemEngine->component(); if (qmlComponent && qmlComponent->isError()) { widget->setUiError(itemEngine->component()->errorString()); delete itemEngine; return nullptr; } auto mainItem = itemEngine->mainItem(); if (!mainItem) { delete itemEngine; return nullptr; } // 类型判断 WidgetQuickItem *item = nullptr; if (widget->type() == Types::Widget) { item = WidgetQuickItemPrivate::toWidgetItem(widget, mainItem); } else if (widget->type() == Types::Container) { item = WidgetQuickItemPrivate::toContainerItem(widget, mainItem); } if (!item) { delete mainItem; delete itemEngine; return nullptr; } // cache WidgetQuickItemPrivate::s_widgetItems.insert(widget, item); itemEngine->setParent(item); itemEngine->initContextProperty(); item->d->itemEngine = itemEngine; const QString wid = widget->id(); QObject::connect(widget, &Widget::aboutToDeleted, widget, [widget, wid]() { // TODO: 对s_widgetItems加个锁? WidgetQuickItemPrivate::s_widgetItems.take(widget)->deleteLater(); WidgetQuickItemPrivate::unInstallTranslator(wid); }); return item; } void WidgetQuickItem::classBegin() { QQuickItem::classBegin(); // context auto context = qobject_cast(QQmlEngine::contextForObject(this)->parentContext()); if (context) { d->widget = context->widget(); } } Widget *WidgetQuickItem::widget() const { return d->widget; } QQmlContext *WidgetQuickItem::context() const { return d->itemEngine->rootContext(); } QQuickWindow* WidgetQuickItem::widgetWindow() const { return QQuickItem::window(); } bool WidgetQuickItem::adjustingLayout() const { return d->adjustingLayout; } void WidgetQuickItem::setAdjustingLayout(bool adjustingLayout) { if (d->adjustingLayout == adjustingLayout) { return; } d->adjustingLayout = adjustingLayout; Q_EMIT adjustingLayoutChanged(); } // ====== WidgetItemPrivate ====== // class WidgetItemPrivate { public: // Widget *widget{nullptr}; }; /*! \qmltype WidgetItem \instantiates WidgetItem \inqmlmodule UkuiQuickFramework \brief Widget的图形组件,基于 QQuickItem(QML),包含图形ui全部信息. */ WidgetItem::WidgetItem(QQuickItem *parent) : WidgetQuickItem(parent), d(new WidgetItemPrivate) { } WidgetItem::~WidgetItem() { if (d) { delete d; d = nullptr; } } } ukui-quick/framework/widget-ui/widget-item-attached.h0000664000175000017500000000241715153755732021646 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_WIDGET_ITEM_ATTACHED_H #define UKUI_QUICK_WIDGET_ITEM_ATTACHED_H #include #include namespace UkuiQuick { class Widget; class WidgetItemAttached : public QObject { Q_OBJECT // QML_ATTACHED(WidgetItemAttached) public: static Widget *qmlAttachedProperties(QObject *object); explicit WidgetItemAttached(QObject *parent=nullptr); }; } // UkuiQuick // 添加附加属性 QML_DECLARE_TYPEINFO(UkuiQuick::WidgetItemAttached, QML_HAS_ATTACHED_PROPERTIES) #endif //UKUI_QUICK_WIDGET_ITEM_ATTACHED_H ukui-quick/framework/widget-ui/widget-item.h0000664000175000017500000000442415153755732020073 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_WIDGET_ITEM_H #define UKUI_QUICK_WIDGET_ITEM_H #include #include #include namespace UkuiQuick { class Widget; class WidgetItemPrivate; class WidgetQuickItemPrivate; /** * 一个WidgetItem的基础组成定义 * 包含重要的部分 * 提供上下文属性widgetItem用于访问Widget实例 */ class WidgetQuickItem : public QQuickItem { Q_OBJECT Q_PROPERTY(QQuickWindow *widgetWindow READ widgetWindow NOTIFY widgetWindowChanged) Q_PROPERTY(bool adjustingLayout READ adjustingLayout WRITE setAdjustingLayout NOTIFY adjustingLayoutChanged) public: explicit WidgetQuickItem(QQuickItem *parent = nullptr); ~WidgetQuickItem() override; static void registerTypes(); static WidgetQuickItem *loadWidgetItem(Widget *widget, QQmlContext *pc = nullptr); Widget *widget() const; QQmlContext *context() const; QQuickWindow *widgetWindow() const; bool adjustingLayout() const; void setAdjustingLayout(bool adjustingLayout); Q_SIGNALS: void widgetWindowChanged(); void adjustingLayoutChanged(); protected: void classBegin() override; private: WidgetQuickItemPrivate *d {nullptr}; }; /** * Widget的图形组件,基于QQuickItem(QML) * 包含图形ui全部信息 * TODO: 定义二级界面组件等 */ class WidgetItem : public WidgetQuickItem { Q_OBJECT public: explicit WidgetItem(QQuickItem *parent = nullptr); ~WidgetItem() override; private: WidgetItemPrivate *d {nullptr}; }; } // UkuiQuick #endif //UKUI_QUICK_WIDGET_ITEM_H ukui-quick/framework/widget-ui/widget-container-item.h0000664000175000017500000000606315153755732022054 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_WIDGET_CONTAINER_ITEM_H #define UKUI_QUICK_WIDGET_CONTAINER_ITEM_H #include #include #include #include "widget-item.h" namespace UkuiQuick { class Widget; class WidgetItem; class WidgetContainer; class SharedEngineComponent; class WidgetContainerItemPrivate; class WidgetItemModel : public QAbstractListModel { Q_OBJECT public: enum Key { WidgetItemRole }; Q_ENUM(Key) explicit WidgetItemModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; bool insertWidgetItem(WidgetQuickItem *item); bool removeWidgetItem(WidgetQuickItem *item); private: QList m_widgetItems; friend class WidgetContainerItem; }; class WidgetContainerItem : public WidgetQuickItem { Q_OBJECT Q_PROPERTY(UkuiQuick::WidgetItemModel *widgetItemModel READ widgetItemModel CONSTANT FINAL) public: explicit WidgetContainerItem(QQuickItem *parent = nullptr); ~WidgetContainerItem() override; WidgetContainer *container() const; SharedEngineComponent *component() const; WidgetItemModel *widgetItemModel() const; Q_INVOKABLE UkuiQuick::WidgetItem *widgetItemForWidget(UkuiQuick::Widget *widget) const; Q_INVOKABLE UkuiQuick::WidgetItem *widgetItem(int instanceId) const; Q_SIGNALS: void widgetItemAdded(UkuiQuick::WidgetQuickItem *); void widgetItemRemoved(UkuiQuick::WidgetQuickItem *); private Q_SLOTS: void onWidgetAdded(UkuiQuick::Widget *widget); void onWidgetRemoved(UkuiQuick::Widget *widget); protected: void classBegin() override; bool event(QEvent *event) override; private: void setContainer(WidgetContainer *container); bool showContextMenu(QMouseEvent *mouseEvent); private: WidgetContainerItemPrivate *d {nullptr}; }; class WidgetContainerItemAttached : public QObject { Q_OBJECT public: // Attached prop for WidgetContainer static WidgetContainer *qmlAttachedProperties(QObject *object); }; } // UkuiQuick QML_DECLARE_TYPEINFO(UkuiQuick::WidgetContainerItemAttached, QML_HAS_ATTACHED_PROPERTIES) #endif //UKUI_QUICK_WIDGET_CONTAINER_ITEM_H ukui-quick/framework/widget-ui/widget-item-engine.h0000664000175000017500000000420115153755732021327 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_WIDGET_ITEM_ENGINE_H #define UKUI_QUICK_WIDGET_ITEM_ENGINE_H #include #include #include namespace UkuiQuick { class Widget; class WidgetItemContext; class WidgetItemEnginePrivate; /** * @class WidgetItemEngine * * WidgetItemEngine 是用于为一个Widget创建Item的工具。 * 内部为每一个Item创建了一个上下文,并挂载了一些常见数据。 * * 调用loadRootObject方法自动解析main.qml加载根对象。 * */ class WidgetItemEngine : public QObject { Q_OBJECT friend class WidgetItemEnginePrivate; public: explicit WidgetItemEngine(Widget *widget, QQmlContext *pc = nullptr, QObject *parent = nullptr); ~WidgetItemEngine() override; // 所属的源Widget const Widget *widget() const; // 当前运行在的engine const QQmlEngine *engine() const; // item的上下文 WidgetItemContext *rootContext() const; // 根节点对象 QObject *mainItem(); // 自动解析当前Widget的Metadata并通过Component加载主入口(main.qml)文件。 void loadMainItem(); // 根节点的Component,可用于clone生成一个object const QQmlComponent *component() const; // 将Widget中定义的上下文数据挂载到上下文 void initContextProperty(); private: WidgetItemEnginePrivate *d {nullptr}; }; } // UkuiQuick #endif //UKUI_QUICK_WIDGET_ITEM_ENGINE_H ukui-quick/framework/widget-ui/widget-item-attached.cpp0000664000175000017500000000271515153755732022202 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "widget-item-attached.h" #include "widget.h" #include "widget-item.h" #include "widget-item-context.h" #include #include namespace UkuiQuick { Widget *WidgetItemAttached::qmlAttachedProperties(QObject *object) { auto widgetItem = qobject_cast(object); if (widgetItem) { return widgetItem->widget(); } QQmlContext *context = QQmlEngine::contextForObject(object); while (context) { context = context->parentContext(); if (auto c = qobject_cast(context)) { return c->widget(); } } return nullptr; } WidgetItemAttached::WidgetItemAttached(QObject *parent) : QObject(parent) { } } // UkuiQuick ukui-quick/framework/ukui-quick-framework-config.cmake.in0000664000175000017500000000036215153756415022535 0ustar fengfeng@PACKAGE_INIT@ include(CMakeFindDependencyMacro) find_dependency(Qt@QT_VERSION_MAJOR@Core "@REQUIRED_QT_VERSION@") find_dependency(ukui-quick COMPONENTS platform core) include("${CMAKE_CURRENT_LIST_DIR}/ukui-quick-framework-targets.cmake") ukui-quick/framework/test/0000775000175000017500000000000015153755732014560 5ustar fengfengukui-quick/framework/test/CMakeLists.txt0000664000175000017500000000124215153755732017317 0ustar fengfengproject(framework-test VERSION 4.1 LANGUAGES CXX) include_directories(../widget-ui) include_directories(../widget) include_directories(../config) include_directories(../view) find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED) set(QRC_FILES qml.qrc) set(PROJECT_SOURCES main.cpp ${QRC_FILES} ) add_executable(${PROJECT_NAME} ${PROJECT_SOURCES}) target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::Widgets ukui-quick::framework ) add_subdirectory(org.ukui.testWidget) ukui-quick/framework/test/main.cpp0000664000175000017500000000327215153755732016214 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include #include #include #include #include "widget.h" #include "widget-loader.h" #include "widget-item.h" #include "config-loader.h" #include "widget-container.h" #include "island-view.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); auto view = new UkuiQuick::IslandView("ukui-quick-frameworktest", "ukui-quick-frameworktest1"); const QString defaultViewId = QStringLiteral("org.ukui.quick.frameworktest"); view->resize(600, 100); view->setColor(QColor(Qt::transparent)); UkuiQuick::WidgetContainer::widgetLoader().addWidgetSearchPath(QStringLiteral(":/")); view->loadMainView(defaultViewId); UkuiQuick::WidgetLoader loader; view->mainView()->addWidget("org.ukui.menu.starter", 0); view->mainView()->addWidget("org.ukui.panel.taskView", 1); view->mainView()->addWidget("org.ukui.testWidget", 2); view->show(); return QGuiApplication::exec(); } ukui-quick/framework/test/org.ukui.testWidget/0000775000175000017500000000000015153755732020445 5ustar fengfengukui-quick/framework/test/org.ukui.testWidget/CMakeLists.txt0000664000175000017500000000114015153755732023201 0ustar fengfengcmake_minimum_required(VERSION 3.16) project(test-widget VERSION 4.1) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Quick Widgets REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Quick Widgets REQUIRED) set(PLUGIN_SOURCE test-plugin.cpp test-plugin.h ) add_library(test-plugin MODULE ${PLUGIN_SOURCE} ) target_link_libraries(test-plugin PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::Widgets ukui-quick::framework )ukui-quick/framework/test/org.ukui.testWidget/ui/0000775000175000017500000000000015153755732021062 5ustar fengfengukui-quick/framework/test/org.ukui.testWidget/ui/main.qml0000664000175000017500000000607515153755732022531 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ import QtQuick 2.15 import QtQuick.Layouts 1.15 import org.ukui.quick.widgets 1.0 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 WidgetItem { id: root property bool isHorizontal: Widget.orientation == Types.Horizontal Layout.fillWidth: !isHorizontal Layout.fillHeight: isHorizontal Layout.preferredWidth: isHorizontal ? childrenRect.width : width Layout.preferredHeight: isHorizontal ? height : childrenRect.height StyleBackground { id: backGround anchors.left: parent.left anchors.top: parent.top anchors.topMargin: root.isHorizontal? 4 : 0 anchors.leftMargin: root.isHorizontal? 0 : 4 width: root.isHorizontal? icon.width + searchText.width + 32 : parent.width - 8 height: root.isHorizontal? parent.height - 8 : icon.height + 16 radius: root.isHorizontal? height / 2 : width / 2 paletteRole: Theme.BrightText useStyleTransparency: false alpha: mouseArea.containsPress? 0.15 : mouseArea.containsMouse? 0.05 : 0 border.width: 1 borderColor: Theme.BrightText borderAlpha: mouseArea.containsPress? 0.15 : 0.05 Icon { id: icon anchors.left: parent.left anchors.top: parent.top anchors.margins: 8 width: root.isHorizontal? height : parent.width - 16 height: root.isHorizontal? parent.height - 16 : width source: "kylin-search" mode: Icon.AutoHighlight } StyleText { id: searchText anchors.left: icon.right anchors.top: backGround.top anchors.leftMargin: 8 anchors.topMargin: (parent.height - height) / 2 height: contentHeight width: contentWidth text: qsTr("Search") visible: root.isHorizontal alpha: 0.55 } } StyleBackground { anchors.fill: backGround z: -1 paletteRole: Theme.Base useStyleTransparency: false alpha: 0.95 radius: backGround.radius } MouseArea { id: mouseArea anchors.fill: backGround hoverEnabled: true Tooltip { id: tooltip anchors.fill: parent mainText: Widget.tooltip posFollowCursor: true } } } ukui-quick/framework/test/org.ukui.testWidget/test-plugin.cpp0000664000175000017500000000212015153755732023417 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #include "test-plugin.h" #include TestPlugin::TestPlugin(QObject* parent): QObject(parent) { auto action = new QAction(tr("Action test")); connect(action, &QAction::triggered, this, [] { qDebug() << "Action test triggered!"; }); m_action.append(action); } QList TestPlugin::actions() { return m_action; } ukui-quick/framework/test/org.ukui.testWidget/test-plugin.h0000664000175000017500000000223615153755732023074 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #ifndef TEST_PLUGIN_H #define TEST_PLUGIN_H #include #include "widget-interface.h" class TestPlugin : public QObject, public UkuiQuick::WidgetInterface { Q_OBJECT Q_PLUGIN_METADATA(IID WidgetInterface_iid) Q_INTERFACES(UkuiQuick::WidgetInterface) public: TestPlugin(QObject *parent = nullptr); QList actions() override; private: QList m_action; }; #endif //TEST_PLUGIN_H ukui-quick/framework/test/org.ukui.testWidget/metadata.json0000664000175000017500000000131215153755732023115 0ustar fengfeng{ "Authors": [ { "Name": "iaom", "Email": "zhangpengfei@kylinos.cn" } ], "Id": "org.ukui.testWidget", "Icon": "kylin-search", "Name": "test-widget", "Name[zh_CN]": "测试插件", "Tooltip": "这是一个测试插件", "Tooltip[zh_CN]": "这是一个测试插件", "Description": "test-widget", "Description[zh_CN]": "这是一个测试插件", "Version": "1.0", "Website": "https://ukui.org", "BugReport": "https://gitee.com/openkylin/ukui-quick/issues", "ShowIn": "All", "Contents": { "Main": "ui/main.qml", "Plugin": "/home/iaom/git/ukui-quick/cmake-build-debug/framework/test/org.ukui.testWidget/libtest-plugin.so", "PluginVersion": "1.0.0" } } ukui-quick/framework/test/main.qml0000664000175000017500000000144115153755732016217 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ import QtQuick 2.15 Rectangle { color: "#E1E4EA" } ukui-quick/framework/test/qml.qrc0000664000175000017500000000045015153755732016057 0ustar fengfeng org.ukui.quick.frameworktest/metadata.json org.ukui.quick.frameworktest/ui/Container.qml org.ukui.testWidget/metadata.json org.ukui.testWidget/ui/main.qml ukui-quick/framework/test/org.ukui.quick.frameworktest/0000775000175000017500000000000015153755732022332 5ustar fengfengukui-quick/framework/test/org.ukui.quick.frameworktest/ui/0000775000175000017500000000000015153755732022747 5ustar fengfengukui-quick/framework/test/org.ukui.quick.frameworktest/ui/Container.qml0000664000175000017500000000646615153755732025420 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ import QtQuick 2.15 import QtQuick.Layouts 1.15 import org.ukui.quick.widgets 1.0 import org.ukui.quick.container 1.0 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 WidgetContainerItem { id: containerItem property int orientation: WidgetContainer.orientation.Horizontal property bool isHorizontal: true onParentChanged: { anchors.fill = parent } StyleBackground { anchors.fill: parent borderColor: Theme.ButtonText borderAlpha: 0.25 border.width: 1 GridLayout { id: mainLayout z: 10 anchors.fill: parent rows: 1 columns: repeater.count rowSpacing: 4 columnSpacing: 4 Repeater { id: repeater model: containerItem.widgetItemModel delegate: widgetLoaderComponent } } Component { id: widgetLoaderComponent Item { id: widgetParent clip: true property int index: model.index property Item widgetItem: model.widgetItem function computeFillWidth(w) { return w < 0 ? mainLayout.width : Math.min(w, mainLayout.width); } function computeFillHeight(h) { return h < 0 ? mainLayout.height : Math.min(h, mainLayout.height); } Layout.fillWidth: widgetItem && widgetItem.Layout.fillWidth Layout.fillHeight: widgetItem && widgetItem.Layout.fillHeight Layout.minimumWidth: widgetItem ? computeFillWidth(widgetItem.Layout.minimumWidth) : mainLayout.width Layout.minimumHeight: widgetItem ? computeFillHeight(widgetItem.Layout.minimumHeight) : mainLayout.height Layout.maximumWidth: widgetItem ? computeFillWidth(widgetItem.Layout.maximumWidth) : mainLayout.width Layout.maximumHeight: widgetItem ? computeFillHeight(widgetItem.Layout.maximumHeight) : mainLayout.height Layout.preferredWidth: widgetItem ? computeFillWidth(widgetItem.Layout.preferredWidth) : mainLayout.width Layout.preferredHeight: widgetItem ? computeFillHeight(widgetItem.Layout.preferredHeight) : mainLayout.height onWidgetItemChanged: { if (widgetItem) { widgetItem.parent = widgetParent widgetItem.anchors.fill = widgetParent } } } } } } ukui-quick/framework/test/org.ukui.quick.frameworktest/metadata.json0000664000175000017500000000054315153755732025007 0ustar fengfeng{ "Authors": [ { "Name": "iaom", "Email": "zhangpengfei@kylinos.cn" } ], "Id": "org.ukui.quick.frameworktest", "Version": "1.0", "Website": "https://ukui.org", "BugReport": "https://gitee.com/openkylin/ukui-quick/issues", "ShowIn": "All", "WidgetType": "Container", "Contents": { "Main": "ui/Container.qml" } }ukui-quick/core/0000775000175000017500000000000015153756415012533 5ustar fengfengukui-quick/core/shared-qml-engine.h0000664000175000017500000000175415153755732016214 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_SHARED_QML_ENGINE_H #define UKUI_QUICK_SHARED_QML_ENGINE_H #include namespace UkuiQuick { class SharedQmlEngine { public: static QQmlEngine *sharedQmlEngine(); }; } // UkuiQuick #endif //UKUI_QUICK_SHARED_QML_ENGINE_H ukui-quick/core/margin.h0000664000175000017500000000333615153755732014167 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_ITEMS_MARGIN_H #define UKUI_QUICK_ITEMS_MARGIN_H #include namespace UkuiQuick { class Margin : public QObject { Q_OBJECT Q_PROPERTY(int left READ left WRITE setLeft NOTIFY leftChanged) Q_PROPERTY(int top READ top WRITE setTop NOTIFY topChanged) Q_PROPERTY(int right READ right WRITE setRight NOTIFY rightChanged) Q_PROPERTY(int bottom READ bottom WRITE setBottom NOTIFY bottomChanged) public: explicit Margin(QObject *parent = nullptr); Margin(int l, int t, int r, int b, QObject *parent = nullptr); int left() const; void setLeft(int l); int top() const; void setTop(int t); int right() const; void setRight(int r); int bottom() const; void setBottom(int b); Q_SIGNALS: void leftChanged(); void topChanged(); void rightChanged(); void bottomChanged(); private: int m_left {0}; int m_top {0}; int m_right {0}; int m_bottom {0}; }; } // UkuiQuick #endif //UKUI_QUICK_ITEMS_MARGIN_H ukui-quick/core/types.h0000664000175000017500000000417415153755732014057 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_ITEMS_TYPES_H #define UKUI_QUICK_ITEMS_TYPES_H #include namespace UkuiQuick { class Types { Q_GADGET public: enum Pos { NoPosition = 0, Left, Top, Right, Bottom, TopLeft, TopRight, BottomRight, BottomLeft, Center, LeftCenter, TopCenter, RightCenter, BottomCenter }; Q_ENUM(Pos) enum Orientation { Vertical, Horizontal }; Q_ENUM(Orientation) enum WidgetType { Widget, Container }; Q_ENUM(WidgetType) /** * The Location enumeration describes where on screen an element, such as an * Applet or its managing container, is positioned on the screen. **/ enum PopupLocation { Floating = 0, /**< Free floating. Neither geometry or z-ordering is described precisely by this value. */ Desktop, /**< On the planar desktop layer, extending across the full screen from edge to edge */ FullScreen, /**< Full screen */ TopEdge, /**< Along the top of the screen*/ BottomEdge, /**< Along the bottom of the screen*/ LeftEdge, /**< Along the left side of the screen */ RightEdge, /**< Along the right side of the screen */ }; Q_ENUM(PopupLocation) }; } // global #endif //UKUI_QUICK_ITEMS_TYPES_H ukui-quick/core/doc/0000775000175000017500000000000015153755732013301 5ustar fengfengukui-quick/core/doc/ukui-quick-core.qdoc0000664000175000017500000000036115153755732017166 0ustar fengfeng/*! \module UkuiQuickCore \title Ukui Quick Core C++ Classes \brief 提供Ukui Quick Core模块的C++类实现. */ /*! \page ukui-quick-core.html \title Ukui Quick Core ukui-quick项目的通用定义和核心类 */ukui-quick/core/shared-engine-view.h0000664000175000017500000000340215153755732016365 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_ITEMS_SHARED_ENGINE_VIEW_H #define UKUI_QUICK_ITEMS_SHARED_ENGINE_VIEW_H #include #include #include namespace UkuiQuick { class SharedEngineViewPrivate; class SharedEngineView : public QQuickWindow { Q_OBJECT friend class SharedEngineViewPrivate; public: enum ResizeMode { SizeViewToRootObject, SizeRootObjectToView }; static QQmlEngine *sharedEngine(); explicit SharedEngineView(QWindow *parent = nullptr); QQmlEngine *engine() const; QQmlContext *rootContext() const; QQuickItem *rootObject() const; void setResizeMode(SharedEngineView::ResizeMode resizeMode); SharedEngineView::ResizeMode resizeMode() const; public Q_SLOTS: void setSource(const QUrl &source) const; protected: void resizeEvent(QResizeEvent *event) override; private Q_SLOTS: void continueCreate() const; void updateViewSize() const; private: SharedEngineViewPrivate *d {nullptr}; }; } // UkuiQuick #endif //UKUI_QUICK_ITEMS_SHARED_ENGINE_VIEW_H ukui-quick/core/shared-engine-view.cpp0000664000175000017500000002121615153755732016723 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "shared-engine-view.h" #include "shared-qml-engine.h" #include #include namespace UkuiQuick { class SharedEngineViewPrivate { public: SharedEngineViewPrivate() = delete; SharedEngineViewPrivate(QQmlEngine *e, SharedEngineView *q); void createObject(); bool setRootObject(QObject *obj); void initResizeSignal() const; void updateSize() const; void printComponentError() const; // env SharedEngineView *view {nullptr}; QQmlEngine *engine {nullptr}; QQmlContext *rootContext {nullptr}; SharedEngineView::ResizeMode resizeMode {SharedEngineView::SizeRootObjectToView}; // object item QUrl source; QQuickItem *rootItem {nullptr}; QQmlComponent *component {nullptr}; }; SharedEngineViewPrivate::SharedEngineViewPrivate(QQmlEngine *e, SharedEngineView *q) : view(q), engine(e), rootContext(new QQmlContext(engine->rootContext(), q)) { } void SharedEngineViewPrivate::createObject() { if (!engine) { qWarning() << "SharedEngineView: invalid qml engine."; return; } if (rootItem) { delete rootItem; rootItem = nullptr; } if (component) { delete component; component = nullptr; } if (source.isEmpty()) { return; } component = new QQmlComponent(engine, source, view); if (component->isLoading()) { QObject::connect(component, &QQmlComponent::statusChanged, view, &SharedEngineView::continueCreate); } else { view->continueCreate(); } } bool SharedEngineViewPrivate::setRootObject(QObject *obj) { if (rootItem == obj) { return true; } delete rootItem; if (!obj) { return true; } if (auto item = qobject_cast(obj)) { rootItem = item; item->setParentItem(view->contentItem()); item->setParent(view->contentItem()); initResizeSignal(); return true; } if (qobject_cast(obj)) { qWarning() << "SharedEngineView does not support using a window as a root item." << Qt::endl << Qt::endl << "If you wish to create your root window from QML, consider using QQmlApplicationEngine instead." << Qt::endl; return false; } qWarning() << "SharedEngineView only supports loading of root objects that derive from QQuickItem." << Qt::endl << Qt::endl << "Ensure your QML code is written for QtQuick 2, and uses a root that is or" << Qt::endl << "inherits from QtQuick's Item (not a Timer, QtObject, etc)." << Qt::endl; return false; } void SharedEngineViewPrivate::initResizeSignal() const { if (!rootItem) { return; } if (resizeMode == SharedEngineView::SizeViewToRootObject) { QObject::connect(rootItem, &QQuickItem::widthChanged, view, &SharedEngineView::updateViewSize); QObject::connect(rootItem, &QQuickItem::heightChanged, view, &SharedEngineView::updateViewSize); } else { QObject::disconnect(rootItem, &QQuickItem::widthChanged, view, &SharedEngineView::updateViewSize); QObject::disconnect(rootItem, &QQuickItem::heightChanged, view, &SharedEngineView::updateViewSize); } updateSize(); } void SharedEngineViewPrivate::updateSize() const { if (!rootItem) { return; } if (resizeMode == SharedEngineView::SizeViewToRootObject) { QSize size; // 最小为1x1 size.setWidth(rootItem->width() < 1 ? 1 : (int)rootItem->width()); size.setHeight(rootItem->height() < 1 ? 1 : (int)rootItem->height()); if (size.isValid() && size != view->size()) { view->resize(size); } } else { bool updateWidth = rootItem->width() != view->width(); bool updateHeight = rootItem->height() != view->height(); if (updateWidth && updateHeight) { rootItem->setSize(view->size()); } else if (updateWidth) { rootItem->setWidth(view->width()); } else if (updateHeight) { rootItem->setHeight(view->height()); } } } void SharedEngineViewPrivate::printComponentError() const { const QList errorList = component->errors(); for (const QQmlError &error : errorList) { QMessageLogger(error.url().toString().toLatin1().constData(), error.line(), nullptr).warning() << error; } } /*! \class UkuiQuick::SharedEngineView \inmodule UkuiQuickCore \inheaderfile shared-engine-view.h \brief 提供基于共享QML引擎的视图容器,用于高效加载和显示QML内容. 主要功能: \list \li 复用全局共享的QQmlEngine实例,减少资源消耗 \li 支持动态加载QML文件作为根内容 \li 提供灵活的尺寸调整策略(视图适应内容或内容适应视图) \li 自动管理QML对象的生命周期 用于定义SharedEngineView的类,用于定义SharedEngineView的源文件、大小、调整模式等. \endlist */ /*! \enum SharedEngineView::ResizeMode \brief 定义视图与内容的尺寸调整策略. \value SizeViewToRootObject 视图尺寸适应内容尺寸 \value SizeRootObjectToView 内容尺寸适应视图尺寸 */ // ====== SharedEngineView Begin ======// /*! \fn QQmlEngine *SharedEngineView::sharedEngine() \brief 获取全局共享的QML引擎实例. */ QQmlEngine *SharedEngineView::sharedEngine() { return SharedQmlEngine::sharedQmlEngine(); } /*! \fn SharedEngineView::SharedEngineView(QWindow *parent) \brief 构造函数, \a parent 父窗口 */ SharedEngineView::SharedEngineView(QWindow *parent) : QQuickWindow(parent), d(new SharedEngineViewPrivate(SharedEngineView::sharedEngine(), this)) { } /*! \fn QQmlEngine* SharedEngineView::engine() const \brief 获取当前视图关联的QML引擎实例。 */ QQmlEngine *SharedEngineView::engine() const { return d->engine; } /*! \fn QQmlContext* SharedEngineView::rootContext() const \brief 获取QML上下文实例。 */ QQmlContext *SharedEngineView::rootContext() const { return d->rootContext; } /*! \fn void SharedEngineView::setSource(const QUrl &source) const \brief 设置QML文件 \a source. */ void SharedEngineView::setSource(const QUrl &source) const { d->source = source; d->createObject(); } void SharedEngineView::continueCreate() const { QObject::disconnect(d->component, &QQmlComponent::statusChanged, this, &SharedEngineView::continueCreate); if (d->component->isError()) { d->printComponentError(); return; } // 改为beginCreate QObject *obj = d->component->create(d->rootContext ? d->rootContext : d->engine->rootContext()); if (d->component->isError()) { d->printComponentError(); if (obj) { obj->deleteLater(); } return; } d->setRootObject(obj); } /*! \fn void SharedEngineView::setResizeMode(SharedEngineView::ResizeMode resizeMode) \brief 设置视图与内容的尺寸调整策略 \a resizeMode. \sa ResizeMode */ void SharedEngineView::setResizeMode(SharedEngineView::ResizeMode resizeMode) { if (d->resizeMode == resizeMode) { return; } d->resizeMode = resizeMode; if (d->rootItem) { d->initResizeSignal(); } } void SharedEngineView::updateViewSize() const { d->updateSize(); } void SharedEngineView::resizeEvent(QResizeEvent *event) { if (d->resizeMode == SizeRootObjectToView) { d->updateSize(); } QQuickWindow::resizeEvent(event); } /*! \fn QQuickItem* SharedEngineView::rootObject() const \brief 获取QML文件中根对象实例. */ QQuickItem *SharedEngineView::rootObject() const { return d->rootItem; } /*! \fn SharedEngineView::ResizeMode SharedEngineView::resizeMode() const \brief 获取当前视图的调整模式. */ SharedEngineView::ResizeMode SharedEngineView::resizeMode() const { return d->resizeMode; } } // UkuiQuick ukui-quick/core/CMakeLists.txt0000664000175000017500000000734215153756415015301 0ustar fengfengcmake_minimum_required(VERSION 3.14) project(core) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Quick Qml REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Quick Qml REQUIRED) set(UKUI_QUICK_CORE_SRCS margin.cpp types.cpp shared-engine-view.cpp shared-qml-engine.cpp shared-engine-component.cpp ) set(HEADERS margin.h types.h shared-engine-view.h shared-qml-engine.h shared-engine-component.h ) add_library(${PROJECT_NAME} SHARED ${UKUI_QUICK_CORE_SRCS} ) add_library(ukui-quick::core ALIAS ${PROJECT_NAME}) add_library(${ROOT_PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::Qml ) set(HEADERS_INSTALL_DIR /usr/include/ukui-quick/core) target_include_directories(${PROJECT_NAME} PUBLIC $ ) #文档 include(../cmake-extend/modules/UkuiQDocAddQch.cmake) set(include_dirs "/usr/include/${CMAKE_LIBRARY_ARCHITECTURE}/qt${QT_VERSION_MAJOR}/" "/usr/include/${CMAKE_LIBRARY_ARCHITECTURE}/qt${QT_VERSION_MAJOR}/QtCore" "/usr/include/${CMAKE_LIBRARY_ARCHITECTURE}/qt${QT_VERSION_MAJOR}/QtQml" "/usr/include/${CMAKE_LIBRARY_ARCHITECTURE}/qt${QT_VERSION_MAJOR}/QtQuick" ) #文档 ukui_qdoc_add_qch(ukui-quick-core-doc VERBOSE NAME ukui-quick-core DOC_NAME ukui-quick-core VERSION ${UKUI_QUICK_VERSION} NAMESPACE org.ukui QCH_INSTALL_DESTINATION "/usr/share/qt${QT_VERSION_MAJOR}/doc" SOURCE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} HEADER_DIRS ${CMAKE_CURRENT_SOURCE_DIR} CONFIG_TEMPLATE "${CMAKE_CURRENT_SOURCE_DIR}/../cmake-extend/modules/QDocConfig.qdocconf.in" INCLUDE_DIRS ${include_dirs} DEPEND_QCHS qtcore qtqml qtquick STYLE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../cmake-extend/style/default.css" ) include(CMakePackageConfigHelpers) set(CMAKE_CONFIG_INSTALL_DIR "/usr/share/cmake/ukui-quick-core") set(PC_INSTALL_DIR "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/pkgconfig") configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/ukui-quick-core-config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-core-config.cmake" INSTALL_DESTINATION ${CMAKE_CONFIG_INSTALL_DIR}) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-core-config-version.cmake VERSION ${UKUI_QUICK_VERSION} COMPATIBILITY SameMajorVersion ) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/ukui-quick-core.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-core.pc" INSTALL_DESTINATION ${PC_INSTALL_DIR}) set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${UKUI_QUICK_VERSION} SOVERSION ${VERSION_MAJOR} OUTPUT_NAME ${ROOT_PROJECT_NAME}-${PROJECT_NAME} ) install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME} PUBLIC_HEADER DESTINATION ${HEADERS_INSTALL_DIR} LIBRARY DESTINATION /usr/lib/${CMAKE_LIBRARY_ARCHITECTURE} ) install(EXPORT ${PROJECT_NAME} FILE ukui-quick-core-targets.cmake NAMESPACE ${ROOT_PROJECT_NAME}:: DESTINATION ${CMAKE_CONFIG_INSTALL_DIR}) install(FILES ${HEADERS} DESTINATION ${HEADERS_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-core.pc DESTINATION ${PC_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-core-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-core-config-version.cmake DESTINATION ${CMAKE_CONFIG_INSTALL_DIR}) ukui-quick/core/ukui-quick-core.pc.in0000664000175000017500000000045315153755732016504 0ustar fengfengprefix=/usr exec_prefix=${prefix} libdir=${prefix}/lib/@CMAKE_LIBRARY_ARCHITECTURE@ includedir=${prefix}/include/ukui-quick/core Name: ukui-quick-core Description: ukui-quick-core header files URL: https://www.ukui.org/ Version: @VERSION@ Cflags: -I${includedir} Libs: -L${libdir} -lukui-quick-coreukui-quick/core/shared-engine-component.h0000664000175000017500000000462715153755732017427 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_SHARED_ENGINE_COMPONENT_H #define UKUI_QUICK_SHARED_ENGINE_COMPONENT_H #include #include #include #include #include #include namespace UkuiQuick { class SharedEngineComponentPrivate; /** * @class SharedEngineComponent * * 一个Component,用于加载并实例化一个qml文件的顶级对象,该对象为rootObject。 * 基本功能由QQmlComponent提供。 * * 所有通过SharedEngineComponent实例化的object都运行在同一个共享engine中。 * 每个object在创建前都需要一个上下文,如果未指定那么会从共享engine根上下文创建一个。 * 可以在构造SharedEngineComponent时传入初始化参数,用于初始化rootObject的属性。 * */ class SharedEngineComponent : public QObject { Q_OBJECT public: static QQmlEngine *sharedEngine(); explicit SharedEngineComponent(QQmlContext *rootContext = nullptr, QObject *parent = nullptr); explicit SharedEngineComponent(const QVariantMap &initData, QQmlContext *rootContext, QObject *parent = nullptr); ~SharedEngineComponent() override; // raw materials QUrl source() const; void setSource(const QUrl &source); QQmlContext *rootContext() const; QVariantMap initializeData() const; // products QObject *rootObject() const; QQmlComponent *component() const; Q_SIGNALS: void componentStatusChanged(QQmlComponent::Status status); private Q_SLOTS: void completedCreateRootObject(); private: SharedEngineComponentPrivate *d {nullptr}; friend class SharedEngineComponentPrivate; }; } // UkuiQuick #endif //UKUI_QUICK_SHARED_ENGINE_COMPONENT_H ukui-quick/core/shared-engine-component.cpp0000664000175000017500000001502015153755732017747 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "shared-engine-component.h" #include "shared-qml-engine.h" #include namespace UkuiQuick { class SharedEngineComponentPrivate { public: // raw QUrl source; SharedEngineComponent *q {nullptr}; // env QVariantMap initData; QQmlComponent *component {nullptr}; QQmlContext *rootContext {nullptr}; // generate QObject *rootObject {nullptr}; // func explicit SharedEngineComponentPrivate(SharedEngineComponent *parent); void printComponentError() const; void beginCreateRootObject(); }; SharedEngineComponentPrivate::SharedEngineComponentPrivate(SharedEngineComponent *parent) : q(parent) { } void SharedEngineComponentPrivate::printComponentError() const { const QList errorList = component->errors(); for (const QQmlError &error : errorList) { QMessageLogger(error.url().toString().toLatin1().constData(), error.line(), nullptr).warning() << error; } } void SharedEngineComponentPrivate::beginCreateRootObject() { delete component; component = new QQmlComponent(SharedEngineComponent::sharedEngine(), q); QObject::connect(component, &QQmlComponent::statusChanged, q, &SharedEngineComponent::componentStatusChanged); component->loadUrl(source); // rootObject = component->beginCreate(rootContext ? rootContext : SharedEngineComponent::sharedEngine()->rootContext()); rootObject = component->beginCreate(rootContext); if (component->isReady() || component->isError()) { q->completedCreateRootObject(); } else { QObject::connect(component, &QQmlComponent::statusChanged, q, &SharedEngineComponent::completedCreateRootObject); } } /*! \class UkuiQuick::SharedEngineComponent \inmodule UkuiQuickCore \inheaderfile shared-engine-component.h \brief 基于共享QML引擎的组件加载器. 一个Component,用于加载并实例化一个qml文件的顶级对象,该对象为 \c rootObject。 基本功能由 \l QQmlComponent 提供。所有通过 \l SharedEngineComponent 实例化的 \c object 都运行在同一个共享 \c engine 中。 每个 \c object 在创建前都需要一个上下文,如果未指定那么会从共享 \c engine 根上下文创建一个。 可以在构造 \l SharedEngineComponent 时传入初始化参数,用于初始化 \c rootObject 的属性。 \sa QQmlComponent */ // ====== SharedEngineComponent ====== // /*! \fn QQmlEngine* SharedEngineComponent::sharedEngine() 获取全局共享的QML引擎实例. \sa SharedQmlEngine */ QQmlEngine *SharedEngineComponent::sharedEngine() { return SharedQmlEngine::sharedQmlEngine(); } /*! \fn SharedEngineComponent::SharedEngineComponent(QQmlContext *rootContext, QObject *parent) \brief 构造函数, \a rootContext 为组件的QML上下文, \a parent 为父对象. */ SharedEngineComponent::SharedEngineComponent(QQmlContext *rootContext, QObject *parent) : QObject(parent), d(new SharedEngineComponentPrivate(this)) { if (!rootContext) { rootContext = new QQmlContext(SharedEngineComponent::sharedEngine()); } d->rootContext = rootContext; } /*! \fn SharedEngineComponent::SharedEngineComponent(const QVariantMap &initData, QQmlContext *rootContext, QObject *parent) \brief 构造函数, \a initData 作为初始化数据, \a rootContext 作为组件的QML上下文、 */ SharedEngineComponent::SharedEngineComponent(const QVariantMap &initData, QQmlContext *rootContext, QObject *parent) : SharedEngineComponent(rootContext, parent) { d->initData = initData; } SharedEngineComponent::~SharedEngineComponent() { if (d) { delete d; d = nullptr; } } /*! \fn void SharedEngineComponent::setSource(const QUrl &source) \brief 设置组件的QML文件路径, \a source QML文件路径 */ void SharedEngineComponent::setSource(const QUrl &source) { if (source.isEmpty()) { return; } d->source = source; d->beginCreateRootObject(); } /*! \fn QUrl SharedEngineComponent::source() const \brief 获取组件的QML文件路径. */ QUrl SharedEngineComponent::source() const { return d->source; } /*! \fn QObject *SharedEngineComponent::rootObject() const \brief 获取组件的根对象. */ QObject *SharedEngineComponent::rootObject() const { return d->rootObject; } /*! \fn QQmlComponent *SharedEngineComponent::component() const \brief 获取内部管理的QQmlComponent组件实例. */ QQmlComponent *SharedEngineComponent::component() const { return d->component; } /*! \fn QQmlContext *SharedEngineComponent::rootContext() const \brief 获取组件的QML上下文. \sa QQmlContext */ QQmlContext *SharedEngineComponent::rootContext() const { return d->rootContext; } /*! \fn QVariantMap SharedEngineComponent::initializeData() const \brief 获取组件的初始化数据. */ QVariantMap SharedEngineComponent::initializeData() const { return d->initData; } void SharedEngineComponent::completedCreateRootObject() { if (!d->component) { qWarning() << "SharedEngineComponent: Component is null, source:" << d->source; return; } disconnect(d->component, &QQmlComponent::statusChanged, this, &SharedEngineComponent::completedCreateRootObject); if (!d->component->isReady() || d->component->isError()) { d->printComponentError(); return; } QVariantMap::const_iterator iterator = d->initData.constBegin(); for (; iterator != d->initData.constEnd() ; ++iterator) { d->rootObject->setProperty(iterator.key().toUtf8().data(), iterator.value()); } d->component->completeCreate(); } /*! \fn void SharedEngineComponent::componentStatusChanged(QQmlComponent::Status status) \brief 此信号在组件状态发生变化时触发,\a status 为组件的新状态. */ } // UkuiQuick ukui-quick/core/ukui-quick-core-config.cmake.in0000664000175000017500000000055015153756415020422 0ustar fengfeng@PACKAGE_INIT@ include(CMakeFindDependencyMacro) find_dependency(Qt@QT_VERSION_MAJOR@Core "@REQUIRED_QT_VERSION@") find_dependency(Qt@QT_VERSION_MAJOR@Qml @REQUIRED_QT_VERSION@) find_dependency(Qt@QT_VERSION_MAJOR@Quick @REQUIRED_QT_VERSION@) find_dependency(ukui-quick COMPONENTS platform) include("${CMAKE_CURRENT_LIST_DIR}/ukui-quick-core-targets.cmake") ukui-quick/core/margin.cpp0000664000175000017500000000437415153755732014525 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "margin.h" /*! \namespace UkuiQuick \inmodule UkuiQuickCore \keyword UkuiQuick Namespace \brief The UkuiQuick namespace. */ /*! \class UkuiQuick::Margin \inmodule UkuiQuickCore \inheaderfile margin.h \brief 包含四向边距值的工具类. */ namespace UkuiQuick { Margin::Margin(QObject *parent) : QObject(parent) { } Margin::Margin(int l, int t, int r, int b, QObject *parent) : QObject(parent), m_left(l), m_top(t), m_right(r), m_bottom(b) { } /*! \property Margin::left \brief 该属性控制元素的左侧边距大小 */ int Margin::left() const { return m_left; } void Margin::setLeft(int l) { if (l == m_left) { return; } m_left = l; Q_EMIT leftChanged(); } /*! \property Margin::top \brief 该属性控制元素的顶部边距大小 */ int Margin::top() const { return m_top; } void Margin::setTop(int t) { if (t == m_top) { return; } m_top = t; Q_EMIT topChanged(); } /*! \property Margin::right \brief 该属性控制元素的右侧边距大小 */ int Margin::right() const { return m_right; } void Margin::setRight(int r) { if (r == m_right) { return; } m_right = r; Q_EMIT rightChanged(); } /*! \property Margin::bottom \brief 该属性控制元素的底部边距大小 */ int Margin::bottom() const { return m_bottom; } void Margin::setBottom(int b) { if (b == m_bottom) { return; } m_bottom = b; Q_EMIT bottomChanged(); } } // UkuiQuickukui-quick/core/types.cpp0000664000175000017500000000450715153755732014412 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "types.h" /*! \class UkuiQuick::Types \inmodule UkuiQuickCore \inheaderfile types.h \brief 提供一系列枚举类型,用于描述 UI 元素的位置、方向和类型等. */ /*! \enum Types::Pos UI 元素的位置. \value NoPosition 无特定位置,由布局系统自动决定 \value Left 紧贴容器左侧 \value Top 紧贴容器顶部 \value Right 紧贴容器右侧 \value Bottom 紧贴容器底部 \value TopLeft 容器左上角 \value TopRight 容器右上角 \value BottomRight 容器右下角 \value BottomLeft 容器左下角 \value Center 容器正中心 \value LeftCenter 容器左侧垂直居中 \value TopCenter 容器顶部水平居中 \value RightCenter 容器右侧垂直居中 \value BottomCenter 容器底部水平居中 */ /*! \enum Types::Orientation UI 元素的布局方向,影响子元素的排列方式. \value Vertical 垂直方向 \value Horizontal 水平方向 */ /*! \enum Types::WidgetType UI 元素的类型. \value Widget 小部件 \value Container 容器 */ /*! \enum Types::PopupLocation 定义了 UI 弹出元素(如对话框、菜单等)在屏幕上的定位方式 \value Floating 自由浮动,不固定位置和层级 \value Desktop 平铺在桌面层,全屏延伸 \value FullScreen 全屏显示 \value TopEdge 固定在屏幕顶部边缘 \value BottomEdge 固定在屏幕底部边缘 \value LeftEdge 固定在屏幕左侧边缘 \value RightEdge 固定在屏幕右侧边缘 */ namespace UkuiQuick { } // global ukui-quick/core/shared-qml-engine.cpp0000664000175000017500000000250415153755732016541 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "shared-qml-engine.h" /*! \class UkuiQuick::SharedQmlEngine \inmodule UkuiQuickCore \inheaderfile types.h \brief 提供一个全局的 QQmlEngine 实例,用于在多个组件之间共享 QML 引擎. */ namespace UkuiQuick { /*! \fn QQmlEngine *SharedQmlEngine::sharedQmlEngine() \brief 返回全局的 QQmlEngine* 实例. 用法: \code QQmlEngine *engine = SharedEngineComponent::sharedEngine(); \endcode \sa QQmlEngine */ QQmlEngine *SharedQmlEngine::sharedQmlEngine() { static QQmlEngine engine; return &engine; } } // UkuiQuick ukui-quick/README.md0000664000175000017500000004456715153755732013103 0ustar fengfeng# ukui-quick 限时置顶: 实现一个任务栏插件的步骤 ## 介绍 ukui-quick是ukui桌面环境的一个组件库,用于为qml应用程序或QT应用程序提供ukui桌面环境的特色功能组件。 * qml应用程序可以通过import 对应模块的uri进行使用。 * C++应用程序也可以通过cmake导入并使用ukui-quick提供的程序库。 ## 主要模块 ### [core](core) ukui-quick项目的通用数据和数据结构定义。 ### [items](items) ukui-quick的qml组件库。 items是qml import的主要入口点,用于向qml应用程序提供ukui-quick的各种功能和组件。 ### [framework](framework) ### 1. 简介 ukui-quick作为一个基于Qt Quick的UI开发功能集合,其中提供了一个基于插件模式的UI开发框架,目的在于实现对桌面通用插件的定义,加载以及统一配置管理等功能, 此框架的特点包括: - 可实现桌面组件一定程度上的功能解耦与功能复用 - 插件和宿主应用(Host)之间不会强制有二进制依赖关系,插件的开发相对更加独立,不易产生版本迭代时插件不兼容的情况. - 插件开发门槛低,无需了解宿主应用全部功能即可开发插件. - 通用性,可以实现不同桌面组件之间无缝共享插件 - 统一的配置管理模块,便于应用和插件的统一配置管理 #### 1.1 基本概念 插件框架整体由4个主要部分组成: Widget(插件),Container(容器),Island(窗口) ,Config(配置), Widget表示一个桌面插件,Container为可用于放置插件的容器,Island为基于QQuickWindow的通用窗口,继承了加载Container的功能.Config为统一的配置接口,提供了上述3个结构共用的配置管理功能. 基于此框架开发的一个应用的典型结构如下图所示: ![img.png](readme-image/img.png) 此应用包含两个窗口,其中窗口2为插件框架中的Island,而Island中包含了一个主Container(mainView)作为UI的根节点,根节点与其所有子节点共同组成一个树形结构.通过统一配置管理接口,应用(Island)可以生成属于自己的本地配置,也可以访问属于其插件的全局配置. #### 1.2 设计模式 整体插件框架分为两部分,一部分是负责保存和处理信息的后端(Widget/Container/Island),另一部分就是负责显示的前端(WidgetQuickItem/WidgetContainerItem/IslandView): ![img_1.png](readme-image/img_1.png) 框架类似Composite模式: Widget:定义了插件和插件容器的公共接口,并实现了相关的默认行为,同时也作为插件(leaf&Component) Container:继承自Widget,作为UI结构中的分支节点对象(Composite),负责管理所有子widget的生命周期 client表示宿主应用,client在调用插件框架时,可以选择直接使用封装好的Island窗口作为应用窗口,也可以选择直接初始化Container作为插件加载根节点. ![img_2.png](readme-image/img_2.png) ### 2.概要设计 #### 2.1 关键组件 ##### 2.1.1 Widget与WidgeQuicktItem Widget类定义了一个插件的基础信息接口,提供对插件包元数据进行访问的接口以及虽配置文件进行访问的接口. WidgeQuicktItem继承自QQuickItem,是插件框架中的一个基本UI元素(插件),是插件UI的入口,也是插件UI树的根节点. 在一个插件的生命周期开始时,Widget会先被创建,基于Widget提供的数据,一个WidgetItem会被创建然后被加载到UI树中.(翻译文件会在UI初始化之前被加载) 要卸载插件时,在Widget被删除前,UI中的WidgetItem会先执行析构. ##### 2.1.2 WidgetContainer和WidgetContainerItem WidgetContainer与WidgetContainerItem的关系和Widget与WidgeQuicktItem的关系类似. WidgetContainer继承自Widget,在其基础上增加了一些用于插件容器的属性,如屏幕信息,边距等,实现了插件加载和配置文件自动加载功能.一般情况下,WidgetContainer用于加载Widget,但一个WidgetContainer还可能与Island有关联(作为island的主视图)。 WidgetContainerItem是容器的主入口,容器负责管理和控制WidgetItem,并将他们放置在某种布局中。 WidgeQuicktItem和WidgetContainerItem只是UI组件,并不包含各种原始信息,那如何在界面上访问对应的Widget和Container呢? 为了满足这个需求,我们为每一个WidgetItem添加了一个附加属性"Widget",在qml文件中可以通过这个附加属性访问Widget的全部属性。同样的ContainerItem也有一个附加属性"WidgetContainer",可以访问Container的全部属性。 如何注册附加属性:使用 qmlRegisterUncreatableType(uri, 1, 0, "Widget", "For widgetItem attached prop."); 更新版的QT可以使用:QML_ATTACHED 宏进行标记注册,但是qt5版本并不推荐 ##### 2.1.3 Island和IslandView Island表示一个应用程序(或应用程序支持加载插件的部分),一个Island中包含一个"mainView",即一个主"WidgetContainer"作为当前的主视图,一个Island同一时间只能加载一个主视图. IslandView是一个QQuickView窗口,封装了加载和显示Island的主"WidgetContainer"对应的WidgetContainerItem的功能。 IslandView提供两种模式,一种是在加载mainView时直接初始化UI,一种是先在加载后台所有数据,由用户决定在何时初始化UI,前者适合开发一些功能比较简单的应用,而后者便于可以在将UI需要依赖的所有数据都初始化好之后再加载UI,一定程度上防止一些UI显示异常甚至报错. Island需要配合Config机制使用,实际上,island与Config几乎是强绑定状态,需要使用者理解Config并处理好初始配置。 ##### 2.1.4 WidgetInterface WidgetInterface是一个插件接口定义,插件可以选择实现这个接口以实现某些特性功能,如增加自定义右键菜单项等.这是一个可选接口,但可以脱离qmlUI单独加载,即可以实现一个不包含UI的插件,仅包含一个动态库的插件. ##### 2.1.5 WidgetMetadata WidgetMetadata表示一个插件的原始元数据信息,所谓原始元数据,就是插件在打包时安装的metadata.json文件中提供的信息.原始元数据在插件框架中非常重要,它用于描述一个插件,包括插件的名称,ID,介绍等基础信息,还有插件的类型(Host),国际化,UI入口等信息.实际上,他可以类比为原生linux应用的"desktop"文件. WidgetMetaData在一个Widget首次加载时加载,并且在同一个插件被多次加载时共享.Widget提供了在运行时访问全部metadata信息的功能,并可以修改部分信息,如Tooltip文本等(仅在运行时生效,不会直接修改json文件)。 ##### 2.1.6 WidgetLoader 插件加载器,WidgetLoader用于根据插件ID加载插件,默认加载路径为: ``` ":/ukui/widgets" "/usr/share/ukui/widgets" "~/.local/share/ukui/widgets" ``` 可在运行时临时增加查找目录 还继承了插件种类判断功能,插件元数据中有一个类型(host)字段,可以通过指定插件加载器所在的应用(宿主)类型,可以自动过滤掉不属于当前类型的插件.例如,在某个应用的插件选择页面只显示可以被加载到当前应用的插件. 插件加载器的扫描路径逻辑可以根据需求进行扩充,例如不同分类的插件可以区分不同目录等 #### 2.2 引擎和上下文 在基于IslandView的应用中,所有IslandView窗口都在同一个Qml Engine中渲染,但他们各自拥有独立的上下文(QQmlContext)用于初始化IslandView中的根容器(MainContainer)。 同时,每个widget(Container也是widget)拥有自己独立的上下文,继承父上下文中所有的属性,同时可以设置独享数据,与其他widget互不干扰。 #### 2.3 插件包 一个完整的Widget插件又称作Widget包,在安装时的基本目录结构如下: ``` // 插件的基本目录结构如下: org.ukui.widget / # 插件顶级目录 ├── metadata.json # 元数据 ├──translations │   └── *.qm └── ui └── main.qml #UI入口文件(根控件) ``` 其中目录名称需要与插件的唯一ID保持一致,一般使用域名反写。 其中metadata.json为插件的元数据文件或描述文件,一个典型的元数据文件如下: ```json { "Authors": [ { "Name": "iaom", "Email": "zhangpengfei@kylinos.cn" } ], "Id": "org.ukui.testWidget", "Icon": "kylin-search", "Name": "test-widget", "Name[zh_CN]": "测试插件", "Tooltip": "这是一个测试插件", "Tooltip[zh_CN]": "这是一个测试插件", "Description": "test-widget", "Description[zh_CN]": "这是一个测试插件", "Version": "1.0", "Website": "https://ukui.org", "BugReport": "https://gitee.com/openkylin/ukui-quick/issues", "ShowIn": "All", "ApplicationId": ["peony"], "Thumbnail": "path to/thumbnail.png", "Contents": { "Main": "ui/main.qml", "I18n": "translations/org.ukui.testWidget", "Plugin": "/home/iaom/git/ukui-quick/cmake-build-debug/framework/test/org.ukui.testWidget/libtest-plugin.so", "PluginVersion": "1.0.0", "PluginPreload": false, "Config": "LocalOnly" } } ``` 比较重要的几个字段: - Id:必须,表示插件的唯一Id,插件加载器根据Id区分每个插件,插件目录需要满足"插件加载路径/id"的结构. - I18n:翻译文件路径,格式为"相对路径/配置文件前缀" - ShowIn: 表示插件可以显示在哪些宿主应用,目前包括:Panel, SideBar, Desktop, TaskManager, All(前面所有, 默认) - ApplicationId: 当插件可以显示的宿主应用包含TaskManager时 ,需要指定此字段,任务栏上的taskManager插件会根据此字段判断其对应的应用,格式为desktop文件名,不带“.desktop”后缀。此字段为数组格式,即一个插件可以匹配多个应用 - Thumbnail:插件的缩略图 - Contents/Main: 必须,表示插件UI入口,为插件目录相对路径, 也可以制定qrc资源文件路径. - Contents/Plugin:so插件路径,可继承WidgetInterface实现一个so插件(基于QtPlugin机制),目前支持增加自定义右键菜单项 - Contents/PluginVersion:插件接口版本(Plugin字段不为空时必须指定接口版本) - Contents/PluginPreload:true表示插件在加载时即加载so插件,否则会在调用so插件功能时加载 - Config:配置文件行为,"LoclOnly"表示插件的配置文件将记录在宿主应用中,为空时将生成全局配置文件 #### 2.4 配置 配置模块用于为widget/WidgetContainer/Island提供配置读取和存储功能。设计目标是提供一个统一的配置文件管理机制,可以将插件框架中各个模块的配置文件实现统一管理.配置文件通过专门的配置文件加载器(ConfigLoader)获取,加载器负责初始化配置并处理节点映射等工作。 配置文件为json格式,一个典型的配置配置文件实例如下所示: ```json { "_views": [ { "id": "org.ukui.panel", "instanceId": 0, "panelSize": 48, "widgets": [ { "id": "org.ukui.menu.starter", "instanceId": 0 }, { "id": "org.ukui.panel.search", "instanceId": 1 }, { "id": "org.ukui.panel.taskView", "instanceId": 2 } ] }, { "disabledInstances": [ 0 ], "id": "org.ukui.panelNext", "instanceId": 1, "panelSize": 56, "widgets": [ { "id": "org.ukui.dataIsland", "instanceId": 0, "maximumWidth": 208, "minimumWidth": 64, "type": "data", "widgets": [ { "id": "org.ukui.menu.starter", "instanceId": 0 }, { "id": "org.ukui.panel.taskView", "instanceId": 1 } ] }, { "id": "org.ukui.appIsland", "instanceId": 1, "maximumWidth": 16777215, "minimumWidth": 64, "type": "app", "widgets": [ { "id": "org.ukui.panel.taskManager", "instanceId": 0 }, { "id": "org.ukui.panel.search", "instanceId": 1 } ] } ] } ], "panelConfigVersion": "1.0" } ``` ##### 2.4.1 节点(child) 配置文件为Json格式,一个完整的Config由多个节点组成,而它本身也表示一个节点,一个完整的Config是一个树形结构。 在上面的示例中,_views 所在的对象为根节点,该节点有个2个属性(_views, panelConfigVersion)。 ##### 2.4.2 分组信息(groupInfo) 想要Config变成树形结构,我们需要指定如何生成这颗树,而分组就是我们生成树的依据。 ConfigGroupInfo就是Config对节点进行解析的依据,它指定了进行分组的属性和区分分组中各个子对象的主键属性(用于区分不同对象的属性).有了依据,Config就可以将配置文件中的对应属性当作一个组,将组内的对象作为节点读取出来作为几个单独的Config,并与当前的Config形成树形结构 . 需要注意的是,指定进行分组的属性一般是一个json数组,如果在设置分组信息时,原本的数据不是数组,那么这个属性的数据会被全部清空。 对于上述的Json,我们可以指定分组信息为: ```c++ // 设置组信息的对应方法是: config.addGroupInfo("_views", "instanceId"); ``` 设置好Config的分组信息后,我们的Config就得到了两个子Config: ``` // 子Config 1 { "id": "org.ukui.panel", "instanceId": 0, ... } // 子Config 2 { "id": "org.ukui.panelNext", "instanceId": 1, ... } ``` 该分组信息的意思是:将当前Config对象的_views属性作为一个分组,并且该分组中的对象以instanceId属性进行区分 Config类有这几个方法用于处理子Config ```c++ // 是否是根节点,只有根节点能读取和保存配置文件 bool isRoot() const; // 当前Config的主键属性的值 const QVariant &id() const; // 当前Config属于的组 const QString &group() const; // 当前的分组信息 const ConfigGroupInfo &groupInfo() const; // 添加一条分组信息 void addGroupInfo(const QString &group, const QString &key); // 获取某个组下的全部子Config ConfigList children(const QString &group) const; // 某个组下有多少Config int numberOfChildren(const QString &group) const; // 通过组名和组内对象主键的值拿到子Config Config *child(const QString &group, const QVariant &id) const; /** * 添加子节点 * @param group 组名 * @param childData 数据 */ void addChild(const QString &group, const QVariantMap &childData); /** * 删除一个子节点 * @param group 组名 * @param id 主键值 */ void removeChild(const QString &group, const QVariant &id); /** * 删除一个key * @param key 要删除的键 */ void removeKey(const QString &key); ``` 需要注意的是,当我们对Config进行分组操作后,我们就不能再通过getValue方法去读取这些组节点了,必须使用child或children去获取子节点配置(Config). 可以通过以下方法获取子节点: ```c++ // 获取一个分组下的所有子节点: QList c = config.children("_views"); // 获取某个子节点 Config *c = config.child("_view", 0) 修改上面第一个"instanceId = 0"节点的"panelSize"属性,需要: // 'panelSize'配置在 '_views' 组中,并且这个组的主键属性为'instanceId' Config c = config.child("_views", 0); c.setValue("panelSize", 56); 也可以在已有分组下增加一个新的子节点: QVariantMap wData; wData.insert(QStringLiteral("id"), "panel-neo"); wData.insert(QStringLiteral("instanceId"), 2); //添加新的子节点 config->addChild(QStringLiteral("widgets"), wData); ``` 如果没有将child属性作为组,此时的child只是一个普通的json数组数据,想要修改某个属性就需要遍历这个child数组,并找到对应的json对象修改数据。 TODO:多进程间共享的插件全局配置文件监听与通知机制 ##### 2.4.3 应用 在插件框架中,Widget的Config是WidgetContainer的子Config,而WidgetContainer的Config又是Island的子Config。 Island的Config的分组信息是:{ "_views", "instanceId" },在实际使用时,Id(主Container的id)也作为唯一属性 WidgetContainer的Config的分组信息是:{ "widgets", "instanceId" } Widget的Config已经是最底层的节点,目前没有分组信息。 Island在加载mainView(主Container)时,会根据mainView的Id在配置文件的"_view"节点寻找对应的id,将找到的第一个符合的配置节点作为需要加载的WidgetContainer的配置节点: ```c++ /** * 加载配置文件中的一个view * @param id view的ID * @return 加载成功返回true,否则返回false */ bool loadMainView(const QString &id); ``` 这里参数是Id而不是instanceId,因为一个主Container也是一个打包为一个Widget包的插件. TODO:增加bool loadMainView(const QString &id, int instanceId);之类的接口,使得_views分组下可以有同Id的不同WidgetContainer 随后WidgetContainer会根据配置文件内容自动加载属于自己的内容,即分析自己的widgets分组并加载其中的Widget. ### 3. 开发示例 [一个简单的插件&一个基础的容器&以及一个基于插件框架开发的应用](https://gitee.com/openkylin/ukui-quick/tree/upstream/framework/test/) [一个简单的时钟显示插件:](https://gi43l7c2w7z.feishu.cn/docx/YXVYdLS8So81Mpx4O85ck3ZQnNd?from=from_copylink) [实现一个任务栏插件的步骤](https://yayqg9ul2g.feishu.cn/docx/GBq9dVTmLodP7ExXr6kc8yLsnDf?from=from_copylink) ### [platform](platform) ukui桌面环境特有组件库,提供一些能够与ukui桌面环境进行交互的组件。 core,items和framework模块都有使用platform提供的功能。 ### [modules](modules) * #### [window-thumbnail](window-thumbnail) 窗口动态预览控件,支持x和wayland环境 ### [widgets](widgets) 为ukui-quick插件框架开发的一些插件或qml模块库。 ### 其他 截止目前,ukui-quick仍在快速迭代中,文档相对来说不是那么完善。想要参与ukui-quick的开发,可以通过提交issue或PR与我们进行交流。 ukui-quick/CMakeLists.txt0000664000175000017500000000211115153755732014337 0ustar fengfengcmake_minimum_required(VERSION 3.16) project(ukui-quick) set(ROOT_PROJECT_NAME ukui-quick) set(VERSION_MAJOR 1) set(VERSION_MINOR 0) set(VERSION_MICRO 0) set(UKUI_QUICK_VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_MICRO}) include(CMakePackageConfigHelpers) add_subdirectory(core) add_subdirectory(items) add_subdirectory(platform) add_subdirectory(framework) add_subdirectory(modules) add_subdirectory(cmake-extend) set(CMAKE_CONFIG_INSTALL_DIR "/usr/share/cmake/ukui-quick") configure_package_config_file( ${CMAKE_CURRENT_SOURCE_DIR}/ukui-quick-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-config.cmake INSTALL_DESTINATION ${CMAKE_CONFIG_INSTALL_DIR} ) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-config-version.cmake VERSION ${UKUI_QUICK_VERSION} COMPATIBILITY SameMajorVersion ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-config-version.cmake DESTINATION ${CMAKE_CONFIG_INSTALL_DIR} COMPONENT Devel ) ukui-quick/readme-image/0000775000175000017500000000000015153755732014121 5ustar fengfengukui-quick/readme-image/img_1.png0000664000175000017500000043341015153755732015630 0ustar fengfengPNG  IHDR uM;IDATx^ϋ'f޾/Ά=]ZhV2"cwFe-yOY0̼2`܌}c`uѠKKSBR9OƓ>_>n9gFP_WDDDDDDDDDDDDDDDDDDd? Cl""""""""""""""""""2Z h1&""""""""""""""""""Cl""""""""""""""""""2Z h1&""""""""""""""""""Cl""""""""""""""""""2Z h1&""""""""""""""""""Cl""""""""""""""""""2Z h1&""""""""""""""""""Cl""""""""""""""""""2Z h1&""""""""""""""""""Cl""""""""""""""""""2Z h1&""""""""""""""""""Cl""""""""""""""""""2Z h1&""""""""""""""""""Cl""""""""""""""""""2Z h1&""""""""""""""""""Cl""""""""""""""""""2Z h1&""""""""""""""""""Cl""""""""""""""""""2Z h1&""""""""""""""""""Cl""""""""""""""""""2Z h1&""""""""""""""""""Cl""""""""""""""""""2ZO?J駟uex 'o^W$""""""""._y_' ??ڟ6""""""""" gj-ClNDDDDDDDD:kڿ6j_._AZ_'UDDDDDDDD;?t̟;Fwmܚ??痯¥?F~گ曦Ԁyœ92,54_/~_˩ɚŷ'_|??6W=̓ `kz?Cy??rzbm+ClogO/_5:o;W'_? 0w?|EY!Js@0;g[7[/rN@KW;5(7s. =3Ǐlv3/p@{Ok*| ]{_ ]}//~/ :WQE~j6zok^滪)1lmalo몱^Cl?Syij l5_Wrݍ;}#f;su|?_,7o :tݲ2Ķ$Cl^.\~7X!WС!9_WClhx33 !66!1  5!6f gA 00Cll?PWCl׷7ùJ~$.r$5] !U 5!6?:8}\Af{罸Xf)͈Cl8~ b ֋ݪ/d-oꯧԴLvi-VRo:7@k{j/~Ջ&D-W:K[۞v8)fػ-&7C1;.b>ʪzu!:6tU秾R]qk;~s[۞v8)f;T1l/v++?/x9.ҕx:0Ƽ޽^Sut )VVӮ[U \.)u/\?YjZ):gA UiZ!-z{q܈C)\Ym x ۤ!6XSLpr3[;)bHbkClUo{D\+KgNm?: & v"?+s c׺ݴG Xbje [{Z fϔ $ 5!*߷=pn".ؕqsI*?M?ua1K;0 Cl>֧Z–GZ fϔ $ 5!*߷=pn".؉o>nkRx_!S7xVqjlR<:Fa g-lJQ,υ?3 T3 ت5`'.\5Pr|0ES7:0g-hcon\b>b˷Y%'~f!1VNw&-}qN9_ĥοչކ17vY~$Έo<~Qb?7oVo}Yn#oqg6@~V~JW8|덝fg .V\zac}l|v̹bٸ–[v[>.Kĸ*yշ?3 &@ 5!*NG]z-.seqq姴opxK_1.01l&~QbMbo7[vA$NԵ{Nn{[u#Ɲ:r|"O\ 9Cl b b?:prĬ{qD:UT"?[Ļv\۬N?( ?:XP' RڤEc6[qkJЖ{s;at[brBnݹ?{qwPNz8uUS:nd T'ǘ7qu} ``3t86uC`K]8~~IZC8+s!1V%o"zh\ꟿxy .ȟ?IWο-W;Ɨ6}jNm``t>VzMkRsqq;?Z!׹at[bClu^]K_N:i~6S78ŃjK_$!%c{qoK-oC[yW^>sO?]jD^ڋ֔TRΉ0:gA U!jZE\~Jޙ-W8:Ӎwv>Q6p5qoK#v1l+b:7gN~-ݩ&JM%Eh,u޹M 5!*c%Eì`S܎‹D_ڋ뙓?)R#N9W|9p>7};\!68L%Z++ϟ:?0s}E܍ܟ9r1bo7WO"HЕYs(?bkClU6xO>뜳Q[P7 ?v+D|T~J;WO-T|Gq񅖾HtRU qgνN@q:/X!68L{罤\h^Kf]^JkVn9^\RoA:K+*q00Cl bJҳI0+.XgM~~tև|T|r:\5u~O(ԕwǏԹ旾d6#Ԙk#z+3ыݥUw;LX|h?::o][\CɡM5-%Eh4Ж&J``[iaV\-3DS78f~h~ Tcߗ!6;O?[GOR.hjZt#٪F|NV͸\RoA:K 6&J``[goaV\:Gԟ|?)Lq>^ͪ嫽dgq9$<|mc!68^>X̹rZ>%UdSW>s.TŦ\JHSƒ[иRR!1V%YhR"?xVj+?KS7vngv{k}6!68.ooǦeaZ&3O?Mӯ.[3,S=L+X\g)BW7Qj 3 ت$=K; 5%ϺO-F#)Lrgk?:ȯS[g̟]S0p\b;>#Oj:7`;Lեma^1U¥cx3n=O $EhZ,&J``[goaV\:Pj?M1[mw媗>}"wp5WF~w`eN7;| ô\3bo*8_p+==|(??[n_u|M 5!*I'Eì`MKعϟݨrٴnu+/˖zΩ\6=T~cnqƕ?Ifs7|[95=ԋݹ/4Mg ع??mZឿrf\a"4zt-{켶m.7Qjy 3 ت$=K; oop q\ކ#:]䥢Sבws3J!,NiO|5:t`+iiVyoyX1-m_;Lo묒ok:[IܲQXGD [b,pR4̊ 6Rnkyo:V3D_Gކ#:]UNǏ 6bo7ϜO#oCxh]Y]UZu]-}h}ͬNro IfsWi]zf -W9V7vnŧVnD;TNZErvPD*-h[b,pR4̊ c`k\\Tކ#кR1xs49K_*ZSr{ Cl'YӞHR6{{\jmiW9++_(i:/W4c-w/i7FW%Cl bJҳI0+.8ى;36mL+y1*Mm׻Ez Cl'YӞ˟jl))WJ615%Xr*֡wJ=Զ.K9[%H;  Oz嫽nxhjf?ڿT4i'J䧢a!Cl'ٷ?~?%|__\j5y7r굸Hiw~sB\dZ(KiRO `,[t[EXةuFoS=+N,kOK:=vq~ŽN9|)pbkClUNYq^|w̹\1W*NoyT^NmjJRr|ͭ;/\A}[nH鱿\_Lq3y 5!*K>v8)ftS[gWxT͊ݽ/of^q*)'pboPB$KO;?w?5-U۪2ySA~+fB_"KXMo+]x&Alc;Cl bJ=I0+.؟[wnrE}Ub/vc[W{q=ȧ_=Y#/ѳY}nV"mRˍ{RD)Vk[be,JJ/6{d+˷0«4y[ov\JXבsrbbzQ,fw5ULR¥ʺj8+ŷ9}5iN9?>-{b೯HFqZ(-:֙!1V%z(;toƍ:uv 0PU? ClUW{QM_PS8Fy#2F6{5 ͝RkZin \z-T|EAXפT|]܆:XKqU':3 تSO;`WLM9$i˲5ުJ&&#hzb[q٩nÚ !6z<:k*.F^$!Q\ݽ/Tlvn4dG_>֝U7t8}|q+`mbkClUNYqܺs;nnNlѕpʇXfVm1J.0FaN\H.︕8v 5!*yZ).؉[5&V{DVϿ~g7$TYIz>ѻ M:tYgq SnWY<07gMWU>s.~8f џ|𨏖 /%QVo>}Wv{P_r'$<+G}$oBhZ)Դ #no[okDq%+WOZ:%8kJF溡䓓eWoai]r5=o6? ɫIahڽf9Z~c9!ך[7vn-x}tnq+nW[{ikDq6)R`[ad-J5p1lO?d;Bo w^[?u6P\W7۲[O^LfFo1iMrr4m+WNM;skzwʊ#Z!WzeA U){+3vjڏEI?溽nF ca{^\ԋ*y_y= @Zoi*y)4`f99;77*sE_H_EMMC-n@FaA UStu6:nĕ¥yRbHv7Ϸqcf aJ^hɾM)X!6߁y굸HKSKɈۤ)Diw[J́[Z:u*fIClGoɅKaWEhQ@_UٮvK w|͇|9svN`DI>O6iQb$U'űԑX:?jKUujsI}.nNʱ-d= ( 5`Clߺx3.8:*s&-q\z8u/_=,e\'Arg/.r8;_8vGNV8f5I_ }0FOkql[M}Q|j*V7Kt+8%o2M.6_J\[k;n@FaAjmOoQ>A\[Nm-{~_\ۚ@ITp&‹֮oo/ Ί`DIf4sB%&xW~gU?V5{"kV&Ϥ[^!OOg#n[ 6Ė?Ey{ ]m͸®Rq/?o@, k*/O6H*&̚ǰ,_e-|>~VAzZ/ d!Qa>MYqFNmcz0nǙ##Wng>yYܱnWv}{;۬$uP}H櫦"ȋM%#m?#?Zʶ?^%GrCVy;6 -Cl 2[?bWs[S7vn[JYqF?!ZkΔ*m]W//{: OX U ѹ͛[%Ǥ?̗鸖ߑ:t+dMsiS`bkߍV?`#y)o~jmNܱ=@^G|=?.᥷ ;v%/T=\vK:. ѹw 5\~gl6\ގZڏwbVM1lirC^zmy;6 3Cl 2a"`*.8v}I0+.Eqk}Qm:r&hityCt*5rڻw{Q2aoƝ,Clt.Kp)mX]V;EIJ N l[^?ܣ +d=moXsd!Yok)oLJ}^5OQ\;7Om}{Q*:r&WOJun3vrj-N)~~*CltkiRKe--N_Tz_jh'ܭ[^k:^O6 5ȐClyS_8SKYq^]4V_5AS7vn_mYB2ľ69gS87& 0 Cltkik9FvYŦAE_~+O[wnՖ 7yiL\p$jQ}/yEzmLbk!j4pmw%)f7Om 5ljUp">ltﱏK9'!~0(0@Cɟsz 0)fv>4V_$M*/v?撆[g?^jnVaI);u$Y~8Y YIEO`9'!e01z7{/>=s~ aZ4̊ vGܮZ31,U.m&[IC4 &T]c+?:S}3ꬼ5ˊ+ \ |nwSf湗zJ%Y!?9(Fw[5Ķtpb!g-|Jy? n(DI0+.8rj-|r*~4VMPhLϪp\dv$}Ǥvԇ!wPFd:?Y#v yk_6|GHY.]nSsT͛ǚcK嚿KXTC^Qw[@[33ئaV\ Q_|cǏ^Jc I1!iݱ,",e|f(U^)ozJQWMzjfZ{l߬S[gW5˿R5[%NCزgK*ʞ~5%_ٸ;d}_ 8:!)fk{c ;&]3iC4 xȥS[g"Cl>~+W5 )o.\^¸|Gq0vj՞>V ȴܥbgV2K:ؓ7j\WTcչSsNCl VCl?TJb2h)ڟø5W Ic}o8֘!6T^L=7To|j-“PƲW\9Y.WP\3ǒ\bkb;Nm[{uHYq*/{罇a16AɿwWD,L{Dhs>dd;7 Znܽ9nlS/_-v4gŦT5ޯvt26h8{t"0o'?j5/`!Cl ClSG?l Ԕ ŵ-Ub|ogk.oz7D#XpE6.0F)PQ!='ձt_LxgqG-`+ֿYO=6c+|Gq%[, o$b^Q*_^-6 d ; ,3ۘ4XaA'Q̏l{3ZFkG 8 }Yv!_(n,h"Y^Şs9y߮zG}zۥz(5#/EG96;. >6;֫|.V&)WM'AtBMz/kRdms|Y6)%689j*3]oE.W~_>/[ן8K࣏L7*ӡuJZ{lӾ\Θ~?DBi?y}Jq:cPCaJ- Ὰ/{n~X~מ}HOGn" ަWl??f;?{{?>{`6Jl7_{_>u$UG)v'}.AӋ/Buz?w/|\yiW?ˏ`b+#@a\n/?XAe"pZаR‘[+6-b>y'pڠm‹E_nW}m??Nll<zlK`l'<'ɩtDy`Jl c%6-%L-N\ K9 KQb19}œx g~qu@1ٞ ?r7NSj_~٫u0Q'%6`Jl CTf Gn u:!ZpN_w? v:7~VUlO?L࣏g*a"XM5R1^6sTwLGI [Pb8Bw_;)z/M&D ^ȵnpJl7;.똓/|܊y+3/en_{{]WreJaβ+ߝ2gFMs񥖾)vG>LlGOa(qLapᱯ?QL`:-b5&R0?%\K|kRg6㎽Υ"^jzWpڄ]4X VN1JYY۱ı(s{+ ǩ&͢]h1~VөC//%/|{o'\/E+Wo hrWa]*Ei|KiUwp"<˶D7%q*3ÅchWl"!Zvj|٧u>;.-qRb㍡j^E؜=c_"lZE>·[kF{eOOw-U6cM_w@',5m:pٷЌXɏ,&BeW A(5 %6S),.LEh15nSb!q]>YZ>p ☟̾T#19{脥BL,wƃka7[o{M|KŹPwnJl CTf /{}1Cg7mO\1xw28~Jl]9–:ߝ\:Q.ْ0zGKa(03%6,~,;v=72%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%̔X=%p/͇w1w>W,H z|/ayK3)%pvO?/aaxfG*Pb`/aq /_,bSbkJlg&[ja^N Y'P`!Jlddn ңO_xp<Ⅼ0Zȁڕ7OKPN8_{`NT¥Sտq9N]u5@FV f)s盥|c'rgU8SZN/d %Fy̌s)M[ׂ>TW?,gr^>~S] Y'P`!Jldnm>^,EmB|<¾}w[n Y's`Ga(Z$P3A3aM/NZ4 >sV[ϿbVLz+9Wٮa^PJ_cx!j,D " ߶Yy]jz_Y8{k߈ɢ뗥(ֳ=.v{[n¥}x^1<x!dn(5 %6Vj|gU9'q>rN)LHvv<‹S}>VFTRb`^׾poΨx!j,D " |[o_#|rNiL6o=\3^BQbkJl Բ$j9{SJ_Xը[ec,lk).IwR]IwqJlpD޵LOu5@fm YǬVť),KYJk*h {5ֽn5ņ[7]5Co5A2BQbkJl &Iߩ֫NA@K}w~SVbM%QaFQw]',6ڄGJSt ]Os,k%ÔOfa Y'P`!JldNϾS9'+oM/~賔@l[/vx^OZYu27v@4y_F7h*/%[yԾ0<>+-N=fu쭾)̥5җׇ)!\qz~х:Jl(ݓǰⅬ%62Ken+ޒw]/9m*⸴}g+'\?JlifpuaNBQbkJl ԲMߥCpcXoJS[S0ʛopӅ[>LbPC{Ou5@fmg_cN/=x˯̻˚Jlk ,~wV6Vf/d %j@ VF61 wi 2]w;_(KRbXe”5l\\yR˿,bm+]6!ݢ,ӕvrgoQk^i3/d@ (Y0s;u5|90<߉+Q(p{=X"ݍ;< ?u)J/vl u8 d[TN8L\l~#N[Pbh`ޥ 4W 7i85p#"~tTN N4>}o .=wܙd\N*#}%}&~JYiW|* ?"]:oKo\y '÷}.>q > \ ߇<L|ocGJG?K#('~kU?&g8"Ou{Klx7K~DVvZ0m, S(sxtQ/d@ (Y0s[Šw;Mnz_,:,K>^N4{x~@-|6,![f$d\սE],_r7T ?+glF(5T/,'IaTt˯ ~iN&N7"]>-s'+{_ z>k/=T~Ru27v@nk ^]p^/?5mSGJ;7F ֤jKܻX9l}M9z|$ΐo=;(~$tcARG5G0gl5܌6QoXjs~"+6Wӫ,+.n[KΟG{qr<-_ yQ=|{xc҅L Y'P`!JldV }S_z~N|2~j"KsNSģg͛z2ͷ6'dUywW|jڕzw֬`\7 k[}w7!4lTO#Z8L+5^^jmz`G/^:;Jl C ՂZV(/OFjҨT}fnc_`zN,s`PNg}i#}\1ۘî^ M6W UK\blGT.m3m];ǖ}_3Xj,i?#Zmxld&vt cbNB X3Hg_cn}93vcTb}}0NP_e.ܚnBԢrU7#;6!`İ!Ϯk&53}X*gN(Klak~z4%>zc|ri}Ӗe%ABQbkJl Ԯ$_ :q Ji3#+D5iFKl;MxXxn/7Ai)?lairej3쥰YUG3d+d7u.2z+A]a=trE;7χ;0쐽;pk0 O3~Mnݻ%@u֕iiP?,?dA ڵֳa%Uʟm>% V8;vcm2[l8‘؇0!Lg f%<1#^Ⱥ@G?(}ZN6|o~99//衇7OC;N>;?+e/o%sX4s[~F1݊fیqwcOvYdž"6ITdžeӛ&aXrτ{,(XZdO ݛ>7񂽳(#܏]P;*H\6tkv`8ٲhsuO'۫Wߴ)8ę:mzoD4Ns2T<䜅}lO˰ϳ}~z^IcWcU( Y7pRbkJl {Jt`9!ñ#Md8wwk~PLtN7 Tv?6?M^'KVVtt0ejaUvR(r،KΌxf \~8dYV8q~#p :gcgּ\`?-͹vWabap܌^:slMJi<WtMܻS?u، QY1if/d@ (YИ {&6E9ao[=}jb7ADk.*zu3-f'z+<ذHrmȑ[>]ʴۚ\ Y's`Ga(Z~+]tqC*hJjBծ9Ľ%^{<43vj8 k:!lByJuQLgnZ*(|EHqG4CkZyViZ Y'P`!JldV ,(}u4}h6AN?$?qޥf)"a~UhcjDo.ypmp{.Wpذlrmȑ[N[yJcr54N[Pbhx?MW㟧aMeީNg5tRwi +oޜ߬-c&.XlBօWmSϿDuӅ[ӧE7flP>٤ܨ37- RjY1if/d@ (YC6u#ez7i f)QmW]1^=h90Yqzf$336m]Enl ʵ= GJa:5|}o.Ⅼ0ZC[Ɣ!*L}@n[^eBcXK;7dgzi~^Ǫ:D],Kn.nsxUL5kNzLgnZ*[okUcmB XcV {o?o"K7}MȽw5Oef)/\z8)>sc`]Ԣ5u_/}c_S>J:6m]ӯ%x^2\rt֥{oд\ Y's`Ga(:@-={)O+8L[/ V>ܮ =BRz>UN8L\f$n(yfe%[X .ym9ĭKN+sʘ7"7x^Q6U:`=`:sp&CQ:T QY1if/d@ (9mһ>Ia"gH>(Koǒþso[A/]A؊K>^Om&nD[n8M4Y=sfV¶k ^eVHKWԦ$^:;Jl C ՑjinJ샃Ʃ;4Tʻ'G+َ. ]\TWyמ5KNפ_3FdYv.37-:֡d*ϊ1M6x!j,D ̑dn.8=TK 4Eܛ$?}9,&Zg@4J-Z^=nڪ2d<`3+ka۳ŀW9R:p7 iUKx!dn(5 %6VGIJ_[4H?*8sRaMlB\ۛ.Zm559l飨Y/l#}k;?>ҺHe4lbXS3Fdj.SyԦ|0=6-cǢIPy2DgŘuAu5@H2H+oa~6y}X`99SiT58m7Ek G0}sktp~ؚ^H cͬmߐהkϯ\rt~k=j{$^:;Jl C Ցjec DK[1,;-wBwqm'4Vin QчR<پ_9-D*^xf&̯*#R֍l_p9yM}Jڎި5T<+4 Ⅼ%62G@j4GJkn+4>!7qC]ӛ{j6EoSadž?+ :v²c1-ۖ00=ٚLloӺf<\rt~k=j{UR/^:;Jl C ՑjMxMŻ􃟿P93Shɂom"XO7wكVئW5~dzNdd4 w e'~z:s"@֯Hӫ7EQߗʘ7"USb˦m*zlHM$eVpL,9]gŘuAu5@H2H#7Խ (Sd7᧌U,f:Db:^6fMRO>5_=W_iy[Cܚlka̽:'d^9WHZzm&ZDlXKx!dn(5 %6VG]TQ9>դQs-}`˯ra VYs+x䱯 6i^{SE 6|p[zaׅ%-MW?|-d|/] ivos̰Je,8(9EH=,hʘ7",1bkf_d=h=`e|8# }>mk U+ϊ~CKg/u9ap\?B Xƕw?x/[C&7 d[0DSldTr 3HV+jc^d8;,y04B0'AyΩѦrPR(]v _OY^2M~o_OʿRj|u /T ӵNZ_wn<ɷw8,o"YچEo,F!?0ϐC]KtU]* CJpW.C Y!bHUZT:3į t-ݕ_Qw:)΀{7V>[g_(}˿=7x8~Խ|sDpF !kEu6~:;}zF ]W<$5Dnf,a?{oCECjHZ{'^yyo巳kvn^1޷ѽw0CqOyH&LC b:=1_uG=db YL vR6|~CЯkm&u-FσhݟyPlpGs˿7)^;WhQMȷ}懟~ C-Y'̓?nOK?? Gl@+kȧcBI[9"*ha_8mWD!7JVGG'.f䯎~O\qءojSuȣB $@Iuznc ' XY7ϐ} |1Y~|^X`@bo`?nC~V+a,s[B NC-@C/n%W6701o ~|>Oڹ;Z~E|0= |qg? V ĮԆY/_EU%!L! x(/ot;O\C_?8?Z|ÛT8^s >Ya"4!6JsqkȆϧ?ie_JR;?/|.7is~K6؆Z 8ȱ}/K헮˿C{}ᛢC‚\C}Q pxr9T2=7%@4j(Owc0 YRT~5pw ww07&c"M컆<_8!]C!HE#a'h\Y!bH6*?ۡW R-50Di0"Blqd?+|0 ( Y!bH6*6)vo:0!Di0"Blqd_n;__ +Ld#"@GS\"0@17x@0eznbK(!6Tj#s ڗo }?0iDi0"Blq4C/Ԋ!,s[B P'-?-v߇`,P`D(shwB/Ԋf~{}H Y!bH6ŦM?Y] w(Ld#"@GS$qO)OM`,P`D(s_ۀ_a'. -|NP2=7%@* PlHu]?\Z/(0ej%zn~˿maik..mC,s[B P~}EG6I&LC bDύc*}` K}?!&L Bl %JCm~_}{q)4MFYP}~{l]WDp@-RiUm4@Q2 5FD =7NV68ma"8 ĖPBl40ej%zn]2=7%@* 5.Ld#"@u&L Bl %JC YP@݅,s[B PDi0"BlPwa"8 ĖPBl40ej%zn]2=7%@* 5.Ld#"@u&L Bl %JC YP@݅,s[B PDi0"BlPwa"8 ĖPBl40ej%zn]2=7%@* 5.Ld#"@u&L Bl %JC YP@݅,s[B PDi0"BlPwa"8 ĖPBl40ej%zn]2=7%@* 5.Ld#"@u&L Bl %JC YP@݅,s[B PDi0"BlPwa"8 ĖPBl40ej%zn]2=7%@* 5.Ld#"@u&L Bl %JC YP@݅,s[B PDi0"BlPwa"8 ĖPBl40ej%zn]2=7%@* 5.Ld#"@u&L Bl %JC YP@݅,s[B PDi0"BlPwa"8 ĖPBl40ej%zn]2=7%@* 5.Ld#"@u&L Bl %JC YP@݅,s[B PDi0"BlPwa"8 ĖPBl40ej%zn]2=7%@* 5.Ld#"@u&L Bl %JC YP@݅,s[B PDi0"BlPwa"8 ĖPBl40ej%zn]2=7%@* 5.Ld#"@u&L Bl %JC YP@݅,s[B PDi0"BlPwa"8 ĖPBl40ej%zn]2=7%@* 5.Ld#"@u&L Bl %JC YP@݅,s[B PDi0"BlPwa"8 ĖPBl40ej%zn]2=7%@* 5.Ld#"@u&L Bl %JC YP@݅,s[B PDi0"BlPwa"8 ĖPBl40ej%zn]2=7%@* 5.Ld#"@u&L Bl %JC YP@݅,s[B PDi0"BlPwa"8 ĖPBl40ej%zn]2=7%@* 5.Ld#"@2Ldf3LBl %PkNO*Pe "@I綸[ TYw߉g:&[B A8sϟk|}o=bj%[^ Tٷ> [B pW?U͝1bk6 k4D@Y]<0J ٜ_@5듿x3 @֝? 'ťs.V<0J ;R|Urvx!@lZ_@lNO^ %OpKw Ά=}\Py..Vq/?'8&[B ~lShߏ }zм^xv3#@O;[;?e(nxv` %zjQsuz_`Rq n-,.w Ό6햞Oyw{y 'ĖPBl3ǦRJU$!6ZM)T ᆪ@L-i_sRJ)UڟPBl w~x~v6RJ*43!b8ΨJ)T?ي0 !6Q;lJ)\vg7K-NP~<_߭/}>+}!6@SRYP"ĖPBl1!6ƞ[B 蓏}:Db8UllbK(!6`f>$_96!pJ;Wn\s-!bFb{>$=\+7w._ZVT&PBlboB¥!b>xSs3uuɱT0Zӽbwnͻ1t %LKÛP+kϞFN ?F;ƩߗVh[R ĺA*t~5TVZ!*0ZBl8^q-597;SxBl %՛׭[SF.*<6C =W=ml1˕$ĖPBl0Q;{?ŷ۝x).\ c`B'{?p%LzVj6&;ݿyv< &ڤϋݷ@[B $lMȪ"96*b//cV@[B {øvnvfbnVVa!6:i+E._Zz41I-`ml]t1wKVTQ|L&s,o]V<~KFJ G,_պA `TM=mwvK6MHτؒJ O} Ϟƃ'XlS֘nʱ%{{ׯnwvll+53 Tvg¥C( XD 'ĖPBl06fٻ5l_k!6z=->h.]L!b1oa} $pƄj-߹U|n޽Bl %u/Y.Icӗ8=Bl@[&j#_lϞc&[B @}mlT*3P+kŇ1lNP=7;Xs!- L&!b:YgnDȱ!6`6wjqtS 4Bl %5߹rZC]~Uͻ10B{{~Y|&[B @]|ǍfunvƟN~g~U|N0*=lCOjqrZ<`\ %Tvg+k:kD T g|V_Y[%!bA6t~9Ϟ8!6,?t>ۋxbK(!6Wo^Lݭf#4llx_8Blhf3?ԥ>c$bK(!6~kCmmpNpzUMW5ƞ[B @մ7g· [S͆ F%αy.I 8Uֈ6`ŧcfƛ[B @uwܸPwnVf(1 =~1cC-"z6׬ ŧF 8ӐO񭥅0ㇲh,߹U|vn޽Bl %#u0#Jc8)Blܼ{;Fh<` %G\\)@sV~g~U|^_N-`T^y=3w>Lݚj6?{:Jȱp"Z_4l̦XbK(!68{{Pku!pLBl1?t~e͝sטncF-7{n=Lerl.]ЏpAh,>cF-lwv/\fP+kZ3?exx= Go;tjbQ`%CnPkrlG&^q-t@iM6`l%͝C,`%α(b ^ jб7^_jG-w?z✻ٍSG㸿vnvVyT!6d//{kiԗ[B I^t1LVVY&Df~XCwz,eέpx @-%/&0fEe!bczuki!̧ݚj6w$`&AcQ|%Bl@X &Y/ǃ8#PGBl %Gya&-N۝x?¥aeP>j6!bBtȿf-߹U<6`w?zPhsgckL7zbK(!6H߹rZ@C-߹%D@?Bl0zJ///P/Bl %I<1 g=:ͻ1H &C9ϞT[B 6wzwO~l/gBl0yznvPBl %bBaȱĄ`?PW=~4P5Bl %z:nM5$ /α9~ 'b?㡟3bK(!6go޽DZlٚj6.0Ʉ`뭝ioP[ci J zjo7aTƑɱBl0>㸷?c0sDU&ĖPBlPoU )αEI VޚC9)//VkiA,!b'?/X/]v=&rfVVؕ1U ĖPBlеZZbE<ZT @Bl0f[sxcFN-"Q{pz 'c_[w[CEe< `J 뙹a6Tx0+u!cs3Z@~N5cρT[B 0;Wn\ `ׯq6JȱC S7~SU̱!b`2Y$ٓc&W>?hoÙk@u%f۳˶Hrl3BlPG{wo4gOdki!0Bl %DAB/|<#1yx8byxgRek@E%՛׭0ukxi<Θ0iFz62R1wyWU ĖPBl{]qe:j< `<@-?k//agI-oaT= ⱪ+!6>R;۝b{-?7wagF-W۝+7i.[{x3w^o 83Bl %@=yal-6ƕ0f`$2 c#?‹ǼpfJ zj--+T>ǃaЛͻ1u!g5`BֈgC-{Vqa/VkK vg¥a OǛ;[xW_-VV1'K-KEtcƆGJWּ&ĖPBlT~ʍka uU;0^gOa'/ dN5W#N[B Pe}qcn'&Vf(P;Blpz zkpzJ jkT>zmP7Blp _HkiA8%Bl %@/, α7a$'x<~2i--cO-Wo^ԭf`p+@ 귧A>rwn_/7ގ[B P{f>cB NDCv8b±8qBl %@`kCɱu$'-0Rʱh[B 0Z۝+7([zm0Ϟ_>3s|bٲ(4lbK(!6F0u쌿'A;҂PeBlp4{3Z(‘ tSc 8)Bl %H[-``rl@|pl//)5%ga)NC>cpb< IZ|q]q-J-8K޼;nM5=;0b!?Lip|Bl %a uUEɱ Tăck//abK(!6@{c=nY- $nɱU#?ԊP8U{f8!bTmwv{VV;xD6FJb~mhpnvg`sgXkL7!ĖPBl'??m~gn*bz g^| ̝$8!b4llJ ΒPYBlPM=ixi񕘿<1bK(!6N~{ɥ8hًsl?@ B5-߹U|Ix `Bl % z0tkO0B lL7b.Bߵfv %'bosƵ0|jOx,ɱU#]sˆP{Vj6D-8>8^0j8Ǧ!-3Tvgw(T'ĖPBlG~0ZY[lP5 :m--x"gO@ll_VbK(!6a 5ز *kέ V !6&P>̝Qj9T\i]5`HBl %z:J3H,Zc@ ٽrZB?j.\(bK(!6w޻~;OP|j{x b`r0JCuC %ko[L5@ew[pƄ1w^_!b`bK*!6ȯ/\fP.0JYb`,?temn0N{5ޘnz= % ª2'rlH0fEeZZcbBl %@Q[slx b`lwj;woc 'ĖPBlw+05|vg7xS%xhokA3ĸ|j&[B 75fg|&Lrlb;.] 3K(D/' %oY3 E 83BlWw0(Ln/ @-x[SyIV4PSb;[@ ĖPBl,n-- T>ƃ |V񄐟%t'@gZj@[B L{)8lwvDcN5os3x<0J[1bK(!6 x׃fEe<LۅK1G&@]?? ()n޽&[B LΕ™?W5݀VD !6osg2)?{&[B L>1 npJ{Y#CC-5[$bK(!6IltemM796DPY=f5lG~Jɿ&[B S}Ŗf`{b-x8 BlTC,FM)XLBl %0B8wkxi<L 86!6*{~Y<` %Xwl896d P=ͯ jrƵx 0ބJ `7g·{ Fslv%L *W-N[ͻ1bK(!6qٽrZ8ʯ- Bc)os3gV3*apx0Yi//axbK(!6z˯u&J So;*b;lQ;[0` %@嗯=[o>f8 &?*P6wgt3?bK(!6*wp.]UZ?j`!6NGM  Tً)#`%@ezzf|8Kwkِ I 'g-(Pſjux PkBl %PA{+7sׯjU0!6o۳fPF*n޽K-O>>87;B~*V< pBlCߙ_lOe67q"ĖPBlձٽpb8-ZY[l@h,GfP`s3 `l%@E C/$?:cbO+:ZObSF8ibK(!6{uki!eX63wނx b UC j4` %嗠{?'n<^*ZK Zo@ko?teme&0NJhbK(!6Q}j6ǃjJ I alwv/\ޡ R3FJ mwvܸNS`Zo.]F ;=PW罕x PBl %pƞ<1 n`/'=KH!6蹃AfP`2'R3MxK-Vy+ 0N֛L8!6z(g7Ӧi9jJ-Ft߀! Pos3>@;[[c PGBl %p^yo_|>~4 0%K ~~&֋)#ĖPBl'yv8͆|vg70JKH396TBlt ?T scki!T[B zcα:7;= 09τ@|V$yx PYBl %p]Y[Yʱ_cL `}/ϞcjbK(!6q.d~c"0򫿞? ~cԅ[B ͝B8ʧx0rl0Ʉ&~{Sq(Nԅ[B _96MBlǴU<'UO>!b8Wo^̝nM5>`+kgkiA &(I)IbK(!6#/ܸ΢._`xwnϢo0 &A 8MiE1@u%Ah`ݳf?0R'"J vg+k"GvIU ƛϱ=guCoo3*`__*bK(!6$=PV<796cBlc)/6 NV~j6gZY&!bR~ZZnW=prl09ϓ?llslb{ !b8~{eiƒ83()koϺ3s;{wŵsݾ_7'\$‹B2${210@: A'҉QN"qQc'gvz -yҗmVZ*Kfǫ]Uݵ\~V!/UD-D#MӗR[I.;g"Um(7,F C`u ogWV[F LgVh4؞ho_# v5GrlD!6qxjh=v>&h4)Jdg@rc)b:Qw@N7֮4#b hFCa}2/b3 iyn9@lyY.BlP)j\rlCa T[F @dS}*54&QC jntz\_tcP5pɤnj[F @ӱq< 96"Ԝ5CYn.!fT*q2Ǧd7*@雠54?rd:!Bl!!6MZ*Ϙk"6@bzP7AP^|ewj[V+C-D#'@bc^j=v>`h08wͣ'./.]P|D ߫?fҾWm/۟s)ԺQ@&hP?2ju]cFhҲ6=UL T(擱OkB%ò.BlHRѬRFC&i4mUN8wdp `MofG-D#ĆϜQzhMԲ^.}DyHܰy "ĆPs)h&jO>7u]h$4G-D#Ć[ \hhu]=q4ibA 1V =LEh4I[v@dɍ|h^Z;P Bl!!628>uǎL{z h\0//ʶ-;eǾhm@7@X96pBl77= fm#kU4ӳ@e@Xή"bCKٵ?Tiof^M!oXE`.M.Sˣ="ʮ׼+w#Ćxϼ\hfԍج}9y!l-iٽwK #x h=v> Bl!!6+O۟oK7y' t棋/>t$۹;vPbZ tfۖ4SgN^X4+hĒjG~;TD-D#i?q;gõQ'WV#8qaxh.2&!Ćf4W̮]+N5.,ХqyФ_>kwܰb 4ٽoԟ *58V vW@KW45 Dz4!64vۧ??mۖgN^>5/o3 \8|ѬR4a2?&6dU[F %J [)F@LJfSw tRrl4 !64#|Zrd+en|$o* JEt:c_71:=n~-B-D#Ćs>đ]lL^cD96V !6D?nk hI1ܰQ 8Bl!!$ĵv>t5C.>'>w#yeswy(7,"ĆRS4#R)f_4;v0Mw6[hXd2-ԛ-۹ ݾhp׃;N鴼5E&@bCMg^Wf7_4!훏|8G hVhV3 Bl!!8G*qP+gN^vnKc: ȱ`)KO_;?Vs 79ykgO}&/oNL hؒCfum==YVʥkR~& c߶Bls|撴H3h+v} yyMekD{]΍|(![LgءOntoJԄ9h_jZߛGMa(7l~y-q T@S[[kG훏 +&mi_jSLe@Y@P![--/-ר@]?|uM-UN鴼Prl4!6B|UtK3gN^z*]ww(\8ݒa 7>hbuϯn{z_u]+ɧ2 du9BlXEO՘ vDs}M7DYgW^hbImz\խ{fu(pV0qgQPذ*Fdz@O:@= J38{_^DӮnJ-T#?'?-璲7 Ȫ\[[Ǘ_k8*q-Blh7@|@ݾhOAJeuH6Y[F-N CGX Vm[vWgfG !ĆFRmzi}K- ;8@Lvol]3s|$/Fv} n$![lWCXgRW.Md;7XMO=M&2kF矕} bCÌ}&W-s9gN^hk.ԻhDɤ/@b ?XjtfpÇXWLed3ymrFiρݲͣZ:xY7u Hѧf-ݒ [F[w'Բ&f} .NgK7KE z Ćzd׻Z NYELeܰ$E4Bl!!62Y[mVCS xv}d~F^:cSh߀;Dlsuߺc :>$![:}J XWr"k2?cPnXvbC]jmW.Mț2@ݾhOAJW :v5,e@ ![>qp}j^ʥV'?#V896jd},ڶ-;YҢ؀2x;IC-D#֌d1#n|mNN &rl !6\!]^R8%MԼh&r"D{ʱQ:[F?ءOYji QVC8e^UO>5bCmuZ*Mʛ/@sK22k (3hؚtRq[gN^0/޷OVPnR`cV-۹%D 6Ͼ7p "f6ݒ+e7[FY $ fԅ=ΘW;vpD96D WL>)lGn?A@DXή,Un@bb 5q2ǖ]>AV1*bCM}fhteh"@v>$![[ &f+u]9FX]_U&!6T -== 6&N,ts`@b ELQ$}ckk$D96V$lVqɱc wsN ;7Bl!!('uQ`%9 dMBlgTڙ] ϭ;v@k2i !b,W`8%9dדcȚTTD +&. I#slC(7sl閌/qE-D#Msu]ԤH@Y96Jr@ȱVF.\@ Iz Qv1UB4Bln:l" R ȱ !6C 6H6c;9|ܰm>![mݱC 6[I7`uO_աܰ~%ĆwOD>5NgсYV[F-jvۧψj @o>jkuq)f7gN^0Le2?#N}7@lb Eė_Z:a\4aL\!/Gk(7l~U-q!s#P 6+ǖ]%Q0:=n~[-R#bBClVInrpȱd2-lѣj~\DU72UWB4BlQuv}Tx7`o;(NؘW @ !6]ץO>Al۲S Q`j-B4Bl)P# {fsr+9XuV=ybCE~>_ EXn|Z^&i6]{?R=v>4;Bl!!5:=n pu}j>AMV=bC%#c铥ڶ-;=&fAdSOˁ@D]i~aròM[Fm֚ק^.v'Q@ !6P)>Z;n|$o28pJ#fnDV Pvyb ^upyÇɉO@#(_Iok%@"b[->HyXܠL;| yk}e7f>M ț *}Q:J[[D"KqnX̱!۪Ϙk"d;7&`&f͒\v]zD964BlpWֽq +&aerCa ٕ%Bl!!Uuv}Sڙ̑7E>@݋@b} >U#jF.@Dk~a Bl!!LsN*sP.Ld~FA@}=d߃ܰ@b Rʥ y@X zpikgQQ Ҟyn`^}h.B4Blg(eM@ܾ\Ttr  G B>i'hOAy \KAD\Āb 5o>)Ԕgr$ G ޽/B2e yh6:=.,hɚQzy+@57nMv]D'c/j %9@rbQmp` ;` :ɟM[FQ +&X YJr Ӧ'֧CP%V0U4G-D#0H3Jun2큈#H&BlNZծ\9T w#.d[Fa GxW Rd) D֡˶-;=5!V0Hٵ-qB4BlR@# Η_ɱ @X%9H!6Q}RT>5/opV0_#eB4Bl Խ>(4@[kazZO QA3Z߀  +PnXv hw郬Q hk}h 2&ؠ{A֎7[jkp}T @Nɤ6ݒQEv h,f{f=mپ]P"ȚZs`@ b\!OjgN^75ԃAG  jFǍ96V04 Bl!!z;{Ot:ÌR@Xcc^) D }oէCP'gN^cP&Bh CaM-`h-K==c>w^9N rl$ ĖpLEht:=&*k/oj[V jhjd3}xU>5/s^jLM>j~:@"Ėp'?ko@DmrM/of@-D#VW|Z^Xmz0{DLZU n4)Bl ^O72훏̇xD\ hZ{}lUriB&Po24rl#ĖdcT1lU֭h+䭊[F~zǶ{fy@XJOg d %Y7k"ԼTLYɪ[2nD![LgU CR[[DPn &%g,v}j^6=DVnCTkG[FN>m}T;{O\ ~%;Yضea\Уj9r,bٕ}XuB4Bluɴ{yk@#Jn!G.G 3ؒZʥ y@#uoܬ޽/ @ku9[`ub Ճt:#ohSzlJ1hN}Njiv)$!dzcd;7;zldZ ; Վw[FzGuOAyS@e;7w @YKɱ!dʮҧ`pmr# @kwojCa B-D#VZc8.9~h @-DOoOA=<+0\IiFe7V![͝i[k`U(<:oyY#Ė@o;-;= E_d- b-۷Czy;jq(:(G1MAs`@bK s-3'/VK[k΍\ /r A-D#V[!}% Y=N(v_>@-D#V[[ǓDmN=T _Sed~fM&Ԫ e7"[Ҵic-Q@+x4 k[ߑ}hBl!!b-Q@+w @s'hFDg(u9hsƟȇA-D#VC'rZhV#B Ќ%o#Zh2WUMgU.KdV ![ }Ay @toܬ|2^(!Ddȳ( EShv\6Wn![ oo\?DA߀z G4h(7)rl#Ė(v}j^ޤ{z7Xή,96@b ՊGIq҄;Ic4Bla>CޡgN^c֦'k^s`@]b ʹMcj9hR2S@4bKׇ}OAy{@D\cV&"5꯶j/@b KCGѽqNޓeM0e@4bKMO ёNǵx+׮_mՆròuB-D#V+u]H^<Y}z45rl#ĖL>ק ѱmN=l4B~M&)n D-D#VO}U} :\Vv]4;c}XEbtz\t:#MSzں}4)wcKdx"1hj}mQG.xDKf8@eV !xA}̷m)oLY=re2-rhмr b@b DGa<|舼 jukwoz J%;18%oLt:/[_k[F&6uk_<DCGQ9rl"ĖL>cٶeNޓk[F&zyy@\<Y^|JnaUU{; F-!WMޕA}zb'K֜Oje"b } SMʥ =xeu @lNɤXSCa F"Ė  J f~'.VBl!!Q4)=~&8qB 5ؒ 7>7["@XtKF b Uz\w,\vL)?0} ;QNg6c896%AG$)HN_)f~5:=n~-z b(ןt/ܚ?]U(Y9/; >)Gpk^`@toܬ0 HsE9UE- z|p%ڽl y-̜ :f~ 1_v:s@5hت׻} ǭ 3jVL.Foft?,\,<͞z{A96G Z%riBޒkas T!6 $NjmyYjb U/8jB#d˿Awx/?1/6T+feZuIk {I=qTqb %A&ӢyyK\-zQ=`3`OUtV;oΙ Nv_yz}X1Bl!!%wf&`Q4Wa2sV&LYA/ł`c[:V &ҒkCT [R]<Yac2oRX ؒ@m2 weC%gwZ$\;]:T8/d"P f~2Wȯ]i n ![L%wY>pQ:(HfFOw<LvXX،8JѝŅoϚ9HXarPs5Ϝ`8N,_x}h~| W.M!,Kqb̚cJ `bOa}BV$@ɚYG2 o5”.PBl} r kRJ63?+d~P;B4BlUWX`c<˜lw?,N!bZlSgaQ[{)0> ufPWX)+A>i 9 "8qc'=&9F ޹ hwo,oFr>M$j%A-T|BX ͱY9vHIDATb3!g ;!63?7sl閌/ab U %"T|#Sܗp5uEQ|Ҟsj4Yi2gA٠98)JΤ'=dض<@=b77 oF+LdO@/Y05 [n2BlhjzKQ4#7ohc'zB4BlU"fV"* (Yv7YhBlw8NYG9>KRZy}3W|ch:H&8=qC$]ߩ-Psb|ZAc_Uiʥfq r(V*P=I26,\![?e9W|YAp{:kr9R,RTNՔW7yܗ `W4u6j O+6-L,0\)NHr|L7끌`Ub=Bl`%;w^@b͑xX،}2yS("$p2@hتD=Κ(ܻf^ {68zQz z,:HbN KZY a6H_>b[^!BlG udL|#M0s`0f7d!JoMQ,E HnE#@@B4BlU&t?dǻ?5MoNZZyP?d_ ͆ ~UVzi֌Ofj ;z Qw/s#)L,0\)_j>{ny$?>qB @d~fM&/ e7j[}A3'/țm~)w+sο]1ڋ[IATJ- Uu3R妀("$I bB4BlU"4ʝEݭ4*EBW+~ZL\ݲwov>+ YTެzoRU[Y d_ WY>pyc09$_ggCpu@~¯p&sߢߞ{<Ŷj罴5ڝݔw*ߚW,k׹5vPq?9~5!6!6896@bM}҄x\r)nˆDq/_U\@t(5墨T\V(H\RѩBA_a.=x=Tq@ H89stz\v"Bl!!*b{,c\\C861`;Rv)x^Qq՜erǗ5>TV ]}`𥐟#6uV}+FmiQQԯ.J.genP$UQ,yM;?/5W-? \+޾D_?cV=X!>믿t ]EPW?*No]3|zଷx=o,TͮyW*:k/Q_6\,taҢ;[~ Y|6rW"_eeA @ZK헳Ou(鼘/~| H|Hټw/5l?y}lY!͛fl6od ͬ-q[Qlq/Yu'D,kޘUbShty=M׵pzg1ڐK̭աj) Bl]{!4F ![ǕQ*E?Ye0MUfyBtYϝEkBf|jFV|zwEݻ }TT3hp[UKs,Sgdz`Go̞>;kvs5 ^=ͭx~&V޲kNUn>̗<> AMџVKdr ^n7o6ǻnKY{1_+DE]fz|T[Dlx9[{u!6B#4S]I}̚[}ҘQMm۲Sdߋr(kwަ:+Bl!!*b+qL(t7T̘YݼkFVi)H|>5#3)1߃O}b(K Axr^r 'ǭ^XƊ2vZk4==wq2x},PTUGTڟI/˃ &ѿO:Vͫӟf?Fx׍u5K/cfYgs-ܕ ,YZ3\- bsbހ3_b2߃OƜxYbjp&>pS@@< iU_Tˍ|( b U[0Sʋ~O}J'>5'tUYwܠ%KiY*۔)V(]:49{kcU;#jˡ-fO59F06b=T,ZG? ` כb L_vn4S+&>/џb +mG _[fmj?+hzs<9mbgM3Y)  9'f*%#ŕʳjmзY☜Q,oӷD 7D etz|M& 閌/#؂7BlUzW8%sï`/%*;_ r|#H @߫/@X#]oPwARUu,bEJ&4,Zj=:eXyL\#vMw( j) Bl$u5WRZ[FJ}oo@$Y^) ɇ-垀5 A`6*㷻M;{̒4ګd3jv^ INY -#ּ[skn̞;kr5_bV']somze!?fXtgI72WXρݲb=BlhVQİfU1Mά)DeW+iνS;ĜYD(xUZtPMbj(7/)j![i#zdygQ,@ǬVo/3f4fؼڬW]̹Sq* u 8{׭n{XRwSx̳Y-AWfOםu0쑧tQ:x&!h BZWJb+Z7=Sgͩ}tGb2Yr bsUDftvݠ@ʕY&xxc*?j) fXP%#b!6|<·g5S^bbS9}U@!9&'5g2{qz0wT} ߇c\u ZV"H1{1vTA/fOם59{W'ʝQ{uw}^bq"-Bl׻}Ϝ oF\oбr{ e CZD2R͹ǮoYA^DoR_ 7G10 ynyxeJhتD*?*3L^a5G*e>B7>0G7ci>4dq7羬49me+u3hq[UA !1?ĝؠPpf[4b\!D{hwd7 {^$3 + a5eAfجйΕ-hEݗ}hHAb_ 7G!69[FJ'r砼ItA(Ƕ^Ѭ9&DJ>f5#=xb:ݵ4f=k=ԌTɔ83 ZVf7׽6kgk18~Y ?u2{X2:=&׍:Jb=BlhI9=e ̧0JE2!x-"`ͪxO~(֪/y=<+ᦀ("\!oUɸ"bsD}N]71 d(麒5bӲACQ87>uWo7Wڬ`UUs o8Gﺕu':cey7Ϲ6h麳&ǎOREn:8bd~Ft`! BlG aԵ/Ycu\"ӫYLav^bsk{5o:%֪ ^Us^}ᦀ("U%Kd G-D#V%Bln/wʓ-fLg̕7K}({_3*vv/w8EKrfE֪ȘgLJLU@y;^ug}n{m‿ڏǃtp>cAN足&+ئyYF-( 0c#[bCc9<.:.JޕLD+E=p-H~AgTVK*QV7c*?j) =(66/]ٹB^v$![ۺDןu)AWXèf?/Y~rX bƮ!?ZWc`f37j-n$8{׭m,q G}``#ctYT'۬x0 ERм0!;i}m)oF0k;cm.DPRLafYB1ͅS=My>m%jn ʥ =u[/9:b/]YdB4BlP5y,FqV@X4GQE c=]_vp!Y&a~T sdmze"`blVa,8X3MɱB!{,_0k;;YZR~R ߖ)Sqµ!U[ slasςxdjX61Lʏn zkhs`H,Bl!!} o|$vŬw׵t bsTΜ`lfΩVc_pyz.{(֭n^{(~8\y׆>3A>ۏ2[T !0q/"+b\!/`!Ėhz2S_)YqOGMo^*_ Cl劖Uo47UToOA=2W[9ܰH Bl!!ݫᙓN·5#dV|.T1Y,xbGd+ӭtd R OY%U0m cslO 3E׆*ԉ,.%2a׎-/y?f7J0 q;wo|꾵`)h'X{} /q[VzUzq[[PP+abK-۷OԏYs$-rdr`2U/:sOARw VXުoHu㘩[*{XDɴ!l2?#8p5:=![Npۖn'îBbJeM}Z`Ԍ|XNVV0Y,FHY7*4G?~ẛxiA*v۳n͗ӍsV6>Kwy\wcc#:N69XIǻ>/bSת\?ұwzcN*Y9-?+bKf~AW*Ȓ _,SIYÞQ+X*H ˛EVlda_d)RzRMpc(7l^C b'ccؽqI.@O "8% <)xg~kC ?||璘 `9A5# ,o\ߢ~W,ުfs7ōqE<,VZz?-w|v]7ǂTJ~{/jKۼK,Ÿfn'zzxh`P[ɳE}P).oϺWիwP郺ڥOH|,O'$js慥n BlIO4]/jCE!`Wɧ iN RwAT *$v,.uv*H./,$s)EAU,]4ǯz9_{Y_FRL#b\!j'id/eEb#+&^6rSS3zA;Kҵ?5HVOSRz&J۬yA!\^\H Q|`}l|Bn~kՆ;a{~Ծ{%n>e!616ֶv9@XbKf~ad5 m]s0,S/HFYo5fed?iO@+K@p_צ'Td-Y@ [F&1TG'Qd)賸KȠOqPQr0W_3zKtk»]R\BTf9V,\gxfO7_vygX*]؏i rAذPR~ݯ]e!fN*M=@mۋ,@- s/+4k%ZqPߒE>3 E7sZCw;thnǍS,m֮'gʸٻEysx b惜^:ޭyYf]j 2\OH\C}En25¤(bKt:yycDd>^Jm‹[Ko֯ ygqūɡɊk2͹Zɴak2?#5!cc!{>'1g~{7>-pԼT\!v}y n!b>ɛĕܺ{#cjǰ?{\ ּB516t:#59kфή,96H8Blɡ>/P|ڽ;1lx8s6u?%5d~fM&3^$![ y҄A,9?,|OH=k0xLY69дNkrDz 0bK5î훏M Pf?Zx/?[6d %{zrP*N+`=Bl!!>}qvTwkMOn/ݚwhwm٩=&G4rl[t[M p, 6vAͱ$*=*ܰԤ@b Љ{`8BX+Z9`4@&Ӣ,WȂj=v>$ Ė(~>{zʛY ]c SzRMgP+fA}A-D#VC}0US7<.q6X9t<69` zjmk\!v}W396H(BlwQ\wȌ~_$S4B" ] ,"Z}1qFV/HL$bnֶHU*^{Z]sF G9ь#bG?.Lvf, !Z{}VePm VV46كϊ@} kϜcTw^zA![e{vvGbQU۰3Êa-QqerYq9ve] V&f qj]kX"cن<!b8xciOw~`%Wbȱ@=#V䊢,,DOQ d2bBlgo!5=YeZHX7!:ٽ_Q?,XK:veyjis!4!b8gEQ𣟈Xzr-іJ=8;#q̺ P/!)( Hڮ.=@=gŝrfS[t@"bcyOD,=}z|ߔBlE $=7p^_uZdʟ?EbBl{TYQ#vl2exrL_ȱ@!Vv{vAg%vٜ`iܘ{E2ٗu5@!bgEQW Xv=ݽv`jie-QI$ @> O.0e|dZ?oӃ,3_e2-bBlUbu{5515؁I=r@xESCYbɧ?;ve] pbBlն^@t?n!aPs'ǾX Eչ3r2S!; u<*H+WcنH2Bl1 !jr LX2#v tjQ]!6lڱvGkaD@iLa6Iveder[BڜKTM;ޱFRPȱ@*bS._5T\`ӎmzD9vL-Oj!/Af26Ҹ|qȎ>8G(WNMau`l޾3  ڵH&Bl1 !%LS%QҺROP[frbazH 751z6o߮'H'l­s&- (ؖF824x]?;P)7J/ j96HBlXPm=ݽvfsLܘ{3ٗu5@bQ-MPw?\:6Q jαݘG V0,i;} rYy)[Bm ^ڔ XDД<@bCIf^d,kGH~ގ9`\Rߺ \XuΞ^kڍboY^n|kۖϝ@![ܘfs#rYgh5Lii]sOK%96HBlkLi?̇f>vuX}bײٜM]xG͕ͺשВom{{M E-F!ĖHdhz.`l6S"IF >g{͔SƅWߔCƊ&Į^}z߳ky5}>@LM̚kԩ|im/߭[B-9vk"C g|dZ&L?S'Ƕ;t Ćvuَ3e]Y P>'ǺDɮy5RkjWߌh˯l;Óʁ?bU%mwҵ =6yc }X|O>>bQ%HPo=!G۷AKu5R"Ć̀lry+kAɱ-;vS)n4(ԧ+t_4[.}Q6Bl1 !96>:m.9. B.+ξ !6kN N,\jԾQyBv i-;v O-JbBlte*_-x[K~%_R2{lrlP'ǖ5 ^׏Qz|hs'+f-%AGn^=-;vlz@-F!ĖXtʱ O $!6+3^_lmCH~zcSۛ^˟P&f[&dN=+1[ny>-gՏ'tD_ZG-F!Ė|gN>ʶͻ8lPY6\eA PPctv>4Wp*\Ac^ٺi99 (CQ|k%}j![M0igmwMMk=ۊƦ+WX?2k,Blg] _ZXE30x)M`^X5|]a"5(jۣל[karl2CכW< RϾ5M'Vwv*bxrlˎJf{y̯.})@-F!VC^]d+C EJ D 8@j:~^^Mټ}2zPs_ݫOE@'v:O_B-F!V[nL+mu}Ԗ |\yvlH+Bl,c˟:i6=qݘm릝Br|qcc9}鈾[B昻֢[.`%P+//N'^#kp0JP(2ֶ63dF Pgq͔{g1@ ^oj|ȹ`CEtY#!`.<0xI_؁#b]o}ڢaJ眡P( %%5=bCܘ\e;+GC s%fs5'۰9-5H df&Bl1 !}6OwHmw9M.myjsP( RCI=nbCU?~#k (S;֕ÓcjƦfںi'@L[V9W52?j![3OnvW3XCkF-O\??ܯ] g>BlGhl=mw$CzՂ<W g9P+s7  >@bBl`l?z\󞾾ב^v>fsk; ?{J6\{/ס?7| a ̢!6,EY2 ׹bg9qeAeK@m|qH/R放IC-F!ĖYN_ϷK !G=\[+^Wu@13kX>6553$n2lPa>|h5t6|qHZޱU x,Zasv=y}ojbccU#$ ![{q\g;+\Ñ^֗x1wl޾E;t>$cذϯƇXD ͻ)9׍6yje9V1Zm0D#bKlҚrXOsY.5lO}_ ?ia8}A K `Kg& /r {eEc%}2CkkQ-߲% Nw۲c bK%glڲm.>%34xqZɴwaDgyN|{ZzC ܀O^ٻgĬ~TC;bTĕѫND2߲dhp_ $![?S9sݫ Z aEcSW7)Ԭ3Ԅ_ JezC }{`K.pPA#]p/LKJ&`C=zckmm#dž"bK1߅BX<ߥL>x[g=,3 qgD ذ̆';[:|qH?ijb׽f2l(OWfnکOH@:|*yomm2zU2"bZZWڎ!` }º oցj?"BlHsW46ٳ–e(tOww>7ow>QAgN9 6H=c3wo^\(Gzu|<@22m|dzĭhlb),!6@j$Bl Ć>x C3 1>2m.:_;˨sӬ>-351~F9r . b+7f&<,:w~H/ C׃>fs;/@\!exrlmW==d'uPg%uhl?sR_x{S3b[7Akk"!(m;e]CE |v˝1* 5!6bCbug=Idm?'^o?^FK6/ we* 6=c˓cC2bQխɱ`JgQc|d /75>^7ˊ&>Cbo"BlHk"Թ1dcSx<@)]8}u}251k.ASe$D !'Ǻ71i6kASޱy}*dkk4|qH?Q@̮y>4CUa:(uN}le v{F#!b4 bl4 ŢdLټ}ۣ,BlwR@ 5ɖJ{= 1>2]Cl6y1}-*kˎ#t)=K[B ڕѫ=21WZݳ@t>۶yWSC<|E>Ò!H qKE P^ޒu??Cu]ϾrcfR_?۲c<- g&I=K[B !"~0yh3#zd)-]#H qoE Phο  ^?]*>ɮa)SNUMخ^ PmbBlk{}6s:߻gS36"Ҵm.s)+<!6@j,Bl Ć87p>b-Çfijb\Nx\=a.S+O,+Wظ y쒣F=!b9;MĒ5mw'a<cI(Yt䅗M,k/d RCmb!6|m[ "mC3nCh]jdi\Cv d͞l^&ev=UE-F!Ć焲m ޱ) IC ΋!'Ͼٽ-y>4P(i(ln]׆z:[vlWa~^gN!Bl1 !6,ލI S(JK3!RC܈b!6ԅ3hP(5W},SD?sR~^Ц&f-5pD-F!Ć3WP}{;֔BTWwZ;|Esb Blf@ ~hY>4P( *抴=ԍI}X.`|˪YSl60Bl1 !6, ta~{w< Gu=) %n17%e>ju!pߕѫgXcm)(b;=gNWCmɷs{h(F){^=@-F!daG !6b@dwq:~^G(iڍr@97p^:@bQ$!6@j-C ͹s֯ݨC D151۸َ)\9a bQ$!6@j-C \5hա"nS׬ѣPYbBlIF vDb!6D,$:4x]\XTF-F!daG !6b@)WFڛ7Sz{u2t~̎/Mz *[B RÎhBl R͔SxUuN;[h[B RÎhBl {ָY i;20xIX@( 5숖!Blxd.`Ә \dl흏1 (!@b2@ &-MMTK!;$?a [B RÎhBl MZOwd:V?fG۷a [B RÎhBl yg_wh0 `y]xM90 O [B RÎhBl ykM;u%ָbaG_ԃ !@b2@ 5scfޞ24x]' Xb}'ؔ_٦/ Bl1 !6$#H ;ekI{{ָYXz#vxʰ([B RÎhBl Y׵ޞ=y#`Y[V>=bQ$!6@j-C Ꭓȴ,VmeEQ, !@b2@ ɵD-t251k +bq( 5숖!BluQ{o{BXFnT [B RÎhBl P߆'YDsy;N([B RÎhBl P7f% H gE+WpDA-F!daG !6bo6{cZd+?ԣ3 Bl1 !6$#H ;e@13i2% H*h{=QbQ$!6@j-C ꘹we% Hi;`e8 "bH2Bl԰#ZO@ u<`ʞ +$D-F!daG !6bc we׉b릝vGj@IbBlIF vDb!6:foL։vj\5$Bl1 !6$#H ;e@2aqE #vB-F!daG @>xޒmݴSHv2zUm@8Bl1 !6$#H ;e@jX%;u (n#W G-F!daG !6bW\%Y֭ߠ6 !@b2@ .]jDzٜ 4C5 G-F!daG !6bKg_cnA(kxrLp@Bl1 !6$#H ;e@]>xޏt Ա1>O@( 5숖!BluimW;uN !ynݴӎ_8 !@bO;cO]PYvDb!6ޱp?vN >H}>^~0E|:yo^yݚֿM^;~u<8 !@b3?x?6?_:Ʃ$2@ .ٛ1S&fuJtÙnMϼWVY1[,?/YS_ֿM iǯ5zBbQ$!'~˙ҏvIW(!6$'wfv=aOt 숖!Bl̤3EGPAlI}zBUg챭dB ^B њ0Yݞ/+ڦ8 !@-W6oؾ$z '}dԬu՗C-o]f'~͋U?eOu}Tġ'9NsI֋k.EW2@ 0{3ֱ1Hg "݉5yOL0=uw3o߸rD CaKt[,;Ks-D-F!db&8groNri3Bl[s[NH&MtSΡu<<_[{.1;eOflڍ:}.3.w[&_+*zIq٠P>Bl_vp}:y}UIWc:WǓe9; OaB-F!db3VM?u]A!zLɜ:3 eKYg/?m'wfkC/sJ8y|#Nǡdm;.!w=P툖!Bl{3ݫ03oq1d2&R2 ֝TԺ }zW8}~wpm;}}y -ěyŢ.ũQu~PBl1 !6$[7esQ7tT3 椭[6QBl!ǰ$saDINnXF|뗺NX۲wܲ}1Gc]O?d숖!Blg]{3vyHxG䄇~0[QLm[eTv+-]!6#}31ZX9:*=R wBbS38c+M;(vh bBlI!6g/DdTw s[62[t<7o}0_YpyLL^\o? ӿ]-fNusSsT#O!U_Z[F:fޤjfN*'VV2@ w,܌]8̂Vr X̼(*}-]Mc~8U ~ya!ɺ"F| %ϙ:%ν Lifo^ӿM^;u<9 !@-c-nYѣ9dc)gw gw$nI 70-;yod_Zypwl9_/%W2@ KMQT$yݾU*Z҉5N@"H?T a^Qnf#ĆbQ$2"%K6%)sKS7#BlN+7Fɵxk~[WK[,%J.5_uK^?^H⭪숖!BlތL|@j,"mh"?VUi \Aft$P=i }b7cY<_Ʉ\݊t;aB-F!db3uzBWvܙgd-f˚2֯,d_踸+숖!BlމeRb+&),l ?%X'Rw6Y-g-(ؒ@ dEQBl(!@-o[.JFV?TW}[ZPZnXWrF}OnggbrעG.u^tDiGOm?#NdYnޔijd9>hw(Gnٚ}S/y }}D޹} n52Q!։zk!m8 piz٠신˂"7q!َ=O$tOSHC/cG !6b?N, w.ddIWy;e`ܽoV|gn}O'v-rNm7`D6ͦWr?#ΔVx ۿ>}SL\ef`9fll$}9w~F9 o;}6M9gx /ٶAbﵐIN?4h,K e#bH Elm^/% y̺/u8l/JGO{ÐFZ?8{!dATsT&pIz%4"u`t,ۈ]|U_[n?A.g<%>ݐN4tN˗|IPωwVovc~Vxm~֗=᝝oJuǼNɭ_+yr!av__ CeHuvDb!6c2iXLh7?L>J,W *L `J0ʋ4tzf9 &pI%gd1AJGڭ/ɞE{]As_m\%[ӷi!!#ⱊVt&f5O?h@{9ڃ [_tw_t_;ooߦ!6[B ɖ76W(/ s Jƃ"NȦYftAm޴ V3QVSSܡmz2H/f i%cd~ 9|_$<K|+5o{dk̙ zag1W-}ZAm쾠s m KhBl PgnL;l6)c5NcҜdJ܍pI ٔ1Zȟ]}yP6G}m"Clw|PK+L`{A'IAYyELμ[+VhڔsxҫX!NP5Y_.s slް%堅ngFuSF-F!dbC9+/Q-d-(#%cd!q [/"?J1w~FB2Cz| |۬i|eFs YLfZAE x3J2F$'-dG'~MomY(gYFfHr ؾ}ogsNKq96[t\pM_ouqs'M}z49-vSb?ZXydk_vDb!6:cXt8 ed&hBA[Hzbd#|y`g8xvy!6wn_4` ԑhj^kuBlEKngT_R6[w_M}pB8XɔbYue300rb+*8)4[ӹ7.PcS!6q4Y!@-{-Jw:_YQP鍠-ϕJHzotK`tf ̐ӑ/Yz+ sNͼĩf 9S1-{):C\3 9y>z}MnS@ӡWv/́'G5C//1[0'O̚lszɚ!"[+ys첳_^,sbs9nflS,И٬C޻l_w|_LLy珓3mojS?(){2ws 4«?y"ڹ#Z&>YG]蚵U?({Pz[ARTtۯkR_]ԗEפ,&e5/I}YtMˢk[D\q4s?y:(I3݉OH͉a3Q*LdfMdw @٦HHЭ)V>fn l%}lɮ`>Vh8[LiN4fsFRgOo#kM7^nyR4_V‰/79a~3νf?&96x oe9AG?s{튮-^;ߌpL %!W4Y&`|bQ$ٲ؜t9ɒYot˾7#FY!՜vsr dtnSF^qzH6EÐyd%$[9OCEbN'XO|7]dzʩmYW6;;lC6'̔Г͙̼z5Nu!or;N.rr:oꠖ>٣m~/N0Hn.;e?ud5/Y[ Q_]ԗEפ,&e5/I}Ytz Y Uឈ,LFOdj$$%zH5݂(}}Bb+ӟEl@Qh/hkτN &߼+ Qn(fQZ3e I>paX κlSǂj67wZfNB.O-G:VX~@ biqB4:#VSAk}#}Ol!1ibL`"bHe9).+ĉL`&Qd'$#v<pBNA V [Xp瞯 x̑MN #~\/BBB'lN =g&-]*/ e|=lwf>tBΩ[v7pG̡$Jwɿ^3!o9uluIXgOΠbrx9D]!';KhOdYtMˢkV}BlԗEפ,&e5/I}YtMˢkR_]Bl`Lx W{, oH ɚȿqQ!엮J *dXXɸTBlEy%_ ep|A+ՖV|YG]蚵Ue5/I}YtMˢkR_]ԗE׬ۃ)<:t%@+ʗXoFXB&uB%Nt&Wr1g*9[Ph#AY'b'b#JKoe=IΤ;W(Ssn9r_JfP,T\cg[qkVa͐$9IxAW4J0|ZPˎqO˸'w0bӰEig-'} PgnL;\A''<#q#Vr,'&' j3!Y'”9QX\Feo29 >:]c8yͨs, Z~qfE!r\Eꋪ؊f;E1;Q"O2Z01B+&J@ޱFt@Bl1 !6$KBM潜>3Y S&? %[-Gby:V.c-.F~tQ _ZQ怣w7"f0og'?t{MA;gK]kfmV%w!oΗ}8Q1R-"W5jbĭ5?U 3hN6{"%S6vV6Vw_8]Qͯ "d2ގGhCYXX[~3rZ"#{lMNXh߮Dlm}oSF-F!d e"waur'F $o#n=&Lm_Щp\Gz#f*\GrjE*/<%;:nETb0缓 GlmTɽs*%+’搕֎{Z>ـ(%gy߸/;e@YdoGu> }d2Àt|B^G$NnO$[h BClN*ʭ3YQkXrm!6haO zނQc("RHɴS@~H ? +yOQq$dQv?L&}1z݈tI C@FQ4ۯKg"]:{F3JWxt̫XfNԸ#6Dw {7 oCxyͱC)7f&0!bHdO<[YrB#2s^eI[98dYlV jlSWuҨQx9t`P"M{ s32ev=[7Rc:q Fig/tź&^E&c{Z>k"6G sfo:V?$hcL[əlfjqN^+)AYWU`lE6NNSMU!N-frR Gʺ؜}J-0ZLl轤J`+L><^h#f дAZhFaQBlKZiX6FպޱFqcq'IDAT@Bl1 !6$KT͙?QG55p8!z7ɝ]O?'kz/ϻx9A] ӋFˉ.On9zGtoA8sgk! iȱ09`PtrY-qSPCl岼7εLOP2^,D[?-LВԋ̇ZE O^/m3|f Z|^~QGό.jvPyG`l㩺>$bu!6'tXrrٻBn~3|Ǒ[?1l_0- J-d׺ zBbQ$YBlNSrڪ9D |Ep̩<>xΘܞA!=۷'?d鈘񍎙0ٚ[6Wo[s昘 #`_b4g ͫ eKmmfSUP5ߙ9=MÒ"wW~ .;b=,K.ǮEv\w8e% dEElE'TljXUsU۩o?4C?]ކ1i+w<+>=Z'?k^KFl+^wq;Y z' Z 7t4OOJ5j#g6y}W~՟ԏexۋDl:7W/7Y~T$?YE0-Jijj3to97T~}􃃫8ΗZ}Z.W3(.2='~j~~&.Ж{"3o~4;='c|5kE .IĖ0"6FrwާgsmV @Ŷ︰Ucb rg V/cݟ*Wl0U~DΰfxQK% d"z[ 9Zg.qӮzoPuDl*vwqq1Ώ߼tlw~to#u&Y%]|tߩ͎$pDl #b(mzO3_FCOkӿ[/;,h"65[baljrs\ ](]V?/O?\`,5?|/\{tzowXy4Wl$o9Jg‚q8%bK@Dl}z3u0>/#7ʅ"6fϟ6cمҝOQ+cj}r% d"cX-v=lk +ZGĆ nw\0;x`ҦK:_w1@7tkX|`0Dl #b(o/}O|xc7~ƛo]|ɗ uDl#Æmq78wNͿ}gܴ=4ʟ?/_<:SRB0Dl #b( V @N.- +PkNO_e0$[ˆJ&b`l#bCP7\Ql)JFP2c#h"6zɰm659`Tϟ TǵD2"L+ZGĆ z}W=:{"n`$xu֠C</RpDl #b( V f -lu mbPp%Dl #b( V vǎuz[@^s<% d"6FX:"6DlKakhº31=/Op%Dl #b( V kؽwofv7>u. W^<f=d0% d"6FX:"6DlD7l}?bjrsXq_w}*% d"6FX:"6Dl.k8;+t͑ٗ% -aDl%06Š!b`&–ێ7\ӧuZ{x1,Dl #b( V })l5W^lw~gXe&&'% d"6FX:"6Dl\ʵ;vͧO GgO%y(^ [ˆJ&b`l#bC-.f+ ;֗&52% d"6FX:"6Dl .k!$7swq92RAF"L+ZGĆ !\ZMnwp%^yhXVi׮xDl #b( V 92R؄kf"qԹnwSXS&&'% d"6FX:"6Dl mfa+];o]P\HvFP2c#h"6Rl†\3GgOi pKփ-aDl%06Š!b D7lMMn>}\(ΞH3[^5NDl #b( V ;r8l5g]qӧMMn+Dwnq!^h`FP2c#h"6{\3O=v8.Ϟw.9/1~Dl #b( V ek]8V.:ݟ7^\`]FP2c#h"6.˫^膍nw3qFۖ+ 7[ˆJ&b`l#bC:̓aN: TnwSX/&&{-^V`FP2c#h"6#lutlD f;r8^P"L+ZGĆ +{ްi̎7u:}\_v虧!bK@DluDlbٳc6O:wx"% d"6FX:"6Dl\K[ x@Wh-aDl%06Š!b KwlO=v8nĮ. h-aDl%06Š!b Ņnjg][U'bK@DluDl篎c\_6=}ɥxɀFP2c#h"6c-ٳ8q`>un%@FQDl #b( V zacoetlc/.&&{-^&`TDl #b(ف?nп(Š!b`ݵӧc쉩ͽ_FDl #b(7f֦!|{aE>m69:{eFPŅ?YO?XXήGmDlŅk†_|v|L@AѾm޽ 6$bK@᦯p, O/M,x6"6ɥ-wl'  ~wlZ% p36ؚw8Jv˿w-[\;j#b`]\Zvǎ9߱ {noO~"^"Dٺ6x@ U/vTH@ vWfN:W=TE% |y>l>WPx'wzׯnbQ!{FlҢ _y}~'}(-aDl=Dl[7Sgw(?G?~gwE'ݿ:zQ'9̓.t9iч?DIPӧ}Wwx3WMN9F!bKFc٨1Qf}FDlcm&l ̮(@ɎΞ/!zӮ]"bKrƛ1QfrK?-;{o#:5٥E瞈/!#ox(-aDl pcL3|v_ x]Z}3xQ9}ܮ}Woc_P8[ˆ6ŅC>ClP{>u2 Q[\زҢS_yhRвUO{e% ?yo8 c E-xţS'&y2BĖ0"6Z&b`̾_ZsꢧO 3CٲŅ 6[ˆh \Z\lS~8 ӧ9-|l>P -aDlL@9^=~qr@/|='k؈Dl #be"6JܑWMN-0{v%ek_}ep;]hظDl #bh}nq!DlFc~+/; <}ɩC<3F'bKUWO= b-.uKt읩O?|d0Sv7~(JĖ0"6r{- N@̾E;S}3N:W9>f׮ݱ1o"7Lpd;.lFN{;>&.6h=|v#_"Ov6&f? `s31ѝ|zFTN>=ݰU7[=.3 Z3WMNӇO.-_0Dl #b*'bh ۖy 3 {fzC<B Dl #b*'bh г_ܾ檳c f '|{}۵fvkʉFTN>anqGڲlv=}ڀ~\04Mvz OĖ4"6r"63k[/]u/w|LwԾCkuˡgԮA/[ˆʉ'b`\-.zTfNo[JvtDkk_} !0;o3Oi`U"O؛[\8{<=ۀܼc 90F&q_w=5LĖ0"6r"6ɥc̨f%\C IDl #b*'bh j\Zl6G{`{_wD7lcL9sTu'}^=Zm CĖ0"6r"6Wܑ3߽w뮿jr*lczϖkϲ{`_u$YDl #b*'bh 7l} K_#'`=FTN>cOĖ0"6r"6{"O% }"6ƞ-aDl@Dl0Dl #b*'bh Kw?^o{ %[FEĖ0"6r"62z'B|FBĖ0"6r"62C;HĖ0"6r"62ɲP[ˆʉ'bHP&[ˆʉ'bHP&[ˆʉ'bHP&[ˆ?v~c #@Dl #b*׻9ى2IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r"62IĖ0"6r= N L"s}"6(-aDl@h #$bKP9'b.2FT1>2r L"s}"6(-aDl@h #$bKP9'b.2FT1>2r L"s}"6(-aDl@h #$bKP9'b.2FT1>2r L"s}"6(-aDl@h #$bKP9'b.2FT1>2r L"s}"6(-aDl@h #$bKP9'b.2FT1>2r L"s}"6(-aDl@h #$bKP9'b.2FT1>2r L"s}"6(-aDl@h #$bKP9'b.2FT1>2r L"s}"6(-aDl@h #$bKP9'b.2FT1>2r L"s}"6(-aDl@h #$bKP9'b.2FT1>2r L"s}"6(-aDl@h #$bKP9'b.2FT1>2r L"s}"6(-aDl@h #$bKP9'b.2FT'nѫ^o@v"6Dle% \'j&[փ #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6ra#bh #@Dl #b*:"62IĖ0"6fߘf'7lozd$b(-aDl@z#6Dd$b(-aDl@Dl#!bHP&[ˆFB L"5 #@Dl #bj&b @F"62FL0"6Dle% `$Dl$bKP3H2IĖ0"6f"6d$b(-aDl@Dl#!bHP&[ˆFB L"5 #@Dl #bj&b @F"62FL0"6Dle% `$Dl$bKP3H2IĖ0"6f"6d$b(-aDl@Dl#!bHP&[ˆFB L"5 #@Dl #bj&b @F"62FL0"6Dle% `$Dl$bKP3H2IĖ0"6f"6d$b(-aDl@Dl#!bHP&[ˆFB L"5 #@Dl #bj&b @F"62FL0"6Dle% `$Dl$bKP3H2IĖ0"6f"6d$b(-aDl@Dl#!bHP&[ˆFB L"5 #@Dl #bj&b @F"62FL0"6Dle% `$Dl$bKP3H2IĖ0"6f"6d$b(-aDl@Dl#!bHP&[ˆFB L"5{'>э bHP&[ˆ@6ozd$b(-aDl@Dl#!bHP&[ˆFB L"5 #@Dl #bj&b @F"62FL0"6Dle% `$Dl$bKP3H2IĖ0"6f"6d$b(-aDl@Dl#!bHP&[ˆFB L"5 #@Dl #bj&b @F"62FL0"6Dle% `$Dl$bKP3H2IĖ0"6f"6d$b(-aDl@Dl#!bHP&[ˆFB L"5 #@Dl #bj&b @F"62FL0"6Dle% `$Dl$bKP3H2IĖ0"6f"6d$b(-aDl@Dl#!bHP&[ˆFB L"5 #@Dl #bj&b @F"62FLg~~}a?v1i^m\|H8tq}^Ws&!<0f2HIq'شd * VWծ{?Kw]]ko>k/`E pbЄ:bXBl$0*4!6΄V  '! M 3!6!0@BlIBbL `E pbЄ:bXBl$0*4!6΄V  '! M 3!6!0@BlIBbL `E pbЄ:bXBl$0*4!6΄V  '! M 3!6!0@BlIBbL `E pbЄ:bXBl$0*4!6΄V  '! M ɟH `8 UhBl@)!6RNBlPg |+B `Ը[&ԙb 5.$V u"H `8 UhBl@)!6RNBlPg |+B `Ը[&ԙb 5.$V u"H `8 UhBl@)!6RNBlPg |+B `Ը[&ԙb 5.$V u"H `8 UhBl@)!6RNBlPg |+B `Ը[&ԙb 5.$V u"H `8 UhBl@)!6RNBlPg |+B `Ը[&ԙb 5.$V u"H `8 UhBl@)!6RNBlPg |+B `Ը[&ԙb 5.$V u"H `8 UhBl@)!6RNBlPg |+B `Ը[&ԙb 5.$V u"H `8 UhBl@)!6RNBlPg |+B `Ը[&ԙbw[&oOhSz_|̥V[&չ_3 I `?{Oz_ӟR*4!6k[&2b`bЄXfBlyBlL 5OBb`  UhBl=WfNMG㯽П+̥D ոt P_)g.KvbЄXwOhνݻwcO4V4myZv|Et'G_JWf/+ %3|̱ U4myڦ7_{eǓp UhBlg.ݻG^MӴ!oͯvoo3oBl؅KG~rȫi6mwx+[ bЄfEyԚim_zQbBlJ{q?im{Qi*4!6չ\ #ժmt'c@r_)c_(~Wb`;}܄t}۞R6=HKt۰h;s\BlC9 29nδsɉ7O<=; ln\9vD(濧 ۦOGlԓkջ~޽kc7n}9͜:{%,{?n~ ޵4:Uuoc6~٠#! M Uל6o0Bm}%׳n}#?9jQjE 5sű޷n!  v/qmbBl+S'doܽk߱#'րUƵ;}{lԄkÙczdL5`:}jD?0)bԄXNY386~_\֞MO =sɳߢf]@qvmt^)=mOmw>(ԇ[&8ygS|]Sjěi͛XX.\*^]{L`qα#'lcc_{ԓ[&R{Ϸ>DvPs=lc67?e>!6V%k@L5ҋn*4!6Α@c;m6_{E5C UxmO;r"ӍkwWl>w/[X*4!6ovUh] >5{׾޽lBl K4oxƵ;{+6h#zmyg?JxabЄV|W׶>SSTݻu+=s(COvqY Pލkw 6lmBl?<ݽkt @v[mWͤ_0Z'~mPK4vȉnQ6; UhBl,ޙ:FG'[oll㑟Ma 05G_##[Bl}쇛pI'kwO俅0{̥-/C#OMs1ؑkX V PUC۞#]rU+MFGo/΍kwr yI[&@yf.7`mH,ӧEu<~Ðb`]yj08 /߸v'v .On}WslorX-*4!6J:tq]t~b}@ 򫋟m/SgS-{?Nkny [J UhBlԜ>wO7C͍kwE7m~sw;,!6VďƄ6:8vD:`t]XwG; 9! M bg.۴9mOpx3h;V̖ǟv#gӉ+wv[&@Ob5'oI+ cُua4n9O0mt1uJ:`Ik|XFBl w-*Mؽk_CEBbT 6Isl[/;l앸h4'i'@ߞݻ7m uv{0$*4!6xj0>.9ƺs3i$@^c2 mCܸv'Psj"*4!6:xa7"@\m㏧= ,}OˆnD B؆j*4!65sa7@ '7&C ͌mFtӳtZͧvHXRBlvunfÐOu>d>Fl PS;w\wt"99>^`bЄֶg =J<=k0kbM=zFcݙҎ[&~h5]t~䩝;Ӿ#@yg.k4օܦI/Mn[ɲbЄ֪ 3rս7(pG @IWf6m~8 FGg 8vDJٽ{[&V{Sg6Ɇ ,!6Jzv0kcGNi}q?O;8! M `M alo6(c.0pBl?Cf۽k_:g2n\[yaR` UhBlkg? cf۾t6\k*bs3ƺ0#7I',P+[i-?>0XBlo$:~pBUI v0~kWҩ T29fܹ{D UhBlko f;}j*@U7[1/[<탠_z!`1*4!60kεlny쉴Bln6uJ: x3>n UhBl]|\+R<=: *b g ݻsX"c7>臯 UhBl᷏۰̶o{:tCvH)F ؅KalOMX"'B4acOA߄*4!6U9 ù,+z9}j*tCvaRUAGBlĞٳ' ն>d:sڝF}4?Bla,lgө,>zgI{+H wN=`I=$V ^[{"vڗN`;IT_I;,H W8mltKl茚ُ V RTȍ +glςmWfuavȉt`}?i}bЄVgqom}t#[(C Q6,WBlImAUBljtaR5SSt ǍkwFGW:!6n|`|"q蓡WzfϞۂ*4!6hK/QܦI'ZOilƴ!6oa6:ڸqN:esT蘚̥J*4!6ըXFqǎH'Zn\:f;s\yAL m;v]ҹ, }iUhBl;'pto}{z bBl\ óf:{%h2x3MpA%Bl̞ C8KT8tOv'!6}4 oLg.OφiĎ,[&{wt+btzwN, b;lrt+bO7&. bЄVwO7{0TEٳ' b^g)cGM(ʢUhBl3{D*񎢍ƺ@ ^c26=H:rڝIQbЄVx/ѩWxGwOH;2hbMc3{0lo{:SϽBڑAIBl*?i,;R@]f#`;mؘeP[&8aKCG7Bluun&F% wݲG2*4!6⇯#ݻ*8tX6?h7!6{¨^ ܎ieUhBlŖocGN*չS!60*{~* ,[&Zaۈ}nz$YYک@mmٺ0*{]B5+i= UhBlg? öө ->:b0$PvȉgmxکAOBla]ҩ ->:b' 5XE.Oφn~ zbЄVv öcGNS) |$PO㯾dj0oTL`1*4!6UXmSg(* |b-[dj0vzW_I6(&V  3υ1h#DQࣘ@=X]M0T9zL6(&V  ߘ c۞N'Q0lߖ5'PCBͱy WBhK{7(&V  /l'I ӳR#%PC0Pbt 3 UhBloօtC(t^͖nԜ@ JY j'[&06l cW M=/>rjhێa(;0>W_I;8( V  3υ$uss)kn\>CEBl5cvڝtZL4?mPC UhBlCGLO,wo&o}b['NՒerЅ=gOQgBlusun& F6.Iz@ߔ`ŝ>5MN8( V  W_ Nֲn%6£Jn]E\*l2}xغ[4_aG+ەp" 3eo>]mN63>?~Q̕ulJ !6P%)n Zwz*_&*]ij RL~[X. kBl{€D:wZr!o2=&uw/T1~Vz@0Y.vx#Ės7g7bЄ0`:[-2EKr\QES^ݹ U2BlB|R9͏)~++.7ORt… oNo5?WGz{U.}mzЋ)bv cGNO.hC=0]8{ x4oxlUiOWZ{׾ЋdA7Bl fK'Nk^.hM+W'3nv}_.\5bUNl6?܊l>D;DleZU@/| OZ7#~kC`kԒV<[]R!6n,]zNEswQKxJW7}ҟǸuxųת!'Xn*4!6!Fk# P ŧPXJ R-}ZE޼~_uC%V5<ʱOcCnF+1];W-zʼ\K{#w}Y BlMBbra6RqZ1%HPŗKHxejLTkeAm jY}k[=rۚ~ځfl0qN:}]X/,.-,=o{P-0,S\Zvpnl%*@UPě{nbЄٙhmO:I8Z ).Be4VzZC!yN,Z_}?v jYBll-7])TF nHl 5YJm{V-B-,eٖ N橋7C/s-O#u'ӧB/ei7UhBl샳ZmClP }Pq[C!NT1-0wP ` e/ jYqaG\o߽PBlt#P7a$6"Ơnpd?&x \K+ %N(}bЄ|ngAXsNMuQE8hm5y+xoz7e n/?\8ozO)P_fr_W|l[y+} rno~] oԗy7J|W[݋K/X o|,~NR;H(v|m7<}| |'ٹ э@݄؈-u<ף泫[ 9~j%I(+ėVPIO+=j}i=Ja^ m^W|-~%boBl0bk '^0SdA(eRIi&̕vӣvQR[n Ϟ/-5=I UhBlL%dJh/1ʮEoT7ʼ7vĚT:%NQ% \T)ߺee级3KZH+;Zc.$Kx`dz<>Rdq, v=g+A={U[{?37-!6Z:7Fbt'Ca1'6P}}[id5)(^wgU۳w/v.Jv.y첅̭ŜHBlMBbf>FkwK'N5WjՀ2{ܻTkVjSPY)u?Wzʞ6q[| h:u T5_d[g9$w@ȶUX)Ėos`; .$W[,Hp1q9M]xa-~g-YB +y4}>-ZF V,dn5Zx_q4TtQt(|)!՛^8t/˦B̑oZRZNGrlb?ٹw˃E Y=t#V  W_ 鬩&;7;= sLnxѥURPs/H vӭ[y-ZvY0ۅāιR'\G|_/zD]ܓyã/Q~zb'˸@m+\<-b8qk{zC[ևCl [ƭᘸU:>Zx񎏛7;>n9>=oBl,"&;-O̥7Tlr)&L __0Syu9ӹ՛rΛw3Qd;!**SϾ y v勜0 Q_׹[&0̄2%.qS"T|@lM8yЖmՅC%[3l (_ Ι\Sߓ@?رJn ǹ enO\`PXgl}km/$ҡ^YXw2ZGFc?̯MNSmum](qmbK9݄7;>nw|9~7!6^" ƸqZ|@AId5)-YdrEFZsQ$$iz@>9asgŃ%!M ` ݗ-9JSz2J^<{A(:L$W Jh+B[\0*nWԖ%o~[ՄS dN&^79gAٱUEM/=.$v 'X4=w_kṡ ҜhQm2x nBl7;>nw|ZO".t\ql"ѩW:V8$.՛LH|Z%+2ٸŇؚmtUŕ"XAG`IGbfBlAk Hfeq*?irUnŚ\,= '1ޜ`]f&f w"H/ ʖ\u\tѷ+ R'*ulmSt9fBl꒷U|u=ߐnqn55!Ėr| 9>nw|9qsj9>=oBl,zNEiqQ_&? 3d=O̧Krⷷu->?gCo||ūkLz~7ے8WbcҺA\vĊC]rAOҭ _Y.WzSكpѭT:;ViR 7O˰)隗_X7 h#! M `)q*]MX"T\7).5Υl2,uV=Ėr2!N/5.uK. TJ)Ė;Ztcw.$5mk,>afV-W |Uf@vSϻ;r"tdٓtԖ@݄XsXPH3PWQzljR P ս^QكE+bq-c>LX&EX:j7! M ` |e(IMNQ-)]XPʄl,:l "KEkW!lpЩj\SL6.i^[|QJ2s@ptU[~Zjb/$|Nz_lVսD[ST~ | (o*w3 [=JQ[BluFb#Bl,)yTtDIl,ӻ"r՛K;E3W]\|u[ZOv|‘=IV<*.N[鄙}~Z/6Ky1}h~MZKA UhBlL-qDu#]OVk%]^FBle)W؟EU/PT]vLV'ɟv^p*䀠ƲއOQ5,{UFn&FX2J:qyZG=u(.+EkO׹!GVg[&0\<Fk7tT+q$^X@ N;* ]J0U5 Uaތ-{G{M-D2=2susz:^un2O],w[_,l+.0= y5 5N;э@݄؈K&Iťž`f q yTg5WQKdâ6HO՟w\G!F߄*4!6!Fk#/eP vK!z5 HLl}%Clϱ4Q5癛~ydU9竿+*n[ ė휹:^u~m.-s3/?MQb3oQo* U=ЋdQ[Blu~lC]M0hCP72PN= qբcWQ חݼ{?]8Z.mJX()h6-,,ě i7UhBlC.Fjb@,Zw*B5cݫ?m .gJ$MK'.k>˭?); E7/uSnUL宽[:+sL,eW\T*>ۂ[w~bc YQװ!y|kbO^Lf@lٺ0;}j*>ܼݷ1ʢ1!W#{zVrU1G+rO1_~خjnŧZq]A쯺V 7! M `ȅHCl{n=XK@BA%+Droܩg Jm>Z3ZdPςZqwˉkjm[).G\P'-)>["5F 3/6[k(7'b[Oe_b!6bcy]X(cLq ؔww5CGvt`y /S) jyboBlZ:{%;J{j'Žŋ"{?JmVO ,dŻ*,Rt|RS+%=tLwM ]X}u&P7㯾c' Dִ] e2pVT2QQ^p}kXؼўtyo;e%7ߢhւ\u.}Suuf}*U',~i㒅'/_@x_B[Sg.l؆΄G`l F4oWt.+b+sfҽ^Y=VoЇ~Blޢ>ʀrBlWfnbЄ_;l9;K>Bj,,-8]vm?$9a_mO@ :UNm ZՂllWY*Sw݅Ut+;ΗT5_jn glmSĿ%c,J >$J:}j*ta[>qԙ@4`a0' „x_\>+!{VʅrnyV$92W@?I֙#W~ɅRy?"ԭ2@wqabVC̕:ȚkFĂ_LͻM4ݾ')rʟ0k8G-*H~ @\MW_I8L `n\N"` 2 ԑ,T[Zy5_IzE~RZ~ Oj]*ucsogy7_9ЇcGN; UhBl/ FRMvl)u?kp]Lz/?̕L){pl F_~_Ӧғr7'W9j6UzP3v{4uo^Pker9pi-%#D==?m[ocz2ӎjVT?Cx.c,~xѯbݮ [xe>$z1?tѭ(Y osw{kb=p~*;vz?tW+Âw#kXc^ iF Сw5Ƕo{:GdjJr[JXp_uȵ$%w[g+l-MN]uKŵ+cU/?mc%sO輚LA! M `~hm}tjU~OZ`֭>lWdYl-ͽҀT]S[/IZu~_ל9Oa2V!/iҚ/yE>~X)EUՇZLN:L{7jN \<cƺtut"wt+^an]X;VҮ䴋Qu5e7h9[u+cg^-T`⅚wbBl0s)j'@*[[{,a/UKmOk_~nԜ@=6lt*al]+x 6IDATnf{2 5wXIeBlW[_PH1[ͻUbwBu⹴k;Ðlrt*Tv}v~XrC{˸V^hP?BߘL6(&V  |CO {'tSO/?,X!gCliBlaH}l֕_DoǦߍƺsYI*4!6UAի[{?=`ɭ Q_/3~SŦ9vD趶l}<@ \<dƾ&n]:_-v`m6~ zbЄVhKRUqlnޝl2<ܾ6*m ,ݻnkW~ j+ ɚl:*Clʀ~[W~ 5ڱ3Ԡ'! M `m+l5invkI7.lB4VU܅Cq^܌ Vކ>KTjkc cGN ̆ y:دpv`}H6⅚?|@کAOBljԎaW4:Tkϣך@5ߐxuij ka5[ڣ߄j쇯ݻs o3ű߼U\s=*޿ jBMOBbX-j+l/?4}_fklkvoooɭ+h?Mnj#eC &Pc͑XmN:d/揿/ /j2 Blju鄊5[{?W]=D@BluFevyz6VU9n]:fW ;r"Vj7! M `m{?NTa}E %;8_sC2`4BoɟM ޞڱ3 &'L@?ϖ\ < ؠ۞oL!V "ovoo_{h.7t5`E{6Ҿ ڄl,ݼW_~OKǛx_~%zaRڝABl*ɅWQ!%܋/} չ00(grO=i_% UhBlEf^IڑA@;>tr+h){BlbGQV i/@~hmN:b/QHBbX]\<ov`lt褞{@ r;NN1`Edl/QEBbXu֏mC8;0$n\f;s\څA ;̎ ^. bЄV^|! ( 8vD%JOBl~̎ ^Wf. bЄVxGfqN:e% bBl46͎ x/ѧvL;/DBbXE9β`9喨KhzfϞ0B`wBtK;/DBbX~ځ0+n}czhwӞ rhj.OϦ X6ǎh^,[&5'Na7fl4Bti9Bl+,[`"6}۹Q>q~wk9Zj<$b1W}›0g; b Lpx/mey|}26z9''s GFϻ[ޞ}~xh96DllFϖ Vc4{nyHv掮埱P-ߚ ZfǿacωvKV}g0[?Ks 聈PXs64x|?솈-0@o~ xQ}64x9wF?:SM5vcv<{h-v vC ç앍YyUy0Dllj|rgk,$'+$=[`"64w_m|QK*`7>0{nyvmlllSRF'Dl2zm옏,WDlla~qs۽[s ا9e|j}%b LU'7fn7G̀sNsn[Y^+R`>5p1{E s ^o?KQ}Y䳉 W[o_S;02|9_N* C"Dlalk(76z9\|d^nz{h|T/i2<`9 ozjf# 있 ߵ=>8(!sNy&bHm^oS3lG߯{cgsDllӗϚ[Fϖ,+kL9>8ST b L͝ހ.#+kC'ćD+"6`gFn@ieymhDsZny>&bHZY^>ݜVyUyvnlO#b L.51~98z{8?W/"6vȩ掮>]Mvcvspv+J (x8?62|Zl~c?؁WԱ{/#'GN' 7[`"6# cg':([Z 쒈 {W7ԓGϋVpDl㓓MO_>رޔ|Vztyj'Vۼ8&/n<u:Yonul~8R^x[:QDl9aAprj<2>[`"6lnp}:6#bqM}l $b`}ssVqqe7w@2) ?uDl:6΀F c@bnl|p"6\FO,E$PyR#b L@9nmg7ח`&[`"6yW 驙f+O@>YZjnz<|D[`"6UHlb+JYZ>ca Lg"Dll黪O-+kcg7^[jDl쫥ds7cwk|brk{oo(O88DlSLbZӗ?$b >;p-.>wU>[`"6J廪Rz|pחW~L@WM77<Ӣ˽[s[3 b L˩cgVP[vs+ᄍS>IpЬ,M_컆{KCD -/<.rj$ ku>5=:9r\p oO]''!"b LzOcFO~|z˩O_>+җWǞ ʓG'Du:^NA<+?;0=5__휿w>9|'D9Dlئ/j76zܦj_yA bCy}5QgO/MOʹҗR^P&b`V:E|(}sioxOr^nn]\xS>vP?24xjϕq8,DlB}lzj|`V׮\~f|_^&b`9O6뜿q[Y^iok5L"6WcWl62|?/#!b L.]_^(eC f~掱Y)O#b L-v''ˍ3~嫛:љ/Wf8DlLWMonT>}+k}sg| -0{gm?›Ww;~|hj ydsqC'~Z^x;=5+V{ʫ1 b Lޚw//  ɣűѳWw[iC9Dlp G6]>6=57.x^m|rCM 0Wz,+k?iӯ* k Cwxin#61~qqMd@dW4?lBQ b L_x|qsٷ1@o}21~qӯ* |&bo͚ѳ6/.;}sgW>r58YFU5k-m@}}6_ܸg]Z햗D@ y''>{ؽmrZNo}3Dl}0/}y%m:q{wzm5.[`"6_\̬ٓON޿%b݂N}G)53;Dk;_y˙"b LazmҗWF?:Y.P:?}Dle~?3^47 `Dl8z믱^e۞|l;;>8ԻO|ֻ=@ 6z}ջG~_63Vݻ|bfzkJ$I$IVݣsGlw~$I$Ibd;=WEweYV5J$I$ImuxrTG窹YV6~$I$Ib O $I$IkwVǨ0UΓ&I$I$ausU΍?BrJ$I$Imua|:FHoUsSEN~$I$IJǨ,sU΍dw>$I$Itmޯ_HoU4I$I$Ig݇Buvzz΍~Uj;TI$I$Qu^,1;)I$I$+7P顪8b{upŅɣ+I$I$}YoP?@g>woN~$I$I]_n'nmj{|=<-I$I$}N_KSYe9XԿdp{;$I$Ie9}엃wwx,D MZKZ=y^23ٱ@FTgtw6d'}Nh~zʮ(x~dergGM:~}iu%}W8iY[m v ]'u}QQw7Fxyk]Ezꀲ,ݼnY k;*d@#6ƈ 0Fl1b ac@#6ƈ 0Fl1b ac@#6ƈ 0Fl1b ac@#6ƈ 0Fl1b ac@#6ƈ 0Fl1b ac@#6ƈ 0Fl1b a JIENDB`ukui-quick/readme-image/img_2.png0000664000175000017500000017733515153755732015644 0ustar fengfengPNG  IHDR|HIDATx^߯&G}'~saO\E3s[K (QbȖH@,b@!fep,e^~Md1ݕl@opaɀ\Oe*L͜ӯZLzmVm K(XQ"`ED!B+" VD(XQ"`ED!B+" VD(XQ"`ED!B+" VD(XQ"`ED!B+" VD(XQ"`ED!B+" VD(XQ"`ED!Gwxo;x"BpX9s0#^;~x&kٳgT! 8 Μ9s7MQ8$Ǐ_2ޙ3gv}}7_<\K/~|OoqcCD!׷^{jV쥗^⏤Ǐ?@p}n_?|Ga\^xܔ2K{i(:ڝuS믿yW;TUT`9>OP7xc3PBWO>diew}u#Ur\;(?>O?(z_o~n?^~N-;2Ui_&կ(^z뮻cGp I'o~(B" `qD!tQ# BXQ$DB,$!G@g! 8: I(YHBD!,(B" `qD!tQ# BXQ$DB,$!G@g! 8: I(YHBD!7Oǧ?s@gnoVWѡT=Uȟ=NºQ$D7~3OqѻO,JhR?zٕ%<8z‘' B7???M>ߎi?-Mr]4wϧ?s*OTLE!ymw0C@g! pU?:tӱ[>Sq~oEwE_\ҔP/ ij<o`* I~ƫB,$!+#f'?x?oKK9al3AF[;բG!f??·y: I(Rzy{G* \0 I_.[J[3SEY:n.mOJE.moa*L{EZK6<P|^' B) uYL6pCS,g=TfQȧ?sfw qLg?yM5wd1INGW-OihRrv4ՙ/3bfQӟ0!m[c{ue|LcQMWTδ_5LQ }#(Q$DB7xxL?( 9KC9'޻kSG? +B xe]22L@g! ManLR?m25ny,[ͤ3>{H~%t[Vz+iM.# FO^bI`(B" 괟c浩S&բbfQQV>?y'MUў_ҹ(ŵ~(g2+Bؓ(B" LDi<LuO`բbfQQ2ϟhRNy6($ :QHYQ$DBOo_4~~#vc/2tGCzOOTsrƿ gClK,y:wxo$Qᦪ(Ώ ER]y !W橍BeG+Z: I(ΪJL%ۿ{>ME!բEOOG׏+gӊ4t어4 %wMcȒ ϜJS)fѝlBUIMO<]y҆\]19_|jLYHBD!6WS|v&Syrd& Iħ6کM\矟N+۝!1i_;=w*a*Ը(|3'NQ$DU:S]nLr~w]]ÏUD\矟N+tjʦR\۾efi2NUQ* IS2Kr~7T%/T(B,$!z+u?uӶ'(RwYʣ%j+Y!.YHBD!Ǚ HC$0O@g! @7e`Hu*s5$K% BGtaؓ(B" `qD!tQ# BXQ$DB,$!G@g! 8: I(YHBD!,(B" `qD!tQ# BXQ$DB,$!G@g! 8: I(YHBD!,(B" `qD!tQ# BXQ$M@u3J@u3J@u3J@Y/v̙sJ@! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!tkѨ{ /~Npm(mO6+WvMYHBj]^/[/;}"">Ng?ݓ8iS|2*u%_^nR4Mџҋ۱Czӱ[_s($Oe~$Om:cw]SwJC{a~RЕRo'66ۖTJR /hUrz!nU%J[=]HBj]^:ޥǛzSc1b^?`4?=vziNHzefC~I*dO6s> H0\ra]UʚT՜t\t<>OcTtb ))$WKm碖]KpS@@g! ٫QwyQvױ~FXWK=u2 !]9)9E()_罵kH6\騢OGIpړ|GGԊahw5Z\31(u[E3yQ\T8@@g! oԕ-<ݼ{G՟ F0bo_-G^8PLv򘈶oG͆JϿ=4*zSG%DhV)h=TyR}6F3p (B2ިo(SP!Ft$"K%#*q vS0jGy&e-GWVͼc *sT4rYڎrf}{aI\: X-Q$dF]^y5S9{e> Ko>~jмr髧iʃ&ڗm!3ɔ/b;<3:42F]'^`ik5GI`D!tu,˳02?牏AQH^tW.Yt͢nMR꛾FÈ=Ol.j8g35 (BWo~y($?ѓ,35A(bZЌ+=,1$~U/.b\)+caDU{q.ӇN~]LYWf>SB,$!{5roo}3qȌ($/sW>*Q3+B:Ҙ_\0vdti+L>j35 (BWXo_њB6tLwB@pu/0|#f]mó9׷?LMk& ը($o}^pA~wMn)sJǞ+Bʷf60S_QtৢT!~{F3\y<0b{t6O~^6f35 (Bru%\λR_Ǟ JQ%4mrb_z> ن ծU_Ms'lWvf:SQH~ӖTfUT8n$_/30bcOUr_*muͫO,}&5YHBV.u7PwmˇlG;i$H$S?|5~(d̚vK{ҍlv@^9m[=;3ӁB,J: O?\AҢ/ʩʛ0e>C64 o/LyO^=1:?LMk& \F]>sR~*o S;D!]XP….~nfVhb&vxnlH~RZ9v>(NG=hݦCW'>L@g! 7/멇KocJ/ؾ"u_}JJG,G?ptļr*C{+2HkGs3\jW.E:ڷ9Ea:3GI`D!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! Ѩ`qD!t:G@g! 9j|ܹs//x(@\_D!tب{|Oӏzb9_7~3M?xlo 02_z1C'?ZzHg^|N<ЇO~,9ͩW>s%7p-B,$!WQui񩃚W{ϟiR;}^d|MtrWx "O~\8əUu}u`D!tبQȗz]VžjÏ>vʫ߫-saҋncχ-^*p>.I.\:;ϼwJJ S/tՁYHBjԥe<ԝ_jI+BzɅy%3u_ m.#kc\wD!tu)rԓ'|h{2q_6hV^ʟt~E!f: I^+Br0ɛf7$\qA/[G䗞uACl޵62w^g>Y/eQ/e.ہQȵ1*{Fi>(BW ,~^tBӏ;J6ͳ0˚STEB޼^t&IZzkNlڮU]l8uFmD?nP=S"S7FG!kE10i(b=tx' TD!tuWs5"<اs;$]?qǻë}6Pb* ɝIn"/r;rP:qQIObhʽtF菒Tq@2N}-K?GF3(d3vco2zyQ5¨ WiYHBuv4Mb^ozRNE!SтvyGE-RGѷe2gr1~sPRDaAsQ bN8$_*hfSʌsؘTMBd/QȁWD!tF{v/_(S.w5_Tұ̽Ӵy~/>S=nԡKX;yE)v1!6I{#:Z>5ɔRc#EY!Ld34.:0 3<$(jN]D!tu]waw4zi}.g3L]FjD!tu1c,M{癋ryQHy%5423S|QEyhk.[I^b4S=@OXHrW)bjƈ3Ƹ ؇(BW.$/;3󸆛ߚu1\<3z^:+=C~2s4(F9bⷢo:lɲ+59IY!^Em15/+ }g^`: I^^Q6tGgf;N/; ^BC(:bg;yG~k%RcO&,}[JVkęS+ˋcj%}_zE!F;! ըt5a8:3+S=ˈBȎ;?,3@h:Q0UR~4iG)~N㽌(3Ol2?m^=TN/iwޝ VmgN}wѓˑ}SB^ϺYb_vX$SwBQLUS*ԍ1 )5 ֦!ƪҎ8(BruSԁl{=Izg S_uN:iꁔ(d޽="MS-OallҩԦ*!]?.U|%B>v۴6 =wޝ~8fcV 0O@g! ~feǘS϶ ֨3zmN>tQ6^D!]=qGs բM7Sh;w4NYtFuA yQzo-Kc$^.DS(d UW50`(Bruۢ1;ڑNWzi+<ϬR^̡U|uFY)h瞥 6.%ҋ33'̜vi4s\#,1% h8: IF# h8: IF# h8: IF# h8: IF# h8: IF# h8: IF# h8: IF# h8: IF# h8: IF# h8: IF# h8: IF# h8: IF# BnfRBnfRBnfRB 61epؼ6BQB,$!u,(BQB,$!u,(BQB,$!u,(BQB,$!u,(BQB,$!u,(BQB,$!u,(BQB,$!u,(BQB,$!u,(BQB,$!u,(BQB,$!u,(BQB,$!u,(BQB,$!u,(BQB,$!u,+~+_;]]sΝ/b: IUl}sLSPv.UC>>Wv::u$t-ҏzz~=ݐ:z{ϟiUo-,(BruLE!w8Uּs<;ﮗ-ʜNÏ>^zHݒ[/ ʼnJSWvj$&o75#jmwܝ{塘(#C@g! ev~2N~4voR/_zqt*O=ɼZᗭR^0 IăKS訖\^JU©A@w9dJ‘! \F2;?D!ScCz%.yK/˖!'5n?TݕB{ 0 9DyQ{D‘! ը1B96L>{rzPPZp夦}Jܹsh,]7 ɏppdB,$!{5^~"3m2VTsy> wjvKvGg9gZz=jyԀ<<x mQNj /WW㥆Drej}+FBʝE&"Q$dFՈB>aN3KݯgioMyzJGnĞa[(d eJWgtW3D~e]4Bͮ[V+lkp\59M.QZyѩSxG!ٌ _J7p:r:e(Wp˫^6ElBr9ϋm.詐MfoD!tu}ҋN=e*h{kݽ#37v8Ko Q7ɋfy5!b* IضڏSQͻ˖1v}ɋy$f,2)RC}v\t\o'rqk&lT,/=C49{DHSblvV/!]uĚT^,our?lƮo%Kśo^LOjq:[$;d0E@g! ٫Q7 ɣRP6ihYzU/4;mCWo6rJ?}TUy#T U蕚t2~_z~Fyњ㦮!pj܄Ed;]\rkچkYKR^6wJ%Ǚ3D!tF{v(St5?v~[-qk/7wU9/cj{bITPBsRW3? ,/RS%̦%hlَztQU%yG]>t+[|($KknyF_+3U?Y^OmBFљY>T0՞ʟ[C\erK85n'ʙ Q: I^hh?*Kޯ|#=QXrOv*hοN/(drn':S%f5 1}#YF{:CS^;yO9bG޹jo>@M.Lu5J(06cfg7yj(mϩ/$S]]N~fۆd*2k7NYHBj](d* /d!S8Й*(d{G^s7f: d*h9f3}ݜt{G{\)*_~JaF}tf}Jfg7豲|{!':Cq>5MN_SLmrZ<5zwa(BWe@~3/㦾wCyTiG7˜\ԕ(<?ޱ>h+ )UC땎;3>su%fIyͶ0dGOg^Vmgg7y[]}%K:>/b=FioK5U5FoXQ6Jsڣlk B,$!{5.:)O7LhULAY;r7>:nǙ呓QHy&N4l/0 'Ol=hҔjISo:l͙O>rUTϵBR夛0Be펻Gd6hS7v!NEzt-Q$dF](YU(MFSzt8?Όh8x:h1 eȉC&K1q)~_Pd; 3QhJK:z:)ҨRk]:ϣY@~F{\Tri>|>ѫQȁ?*揄ҿW`(*6Dҙ@mMh- Q$*6:?%x'b.s+1i%MӑνM3dfkU(or3;F!edGLI@J1zz6Hc׷-s;MůELX&% 4Js[aD!Ř^UE|5ӿ+?S?QiM$7IwQZ: YHBbD+[ԻK}}tR'w$yWCϡIEozwe~0oRQ$6R-u{4աJBM+.+ߍQS}&鯯>gNys"g{yA̪[J2zR3l'Y)̚6MN!0*ۋ%\\{qNezgN~yn.(Cz%a":)ս]T/V¨7IHgًQ$D×G\HN 8w\NCѧc`D!t:߹ݳTz}Փ89 j0YHB4X޸15rw|񅏶a7bfXQ$DE(CFIӾ ~Q0G r: IFK񭗿=ˏ/s#=Q>?4й݇rFB,$!upU_~G+>G&G@g! Ѩk(Q$DYHB4XQ$DYHB4XQ$DYHB4XQ$DYHBvz1ӧOS:l6y̙z1O|ԓO>Y/><]wUY/C؟SՓO>YՍ7X~N>}^|xD!׫:^Crԩn*Qu,.b8 oF^PBc/BihmaObZǏ?xzC% ;v,67zz VΞ==`ZԷc2Q7޸bw\Lrرz\fdj'vΩSDLE(XQ"`ED!7xˀ#D08uT"kV/ QDBҟQ! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  D!(VB0JBX Q@+! B`%D!Q(`  N8QG(`{__+QȽ[_rJ UxgϞW`Ξ={7A

yLQ:{>yL Q:?yLQ.hL18D!\vr 7BX3Q:gϞc_>B5d<G(Xh %`|Lƣ1pĈB:1# Vm1# Vm1$ n1Q% yLƣ1pTBd<G(`Ph mh mwwv/[A+ WS5bh`. (ZK4+/Ӗ>ImB<^rǜޟ3bgf>3ge~\|ws P P ޻oƫZo tij>{…W!ʠ˔?lL7xZĬQ @->[#`˗/"1;BPw} !mݺeʡ{wqWܹGo~/?W^u7o~^uU\R,|\"C3g΄#W^y+ n,P yGl Ν;C4fBH'NI?un!3G)ߛ+8 N;Չ A@=񪫮 {bm(`a:dΝ;ál`SN@B0_W,)!m߾~C>c/P,Po~pأ>j!ylQ X/>p+b!򕯄@B0rE872[B0B)Z(C) *!BRH9B0B)Z(C) *!BRH9B0B)Z(C) *!BRH9B0B)Z(C) *!BRH9B0B)Z(C) *!BRH9B0B)Z(C) *!BRH9B0B)Z(C) *!BRH9B0B)Z(C) *!Bj?8_2\wҥ~׿89ʧ)iR,P λs gvp1D>A\3 JBʡqJ!k%N?+Ke9O36xwuWoIy(wŨr(`a\%RZt doKm)X*#xpf~/Zx)DwߥSMΉVO\k%8a=djO6\*Q )RUB(/v,uQt_|{o?/~wYx)D; Oʦ-sO86/ևp٪'(hrIȥKŨr(`a\%R a|PТMAi K^ Ze IdE^ӬXìJ!X(C) *!BfWGcnԾc](Y35/\t_k<2sRFF)J!XW 2Q :zII˓A=BmRȥK~?u-bn'Շ$.{[O83U◹[N[il B+ duOF*=:#}KvF@ܙsm2?A㏨l{6w[+l J!P ¸J5U}ÏWm7]Ԑek lyZz+>cԾ#Q?L{E #۶w햯([eœ~J6U!Hn^}qlN(C) *!Bf WE[fl<־Vnkw)XZ'[%3Ǔ 7n= .;KsKN3HNW\bɿЧg)dŞ6ERH4JWdFrL[$GkP )RUB(̆aL&ȸ_wHu :J!PK_I2gk~*^_k(~֯Lr\V ܝfEA%yҙWJmG6!Zѭb9Z}:nƗB%0+u9ύGR6"kjHpd_˾:|d=K!r,`_08d_ ߔ\6r(`a\%Rl]e̬ԋ |CW ?ΕBrT>kg?}'qXomR ๳(ΰNPGRH?M[Bzr?_ V Ul(* RH9B0B)d6(G/sdZŰAT}Inv}?%ؚZ])xBCGg:Or%91}Kn}J!q¯e=!ExjIȡR,P ?ՁfFkspn9ֲgj_3iRHwsV\+Y2X] ٳ"`3,'cy*C)uS |{/l MJOёND ŠZ7'_[TPhe ,,w;٧_ZFGJ!rA)_? 䪫{}0<}X-{-_` )2Ov^$wk)/L0Ynu%)iǦ/ t}풁ޗ::ӱK!#gI>P{InJ!"6ӛ:P )RȩS#`?0TX<`UDO?ڞ@[z[TN'_cSV,,+,ҟpq?c$JѪ2m)?4r{a)ׂvu (So){,00Bĉax8j]G~(&n rC0U_ȏW>|}]┠*.g] d?LRH\ q3,,,㯂:P )R˗swl߾ڼys&0rX;`w\Ua7ؓDt zbײkW:d`wtX&;9JUmV߷]%oc˝_[5~T>`-= ~'q)1tRNz;w9s&lJ\`0W;cE6::m#Zr1~?ؑw?WUu.$2o 1Y'ˌR,_/wBOPZ&/R-{m)d}ahR);@)J!= W\y啒? GA`:c A{':PtMkX@'NXӦ-,O!Y҆UdQV XQ Y]g4\g^ )3SpO_r*\pE+6o? ̙3>F\}au(uG|YA/lنZAǒS(V$CT6u 9{N0'k$A9es] ;g&+I{鞟7RɒhrB_r*}.^M1駟ad_;5Q軓|4o S<94YXͩ`:־20ۦj\m_?zxKJ mQ[[NnpFcֱ`G( l?eV0e 7맜giT RH9BKB}m߾"֭[X`q+5j,Gw[0Z)TQ)ĉ1˿KS-X .bZwa(M|yQ )Rnß& e;}2tV㚫 r**Gk /0q]wYxp`]B.x7 |1L `RNEy`* ˿Xo u%B/@`âRNGi0$.$׿}{ep(9O9B i0$.$rr(`H\H-

yխ i0$.$rr(]~Wc:qWoZg~=VضYByfR|Riٱ־6S^#kᥐ~K&K ;J!E3[B.;K}]㖯, */^"kd-"G; Q %,r٢tyD|?˦-R.]{aӅBlBPJ!E3[B..]_4r~moJ -@)5 K+K$癭BBP/}?Ad ,tsOZU?/c9뿏^tiR>UWm/:Iou&;֏r쉪ߒ%[˗H$:mrwձp,X%9dG?}OqO K+KBʩ(8H)/f/~"#}(GS`K!:v$&BP'J!j@)BBP}?( #IG)_1\l=;|/~Q&zъW|*2U'R;o7s@)V@ (SQqqR*+_kW[Xz r<|/{p~m8D7ܾ\*;Ϳ籩`H\XQ^ 5RNE!AJ!.C|{?a3l;R/oVpF%/  (z(B!q `Ey)ԀRH9).}ђ 6mVWW^E&Z~^8/4xB)5 K+KBʩ(8H)u}So$9ߒ5`S~X#(j,;|,o'J!`H\XQ^ 5RNE!AJ!=DkV5_i+͌WYίܾ?~A9. `H\XQ^ 5RNE!AJ!w=ybO6}yhŎZ˓*j0/}&jrʽRj@)V@ (SQqqRkT_jvو5VI\cl0d}IPɡP %PJ!Tr\Xb`lr5 o^5Vg _}[ttI%Iߍ?J!j@)BBP_77أ:r*ӡ[9?9N+u ~_=a?|XJ!j@)BBP ӿb&xTrܾ^C[zj/l~M?/>HVyvIpgͥKILqJ!j@)BBPઍQw h_@Q ?StF xfN\\Ѥ/mj,0OB!q `Ey)ԀRH9)FA! G/dQI%.R|\tK_),p#kvo;Wpt#h?J!j@)BBP#}"MI:% ?AzOOh[Zjڝ{۶dkm_2交 3J!j@)BB^J*M=b`ݢ K+KBʩ(8H)6˓>bX(CRr* 9.R z\tk%:E)V@ (SQqqRK([z{Z\`H\XQ^ 5RNE!AJ!@/.] O\ K+KBʩ(8H)¯ |ӷ%/*J!j@)BBYB!q `Ey)ԀRH9)(CRr* 9.R dQ %PJ!Tr\Ȣ K+K՝s?{̟=? X(SQqqR R0$.,^3LKKK᲼BΞ;w+7NMՓ0꛷^.Q )`șBYB!q `g5)\7Rsw~=|غZl%J-RH9Cδ\Ȣ K 楋*\[]o~{9nV;jUTr i8H)E)KVqĢJ!v=HP.?^s)P )Wș)(C^y2m`!{rǑO&Ξ;"ײSCB)^!g>\Ȣ K{J!ߞ]rR=cUک^##}1. RN3.R dQ %ɦpU%^{Ա_.U!{H0hpﯾsAg+ʌ'Eǟxo_6;YQNE%lJRN3.R dQ %ҙBΞ;l*[?Fԥpi˵8Wk|ORuC';Re;vɑו.呣OWщRPrzpqR R0$.앗ζocf޶KM&Yt{;ÏXatɟC;{Hru+ȴi˵ri` @mc-R(+̇BYB!q `t+~jH=K'I#2K2.MԈj ry|}Jp쒐|#;o~PrzpqR R0$.L祾1uߩ+Cu38TY)$ׁV I%ۦglӖkL퓛0[BIpqR R0$.L2dO^a+MVMvރGv_ѽS]*'!\H] g;}7oTr!g!\Ȣ K{3A&9K]9C7wڽtiRH 2  `u(+̇BYB!q `tBs햙v/g>?__+<Q )Wș.nX\+/F)d<\`pY K!vBJ! 5pٽLՕBoȦmo1P )Wși޹W^:Rsu摣OI?۱x;^:4X])D'gIߘw(RH9B|8@/zᲔBĶ\yuq®uÏث)[]rb?'k{8`]EΏ `(+̇W<@wmX\X0/͕B;Ր۽!={=vT?\O!$/HV:dUBr0B)r`EԆsqS;M ǟxJo ٴZ}GPVMs2<d2hu?j5iG=$+^*J!ݶI$o怜!gZ.V+@mH !q ` "c~æ[ou/ v!IGdk_;}2hlf$+^*J!cVnz#*f!gZ.V+@mH !q ` P)SL% tg(U(=CߔfS]!э$;WLd~K&@DdʎwlrMG>XIů`y)r`EԆVd}f߽efB P`H\HX%#w8&`ESNE!z i0$.$74k? SQqq^jCZ K+Oٱ"v9O9+6 "M;vޡow~ rr* 9.V+@mH !q `].~у,w܍3fB P`H\HX){/sFSNE!z i0$.$rr* 9.V+@mH !q  SQqq^jCZ KB P`H\H-?SXH !q `u ,9O9 ܽgLgϝͪ??plʦos/--MFvd`H !q ` #rr* 9.3P S,o&? ~ڼOת͑OG\+[޶ps`9O9 jQ/n~󫯿y%ÑOjB .Юz?H%!`=")`^=w~M,yu[o&dξ{WCEZ K{eqSQqqWqV R 'wey=PC^ l)#WL1X҄޶KO(` )`^Ͷ"c{??ؗFIM5&_X"'Y0T/눋 W #}tk|6?6bef` )`W)"(>ȕBV*YVG>)3㗿$ɖ5&ɥ,Nm*w{Nru_ɩ3J[`H\`")`W1=Њd)̩ w7ܜ,tTj7V I?KL9'I?S.rH !q `: B^0Ozʭ w߯,1ɒ8SVX[A&v/gtB$g*}LIrSPi0$.LgaSQqqWtTQ HJB3dYa.~=Nu=t{KǙ(سBbmA0+W9O9{je_}~~N3 VQ 7ټ:zv*BH !q ` 6rr* 9.`J!"v/gOHI_[3Jm怴` )`^Ͱb*YV!Gv{8`u}ʾ{>xa୷풿FZ K{eqSQqqWfX YZZҙum?,+'?\G߽LUB:~&,i0$.ASNE!^! ݧ6!Y |ħ KKK~d+>7Tߴ"-oѷƻZF-LK6XE)DWu援` )`^J!?H!ӱ?{oOntf\Vǝnz_{ԙ?YOܽgο]?5YŖ&+^*J!v& 憴`=")`^J!` jIJ-$[?*sUY%2v$?r`(`=")`^w~2%G?HVfzg$?rɠW%YV---V(t`5 }n] GiJ -%3@Xyʩ(8XQu>2n;Z (lC@2@h!)`Eݦ㢏[oە@  pz^O[ZZz8.Bjg&}VPi0$.,J&}ΆUQNf%bO;p0P< 9rq^mp.t?bf;_Q h{Lj,*GSN3-+9ɛb~{w0$x){p!Pi0$.,.T^.,w/V`>y)r`EUCI\WڐC^`R>`&.jtUgϳ`Vl[}J!en9cs < 9`EԆg[ nȰٞӎw$[;X߯|Qܟ~C7#eT^!g>\WڐC^`RȎw}oXZc7 ߴ;'7[{{O{5L޶K#JOjtguJ>^fZt3A5aY vރRc= d7FiGSN3.V+@mH !q ` D)D'}$xo3^t KU YRp]CG`yy~>]gϝH !q  SQqq`^}{(z&}RN.Rȝna`H\X0B{%oE *V%pA5BL^}p3M`H\X0B{RtzRxR]u=zk 9+\po (%3@X<\E!^*.2[*`)\|̄}@P΃.OS B >J'Nꪫ Cf¾w| P"灪(8ثW-?==0BGvٻcw|c7NIm)xҒ-fen֗~$SrFI'eui/}I3O<ǥu;ep\fRnD6UF3c~4ٗnD;#m죙g *6-mݺՅOWrf(8ثW- 7)m2srq&ێ7IhȊps=Y[#MQ IvO&>eJ~C[]6Oq@Uf\pa׮].|&l zkK6 d2cpA 0BzU"evJd ~"i)Ym" ?u*\eENJlҷV"kI{ifȜ`%=I?e/"gHK=cpF+L=hǥIM{Z#{IeXz.u!*G,D33LG.5$zsŋOJyTݰeႅC8vc-{^^]<)r`^(:3~nk N6jޕnm4 m_~8yɵ?e06o좌_B;J9\3i˵"iM3jƴĉW_}g!'%\@1L{h9CLK*q1sh{?rITwXT)D\ jixg9OmVYQr s-Ъs`ș^{BQbA|3w켣דZݷ=:\-.+pL3+QvRrgsPU.\{> 3ht mVPҴ_-#ǗB$y 7t`LVA/]vVKe3Rd쨧a\ ŎY}5(`-zpqW/yn}]WToŮVrk>`'{Zqən'ES 6iS/\4 Eʙ Pұe XJjg|uߞ])+fDJsYqkƷA.5d]َ\>Znz]tϞYnRP᱾%g'f?31c;ʟ1gzש΃i 9`^%_|u3;btz#/W_Sб*gîB Lr{_Q0ǔB OZ$ eY=l=ᛅ3Wo-HގKIƺK!q*+ȆlSg={s53XMΥF.$5$va'i>% Dic%cztΧ_:nDdGAs4;J!r*H98۲?g@Dt.aG=F+v}M<OY-8lcO|MUpmk' 9`^%_j$ZA$xJo Jֱ*> 'q#eՇa)FwZp߅O]yǎ {ql7v]#l5v]#l5v]#l5v]#l5v]#l5v]#l5v]#l5v]#l5v]#ln.KYKOKz㱐dPr69/"l6hJ;HgrDr֪g/92;?>trx;=SҌuKlM2^3NR߽v-撐QcwPN ߵ}ܪ?ju\ի58T$&ts ckUK_FǍՐI>z)sdS 6رcW^y')aFخkaFخkaFخkaFخkaFخkaFخkaFخk 17UBe2B, 2mD89 Xܚ^q85K5÷fjǫgg'q?SH7P_cZ̥z 4YR~"+$?};Qاq.lPܠտUyFB|8ثW-_䷫CBnfLh* oh]b'_av5Z+xuUW=Ac7M^خkaFخkaFخkaFخkaFخkaFخkaFخkaFخkaFخk-eWRȸI2e2O2dTN9["\j+tt#xw;C;uVggG$? NmBG23*n?>iJDvv >5Υ*ГEFzIH!>7QsE:,{⯙^(b^{V'o:>"bW];crl/2{yڟ)ݵ\r׹IoEă\g|)njDmrMY °ֶAU|OmݺUV[Of2ŬջvMTZ}?u/N0Zʡ ,&*M%*9<=iR[3F?n *7ΥF1݅={u|8Iʡk%I`--~Fq^vKk LJ!΀fJY|*8xNnjjJ~ئkdT1圝Hw\2f;Ju4Ց.P9 `^~k&VO*.9[}&je`#VH_a7ƓtwWC)3::<.l!ҕ\arMY ٔ<:uj4]v2]~4pOmd ,鍮.C{ƓՂg+$zSL'H;)ۅd|H*5GMO+}7QNNd)}u tx$r 7HZ,MeG-#na>]vT1yr_\,+~#l|%}dQ~{%s$ği?7~pp,m)klǪ+7~.%;eQ,+wܯ%{O Fv\&-ZAUEf{ޙme$'!g@MM {D9 5Wo&q3ցn0.؅%ESrJ$3).XsNބX 9ލ3+NFG"=j݂U|6/SB!PvE)Lu;M.G=MU >}3W݊?HǤگ v1|FvLӴ9rz*҂ .}.|ꪫz嗵^@A~fU 1 t\.)O]%UU>~Eri-mڨ;J'wml-RJܑa%q3f{2 Tيl(9c2'NG>Tn{BƮxe#{)Q˰?IKߎ9ۼWiZe<3&o(m?>v̴?uZE΃zpq^j@VL>@oۼ0Ѵ_=?9pW/=]gg%Nƭ sў/Wy`` Dzd)Dw9ΔBN⽿Ʃc%#V+kdVιIrs!'D:Vq4!gZ.V+@mfֶlB'FW/W T<JǓq weOɰJ&nAY$ez#u;Ȫqmm4^*~V=I6.eG핽jp kck";pY%y6e +1Hg5*٘oE;` :ٓUԟ%Q'K!I!v.IKПI.C֖V 4y(umkS'?H ^Y%~.fEVtC.ElsxCδ\WڔH N8_.P}~rN٦$cx:vddP6c3gSMNQWst*6>qlO^B\Wt6++yǩd+?Z TDU0LzMˇ" sf__=M"XN`:uo':񋴫0[U/&̑=Ƹ8 e鍏N(r1.84KtNMVIVdoY=eZ3}ƓmG NQ>}ces)lv;V]ğ_??)`L)M .\0m@2@ɵJK-u@ Es `EԆ4ecw+G)rr* 9.V+@mH !q  jϘȽ>fylQ`yʩ(8XQ!-%d>3ڳ`7mLSQqq^jCZ KQ%{7ܼ}nW-&"I>-rr* 9.V+@mH !q  *o ^.;ukASNE!z i0$.$DM#)`EԆ@ 9O9+6 Zyʩ(8XQ!-%dBSNE!z i0$.$rr* 9.V+@mH !q  SQqq^jCZ KB P`H\H-YYW+^X,rrzpq^jCZ K{e+ 2SwltL~;ZgyH۷?g/n'7%3{vd@.L2?hl{.%wWCĐu x֬MnP>ʅd51 v:6r{e/$3}{`yrz i0$."c~&٠}Ӗkylg߽e/"h1B^˺2٥w[aBm'$J>m/ҍȟG>3҆]w-{7u>Ɵ پ_jmEm"[=⅜=92K'YK;ZٔeE V J0~GQikoe2GzkESN3.V+@mH !q ` puq9%wx2*@PZH]2VVZbg|҆=~a=o~G'*ؑG'iňd;oz8AuCm`o.g WR.sFSN3.V+@mH !q `:`~\8م6Ɋ[67X1"9B\R7}9z~}|MJ\]xq ƙU%Rge#9ᲈUrۑvPϭb=yZ!*9 `EԆ3@8%I2~]~{u}E~fBbĸFZs =׉x:\T?n֙\쥻KvNT1n~S@vͱ'_gXrr!g!\WڐC^f>^Xc4)F+ tfϽX)$\0Kw{O|z6kod# o;_-&yiFacI/?u'eښ{Ǚ^!g>\WڐC^J!JvuܔBwĺK!ɷē6Z@4Rxr=[dzA=.FXU{ċ_ӽ3 , 9O9B|8XQ!-%2ٖBƓG]X5f?{O'?e1ic{ŃGBƓSULSpkdu#lS!.?rq8{8+̇ P`H\+y)dF6B ݥcϪbdVmK>>@(ϦM;q&E!)Wș+6WX#P] GlJhcޓnBƓjhMb=Ϧ#i7ow7=rTYy+ do`fH ,@ d l@4,[KڴĬ7[KުW<}>eTVEխG!ˎqo='!]P{&˜g:J `NppTțޮdDԫ[Z$ک#5 ǤNf]=9Yv2b\1fSCh5 <圌vT_6y7)`‹sF![= 2y+ۮ=#{q a#",ًǙ56)sq6x+{[!!()"ɴިI0]xT1R%iGNǘ 9팧[=oV)ﺼ3_N+raTSޣ;XZrb_1ta0'a8"< R閊lI5޼ W⽝+r\9vxC%>r=B'jWg[ZBa0'a8jg:u9U* ~;jֳ& )|r=vD֔_`;}`Qx\߶y~\#>gڧ,d;۔?eNL2usI*G.#3N7 jkϪeؕ *ڈ|GVmoMO4n3]QL)ggePΨCvMB%bN_.y*诘Ϳgk$kFF헟ZoI?0 Son~[rS!WKjQO ʧCxz%NvN֚鋯n_,S)nsa0'a8jg:u9UBVpW$%.,:z&OCx$zTp6e6e+ڗ=q- p HkVlj;V"T_X_\c[|du|g|kK,JcT=byJxyX6>\?P]BT?>ڳ oPqXimvoKN Gk⩐ZN͡bB {"y+:6Gg5ַMɟ_3\\!pڢ}]IPI̷#)+U)$qb| k)B|*)h6O|Xm'#4GU(=^u]bc8+TZ?Qun?E)gdCnoSrV|C?vSoŲg 9 1t:rB?X.S-$UisX+=t#abd"y  bk vE;yd/gCw`~CtFVK2Sm?ɏp~G䝒#~Zfsg暝x FI Ul߄uH;1,$ #@X,< `T߯@Q** ,^y퇿=tީk 2eDsj 6ŸL?>boE/PQ1{^@Mq!:<jjIb*QJڪ7?7lӦZb*dȒws/[^T/5z^!'s Ʌ0.ovwik,35f;R.W|ygXZs]XgOI#6ՁĥSɦMV|E: 2a4_EpsE|J_emTێ+V߾bcXIr0NG]NGJaL-]*he1*lGE‘$Nk  'w*vƑEb'+?~W(>#o;r-鐟Nh%>îFQ#:ԓwJ6զb՚⚼=QPd=R!{`Np1t:rB?8T cOC1wjb|P/EUX[|F1揍bhhCe♈b8ґ=/"UL0ӖscԒҶ;۠.eDɚælzZJ>R*D;[by{噋Y!J+0sTmG}jųH믲6*nG8? ,ct~pT13_;E-BeVnxR GD3 VyrJT VH^1.V a|1Q(xviaSEkfRTݘ&_ZřbhUF툚=]H3 ,ct~pT,Դ 'd~9/WEa [#|SE9~g%G6fs)%,b*F[s|MyI&+\y*,«d}Sq}}O kX<?:ǰ0pgct~pRY(uatAY -вE1~)ib bkk׷.t9LޅN{71{LQ 6˦jF%}_<7_;c3P +`n}/ǖM.|ߩt &ڕV,vTX֏ڎJ淵:Wmm5lSŏ${9~F#+f-~_oiŰ0pgct~Ra~\?%G;}La0'a0NG]N;*N4.;&ð0d[L.' 4G4x 0,$ < `GşٸlcXI2-yQJ\|/kڟuXLa0'a0NG]N;*g׋v}1\< #@˜g:u9T~_~o}[khLa0'a0NG]N;*7 9 @F1t:rB?QaXI2-yQJ `N la3vT*@oeXp[ns0d[ẽvT*@o8,u+W,:q̃vT*@o2,|rm\}LẽvT*@oċAr\ML7u9T1,/qyp*0]˶ǘ#u~RzsaAb',~elP:rB?Q?,s1HC}2XcVG]N;*7c$!Ɉ߻t,ۘ1QJa~??p|^x /lc쭣.' Лư}I6.1.' ЛڰΝ;Ν V+6u~Rz1pw_z5~g8(:rB?Q9,uֱ\8VwIK,nט{ `Gf(;w'#~el#AMG]N;*72,}v: i@FX<( `Ga0'a0NG]N@ /Tl#20 ଋt,t~xǼ`XuaѸz@*d:u9`Y2X6R! ]x罿`XuaѸ~t׿a!Ml&t~Rzð0<{_|uk/[0W^KO>ciZ.ʻfĚUiM.)a3rB?QaXIЂ7/x/NY=77f_ܻΨu=tlFYp&-ys]HT0,$ '1 rϹxQMo^z;JMx tcO<=t@9vر[˦xx v&_|tN cLJ `Np`̃Xo˯='uψxZ*xS!ʝS!rZg2Lg.PB?QaXI=?6]~?Ƙdo߸ՋIoL+y39T0,$ GK|׊'? l=3r8ՋS4q㋀0Ψ.d~Rzð05/KB9w_`ەwWؙ6_|9ʹE*6}ZL|s[R-+SE[|k[W^}!/m>H W7:vgOZcH%C"ym-q>ZN_^`.CaS5U_C b:XvEhm/;nV۷;Mɋ7_!YM4Ūn\3b!q:QL hb⧒PX<򚝤TLgh5Nb$H{GDʛGJx1ŐLM\;bَ`3Q]`Ga0'a8jG*_v9l_޲s}u+FLLSy?ZO$C{Q\CZim/6>l۱EV-R4ynQ{b%׾lE3W!k#ƷZŰexVYeƼ@UWU9Ğz+쮖Z-&<|Vvh_6Ǜ8f/f{jyZYۋ&gGP#$'(SH45$bhfdm/iQ&f5IckzUٻMNr,/vRϢ 2L圊vT*@osS-BY TU#  ײ'!.-Fe')FRC%Ly9ot8*d|rcRZK(I+?XB}*Ois+dL圊vT*@osF {{B$/ƇQ#?dDHc/$mūW?"}50TjSxJp䈩ڡ9EFy$_˙7&UB|/c8?(/a܋}}&^<圌vT*@osF{B;& #a"6t/_OzdFFZ"}*#l:Fy$_ٞN r~NHzj7 lWGlEa3Q]`Ga0'a8jG*xՒ\LJ|צ!!_"#}KtLSɅ6O1NMlE(+x9fS|I^֔r^;OyOfvċ˜g:J `NppTȰy<4֞LJ+PjMY/J_lTW~ Q1.&.xQLgTs2B?QaXI {"(hϟLJԧ=m^vB7/?"}KqŠաY#~kRNR$(W8'UXR!TL^^S,a}/ƫcċ˜g:J `NpbN !Y"&mzRb|鳵<1-nLf6RK ]/P~L;}ORjb mMSIzV"QpJŰtJ^[Q)DK Z}%|_x%Lb~&{'WGM"<圌vT*@osm^z嵫}l ǧ"}<V=]ؾ/H5YF*D>_<+2`O]٦_Z+ӋѸ+ 89g9~gj$ɓHm?buDrIf@)-t }ȎN իj&VB?1P8(b3ɻB?QaXI":bdVmq_GZ%=NrV4?'THq bylf ^d>T_{_˯isl#*UQڙ\l &FcD3R;*7 9 ZfA݋/j!& ̊ᱳm1?ތ-ٮy؋E]R}m V6J8dkE*V0,pu<6#>ci_5q;?j,>bllp kF41 u9c~Rzð0d[L.'  #@˜g:u9T0,$ < `Ga0'a0NG]N;*7 9 @F1t:rB?QaXI2-yQJ `N la3vT*@os` ct~Rzð0d[L.'  #@˜g:u9T0,$ < `G,p60,$ < `G|=']O+Տ>)]v5th 9 @F1t:rB?QcA/.8~~BϿ:]֍>q0T7]Vg+GlJצt楷JgŕwOtƾVN2$sxyQJu,`+nsHtˣ c6{m+.!}߼YWl??M/wjxQ+U3<ӪC5Ha0'a8 1t:rB?QF:]>d2֕W}J~,{W7..Bzz-T?{j>.[RӪϋ/.8 9 Q#@X< R 988>؟o^zHۋI.ū}|qݨU&e-A|~-y0ʩi˵\ڦyXi:T0q;)gی65cf^;sm},\ѓ`s 5#SQtci{]=𣱍j;O;zk(~?c_L]_7M Imt57/k4G~򥰃G.lΘ/ݰ9% ߎT Q#@X< Ry.h80'緻=dk*.NGu&^y-rm 歮}OS Q,B=83/o3 rT0++XٱrvI{؋',@jfr,x,vn4)qJvdX:_ҔZv^9y#v:Y%ScR{lr CR \LSEjgE%VT1IOYZUXC/·1,$ G`9L.'JG*Ģ -BͶ`E}lTikGX+L{BV8ؼd(T oVY>l;F}>esl5mlє{hd!NjVUS!VX0: #V(u,MMRWjضlPV(n-)hɹ乕aƼ@-zM T\lQ[ubJHW9~}vodgڔoZg2?a As,ְ_/YotjBT0+}Ў(,z&/Ok9vMq~¿A˗&)ZO~ګ%tbxfU8`Np1t:rB?8T VdP>!4| $q"&*D8{^rO$?Z㋒`);|oݚ+T\H1۷!h!U{`=ifrZu%Cs&5|Ϡ%mJxҎXQ]V:cP)puOoUFlS ~x.f4!Iy4KȓSx[x+i[ě)/'tXjy*9ߵ'hH 9 Q#@X< `T6K'PyP4RTNp܅_Y{ue+^YPk)ɏb9l6oGfäB;2gRڙS\Y)aߎmUlq/;eET*6W7U Ui}X#1,$ #@X,< Ri\~Ak Z?8 O!Zv{Q~_֯ A?0Y cQ_=ŽI#0O(]+<&xx)4?$+GPChOR]ߙ0<9S,F]53v 7S53YC9ŕXT<ɋ̋7^~GU~P9x?URosFyQQ/" $ ;/7_k7/)(nPvC=UG/Ѱ*APW1X*+_<[R3 X7Rŏ Uk|-kIhQ{ٟ_|uΙ [~ڳ!M6x_պҒ] |U6O/I+䬝9ŕeglwSo}m oxO6Ͻ6焚F oPD,R^K^_lhCaXIN21t:rB?8U忩zS)dmZY+C^ᇇ>e 1Ӌ//7u,{|nh2 h#KjaoڎjKcZ!N־Śu1ũxEs>E=_q֧Ã)o*d(}| J :g>_fՊ 2 9_1YTy=^UQ<X#1,$ Řg:u9Tەwߏc /M]a_?C[QBt}=wϮ- N*mԉ֣:VBO@}YUK`z,hocJbkQ. iS^tZZ܅Ipm+-YߎtUJXc{5N>@kMdP{ګ{%N˸XU]͗Fi4t`?Cv]Tyl~~'KmM{ll^~>ӕU`fPs,b3vT*̉n7=+ ?Y,@- Qes*<ǫCltpL_O>H`.>__T0/abs:J:fND樽.JsJ6NpU\l\WkHs*<ǫCltp|/ ѓ ҊhJpq\RC*0T19^%b3ˀ}z^|/'kڟ)c6\|R R! #@˜g:u9T3a~kY<0,$ < `GNßKo}?x* 9 @F1t:rB?QaXI2-yQJ `N la3vT*@os` ct~Rzð0d[L.'  #@˜g:u9T0,$ < `Ga0'a0NG]N;*7 9 @F1t:rB?QaXI2-yQJ `N la3vT*@os` ct~Rzð0d[L.'  #@˜g:u9T0,$ < `Ga0'a0NG]N;*7 9 @F1t:rB?x׭[]|z*] L ƥR!˱fvZ˗/)uTl#2zʛ_Ovy/^.p.R!Tt:r]bYg3%~elBQn{G8w\YF*d:}u9xx*`իWc7_t,$?Ν;JEZ5A{.e#2w7nHW,̕+WbpWs2,']sς/qF n N]Nry2Onf#~e]W?tA݅?qLQoce۷zGgUϥ # 2;wX3n_y^x!π$~ewj -RxL~tAQ?I7.e{˯g CpHLg.X׆z`fw<]vVH'>c)XR!ә9^׮]{ꩧB/ X矷@=}O Cp',,-:Jǒ 9"d{_ɯ+q}ґqt! Ψ.wܱƍZ 5.s}Aŗ_X.ؤK77M:[?,R!/;UW |d;bLL*B32!ƥ{BߔR/Ja?_\LGtixp/;S!z`j~w0e*$Vcda2kjIc۟$!ڥ>bŋ>46mDťG)ّEAΛގ OSܚK?.V?LŽ/m/)nGI*߾nZ*-nB"F#I3eDuemm5W7 OAM-=+I=+T#fM'!rHLgTL!dBFKH H^}X|VۗdaǍ~:!R,hl} n-q;@q۠o=#QBt<eɷϑ#jkISc㻁;}Rc_$:LӣCF.)5 ZUxGgm*φ44[lI"2Q]0 5./@.B;e7Ԟ֩{{-hT%߻kPSNѩ5V!*|{vFT5ţVylIU } Ѷ͢S^zզ|R>C*d:` !2j\_*D!b& 8xA황գ D77GT.'Ib]O$%Q[_tdM8 QUԖ6R!~u j>8S+[s{΄8_|LZs{O4R!H)$9~f/`iڮ@{~2< Ψ.BȄǥ vNŠ1WR tSޮ5IK06\-SB1vXNUQ[H y2w p9)Ң Na׏)Ifکc=8͆pqGbg, &B*d:.8!R^X?'Ebp 8S<"m*X{`ba)GF1MJ".-GpZHLgTL!dB&* Wb7Mk/HbY*=e?d6ِ/~Bbjb[IlaxZ9փ*vU(T\m_GtM*{W_IChúEl;q(wg1t&؎ƴں}i_M-S!N^+T^T˧lǣ`"B3yԄLR W_JU=GPjk$VXu'MyY]mjNHZ֌kQc֜xԾq;}!'kdК1/CYkVٛsUVy-`2`s*"ƪWmWB̝:slX5~VX+3v@*d:w9@MȄL>. 1V(ͯzXOOWU)#ԦqyLnw|VFm&+'G_{U`rbBSAKk1vb.OZ>~jR+ kw +W{m I}xx}6[wfcSjL:^K;4LTt&r YָT?+,YlH~-\隚=2c(k٥R;f:3%I!2eu9JȄ,k\3Bm c(o$S!F]ctpp#R!g,@WB&dYR%Je5vuY(Z;\G*#2eu9JȄ,k\bߌkst=ڈrGa_~W7y]Q|J4i*DL}^ny!r֑ βt%dB5.MތڟnW O>jn5i*Da_|s[?U!r֑ βt%dB7.޸V_~uDjxGVބ2ubRVx;d;C@ƼQ pF #dB;. r a\ [HL.&dBR!ӡ ƥTtrpjB&q)l!2 a\ [HL.&dBR!ӡ ƥTtrpjB&;w`nܸC 9^BpjVt1,˗_=]# S6O?tHԼ>ꩧ`7nqBpjCͭ[5`^7}b ϧ`yn߾/ y5p4Bp]?۷okhH;w.\pΝGy$Ȼc@*w}w?X7nb̹sҕpH%O~ʕ+wΝ0 oӟd%7Řj~!.|wɵ!XΝ!!^ܺu+yn,? "\v-aw}F*=u믿nQ׉߯]z;bN  B  B*, B  B*, B  B*, B  B*, B  B*, B  B*, B  B*, B  B*, B  B*, B  B*, B  B*, BXI[4VIENDB`ukui-quick/readme-image/img.png0000664000175000017500000023560215153755732015413 0ustar fengfengPNG  IHDR 0vTIDATx^ay :U~d)(O!<@,rޕIYLk K[8nCG@f$ Rtr4`O(O{n>}]rA۷e|?ϒ$I$I$I I$I$I$I"c$I$I$I,#%I$I$I$e)I$I$I$)KHI$I$I$IY2FJ$I$I$Iʒ1R$I$I$IR$I$I$Id$I$I$I%c$I$I$I,#%I$I$I$e)I$I$I$)KHI$I$I$IY2FJ$I$I$Iʒ1R$I$I$IR$I$I$Id$I$I$I%c$I$I$I,#%I$I$I$e)I$I$I$)KHI$I$I$IY2FJ$I$I$Iʒ1R$I$I$IR$I$I$Id$I$I$I%c$I$I$I,#%I$I$I$e)I$I$I$)KHI$I$I$IY2FJ$I$I$Iʒ1R$I$I$IR$I$I$Id$I$I$I%c$I$I$I,#%I$I$I$e)I$I$I$)KHI$I$I$IY2FJ$I$I$Iʒ1R$I$I$IR$I$I$Id$I$I$I%c$I$I$I,#%I$I$I$e)I$I$I$)KHI$I$I$IY2FJ$I$I$Iʒ1R[ 2FJ8_}/ow0/ c#%IuoζsqWы?y,2c$I#_:>\S$$?bhl_ybc]SHIFԋ/=PhC˳3{g{nj_p,$ƯN~')~00+o{Ͻ?,&c$I?yAp!~ 0=Ư>S싵#%I^i{}l\$e^oq|uֻed$){WEĩUɑGd$){/l`nd{[& )IRcdp`av1Rq `$IH c$I2FY#%I1)I@HId0FJdz׏633ӿ2F9#%I* .|{җ`G_2F9#%I*o~W ݌`$.\k׮ 0=Ӊ}_|~~ާ/>[n©cEF#~;0FJhu1`̕9F>wO3BͧA+7ofo?_Lw[]HI{.C_\\H#s=0mVàr| s |˖}Kx/c$Ra$i17?_9r݌KK;F^{Cvx\||Zcߺ{7֦?r:/:On>(1REK_R{|'pB}dXZrLkX ;߼EƯn68(h> f{Ke$ip'?9_~%f(ӛܱ|M_ܩֆ~>TxZds EC!7n]/on޲ۯtП)IҨpBDNLL<k$c$@!cCA;pX|po'._Ѽo57~,!㐋 R/tA7 #%IiyUWEY㌑e cg>.21>w6Z#~k#3C!D%X/W}zub|`#%Ii6c$@|A,|3x2|ڷ_~YqckƯ gо1lwJc$itu]#Yff(S>q7ѿ-|3>HL| ab c-O/<_|06-T0F-ܽ2FJ4{YHR| __s"zFf1/1/z‡}/:n}rdx{ d$i]pho\Wgm3F)eZ/93z[ |x7w0I^~exSl P8c$I(~֫ P/t?nrlgI~s_x󝷻o$/ŹS>:\ƹ\Ed$){"'&&7x#4iM©c|ŲjwQrq~Q;gc\"2FJm۶uȦ{E PN}!SߍomfټgcWt/j>aAz^z?)IRfffzKdQ/2F,9ao51/|~~mou-yql.zz7N#jg$)ooOvH„WI$zSߠ1rƍ=s#|-˖/oGO cOռ1 G>$e,~~[d P7yݍ=>Hd;_3OOl #>t}rdocC~1䐋:,"c$I{'zc W* c$p`O\bO=wwc4Ş ?Ԡ12A;7y{f8`.C\"2FJwo(#c$@|[n61/:6ә {?y3u7K8(8$eD6=k1Xo]}#4_΍΍_r˭ۆLvl><|~~m4X|Vc$3HI#9ҿF# srO^5L=ӫ7їbm_~ ~E>b>:dqrQn9~|"2FJٿF1#џn/;;~bW< >U=Wmm߷{0F^3#w9܆ƹ\Ed$)WG1FoO^Sw'0%c>k0F#;אk?w z|"2FJ911ѿF1#J3hl]{?r_d.Һs=C^5Uv a$)W>`wꪫ(&c$1R\u]H`#%Iʕ1sHIreƜ1R\#1g$)WH`#%Iʕ1sHIreƜ1R\#1g$)WH`#%Iʕ1sHIreƜ1R\#1g$)WH`#%Iʕ1sHIreƜ1R\#1g$)WH`#%Iʕ1sHIreƜ1R\#1g$)WH`#%Iʕ1sHIreƜ1R\#1g$)WH`#%Iʕ1sHIreƜ1R\#1g$)WH`#%Iʕ1sHIreƜ1R\#1g$)WH`#%Iʕ1sHIreƜ1R\#1g$)WH`#%Iʕ1sHIreƜ1R\#1g$)WH`#%Iʕ1sHIreƜ1R\#1g$)WH`#%Iʕ1sHIreƜ1R\#1g$)WH`#%Iʕ1sHIreƜ1R\#1g$)WH`#%Iʕ1sHIreƜ1R\#1g$)WH`#%Iʕ1sHIreƜ1R\#1g$)WH`#%Iʕ1sHIreƜ1R\#1g$)WH`#%Iʕ1sHIreƜ1R\#1g$)WH`#%Iʕ1sHIreƜ1R\#Իgw뾽yi<_zߜc`#FHIreS?5 W_/rX\?b*c$)W?b2މg{(D#%Iʕ1r?]Ke<]\R#FJHIre7~W-O믽0w͝#:yOW{~E$12FJ+c ={{}'oߑ{w?>{~B1R\#מz0{G=?@9|T)IRIdˇ1R\#I3zN|/KV-燱u۝7?-_t5]4J{X|>q5m_F9#%Iʕ12ɑ49>~ncCr>næa*h=?TliχhێK[͍ķOEh>>_`Ϥlq_>Pr1u3FJ+cd#irh.R|6lZϢ8|-;5Ks*㟧Hf]r} GNmqkw_ .^9GKbayˎg\޳/s |}:Zy =/pG|Lc?E9GKa3~N>{wu':vi; \|櫘.G|LԡG@݌$HLo_W^=:l_c-^ne݆MЩpHJPђ{~XtێW] Ͼ aP\vي'l`ջ?IcAkN̾${!䧇-ݛm{o޲毓msz}-{^>~kH=,$Q`+#P7c$I2F&9&W_{ _Ҽ-;w  Ɨ:I 9Zrk_A󚷇2 ~x߭w'ilcd@51<ݾ끹{]Oϼwټeg]7hv-[2< 7/WK]9#%Iʕ12ɑ48}>^HQ߿EĩC''fI2]o4\_u2FvߥA>F65sr1u3FJ+cd#im޲sb?_{ݍ]I1s_س🕹ȩC'-_9AyaF9=0Ls1~N#a\1R\#IU{n޲cϵWXvي/T%Pvȇ>6_qʬcd@lKlW_31;I>ʠw}ᢕWiɴ+cÈ U#FnHIreLr$MV!ԡ[:|\xº &=q|8!m_9I9ZrnL=6o8Kdw2>h>!zCn'ti| nL{'jâY@My)IRI*yާ$^{fb-%7?$7geb. p . D?k7|01r;i2F&?1R\#IUs_n&όds7h;lm7?s_"gK!@Gi+wdݓo 0F7.[2pQ:1R\#IUx!n}9y t f+yPJ9GKzs 3dcw}͏eWYn.ck?d scIXy)IRI*hT7hQ*h=?ToOd*tkSc䂅Y1^Jxdr*P#FnHIreLr$MVO5[/}^sRxycԭG@݌$H`49Zr,QW]&hL?WTG@݌$H`49Zr,]y/6lj⋀:1R\#IF9GK.[1d|_@9#%Iʕ12ɑ4hsXN>q|+#P7c$I2F&9rG@݌$H`49ZrPr1u3FJ+cd#i(h=?@y)IRIF%e*#P7c$I2F&9rG@݌$H`49ZrPr1u3FJ+cd#i(h=?@y)IRIF%e*#P7c$I2F&9rG@݌$H`49ZrPr1u3FJ+cd#i(h=?@y)IRIF%e*#P7c$I2F&9rG@݌$H`49ZrPr1u3FJ+cd#i(h=?@y)IRIF%e*#P7c$I2F&9rG@݌$H`49ZrPr1u3FJ+cd#i(h=?@y)IRIF%e*#P7c$I2F&9rG@݌$H`49ZrPr1u3FJ+cd#i(h=?@y)IRIF%e*#P7c$I2F&9rG@݌$H`49ZrPr1u3FJ+cd#i(h=?@y)IRIF%e*#P7c$I2F&9rG@݌$H`49ZrPr1u3FJ+cd#i(h=?@y)IRIF%e*#P7c$I2F&9rG@݌$H`49ZrPr1u3FJ+cd#i(h=?@y)IRIF%e*#P7c$I2F&9rG@݌$H`49ZrPr1u3FJ+cd#i(h=?@y)IRIF%e*#P7c$I2F&9rG@݌$H`49ZrPr1u3FJ+cd#i(h=?@y)IRIF%e*#P7c$I2F&9rG@݌$H`49ZrPr1u3FJ+cd#i(h=?@y)IRIF%e*#P7c$I2F&9rG@݌$H`49ZrPr1u3FJ+cd#i(h=?@y)IRIF%e*#P7c$I2F&9rG@݌$H`49Z=[ď͟~ W-?>ủx 'r1u3FJ+cd莤[9GK#EOpďu(#P7c$I2F&H`s4{^O/'r1u3FJ+cd((h)=k/^3>5Ʃ}+SgOKiSk^j H9#%Iʕ12)ߑ4]-!+,Qxsv:%Q#FnHIreLw$ @W9GKG;Ÿ?*#P7c$I2F&;|HVg^ea$)WȤ|Gts  )IRI*h)=111R\#IUR{~c$P+c$Pc$I2F&;|HVH&HIreLw$ @W9GK@@M$Hr#Z##%Iʕ12)ߑ4]-7F2F51FJ+cdR#i9Zwojejb$)WȤ|Gts  )IRI*h)=111R\#IUR{~c$,-ko_Ҷ{c_aӉK )IRI*h)=1{۷xۇN\/jbM:yu6mݾ+(11R\#IUR{~c$nUWΐIrQM|V6lj-+]s񥥙_aɵݸ(όcrV_}1Jfjb$)WȤ|Gts j{Ͼ|Jc;FvFc$ )IRI*h)=1t w}6lZu5׬[;_mzy~zw7|]O7Wn. cdx-to9|'|֒ ?3lx Xɛj_{qwi_9 3F51FJ+cdR#i9Zwo2=!/:\.oZsxj^?oIu?y aФޥ J~bMMϾzm]6l]?|v?3dg )IRI*h)=1taKmC+V޳/jV]&9Mn{b[upQ;I6juƛniMza&l>`Gԡa~Bko^uҼw ́B##%Iʕ12)ߑ4]-7FBʵ5EC>~.Lq7:#ƛn龽#'fg{M:|{{HsSU= OC+>L~Vݯ=bg )IRI*h)=1ʴ}d}A.ƅOc򕽷#iާ7m?[?vpM~tL~x9G>sH&HIreLw$ @W9GKP0w +qM>o2lr_tz712 LWO`amw~k_/ }@!@M$Hr#L Xک«&k>Nn޲s݆Ma+?֠/6y $p7|qbwO&3)bg )IRI*h)=1VQ>+x6[+䕃Xt7y0F51FJ+cdR#i9Zwo2 x<^FAoN>ΐzhWXg5ArG oYyŚ>m4y#23 ajb$)WȤ|Gts e`*d/M #_$?֠I/y #yˎIA9Pc$Pc$I2F&;|H(Ӊô5񥃄wYsT򒏑SN/M~A^ArGx-Kȼ ́B##%Iʕ12)ߑ4]-7FB/SN8}ۛ|qOXc=]4=`_4%$wć>qˠ(11R\#IUR{~c$󫯾6eܗx-_}dݑrrvcdB |>L~A^qmǒc{&nd}@!@M$Hr#dSNW^}{7{'m޾k[;f?|\s#z`tt #i(al>i/0\F-'~]e(پzh(?Fs#l0[jbjb$)WȤ|Gts ;|\6tK~ӝge+<1 #_ڧB{#F_p{V^5Fiebjb$)WȤ|Gts K7onl_4;dB;|\v}l9=ǵxhåEͭmݾ+,|ݱћw?Vxc1k?x^nEnçNҵCn$}!11R\#IUR{~c$P+c$Pc$I2F&;|HVH&HIreLw$ @W9GK@@M$Hr#Z##%Iʕ12)ߑ4]-7F2F51FJ+cdR#i9Zwojejb$)WȤ|Gts  )IRI*h)=111R\#IUR{~c$P+c$Pc$I2F&;|HVH&HIreLw$ @W9GK@@M$Hr#Z##%Iʕ12)ߑ4]-7F2F51FJ+cdR#i9Zwojejb$)WȤ|Gts  )IRI*h)=111R\#IUR{~c$P+c$Pc$I2F&;|HVH&HIreLw$ @W9GK@@M$Hr#Z##%Iʕ12)ߑ4]-7F2F51FJ+cdR#i9Zwojejb$)WȤ|Gts  )IRI*h)=111R\#IUR{~c$P+c$Pc$I2F&;|HVH&HIreLw$ @W9GK@@M$Hr#Z##%Iʕ12)ߑ4]-7F2F51FJ+cdR#i9ZwϿȓ]?_}wJV2F51FJ+cdRH_.%qr4cd|),Gx%+{Z##%Iʕ12{8W"w!.3_w#crH;jejb$)W:_~&Oї=m<_~H]4t|JɃ3%P+c$Pc$I2F1r,M:lSL|㍵_ĥR{g拚|⋖w11R\#`D޳o݆MҩC'K?׻-;,]FC[/s]KEɃ麾w/js V]ὓ5w11R\#`D<L<_upmw_T}S{kXʐ%izvLڼe+״?KAN֒d~!eh|C/_; )IR0c\" ;=K\~q۝wmf!" / W5*yPRi'؍7W_}mxR%P+c$Pc$I2F1r#[v~ ^w%BeP~N(U{g!;Vċc4\>~•C@M$ 3ȥ#"H^I-;V^KZ|<_U2) *E|.; nd9q|Γ& W2F51FJ+c$#A#Ax=6tK<>~.S7޾F =[V]f5뛿#g5næ]~c.˖lwێ{C{nޱNZ93\JT|7p~t7W<jejb$)WH1F.Y\Ht5u=}vTwh 'N_aS/n O.T[n mu-_|u~A4 ;7.V;F~lYw11R\#`t^;o޲57Tig~{{w`>8p`7p*'%5$  å$ 6T"1VĆ Fpʆ_ $'؆kJǽmX~xf^GYf$_koֻvwI_AwęJt 'ԋ`ڽg&"5˯J#Ëat8O{ag8A\v# vV_JJe$Tȉ[ْ^˘R[zM?YgXz;7_j*-j8^ 0{xυkgk'$^D}v(2(2RDD$WP)#'JlG=ӛվ.7.Ai@4P9r򫦖%qFKOi.x>']K8c*pPq̖{D5=|P*e$PeH(#RFNvԋkn\ȉ3q|cB%}ް"nm*G$Xl~~# _;J D)""+HfϞx=[ҟbVv0P.vr՚4=3+7[kG.#,QpPqz9q&>6+A'HJJe$TI;j&*6D>1B%Uڈԅ4^c}Ρ1E6BevN>*0ׅaW\[2'NJJe$TIrէO4IJdUSi@qIc.qqkPsesӛAgkvy*Ѱ3 }.T\;iLc^;k~C@I"""J9izc| "*K%M8bz,_qmJBy RXjmPqL3b{>_;J D)""+Hhl܊†ٹT-[m߹7r=[7nzh3-[3ͭk/ JG â|"MF22>*R0} |kR)#(#EDDrE 2K^ v8]ib>*Y\}v(2(2RDD$WP)#K鎜8_;J D)""+H'}.T\;YJJe$TH>*RF%QF2*e$I }kR)#(#EDDrE 2υk>C@I"""J }BŵCڡTH$H\QF@>sڡ|P*e$PeH(#RFBPqg}v(2(2RDD$WP)#O\v>_;J D)""+H'}.T\;YJJe$TH>*RF%QF2*e$I }kR)#(#EDDrE 2υk>C@I"""J }BŵCڡTH$H\QF@>sڡ|P*e$PeH(#Zecg2zmڡ|P*e$PeH(#Zze??g{\.G~<sv譞_;J D)""+H^{v|+/|~9{kC@I"""j镑^艣֟3Gk\;SJJe$TK xz=Џ>S_ڡ&ڡHH$H\QF@$à<Nj2AERF%QF2%YF^w'e^t˟ԟ0uڡ'&ڡHq)#(#EDg'O6___Dw_kn[P҄O>ts[+_np<6իWכ+ٳA=2|RF%QFSZsr?8 #2$7o7!7nl_䣌2(2RD$o戋2߸_=&r+D"c䣌2(2RD$c^Ts?77#3_Flٲ[d2|RF%QFdɧNY|wYEu_uDKq}2|RF%QFde|E޺룥V\.9 GО: l"9QF2`\H$Hϲ_b|ƇMnÿzgΝ;ל?S?ꫛe$(#ƥJNo,c_Oua~yyٳgrWLWE(#G 0.e$PeLvz[Fezj0haֺcZۏrM_ 66<5w\ȳg^s5r<~xst e$@I"2gYv5ng~|qXv?a8(|ׯf9M䤌2(2RD&;=,#ϝ;⚕q䪿Rc/:nDyt`Ҿέ~z*2zr-[4KȹLnGe$9)#ƥJNoNkD!7kק#1e0=s}?5U^@?>Ѭ!_}Od=HQFK D)"^}- nl/Wnilt5^9]F>S|6n=iQF2`\H$Hl<4밭|.#bÏ4޺i ?ݟ^_ug[b?MUUWn6s+^ze$(#ƥJN?և_m?ܺ*qXFt;w.febڬ/L^ϳmbK/}sks'Ѭ(#G 0.e$PeLvSF6ƌ>X/[fÏ>ڬ?a#b"ȳgϮ[Cϖ-[;Lf䣌2(2RD&;)#_ <#q@i׬loM\m}>if8pnZoM=ӡYFƼKW\qE5\SUUsI2|RF%QFd?ed!_-aؖ /:s翤rH4:ȓf<ӰSpǑK$?=S&*HQFK D)"xi߸b)#hMT/ 1]F?DrՓDR @>Hq)#(#Edӫ2_u_e`G„袏im?53ZAI?_=VPU\>ϽK&!HQFK D)">~qz2j"Lz7.{?5=5e.}7M6~JMtm֬"{e$(#ƥJNosΥe ?aiSbZζזV՗+l<6fI!ǏꫛU\V^}=2|RF%QFde7{jgϤ&22+őiYgIMőKټys5\3A_! e$@I"2ga߶G\\9kLq?M'Cs_=U׬l,#8Drٲe6r+$'TF2`\H$HLO7 [~_mM=p놯ǭ_M ztn nL3f+ O=~J\R9{7ȹ62HQFK D)"˿J]`[. S~yq篞W{cZjL^vml#6o׿(#G 0.e$PeLv.L jEȓfG#+#+cu]7gFٟ?~9gQF2`\H$H줯6/z8?OY?Ž[~$svv滟}.IOyѽ"Î_mo}_JG/l޼QFaHQFK D)"ԜE=z6?; YWtuz* xLlng/+{;w.L#19>wK._}9OQF2`\H$H;wލՁ5]dΧ#KmQF֟@[?{}+55/tܷ^ a.TU(#{͑HQFK D)"zoxkZ70)#edzjɮid_ȺueW^ѧ(#G 0.e$PeL|$޺i/ 3qeG~d^QKO<5q>;2O^q䥗^!ǏoM䣌2(2RD&>#]'ۛ.{{ ȅsW_97QF2`\H$H)!'.}|+Yh║@ζ;9Ӆxia,lDm믯}He$(#ƥJX<+y&2#A\JyW J4Oed]K'TF4)#ƥJB _Ҷ;?WȱikQFwgIe2rݺu2|RF%QFH!9w\//?9נ\_ߺ덵#IRX@~f4믿97QF2`\H$H)*#}_r2^(꯾Noϝ<5;bmwSeoa{]4}ZsR2*e$@I"RZ~y]"a=Gedq_=?xѭEƪwW_:ŦK!HSF%QFH9w܁W_{򩧿q?}- O/*;yj6sJ[e$PeOLE-V\^)ΝK3 4ӲJ 0>e$PeOlnՊ ۫zO7G/(#RFO D)"rOmSOGc-FLZpq4id%e$TH)#(#EDDrE 2`|H$H\QF@2(2RDD$WP)#ƧJe$TH)#(#EDDrE 2`|H$H\QF@2(2RDD$WP)#ƧJe$TH)#(#EDDrE 2`|H$H\QF@2(2RDD$WP)#ƧJe$TRF|C߿Q ߃NF D)""+H_F~#@۩C;'2PF%QF2^ש#/7ObSF%QF2>^m0~-2(2RDD$WP.E022RDD$WP)#\wMӛƑg{f¿׷>cnko޴ŷs{_|#6kn99ΉO7)ٳp. s)#n(#EDDrE 2 uqM|~{em<XFNwN|ƛ6ko}ő`GHH\QF@Bz]w~](#a]߅YW{e,=e$TH(ѡO*Mv߸顛߶;h{ӿiaS8Fq]9p(~I}Tu y|E~x1̓´ˢ7}'7? G022RDD$WP)#Dqa0=_v޾ fʿ^g$iL8Vq]M,ӴwOmLwFfk;)}ki TFnޏ[]?RFPF2*e$(sW\4Zj1[}}/{LzkKeƽ a7՗A'iL<\Ma{87gF f k/Lo' C220EyjzLA2]?RFPF2*e$(X~nTŭ;,.#6W Co-WMstb<_o5qc}Zz,#.xPڰc -n?5V]v57CMmȁR|{f><. s)#n(#EDDrE 2Jv?75sO1m{ (ք/>ӛ9xw9}MoDmklMRomK1G8ɥ{"{[]?RFPF2*e$hks#8>\F9q;[\'Flklqa\ˈoر-78I&5. s)#n(#EDDrE 2 uę+>w5گ+`nrXA +#Sn=>Y-k5a ٹs$ Lr珔@7"""J 9x4kn\3HnwliMo{f¦0;[/N]Q-LW6N27M'3ۧ /j*>b[jwo639@EIƒ5cݷ7?9A?[r珔@7"""J E9xt 7{wI}dC҅Ab8[[7Y7uݚԭ+#gk}d[8yN2OF ,=e$THX{wUS׭cz0 €4~ 7|\ٹ AgKtę!3å~8aXv=ËNlvn-f- ?<'~.* k \T022RDD$WP)#. s)#n(#EDDrE 2(Q022RDD$WP)#. s)#n(#EDDrE 2(Q022RDD$WP)#. s)#n(#EDDrE 2(Q022RDD$WP)#. s)#n(#EDDrE 2(Q022RDD$WP)#. s)#n(#EDDrE 2(Q022RDD$WP)#. s)#n(#EDDrE 2(Q022RDD$WP)#. s)#n(#EDDrE 2(Q022RDD$WP)#. s)#n(#EDDrE 2(Q022RDD$WP)#. s)#n(#EDDrE 2(Q022RDD$WP)#. s)#n(#EDDrE 2(Q022RDD$WP)#. s)#n(#EDDrE 2(Q022RDD$WP)#. s)#n(#EDDrE 2(Q022RDD$WP)#. s)#n(#EDDrE 2(Q022RDD$WP)#. s)#n(#EDDrE 2(Q022RDD$WP)#ԑOz7'.pzSˊsٙ>Sӻ[#Ё~?;9r珔@7"""JߟŎA9u5/1.cgmvQ]S?ۿ=,=e$TKWzxXt핹o*m*jea#e$ eH(#Zeox:멷v R|ZI\'jc>;x:N ~oqP022RDD$WP-2߻(nW}qd{@. s)#n(#EDDrE ,#g SP/{~eT<|,=e$TKlm.WRM3GHH\QF@[7ЗJIp)ed{H tC)""+HbR)#3ed]#e$ eH(#RFKϔu珔@7"""J ,n>/2>SF?RFPF2*e$غTHLY{H tC)""+HbR)#3ed]#e$ eH(#RFKϔu珔@7"""J ,n>/2>SF?RFPF2*e$غTHLY{H tC)""+HbR)#3ed]#e$ eH(#RFK\ly2ؾso{EY{H tC)""+HKo'M-imGNioCo}7_9p$|@_*ed0s򫦂{f[ `-ڛX,ȺGHH\QF@,#Zs|;GNF{kzoLWۃ 2rcnZ[4n98?;7-Y\. W}|~V==e$TnZUkܸ_}aOg˓A׍ ;IMo} ӈ \lǷq㉅?*ۏ=7B<:ѺT[F 1m\t Tkk-\zKWk,8GLWkܥ~#x0H5n .9-}/fSc&2.22RDD$WP)# }wKɺ57kx@wl, @i̡O۳aXw g{kO5;xۺ{kߋoi ,u{S׭-a|"}-\IqU|qΣX '{L<]fe 3Z%Fڕhd媿-ot}&LkzqvM.#T+?+`w xzes5a}G2[Ga0Uنl2vŸcrU*!2 {HqԚoDaT[F*W6.x+x4Z>[_5n T߰^Rlzm@O@ٙGHH\QF@,Ȱ~nu`!7~a~fӉBaEER_L{?[|Է7?Qd9226 3CEz[Zf91\]xJwc<7xhFHٙGHH\QF@,/*~ۛfkXX5h`7{x+#X:Ŧdۏԏ{zl )wi+@7Зje0S9q&omed îq[vԃt{H-1=]zNY{H tC)""+H0k|3b]f2rG^o gŶ}aovh"8W6om+)Gx1K5ed@b]Wƀƿb!9GqlW٭3{~־!#Ϊ=HٙGHH\QF@,HGatk{ h~u߹/~c>VFXMdaq։22+ ZuZb~ q zLmgƛ]HٙGHH\QF@,Hz>eCmkŨE:IDAT]kG^8jp;w1X18XoW[lt$ iu}& ՔxSxs_8B$nmWZf۽g&~dcQxMuyLo֮op)#r)#n(#EDDrE 2 ն>uaXdZ|?G+ M d&z|@_I/#cx6.x+dоe =L$nmX=6shLuC )# (22RDD$WP)# sUgwjCTrșfhdžZ22~b'YTFz22>y5^S׻1 ^DîZBǁ74[5\wLoKw!Fن迅=e$TȲqE?qӲ!wM,.z:!bþOnʁZ$>u\|@_I/#}tus Z9nm1NZaϸi|"BRFv&(22RDD$WP)#rę7zgSZ}dZ^l<0}?eC:a^1ac5R8}*#g] ?a {? yGMiaޤRMz9{2ˎ^-=`؅oHW4n ӾW T./TFv&(22RDD$WP)#3sh\ٳlI~ 7o}lW[! Ëqk{S{Ǣ. z.c?qbܹ=]qkGL2̡>'֮XswN|\n>/Ued8`إ4B /oVqXo 7=*0)]z][b=, w4y9< ho?ed]#e$ eH(#RFKU@+qZȉ3iMv{#)#r)#n(#EDDrE 2Xl|@_*e"J_8pw |3mȺGHH\QF@[7ЗJwO7EGN2r,ȺGHH\QF@[7ЗJf59}ﲹt:ed]#e$ eH(#RFK\\ko;}ff=r̾o~羕&rߋowd ed]#e$ eH(#RFK\\GNczS#޲ۧ{12.22RDD$WP)#RFp[Oeí}^5(#r)#n(#EDDrE 2Xl|@_*e$}=e$TH`u}2.22RDD$WP)#RFgȺGHH\QF@[7ЗJI)#r)#n(#EDDrE 2Xl|@_*e$}=e$TH`u}2.22RDD$WP)#RFgȺGHH\QF@[7ЗJI)#r)#n(#EDDrE 2CGyn=?[ ~?;I7З./ed]#e$ eH(#RFv豳t(.p멟E'koKY{H tC)""+HxK!鏙>l.n>/U?V{+\^ȺGHH\QF@ m"RK奌=e$TȜIz\뽓aх_W澩➪=xt}[RF?RFPF2*ed6Gm/OvNu=.W^0r>&(22RDD$WP)#=KM?\n>/Ձ?q;[F=orߋr)#n(#EDDrE 22z }?k{?жa,?z?m񅦹E珔@7"""J2> j,9x}/4AR^{H tC)""+HyLh]7-ho F:sp@wӍwLoZ@]:p1}_^2^^y'GHH\QF@cB;Ʀmߋo}ϖhoݽg&n=ls-7?:Ȇj塷>hsH`)}/=e$T<&9x4vl[޺u#[Ƅ{F"ۛSaӭߕΉO723e$?RFPF2*edZFΞxȉ3kVڭeâܢϧ~|[o+5q#R^{H tC)""+HyLn;57kz,W}|p'^<^{[?SFKA{Q#e$ eH(#RF1edbȁo}lW\(}:{Lly2H"镫֬q]C,8G ܴK?i(yaWMnБgvL\=[2F}/=e$T<&+ >\șG>]/^Fyę}kӫo=\rƁ6nz=fUS]1} w2X rߋr)#n(#EDDrE 22-#;_D|"2uݚ#T+eC8PYedek9qfc3] *4-vvMb1:ӞGap֞SFKA{Q#e$ eH(#RF1eom-; +<\.T?VTq_\qtJ{@jEJo)^匤7x8NRH$_E H fPIڴJ&L*2'c?`b>{ڻzT{ڛگ׬tn"'(1h1RP#c xڑDkezI8F8hґ2JҌ_o4$sI];69iEO@;$Eec|K^)_2c'N-8FVI٘+['gAO')+?zO3JTnmtW ,ߋϟ#v#%I 12@ՈF6hJqf>1;㕑_6{~Y榇Zz?/t1XE?1F0FJ1 cd#kӕZ:XcdJόnOCՌVK_5pG# {Qc$c$IQ#0F1}z=_W'J86ߑ5=W/ζ}O-ަ5gEO@;$Eecȴ/E0KzFtG5N2Y~tT;b4=6ϡqs>Dtq_eH`!^} )IRTH(1}Lzgyuƕ. /h1tLKcdn;FצK_:Y{"wcd0 eEO@;$Eec748jqqܻ{.3F A'HHI2F@a1cd5kbR ~#\8Yݾh7>w#~uVV'l쎏oݕn_/.ܤz76ZV>V6Rsxz+-嗤$ӵ#YOc_|?~|N|?.3F A'HHI2F@a1c]lڦ82q|Eէ3huc'Nk7g?7)dyN||3$K9ĵB(1h1RP#c }n)xc(o:9}gN82}me:/랍L/ˮaUQpd:MY=JÒoN^}w}pōgכ~jஉLB(1h1RP#c )gѱ7=-  ,ߋϟ#v#%I 121t;q:7Iܷ~s~9c$D/>ba$)*c$>>{2+;qڕ ,ߋϟ#v#%I 121{k\=w_IꌑB(1h1RP#c#kŪ[zt;_ٻ|/[w_yDf ,ߋϟ#v#%I 121Nctm#s+Vrȩ1XE?1F0FJ1 cd cdGNmzd}7XuMY̐9pƏMşʿ~rѣMEO@;$Eezn~//c?6ܖSPm*iO(1h1RP#c_e^إt_)Weߋϟ#v#%I 12F^_ȁ?k̯ ;cG/>ba$)*c$n{a~#oK7F'xwϳEħw??Oq"?E?1F0FJ1 cd07~l //|cEO@;$EeI#;'cMǦ{cϏEO@;$EeI#ѿֿyv`ƾ1](1h1RP#c#2cd~fu~@D/>ba$)*c$He[ֿS{0_oݢE?1F0FJ1 cdze;p1>k~@D/>ba$)*c$He{eO;臕1.3FE?1F0FJ1 cd}/}\_- {~1臕1.3FE?1F0FJ1 cdG=^bؽ# W}H@?t1.1h1RP#4.ܲݣGxgӟl~oghaeˌuO@;$Eeɑ??-\"IqY7MZd?v^V vGKoݕE#ϟ#v#%I 12;>=rd/P?3 |hAoКM.{/Z}ٌߗV%~1FE?1F0FJ1 cdw&>}_xoL_Oba$)*c$ȶ3~}=n9}AdU/.OڦG.nK}7w-+θkaSX~jמ7sg>ba$)*c$=?- _Vja5ck8Fnˈ8/c=o*Agd>ba$)*c$Hy~X 6׭lܞޅѵKZ>2.Zt<ߑuF#w}~1r^D/>ba$)*c$Hy~X Y}0䌷ozdMޙJ_}o|T}d+^/9\W/ITsK-^Z皵8]?/,IƳUcdY} )IRTH(@12]9]xg.Xܻ|#򱰺>V7Q8}_M|ko?9=mVZ9Y`~̢KEuL>gT9E?1F0FJ1 c$o@?wq||Y[^~cd5+XuK@w|L_'6vGϰ,.@>V;twMEVG7_X=t:Ol?xtN|}+hlba$)*c$Hy~X YJ ]lny|@Ę޻yf#r92 '?c5no*]hWIurzLIx)o|gÙ{R:fݿw1.1h1RP#~ka5cdZskGj#M\$99vKG(+KcdzJ3~I:1:GSϡ~c䦇J'Άͥգ]~wQʿ㌑uO@;$Eeb|\^Z /#e.m{զX~8o\8Y}&ec>{cd?niݻ<|?92-7_w>\~ݙ7}큺Y} )IRTH(kϞ|QI4cd5"gRg#ٯu޻Wv*?a/2cd]c$c$IQ#Xxc[>'qr쥳{mt_4:83#fs;g57dp~#ϟ#v#%I #'Cz ߣ[ۛp.=F^uOgcJztcDt9ֻtozyu;zdmY,d6kY} )IRTH(Y>o}6/'> X7cd"V%iKd:`#'qf{Vq{y|89"g01h1RP,1qqā{'ON6~2f<|~4cdz+>Ϋ3mih\xiu@ciU}cz\7#W].m,?dqg{mZ]~a#ϟ#v#%I #뷻\" wg=o/ԅ18 N~ff#O716괍GL?r6.Jy71ٓߝ8tʑޢ㌑uO@;$Eebgzc}f&|x򷻀s8ā{kA1rQ[>02IΚXv 3t{Stc>FpMtFոy쌪I5;6y񕻲K0woM_qȺ'HHI2F@PU}-cj2ĤFfq1}o|tGNp׭Nߺ+ݾf탻_u]Iom:|m< W[/IWI.|mmK/?=գG8cd]c$c$IQ#Xcdr?(p?Y/wݳ{'k, 6FNņAAK0gzt]:˱ӵ3ʟpOrr z ./>ba$)*c$ ~D['@{m[\C0Fn߹wŪ[J3^JNN_8Ӈ;L_[{|Kgc5k~?إW-G,O|,7zg(嗗gK[:yyd#ߵ*E/-O;pZ1FE?1F0FJ1 cdy1'>Kg+sr_%..[q;qz#F_ȌuO@;$Ee %>>q$p]䌑uO@;$Ee %>>{2+;qڕY} )IRTH(]b죽Wǯq$uȺ'HHI2F@acdXuK#W{z+{8_p+^{̿#ϟ#v#%I 1K5vk[ꖃGN_luO@;$Ee %ȶo^ꖛn[w!/1.1h1RP#I#ϟ#v#%I 1Kt1.1h1RP#I#ϟ#v#%I 1Kt1.1h1RP#I#ϟ#v#%I 1Kt1.1h1RP#I#ϟ#v#%I 1Kt1.1h1RP#I#ϟ#v#%I 1Kt1.1h1RP#I#ϟ#v#%I 1Kt1.1h1RP#I#ϟ#v#%I 1Kt1.1h1RP#I#ϟ#v#%I 1KcG1} )IRTH(]Кc+?Xħ1=F?1F0FJ1 c$@t˩jٹ//O?'x'?k'HHI2F@a}/}\1~l}~LDO@;$Ee 1{|x t>FO@;$Ee 1#ѿA;ޙ'߯|"?'HHI2F@a'G~6p$wg4iO1z,>ba$)*c$Hyg'xGn~֟l~[>ܲ~y{ ޠ5 ϟ#v#%I 1ޙ1a~=;DNƏO@;$Ee a/<[< o bY+cac$c$IQ#0Ft;~{<߇ CkSuAd%z,>ba$)*c$H1~leK~BşlG5w8@'HHI2F@aQX} )IRTH(0 ϟ#v#%I 1Fcac$c$IQ#0F(z,>ba$)*c$H`EO@;$Ee 01h1RP#a=F?1F0FJ1 c$0'HHI2F@aQX} )IRTH(0 ϟ#v#%I 1Fcac$c$IQ#0F(z,>ba$)*c$H`EO@;$Ee 01h1RP#a=F?1F0FJ1 c$0'HHI2F@aQX} )IRTH(0 ϟ#v#%I 1Fcac$c$IQ#0F(z,>ba$)*c$H`EO@;$Ee 01h1RP#a=F?1F0FJ1 c$0'HHI2F@aQX} )IRTH(0 ϟ#v#%I 1Fcac$c$IQ#0F(z,>ba$)*c$H`EO@;$Ee 01h1RP#a=F?1F0FJ1 c$0'HHI2F@aQX} )IRTH(0 ϟ#v#%I 1Fcac$c$IQ#0F(z,>ba$)*c$H`EO@;$Ee 01h1RP#a=F?1F0FJ1 c$0'HHI2F@aQX} )IRTH(0 ϟ#v#%I 1Fcac$c$IQ#0F(z,>ba$)*c$H`EO@;$Ee 01h1RP#a=F?1F0FJ1 c$0'HHI2F@aQX} )IRTH(0 ϟ#v#%I 1Fcac$c$IQ#0F(z,>ba$)*c$H`EO@;$Ee 01h1RP#a=F?1F0FJ1 c$0'HHI2F@1׾:#ab$)*c$#{mٯ:~8?>s~<#ab$)*c$#Or ?0c/qo\P{̏''?63F)IRTH(kc$0L$Ee p01FJ1 c$3F)IRTH( c$IQ#0F?c$0L$Ee p01FJ1 c$3F)IRTH( c$IQ#0F?c$0L$Ee p01FJ1 c$3F)IRTH( c$IQ#0F?c$0L$Ee p01FJ1 c$3F)IRTH( c$IQ#0F?c$0L$Ee p01FJ1 c$3F)IRTH(Z#?(?`@׌0FJ11V}G~Yc/dV#v#%Io^zT @#8c$c$I1K8 @c$@7#v#%I kiEQ4׌1Ft1h1R:#WZ3ͣ3Fqqן[ƉvO͞<7>2FJ4?~e]vY˓1hOLWHgߝJ'7>2FJ4oEdɒy^zUy޻ @ɿo쑫ߵ;{~_^ן[$voxVHI泩+W6wȬ+bٲeV8]Q]\HZ0?U0 )I/4;tP1cͣ#c$)IRx˖-kӟ 9_oʚg"#%Im͚5K_ZrT D0FJ؎;;䗾f͚q1`$)Ǐ7w/}iÆ :1`$).D~̓1`$) Z,Yҩω7cxf셵?cՏW}?c/7m~<:)IRKK%\%u|/(7+uA$ 6ԗȲ;v4R5FWov)$"#%Ise###̓:ּO cӾ1R>c՗Ȳ_|yPj6~}ūcǏ}__^;+s/=skwe$ϭ\D^s5#Wch;ï>khxiɃ+x}d$MMM՗/u"S-;<" ‘?ڹ.$_|1FE<{9Foīxb$~%rɒ%#:Ycx)ghyz71R~ve 64dm\7h_/HIY},;tPNY'1hA<HIYc#v>@J݁<HIcdf1򿍑СCs2F@#S2$I1rɒ%#1cd)I6lP#] 12e$ L^# )c$i`2F ]`L#%I1Hcd)I0F@#SHId12FJ&cd/۾s;q:T^{m]47~*彫GוGw07cd)I0FCou+G]ߕ,jyy[ǯT{ϛo|t3<%ϰ랍]}ޝ[io 12e$ L^#{ϛ#{Ƿ*X~Ps[y}75Μ_znI]{Cyˢ˿RRJ+lyWO89} K˻VGt)d;H96\ZM[)pT12e$ L^#v_=d5vtZfm(3ywJݷ~3o9^guK:m!vʌ'Mz=8[5ҟO~2FJ&cd/K /E7^GTV7qn7ظc#eO2]Kx;L_|>3|tMG#abL#%I1H` z/Ry˫U5}ccs-~30LoZ4l"-3?1X)c$i`2F 4VUymJ󙤕nŪ[7V߸|aG׍|q/bCMN_XȺ4gKKZ$Ml+lXK]۸4}9`&cd)I0FCit_5_}kU뇥\tWѱEھhL oٵ7w͡cpLߒ c#abL#%I1H`(uƑV}a}hܻ< ZӨv%nٖ6iMe\.ͷLk~W]a {IoI,+VR"ctl'PȔ1R40#{aOJ76Z>>\UNuצ p+-m {*nHW~L{IgߒXӬi12e$ L^#.l><2I9㻕Knm4z#Ư(3fŪ[GuWP&-_yɴ12e$ L^#!6eP4ݷ~kh{uեL#/`؛͌cddnwtt 3ޜvƇhH7zM<2-Ȕ1R40#{a[wL_ű,|a,o;ʯ:xT~Jm#v,s5sLSb~{uգc͕ uDzb}#9o[~fE=3_Ȕ1R40#{aǫ*]={1SI+=GO,7yӭw޿MȜ>ݳRfTkG? "cd)I0FmwztݢKGgc=+i}׸A҃ھ=FZ~{۴1<91?k>|5YtWʃg5>Ȕ1R40#{a4Unδu=uW~c>w~12^mZg{E:K721F c$0|p5ֵ{ϛR´U6=>wFc3ЂHIeEG_M3 LKy}a$o#{_~Dve?:^#%I[^īիc8G&Xt:^#%I[tu>$yrw2/HI1G#7NHױ>Ξ_{W)IR2FɑɖIz:)IR2Fo쑫ߵ;{~}//ϭ_y7Bߠ51FJԷeo@@?-,1R>fOY!PՖwg#%I[ Sܳ"_nG\Y1FJԷ?#;^x瑷<"c$I} Pg$o#ꌑ$-c$@1Re3FJԷuHI1)IR2F#%I[H:c$I} Pg$o#ꌑ$-c$@1Re3FJԷuHI1)IR2F#%I[H:c$I} Pg$o#ꌑ$-c$@1Re3FJԷuHI1)IR2F#%I[H:c$I} Pg$o#ꌑ$-c$@1Re3FJԷuHI1g~(bzY 8`|@/aD&0r+)(!d$@"!d@F'BȺd)ˮy]k^kݟG!?ջn a$B!ԛ#4!Po"F"B0@CB&H a$B!ԛ#4!Po"F"B0@CB&H~on_^r1,-{/'’W~.<ł`(ǭ9CCwh#BD 8O|B_@BhsMoy- qz1 Is&Cz8 \`8NB(HCM2ꁯ{?8` GHky!D!za$tonI_4B[#8Vm7qBKP CUuzwf0!MPNU_1B^TNG(~ڹ< к}!WF"B0 I=Eg}}w/7t[}9o}5Bp:ћ)DžM8SSGi8Bhw a$B!ԛ#;8{^b=4sMޏwo۾]_+n77-T-虗]tunj|s| rUc&lo`h:.X`F"B0r{􅢙c`v~c, dݛ7S @?6︿Z!D!za$L > }~wp:@އcS90dqHㄑ!P"<=A| `C9 !o?pftM8Z`΃J0>ž~C&8.ǻ `hF"B0~}Ar9pW}syޛbg%~/o7F8V뛵S^R֫ZZ1/n!Po"<{/٧W?]ftދpgh|wS q|-` `&AB&Hcou??\_[wMmQN(/o}G~SZǙ"?3~[З!Po"<Ô7Ǽ={zRx7` q,MC0!M`!h=/={zRx7` q)Ya$B!ԛ# Bоkjrp:@!)M7M!8 1wS ˜Ż `hF"B0Q,6(zRx7` q)Ya$B!ԛ# Bоkjrp:@!)M7M!8 1wS ˜Ż `hF"B0Q,6(zRx7` q)Ya$B!ԛ# Bоkjrp:@!)M7M!8 1wS ˜Ż `hF"B.` XwMmQN(D;)0fn*Rx7 HB70vea$| XwMmQN(D;)0fn*Rx7 HB7}<]bYE 0(]Sx Niji q)YJ08,MC0!M^'K,#Bоkjrp:@!)M7M!8 1wS ˜Ż `hF"Bĉ^_ -#Bоkjrp:@!)M7M!8 1wS ˜Ż `hF"Bѣ^."D A <h448,M%Bp@ c&AB/Fs4%X`!h5G98픦֛˜ŻSHa04#BzA0`>Q,6(zRx7` q)Ya$B!ԃ|IsyQ0`>Q,6(zRx7` q)Ya$Bhu_>{W/̕>/w۷oK,#Bоkjrp:@!)M7M!8 1wS ˜Ż `hF"Z'O<=ş2]%]旿er0`>Q,6(zRx7` q)Ya$Bh?,ad K͂>J~ֆb7y_."D A <h448,M%Bp@ c&AZvO['|."D A <h448,M%Bp@ c&AzW]+TUt?k/Td #e^Sz`)W< #5N]`kG57A]n30`>Q,6(zRx7` q)Ya$Bhf /,󱟗E:qĎ;rD`!h5G98픦֛˜ŻSHa04#BsՒ%Ou\X~^arw6w\p'좛D`!h5G98픦֛˜ŻSHa04#Bsadd% #wc"o߾I_*"D A <h448,M%Bp@ c&A 3ySiaQ\!Z˦G$2(Ln*F" }tBSZoBp@ cL!8 1w DU<)_촰(.?1aTx"NĽw~TOO>v&H@DPvJSMSHaT)0fn e<)_촰(.?X3]) 8'O{BWs+)I)HyADPvJSMSHaT)0fn:{o SC;珞[ ͢w}w>R5S`!h5G98픦֛˜ŻSHa04#BsUE?,|n;MPڤ#'|c(_X9LֹzJ'OD(O+ܻO~,Z2w۷o$a$ XwMmQN(D;)0fn*Rx7 H\Rc}qMM8.+60rC񇬵Tad,|_~}Fw}׮E 0(]Sx Niji q)YJ08,MC0!4Wf`a|Ͻᄚ5IճϽ`g)a;MvaD?waE]w݉' \`!h5G98픦֛˜ŻSHa04#BsUkv{d~K-?e;<`睩X~?6m0ҿ4ş2Wϡخ ;O8q"5 rG+l F" }tBSZoBp@ cL!8 1w DUa]kºqw:3|OxL(_X˷~Q02me:߫r׻믏F#s(ڽ{{;ka$| XwMmQN(D;)0fn*Rx7 H\F=>IGz7+|GE#_3wH]3v&ԉ'02T>3wa}EpѣGonnE[o$a$| XwMmQN(D;)0fn*Rx7 H\T~>%Y]*[_?~.3;va=fuڪ #C UE):rHjCқJ`!h5G98픦֛˜ŻSHa04#BsU&-oj&Oe,q-[yj-?w MuadWZ:7}_r\p}+_KoF" }tBSZoBp@ cL!8 1w DU,s+7yNḺuӗnf4atc3Cرp_o1|O˸s#%_'ˬxV.'Nإ7#Bоkjrp:@!)M7M!8 1wS ˜Ż `hF"'=Q 1J'^aa37ꗋъ!87|_kQa\/w:dQ,6(zRx7` q)Ya$Bh~i8}㸀2{Mϻ`ɓ'E)FtiiQad( 'N\ve2ѣvѥa$|lV}lӷ$G?MS+ cL!8L7@Ya$Bh~FO!q;Om1 e]߼z5ׇs4_oW_-;6Z #ÆF7rggE`{^=C?۹kϵ7gj鋷Oۻ> ]SxӋfOOG־/~F_y'_Мk@pC㙱2CK8xw[yMh44qqJjזq"0c#kvDž9λSH-\xס~~Y2?lw-u˱9lC'+3RTS b04#BNRCug:ϟz̒zK<(.b>W)ʋ?Qj;0_utuי0."D)8B#=;U"#Khz%NuMD?K,9t"íY9 ebrU+wMmQN/N5O/G}x~ S?Fy'&Ch~R]Lgj٨Ɣ,ZMXG? #Xhuo~׽jE;)dkl#r:S؇XT;R?+dsaYHRna?y7` pԳ.v H vvh&3CX&}T`MGvzG\y7*4o4tI9'll<J$ic&Au= qiɓ'},|߲ _ ;usFv0rtĉhdn47HHKDA2yJ:&RdZ䯽jf !fr7Y&^2 פ'Tp_{." +E$ !.g:x-J׾kjrzqԌ"խ''_wt:d?}*!(t>T_i%dNPOij ;^Wh44,bf]WI~;!w8Gԍ'S6qǺq]㸸8L!SG.Jz}mMzyk4UwhbE_TJ4?]0N7?|6_=VfD6W/u<,MC0!4?żk%]wfdtrY7,k [,,dQ #ù [Pͥ_h9r."3FS#HgGϊ~8,@J 3A^H*{_ Ύ9Ӄr?y_xk_!{~B ;)1CwMmQΌN*Ļ>BKÊ?}dM:OEtKwMGdCUx;THbrF|=Zb[mbN ]uCwX+̂vJSMSHu3!mȷk7k61^bi_{G"!^GJu +뷝}_ 4_~ކc朶zs*]3HٓpRu{1fn)Y4??BهEq) ymύ# 囑[!=wfd.2}{n0`>Q3;t~g!":0ZSq "v:|Gg>adֳ#KZd[Apt{*>qHz wMmQΌNQ3:U2\ M;YoY%;g ˏW3dƆSW{ʫQ/ftQ^ySZoBq~ .|h=ߕvJ8#jR&zIs;uYψbkУ™qcx7` pC?/V~﬿ 56o1^Tl/D}# *+02Em[6lmɩV W?4wƓRWc&AN<),ٿ5bad%z'vea$|1W|XW|h; "rm}g(z$_W om_Qa,> >d[ǯFIu?Sߴh5G98=bz%eo [[hxņrm}kyZGVߥWE#d՘Shv|%z[G֤1S3tF(^SZoBqzX8kq1D Kp^2 /~'8NvOOQ*8Ο~7ci︘XCOzT)qv4; n`76[_SΪˏydcR]~zUV6 _L>z z}<&xMB.u|1w DIyx7PeEa;Lz¿6m0^ߌ,щ'o߮5=jZVḞY"qYz^B\l)ctPDպV 3Aסt~!*Ѥ#mw푉~PW]o4W/F2:0]ۀ]Sx3&&vF̾{wC<60?~UpjWxg9WۮzAy"C;1Zn :42Yu i,+`픦֛es0 n b&ߚQqֶ1q&Ff'qJ/:PBZYJ0;hrvY-`JOQs'W|nI$R{+ՒI /* ҷ}_E#"p~VĴd1%>vUB`<1w DIw8x~0u{Kh-ߥFN(ad̛ZxIJ0`>Q ck7kϛBk%OgHɶZ׊^HI nk]F3at_>65zBJ C|ZiĞ8E: Zzg?'Z[W6(gǎ9I7|c"eRq,w†2 8xGHbrkZ׊g\0K^dCD#I|4̖#d.h44 qQ@;.sv[$vfqGRZ׊ :o q T\~?8y7` )w\~yN.3Ky j,[?Y^Zhrb9_TIi~MV߲j$f?U7 P}SYr8a$BhNI.; !|ɧ>۩sߍNբg$RR'Q]w$,xZYد\.~z@:Z˔[$Z#kzȈ< ozg2cz]{RptHia֡}tkm^Z08SQWrх{Bˉ߽M įS Ov8?䷕cPMzwF{NGg>( #qH]f[/92UvJSMS28nU^%5MqFi75< wwǵ@t4s\f|S|QxI:.‘k[!'98rT+KqbI?.o9B=^.K,#ÌCyBn}p8[Gkטj m#Ii]{tWNjzZ+Df֝6yb;zg}D|)u觯u/aOZ\Y{?y кz!ݐ^Xc.!bX7j)l1ŝsz+D}t,ĕl ou *!װ)yIvY0Sh5G93:]cxm2x\C=: ?+& [!m.zN!FĭxSSr:fDNAfxD9tWvJSMSF8O_{*&nĉP7}Xl"F@# ݇#FH_yWz*٦ֿe~xtѓ2mҳRf񤧸ue8q\O\e=zb|5f/l݇ #KX- u/|9'iLg 6(gF ~Pi?}d-#~^to&k%0^N^;RpuK: 픦֛~wIz*8.Vq&qA[d_QH5ōs\L>%tdɺtwS I홉O<S?ptz nٔebmghrŇ*~®ʏ\:trgHrt+Sr _H+K/V2d8jY1w Dm@E: :2/FЦ+bmCzvw?}gUi aBD 0f(LDqAZ;1IDAT:NTbo+&7ȕOHٖ+=gݗfLlUbh^!akE*zoo,aŅY3TM5?XavB <ʙBP]R۱qʠaGO.jujMl{bwk(D0ذK+ݵd)eT=HH!a1;R {.}S,SvJSMSH_+8(LUqB @8Y>8Y#wo"ܺxǽ>=_Tԡi cL!OKD\tEh Ar4V&# LPFQDYmX99; ^s5VZ#}}[l17㛱p 6>uhac04#B.uF{_1kJ %&ݣa޶g~Q>*!qDQqv .Rxa-ŝRHr|HiXot wj/<]Sx3l*5z CGY%TH>dZ8*u#M)C.Rkb&R}Z{.#h,U@@>xSZoB*+qr|:RqWx g!k}kt\MDž6'˧ kMbᅎ&RIjVRAT)qҊ'ljSKmuwX NGG:jG,$U UK':mN;)q"qrJgY*vkMɹ~? 5̊y4eл~/G"&M.74/nOc7WRr)Y픦֛rDž'j:NWRUtjB^2(=5bp)p㤄:;t[Y&독~]Wrh8L! u/ul=]~e[ӑVyo+zk#[#vHRͦu.\;u)fV:-yg tg.=%VbYa$B!ԛ# OuD!?zO~&tI>lJHo1"T߇JZ GGI΂c24}LH>|hd#N7Åߌ ܵ'?Ŧэ >)nIQ wMmQΌN|)v0 l-SWq,թZU vDfzqIᭋyW_j$~V m6:4'.A lh`Y8 e2H_K)D;)/Ǚ%Q8.ULO'$|'kşմq%.MS'%d64qu/Fz[4?4Aj/:aT)qRWzC8F9fddlN_5VʯKƜVۭ~Vr~Қ9LHbLPK _͕6ٻ֝̐9^k1w D!za$%H>8WtkTt_~R]jE.Ƴ[' 8֣n]%~MP;{fڤ'udx8R_f[+# 6?yW?sמN%-0*A <ʙ_?8umkbɬH]W* uP"dzqɺ~P,WH '[6VA WE6e Niji q|g2=~+)tcTN|.iq)!!hU^qrR[3Ɠ9HkëØŻSH㤢zGi0%,.ߖAItva[/՛P[WbHtZX c7Cj+,MC0!Mg_2kR@KtdzUk|߬!V߿Ժ [Z4;4NQv5,L =Y;0f OPki~V M;* tS/UdCzPfD\:n=Vdh0$_l\v)Y픦֛'qezJZk%Z Gq,é8.5+[pk9OZ;.ܢ Gmw7twS I)R&kSRw>=,Gd[o X~C+[/!n}ۜIAo˕З [/j CƻѤR'oR%MĘ%x7 HB7F@Bڤ0v?C"79_Oy&c/~u=+7e겘,_|*_l |X<|mab*B:$;Kk$Bd[=]&PݫgvSboo8qGΑ^1ϦkJ:Jоkjr>@=k,+F2"u.^㋝EK'eZ0aCiyݨU0b9hǒÒfVK#`W'ߋl$M~hK::-SvJSMSH m*d)q\aʣ ;Yw 8N|0qrPgJv,BZyl㼛J0;Nñ=YReV2xOEϟى&WkȽxrL'OD[],ŗ|tJQURpCn=L|W,i*%,Ż `hF"B0TGgn?ܣĮ)3ߡ&˿Y)bdn :ym2w$R}+ꛑLZ0pabI Yt ηY)0*A <ʩvX}-uIXjDh=R_pD-~.2?Bth5Rs״J1K0fn!Po"ȣӏ?zgmqq& y>I~yk Ҥ-隟$vE <ʙ􀌵訩̬H_u+d|vLOoE bOե&/n.guZ픦֛WLU qn*, ?d8ga'YJ0tr\8`J$L&EI$uڒwZ/&RwǵR b04#BD yf(6] w&tlG <fA;)0fn*Rx7 HB7F@AE tg>7b<+T} zRx7` q)Ya$B!ԛ# ">2̡?|O} h448,M%Bp@ c&AB&HCDPvJSMSHaT)0fn!Po">ҭqvŒjE%=D~߱9cv.(C5?xTLԻaUe@})A9W9n۶8L!8q0!M6tܸmsv {u$sٙS!+؁{ghف:oBpxp`vS\p;MKo['Zwi=xh*(C0!Mc PN7Yt K.Η֞x_9>BxWY)^דּ϶{_,.=ٻ-L۶-F XJnؙo¹}nmSB7M!8n+ѣ$td~%{q\YSJlp܈!匎J^ HB7F@"!0n?p Xlwj}8x2E0P婤Y.(o1n?0ym'[KocGĤx֬UMSlFDžq%-߬SHva%=y"=SnmێnL)-u#)$8.\=oY0}i%BpܲQ8rqSD_ D!za$!<u_ATn(=$o夢>u; (h Λ$8OnWG?L!8nIqs’AB&HCD/bԹL ycG^r= m:O/W]s5H@fuK0q2H̩öɍ_S[,O:=y@Ba$B!ԛ# (j7 p[~½5U25 oM}J;+qmO<YgB/kc*B/(㸍f M98nT|\=f8a$B!ԛ# U\ ^C}pv1c|g>=A k7qWXg<7v4̐D^rFyg?#7gy͇o9o_ ~aBB&HW sv:G}l4O/A2cɑNؽ m@wtd3M΃xW,-!Po"nUg}|A/?5h& j:w+S.6gIW~ 2?uID0}8n}隉&L}qUT| YwF"B0 yضmѪsv:p}#ԛ|k٬J0 oM H@PM˓^LJ8/Y9/(p45lp4ެ,'!Po"NǎuґYp-?FoNI+PE%HR_ #ɠn-q !Po"^xWn;uDѕWޑޒ4J?΅{/Vd a[Q۶~ep=GÞp-{fE| ˿tvsiǕ-Yk:.F8`AB&H藗~}wwDuGoΝ~R+<9Wދ~;B ckOTd$Q%]242ұ8X̘J <Wԝ8`>F"B0Ko]u}] Ax_E p̓:.ޯ8,!Po"9##&o?pދ# -CC`8n䶰>m8.&8NB(HX,o=t{L_\C/>#z#ϼKCۮl~}/D;3 K8n&%86qa'[ q@+!Po"eg^CcXa^QH@9/tݞ}ɾ%=d, }teq ],ܵ^J^с!D!za$l󀌤]{X&̒YB>ҋ$h숿)"u2C8W0dp܈wta$B!ԛ#` #]?$lb.8`80!MHB7Fh#BD !D!za$0!MHB7Fh#BD !D!za$0!MHB7Fh#BD !D!za$0!MHB7Fh#BD !D!za$0!MHB7Fh#BD !D!za$0!MHB7Fh#BD !D!za$0!MHB7Fh#BD !D!za$0!MHB7Fh#BD !D!za$0!MHB7Fh#BD !D!za$0!MHB7Fh#BD !D!zѣGu{nIJ06HB7: K,#`# D!za$0!ۻި8}KL:q !&%lpLML&4c.4b妺@`znί='\/d,2=%c$I$I0M9Y2FJ$I$I jc`0HOd)I$I$)"c$LS׫ǎKOd)I$I$)"c$Lp8UUc$I$I1d#%I$I$Ed)k1'c$I$I1l0ȉxʌ1R$I$IRDH,12#$I$I"2FE쑣(=c$I$I0}UUZyLe)I$I$)"c$1ra$I$I1B4?("=f I$I$I{ QFQ2FN \NZپXм1H$I$Ia)kzM. ͛$I$I$5;r6ƌ#qeYEn@?&I$I$I;ExH#x]e9 )21`#xM_F~,ߌ@c$ 0F!@c$ 0F!@c$ 0F!@c$ 0F!@c$ 0F!@c$ 0F!@c$ 0F!@c$ 0F!@c$ 0F!@c$ 0F!@c$ 0F!@?T')jIENDB`ukui-quick/COPYING0000664000175000017500000010451515153756415012644 0ustar fengfeng GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ukui-quick/modules/0000775000175000017500000000000015153755732013254 5ustar fengfengukui-quick/modules/window-thumbnail/0000775000175000017500000000000015153756415016543 5ustar fengfengukui-quick/modules/window-thumbnail/window-thumbnail-config.h0000664000175000017500000000372315153755732023455 0ustar fengfeng/* * * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef UKUI_QUICK_WINDOW_THUMBNAIL_CONFIG_H #define UKUI_QUICK_WINDOW_THUMBNAIL_CONFIG_H #include class WindowThumbnailConfig: public QObject { Q_OBJECT Q_PROPERTY(bool pipewireThumbnailEnable READ pipewireThumbnailEnable NOTIFY pipeWireThumbnailEnableChanged) Q_PROPERTY(bool kywlcomWindowThumbnailEnable READ kywlcomWindowThumbnailEnable NOTIFY kywlcomWindowThumbnailEnableChanged) Q_PROPERTY(bool xThumbnailEnable READ xThumbnailEnable NOTIFY xThumbnailEnableChanged) Q_PROPERTY(bool realTimeThumbnailEnable READ realTimeThumbnailEnable NOTIFY realTimeThumbnailEnableChanged) public: WindowThumbnailConfig(QObject *parent = nullptr); bool pipewireThumbnailEnable(); static bool realTimeThumbnailEnable(); bool kywlcomWindowThumbnailEnable() const; bool xThumbnailEnable() const; Q_SIGNALS: void realTimeThumbnailEnableChanged(); void pipeWireThumbnailEnableChanged(); void kywlcomWindowThumbnailEnableChanged(); void xThumbnailEnableChanged(); private: bool m_pipewireThumbnailEnable = false; bool m_kywlcomThumbnailEnable = false; bool m_xThumbnailEnable = false; }; #endif //UKUI_QUICK_WINDOW_THUMBNAIL_CONFIG_H ukui-quick/modules/window-thumbnail/screen-casting.h0000664000175000017500000000303415153755732021622 0ustar fengfeng/* SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez SPDX-FileCopyrightText: 2023 iaom SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #ifndef SCREENCASTING_H #define SCREENCASTING_H #include #include #include #include struct zkde_screencast_unstable_v1; namespace KWayland { namespace Client { class PlasmaWindow; class Registry; class Output; } } class ScreencastingPrivate; class ScreencastingStreamPrivate; class ScreencastingStream : public QObject { Q_OBJECT public: ScreencastingStream(QObject *parent); ~ScreencastingStream() override; quint32 nodeId() const; Q_SIGNALS: void created(quint32 nodeid); void failed(const QString &error); void closed(); private: friend class Screencasting; QScopedPointer d; }; class Screencasting : public QObject { Q_OBJECT public: explicit Screencasting(QObject *parent = nullptr); explicit Screencasting(KWayland::Client::Registry *registry, int id, int version, QObject *parent = nullptr); ~Screencasting() override; enum CursorMode { Hidden = 1, Embedded = 2, Metadata = 4, }; Q_ENUM(CursorMode); ScreencastingStream *createWindowStream(const QString &uuid, CursorMode mode); void setup(zkde_screencast_unstable_v1 *screencasting); void destroy(); private: QScopedPointer d; }; #endif // SCREENCASTING_H ukui-quick/modules/window-thumbnail/qmldir0000664000175000017500000000040515153755732017756 0ustar fengfengmodule org.ukui.windowThumbnail plugin ukui-window-thumbnail WindowThumbnail 1.0 qml/WindowThumbnail.qml MprisPlayerButton 1.0 qml/MprisPlayerButton.qml AudioPalyerThumbnail 1.0 qml/AudioPalyerThumbnail.qml VideoPlayerThumbnail 1.0 qml/VideoPlayerThumbnail.qml ukui-quick/modules/window-thumbnail/kywlcom-window-thumbnail-item.cpp0000664000175000017500000003043515153756415025163 0ustar fengfeng/* * * Copyright (C) 2024, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #include #include #include #include #include #include #include #include "kywlcom-window-thumbnail-item.h" #include "discard-egl-pixmap-runnable.h" KywlcomWindowThumbnailItem::KywlcomWindowThumbnailItem(QQuickItem *parent) : QQuickItem(parent) { setFlag(ItemHasContents, true); if(!m_display) { QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); if (!native) { return; } m_display = reinterpret_cast( native->nativeResourceForIntegration(QByteArrayLiteral("wl_display"))); if (!m_display) { qWarning("Failed to get Wayland display."); return; } } connect(this, &QQuickItem::visibleChanged, this, [this]() { active(isVisible()); }); } KywlcomWindowThumbnailItem::~KywlcomWindowThumbnailItem() { } QString KywlcomWindowThumbnailItem::uuid() { return m_uuid; } void KywlcomWindowThumbnailItem::setUuid(QString &uuid) { if (m_uuid != uuid) { m_uuid = uuid; active(!uuid.isEmpty()); Q_EMIT uuidChanged(); } } void KywlcomWindowThumbnailItem::BufferImportDmabuf() { if(!m_thumbnail) { return; } Thumbnail *thum = qobject_cast(sender()); if (thum->flags() & Thumbnail::BufferFlag::Dmabuf) { createEglImage(thum); } else { imageFromMemfd(thum); if (map) { const QImage::Format qtFormat = thum->stride() / thum->size().width() == 3 ? QImage::Format_RGB888 : QImage::Format_ARGB32; QImage img(map, thum->size().width(), thum->size().height(), thum->stride(), qtFormat); m_qImage = img.copy(); } } m_format = m_thumbnail->format(); m_thumFlags = m_thumbnail->flags(); if(window() && window()->isVisible()) { update(); } } #define ADD_ATTRIB(name, value) \ do { \ attribs[num_attribs++] = (name); \ attribs[num_attribs++] = (value); \ attribs[num_attribs] = EGL_NONE; \ } while (0) void KywlcomWindowThumbnailItem::createEglImage(Thumbnail *thumbnail) { EGLDisplay display = EGL_NO_DISPLAY; display = static_cast( QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("egldisplay")); if (display == EGL_NO_DISPLAY) { qWarning() << "egl get display failed "; return; } if (m_image) { if (m_thumFlags & Thumbnail::BufferFlag::Reused) return; static auto eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); eglDestroyImageKHR(display, m_image); } EGLint attribs[20] = { EGL_NONE }; int num_attribs = 0; ADD_ATTRIB(EGL_WIDTH, thumbnail->size().width()); ADD_ATTRIB(EGL_HEIGHT, thumbnail->size().height()); ADD_ATTRIB(EGL_LINUX_DRM_FOURCC_EXT, thumbnail->format()); ADD_ATTRIB(EGL_DMA_BUF_PLANE0_FD_EXT, thumbnail->fd()); ADD_ATTRIB(EGL_DMA_BUF_PLANE0_OFFSET_EXT, thumbnail->offset()); ADD_ATTRIB(EGL_DMA_BUF_PLANE0_PITCH_EXT, thumbnail->stride()); if (thumbnail->modifier() != DRM_FORMAT_MOD_INVALID) { ADD_ATTRIB(EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, thumbnail->modifier() & 0xFFFFFFFF); ADD_ATTRIB(EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, thumbnail->modifier() >> 32); } ADD_ATTRIB(EGL_IMAGE_PRESERVED_KHR, EGL_TRUE); m_format = thumbnail->format(); static auto eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR"); Q_ASSERT(eglCreateImageKHR); m_image = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs); if (m_image == EGL_NO_IMAGE_KHR) { qWarning() << "invalid eglCreateImageKHR" << glGetError(); } } void KywlcomWindowThumbnailItem::imageFromMemfd(Thumbnail *thumbnail) { if (m_thumFlags & Thumbnail::BufferFlag::Reused) return; dataSize = thumbnail->size().height() * thumbnail->stride() + thumbnail->offset(); map = static_cast(mmap(nullptr, dataSize, PROT_READ, MAP_PRIVATE, thumbnail->fd(), 0)); if (map == MAP_FAILED) { qWarning() << "Failed to mmap the memory: " << strerror(errno); return; } } QSGNode *KywlcomWindowThumbnailItem::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) { QSGTexture *texture = nullptr; if (m_thumFlags & Thumbnail::BufferFlag::Dmabuf) { if (m_image == EGL_NO_IMAGE_KHR) { QImage q_image(width(), height(), QImage::Format_ARGB32_Premultiplied); q_image.fill(Qt::transparent); texture = window()->createTextureFromImage(q_image, QQuickWindow::TextureIsOpaque); } else { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) const auto context = window()->openglContext(); #else const auto context = static_cast(window()->rendererInterface()->getResource(window(), QSGRendererInterface::OpenGLContextResource)); #endif if (!context || !context->isValid()) { qWarning() << "OpenGL context is not valid."; return nullptr; } if (!m_texture) { m_texture = new QOpenGLTexture(QOpenGLTexture::Target2D); bool created = m_texture->create(); Q_ASSERT(created); } static auto s_glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress( "glEGLImageTargetTexture2DOES"); if (!s_glEGLImageTargetTexture2DOES) { qWarning() << "glEGLImageTargetTexture2DOES is not available" << window(); return nullptr; } m_texture->bind(); s_glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)m_image); m_texture->setWrapMode(QOpenGLTexture::ClampToEdge); m_texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear); m_texture->release(); m_texture->setSize(boundingRect().width(), boundingRect().height()); int textureId = m_texture->textureId(); QQuickWindow::CreateTextureOption textureOption = m_format == DRM_FORMAT_ARGB8888 ? QQuickWindow::TextureHasAlphaChannel : QQuickWindow::TextureIsOpaque; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) texture = window()->createTextureFromNativeObject(QQuickWindow::NativeObjectTexture, &textureId, 0 /*a vulkan thing?*/, m_thumbnail->size(), textureOption); #else texture = QNativeInterface::QSGOpenGLTexture::fromNative(textureId, window(), m_thumbnail->size(), textureOption); #endif } } else { if (m_qImage.isNull()) { qWarning() << "m_qImage.isNull() " << strerror(errno); QImage errorImage(200, 200, QImage::Format_ARGB32_Premultiplied); errorImage.fill(Qt::transparent); m_qImage = errorImage; } texture = window()->createTextureFromImage(m_qImage, QQuickWindow::TextureIsOpaque); } if (m_needsRecreateTexture) { delete oldNode; oldNode = nullptr; m_needsRecreateTexture = false; } QSGImageNode *textureNode = static_cast(oldNode); if (!textureNode) { textureNode = window()->createImageNode(); textureNode->setOwnsTexture(true); textureNode->setFiltering(QSGTexture::Linear); } textureNode->setTexture(texture); QSizeF scaleSize = textureNode->texture()->textureSize(); if(m_fixHeight) { scaleSize.setHeight(boundingRect().height()); } if(m_fixWidth) { scaleSize.setWidth(boundingRect().width()); } if(!m_fixHeight && !m_fixWidth) { scaleSize = boundingRect().size(); } const QSizeF size(texture->textureSize().scaled(scaleSize.toSize(), Qt::KeepAspectRatio)); if(size != m_paintedSize) { m_paintedSize = size; Q_EMIT paintedSizeChanged(); } QRect rect({ 0, 0 }, size.toSize()); rect.moveCenter(boundingRect().toRect().center()); textureNode->setRect(rect); return textureNode; } void KywlcomWindowThumbnailItem::releaseResources() { if (window()) { window()->scheduleRenderJob(new DiscardEglPixmapRunnable(m_image, m_texture), QQuickWindow::NoStage); m_image = EGL_NO_IMAGE_KHR; } } void KywlcomWindowThumbnailItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) { switch (change) { case ItemSceneChange: m_needsRecreateTexture = true; releaseResources(); break; default: break; } return QQuickItem::itemChange(change, data); } void KywlcomWindowThumbnailItem::active(bool active) { if(active && !m_uuid.isEmpty()) { if(m_currentUuid == m_uuid || !isVisible()) { return; } if(!m_context) { m_context = new Context(m_display, Context::Capability::Thumbnail, this); m_context->start(); } if(!m_thumbnail) { m_thumbnail = new Thumbnail(this); connect(m_thumbnail, &Thumbnail::bufferUpdate, this, &KywlcomWindowThumbnailItem::BufferImportDmabuf); connect(m_thumbnail, &Thumbnail::deleted, this, &KywlcomWindowThumbnailItem::thumbnailIsDeleted); } m_context->thumbnail_init(m_thumbnail, Thumbnail::Type::Toplevel, m_uuid, "", "true"); m_active = true; m_currentUuid = m_uuid; m_thumbnailIsDeleted = false; } else { if(m_active && !m_thumbnailIsDeleted) { if (map) { munmap(map, dataSize); map = nullptr; } m_thumbnail->destroy(); } } } qreal KywlcomWindowThumbnailItem::paintedWidth() const { return m_paintedSize.width(); } qreal KywlcomWindowThumbnailItem::paintedHeight() const { return m_paintedSize.height(); } bool KywlcomWindowThumbnailItem::fixHeight() const { return m_fixHeight; } void KywlcomWindowThumbnailItem::setFixHeight(bool fixHeight) { if(m_fixHeight == fixHeight) { return; } m_fixHeight = fixHeight; Q_EMIT fixHeightChanged(); } bool KywlcomWindowThumbnailItem::fixWidth() const { return m_fixWidth; } void KywlcomWindowThumbnailItem::setFixWidth(bool fixWidth) { if(m_fixWidth == fixWidth) { return; } m_fixWidth = fixWidth; Q_EMIT fixWidthChanged(); } void KywlcomWindowThumbnailItem::componentComplete() { active(isVisible()); QQuickItem::componentComplete(); } void KywlcomWindowThumbnailItem::thumbnailIsDeleted() { m_thumbnailIsDeleted = true; m_thumbnail = nullptr; m_active = false; m_currentUuid = ""; // 如果有缓存或渲染目标,请确保也清除它们 m_image = EGL_NO_IMAGE_KHR; m_format = 0; m_thumFlags = {}; // 触发重新绘制 update(); } ukui-quick/modules/window-thumbnail/window-thumbnail-config.cpp0000664000175000017500000000325515153755732024010 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #include "window-thumbnail-config.h" #include #include #include WindowThumbnailConfig::WindowThumbnailConfig(QObject *parent) : QObject(parent) { if(!qGuiApp->platformName().startsWith("wayland")) { m_xThumbnailEnable = true; return; } else { if(qgetenv("XDG_SESSION_DESKTOP") == "kylin-wlcom" || qgetenv("DESKTOP_SESSION") == "kylin-wlcom") { m_kywlcomThumbnailEnable = true; } else { m_pipewireThumbnailEnable = true; } } } bool WindowThumbnailConfig::realTimeThumbnailEnable() { return true; } bool WindowThumbnailConfig::pipewireThumbnailEnable() { return m_pipewireThumbnailEnable; } bool WindowThumbnailConfig::kywlcomWindowThumbnailEnable() const { return m_kywlcomThumbnailEnable; } bool WindowThumbnailConfig::xThumbnailEnable() const { return m_xThumbnailEnable; } ukui-quick/modules/window-thumbnail/x-window-thumbnail.cpp0000664000175000017500000007275015153755732023020 0ustar fengfeng/* SPDX-FileCopyrightText: 2013 Martin Gräßlin SPDX-FileCopyrightText: 2023 iaom SPDX-License-Identifier: LGPL-2.0-or-later */ #include "x-window-thumbnail.h" #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #include #else #include #include #endif #include #include #include #include #include #include #include #include #include #include typedef void (*glXBindTexImageEXT_func)(Display *dpy, GLXDrawable drawable, int buffer, const int *attrib_list); typedef void (*glXReleaseTexImageEXT_func)(Display *dpy, GLXDrawable drawable, int buffer); typedef EGLImageKHR (*eglCreateImageKHR_func)(EGLDisplay, EGLContext, EGLenum, EGLClientBuffer, const EGLint *); typedef EGLBoolean (*eglDestroyImageKHR_func)(EGLDisplay, EGLImageKHR); typedef GLvoid (*glEGLImageTargetTexture2DOES_func)(GLenum, GLeglImageOES); struct FbConfigInfo { GLXFBConfig fbConfig; int textureFormat; }; struct GlxGlobalData { GlxGlobalData() { xcb_connection_t *const conn = QX11Info::connection(); // Fetch the render pict formats reply = xcb_render_query_pict_formats_reply(conn, xcb_render_query_pict_formats_unchecked(conn), nullptr); // Init the visual ID -> format ID hash table for (auto screens = xcb_render_query_pict_formats_screens_iterator(reply); screens.rem; xcb_render_pictscreen_next(&screens)) { for (auto depths = xcb_render_pictscreen_depths_iterator(screens.data); depths.rem; xcb_render_pictdepth_next(&depths)) { const xcb_render_pictvisual_t *visuals = xcb_render_pictdepth_visuals(depths.data); const int len = xcb_render_pictdepth_visuals_length(depths.data); for (int i = 0; i < len; i++) { visualPictFormatHash.insert(visuals[i].visual, visuals[i].format); } } } // Init the format ID -> xcb_render_directformat_t* hash table const xcb_render_pictforminfo_t *formats = xcb_render_query_pict_formats_formats(reply); const int len = xcb_render_query_pict_formats_formats_length(reply); for (int i = 0; i < len; i++) { if (formats[i].type == XCB_RENDER_PICT_TYPE_DIRECT) { formatInfoHash.insert(formats[i].id, &formats[i].direct); } } // Init the visual ID -> depth hash table const xcb_setup_t *setup = xcb_get_setup(conn); for (auto screen = xcb_setup_roots_iterator(setup); screen.rem; xcb_screen_next(&screen)) { for (auto depth = xcb_screen_allowed_depths_iterator(screen.data); depth.rem; xcb_depth_next(&depth)) { const int len = xcb_depth_visuals_length(depth.data); const xcb_visualtype_t *visuals = xcb_depth_visuals(depth.data); for (int i = 0; i < len; i++) { visualDepthHash.insert(visuals[i].visual_id, depth.data->depth); } } } } ~GlxGlobalData() { qDeleteAll(visualFbConfigHash); std::free(reply); } xcb_render_query_pict_formats_reply_t *reply; QHash visualPictFormatHash; QHash visualDepthHash; QHash visualFbConfigHash; QHash formatInfoHash; }; Q_GLOBAL_STATIC(GlxGlobalData, g_glxGlobalData) static xcb_render_pictformat_t findPictFormat(xcb_visualid_t visual) { GlxGlobalData *d = g_glxGlobalData; return d->visualPictFormatHash.value(visual); } static const xcb_render_directformat_t *findPictFormatInfo(xcb_render_pictformat_t format) { GlxGlobalData *d = g_glxGlobalData; return d->formatInfoHash.value(format); } static int visualDepth(xcb_visualid_t visual) { GlxGlobalData *d = g_glxGlobalData; return d->visualDepthHash.value(visual); } FbConfigInfo *getConfig(xcb_visualid_t visual) { Display *dpy = QX11Info::display(); const xcb_render_pictformat_t format = findPictFormat(visual); const xcb_render_directformat_t *direct = findPictFormatInfo(format); if (!direct) { return nullptr; } const int red_bits = qPopulationCount(direct->red_mask); const int green_bits = qPopulationCount(direct->green_mask); const int blue_bits = qPopulationCount(direct->blue_mask); const int alpha_bits = qPopulationCount(direct->alpha_mask); const int depth = visualDepth(visual); const auto rgb_sizes = std::tie(red_bits, green_bits, blue_bits); const int attribs[] = {GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PIXMAP_BIT, GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, GLX_X_RENDERABLE, True, GLX_CONFIG_CAVEAT, int(GLX_DONT_CARE), // The ARGB32 visual is marked non-conformant in Catalyst GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, int(GLX_DONT_CARE), GLX_BUFFER_SIZE, red_bits + green_bits + blue_bits + alpha_bits, GLX_RED_SIZE, red_bits, GLX_GREEN_SIZE, green_bits, GLX_BLUE_SIZE, blue_bits, GLX_ALPHA_SIZE, alpha_bits, GLX_STENCIL_SIZE, 0, GLX_DEPTH_SIZE, 0, 0}; if (QByteArray((char *)glGetString(GL_RENDERER)).contains("llvmpipe")) { return nullptr; } int count = 0; GLXFBConfig *configs = glXChooseFBConfig(dpy, QX11Info::appScreen(), attribs, &count); if (count < 1) { return nullptr; } struct FBConfig { GLXFBConfig config; int depth; int stencil; int format; }; QList candidates; for (int i = 0; i < count; i++) { int red; int green; int blue; glXGetFBConfigAttrib(dpy, configs[i], GLX_RED_SIZE, &red); glXGetFBConfigAttrib(dpy, configs[i], GLX_GREEN_SIZE, &green); glXGetFBConfigAttrib(dpy, configs[i], GLX_BLUE_SIZE, &blue); if (std::tie(red, green, blue) != rgb_sizes) { continue; } xcb_visualid_t visual; glXGetFBConfigAttrib(dpy, configs[i], GLX_VISUAL_ID, (int *)&visual); if (visualDepth(visual) != depth) { continue; } int bind_rgb; int bind_rgba; glXGetFBConfigAttrib(dpy, configs[i], GLX_BIND_TO_TEXTURE_RGBA_EXT, &bind_rgba); glXGetFBConfigAttrib(dpy, configs[i], GLX_BIND_TO_TEXTURE_RGB_EXT, &bind_rgb); if (!bind_rgb && !bind_rgba) { continue; } int texture_targets; glXGetFBConfigAttrib(dpy, configs[i], GLX_BIND_TO_TEXTURE_TARGETS_EXT, &texture_targets); if ((texture_targets & GLX_TEXTURE_2D_BIT_EXT) == 0) { continue; } int depth; int stencil; glXGetFBConfigAttrib(dpy, configs[i], GLX_DEPTH_SIZE, &depth); glXGetFBConfigAttrib(dpy, configs[i], GLX_STENCIL_SIZE, &stencil); int texture_format; if (alpha_bits) { texture_format = bind_rgba ? GLX_TEXTURE_FORMAT_RGBA_EXT : GLX_TEXTURE_FORMAT_RGB_EXT; } else { texture_format = bind_rgb ? GLX_TEXTURE_FORMAT_RGB_EXT : GLX_TEXTURE_FORMAT_RGBA_EXT; } candidates.append(FBConfig{configs[i], depth, stencil, texture_format}); } XFree(configs); std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) { if (left.depth < right.depth) { return true; } if (left.stencil < right.stencil) { return true; } return false; }); FbConfigInfo *info = nullptr; if (!candidates.isEmpty()) { const FBConfig &candidate = candidates.front(); info = new FbConfigInfo; info->fbConfig = candidate.config; info->textureFormat = candidate.format; } return info; } class DiscardTextureProviderRunnable : public QRunnable { public: explicit DiscardTextureProviderRunnable(WindowTextureProvider *provider) : m_provider(provider) { } void run() override { delete m_provider; } private: WindowTextureProvider *m_provider; }; class DiscardGlxPixmapRunnable : public QRunnable { public: DiscardGlxPixmapRunnable(uint, QFunctionPointer, xcb_pixmap_t); void run() override; private: uint m_texture; QFunctionPointer m_releaseTexImage; xcb_pixmap_t m_glxPixmap; }; DiscardGlxPixmapRunnable::DiscardGlxPixmapRunnable(uint texture, QFunctionPointer deleteFunction, xcb_pixmap_t pixmap) : QRunnable() , m_texture(texture) , m_releaseTexImage(deleteFunction) , m_glxPixmap(pixmap) { } void DiscardGlxPixmapRunnable::run() { if (m_glxPixmap != XCB_PIXMAP_NONE) { Display *d = QX11Info::display(); ((glXReleaseTexImageEXT_func)(m_releaseTexImage))(d, m_glxPixmap, GLX_FRONT_LEFT_EXT); glXDestroyPixmap(d, m_glxPixmap); glDeleteTextures(1, &m_texture); } } class XDiscardEglPixmapRunnable : public QRunnable { public: XDiscardEglPixmapRunnable(uint, QFunctionPointer, EGLImageKHR); void run() override; private: uint m_texture; QFunctionPointer m_eglDestroyImageKHR; EGLImageKHR m_image; }; XDiscardEglPixmapRunnable::XDiscardEglPixmapRunnable(uint texture, QFunctionPointer deleteFunction, EGLImageKHR image) : QRunnable() , m_texture(texture) , m_eglDestroyImageKHR(deleteFunction) , m_image(image) { } void XDiscardEglPixmapRunnable::run() { if (m_image != EGL_NO_IMAGE_KHR) { ((eglDestroyImageKHR_func)(m_eglDestroyImageKHR))(eglGetCurrentDisplay(), m_image); glDeleteTextures(1, &m_texture); } } XWindowThumbnail::XWindowThumbnail(QQuickItem *parent) : QQuickItem(parent) , QAbstractNativeEventFilter() , m_glxPixmap(XCB_PIXMAP_NONE) , m_damage(XCB_NONE) , m_pixmap(XCB_PIXMAP_NONE) , m_depth(0) , m_image(EGL_NO_IMAGE_KHR) { setFlag(ItemHasContents); if (QGuiApplication *gui = dynamic_cast(QCoreApplication::instance())) { m_xcb = (gui->platformName() == QLatin1String("xcb")); if (m_xcb) { gui->installNativeEventFilter(this); xcb_connection_t *c = QX11Info::connection(); xcb_prefetch_extension_data(c, &xcb_composite_id); const auto *compositeReply = xcb_get_extension_data(c, &xcb_composite_id); m_composite = (compositeReply && compositeReply->present); xcb_prefetch_extension_data(c, &xcb_damage_id); const auto *reply = xcb_get_extension_data(c, &xcb_damage_id); m_damageEventBase = reply->first_event; if (reply->present) { xcb_damage_query_version_unchecked(c, XCB_DAMAGE_MAJOR_VERSION, XCB_DAMAGE_MINOR_VERSION); } } } } XWindowThumbnail::~XWindowThumbnail() { } #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) bool XWindowThumbnail::nativeEventFilter(const QByteArray &eventType, void *message, long int *result) #else bool XWindowThumbnail::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) #endif { Q_UNUSED(result) if (!m_xcb || !m_composite || eventType != QByteArrayLiteral("xcb_generic_event_t")) { return false; } xcb_generic_event_t *event = static_cast(message); const uint8_t responseType = event->response_type & ~0x80; if (responseType == m_damageEventBase + XCB_DAMAGE_NOTIFY) { if (reinterpret_cast(event)->drawable == m_winId) { m_damaged = true; update(); } } else if (responseType == XCB_CONFIGURE_NOTIFY) { if (reinterpret_cast(event)->window == m_winId) { releaseResources(); m_damaged = true; update(); } } else if (responseType == XCB_MAP_NOTIFY) { if (reinterpret_cast(event)->window == m_winId) { releaseResources(); m_damaged = true; update(); } } return false; } uint32_t XWindowThumbnail::winId() const { return m_winId; } void XWindowThumbnail::setWinId(uint32_t winId) { if (m_winId == winId) { return; } #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) if (!KWindowSystem::hasWId(winId)) { #else if (!KX11Extras::hasWId(winId)) { #endif return; } if (window() && winId == window()->winId()) { // don't redirect to yourself return; } stopRedirecting(); m_winId = winId; if (isEnabled() && isVisible()) { startRedirecting(); } Q_EMIT winIdChanged(); } bool XWindowThumbnail::fixHeight() const { return m_fixHeight; } void XWindowThumbnail::setFixHeight(bool fixHeight) { if(m_fixHeight == fixHeight) { return; } m_fixHeight = fixHeight; Q_EMIT fixHeightChanged(); } bool XWindowThumbnail::fixWidth() const { return m_fixWidth; } void XWindowThumbnail::setFixWidth(bool fixWidth) { if(m_fixWidth == fixWidth) { return; } m_fixWidth = fixWidth; Q_EMIT fixWidthChanged(); } void XWindowThumbnail::stopRedirecting() { if (!m_xcb || !m_composite) { return; } xcb_connection_t *c = QX11Info::connection(); if (m_pixmap != XCB_PIXMAP_NONE) { xcb_free_pixmap(c, m_pixmap); m_pixmap = XCB_PIXMAP_NONE; } if (m_winId == XCB_WINDOW_NONE) { return; } if (m_redirecting) { xcb_composite_unredirect_window(c, m_winId, XCB_COMPOSITE_REDIRECT_AUTOMATIC); } m_redirecting = false; if (m_damage == XCB_NONE) { return; } xcb_damage_destroy(c, m_damage); m_damage = XCB_NONE; } bool XWindowThumbnail::startRedirecting() { if (!m_xcb || !m_composite || !window() || !window()->isVisible() || window()->winId() == m_winId || !isEnabled() || !isVisible()) { return false; } if (m_winId == XCB_WINDOW_NONE) { return false; } xcb_connection_t *c = QX11Info::connection(); const auto attribsCookie = xcb_get_window_attributes_unchecked(c, m_winId); // redirect the window xcb_composite_redirect_window(c, m_winId, XCB_COMPOSITE_REDIRECT_AUTOMATIC); m_redirecting = true; // generate the damage handle m_damage = xcb_generate_id(c); xcb_damage_create(c, m_damage, m_winId, XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY); QScopedPointer attr(xcb_get_window_attributes_reply(c, attribsCookie, nullptr)); uint32_t events = XCB_EVENT_MASK_STRUCTURE_NOTIFY; if (!attr.isNull()) { events = events | attr->your_event_mask; } // the event mask will not be removed again. We cannot track whether another component also needs STRUCTURE_NOTIFY (e.g. KWindowSystem). // if we would remove the event mask again, other areas will break. xcb_change_window_attributes(c, m_winId, XCB_CW_EVENT_MASK, &events); // force to update the texture m_damaged = true; return true; } qreal XWindowThumbnail::paintedWidth() const { return m_paintedSize.width(); } qreal XWindowThumbnail::paintedHeight() const { return m_paintedSize.height(); } bool XWindowThumbnail::thumbnailAvailable() const { return m_thumbnailAvailable; } QSGNode *XWindowThumbnail::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData) { Q_UNUSED(updatePaintNodeData) if (!m_textureProvider) { m_textureProvider = new WindowTextureProvider(); } if (!m_xcb || m_winId == 0 || (window() && window()->winId() == m_winId)) { iconToTexture(m_textureProvider); } else { windowToTexture(m_textureProvider); } QSGImageNode *node = static_cast(oldNode); if (!node) { node = window()->createImageNode(); qsgnode_set_description(node, QStringLiteral("windowthumbnail")); node->setFiltering(QSGTexture::Linear); } node->setTexture(m_textureProvider->texture()); QSizeF scaleSize = node->texture()->textureSize(); if(m_fixHeight) { scaleSize.setHeight(boundingRect().height()); } if(m_fixWidth) { scaleSize.setWidth(boundingRect().width()); } if(!m_fixHeight && !m_fixWidth) { scaleSize = boundingRect().size(); } const QSizeF size(node->texture()->textureSize().scaled(scaleSize.toSize(), Qt::KeepAspectRatio)); if (size != m_paintedSize) { m_paintedSize = size; Q_EMIT paintedSizeChanged(); } const qreal x = boundingRect().x() + (boundingRect().width() - size.width()) / 2; const qreal y = boundingRect().y() + (boundingRect().height() - size.height()) / 2; node->setRect(QRectF(QPointF(x, y), size)); return node; } void XWindowThumbnail::iconToTexture(WindowTextureProvider *textureProvider) { QIcon icon; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) if (KWindowSystem::hasWId(m_winId)) { icon = KWindowSystem::icon(m_winId, boundingRect().width(), boundingRect().height()); #else if (!KX11Extras::hasWId(m_winId)) { icon = KX11Extras::icon(m_winId, boundingRect().width(), boundingRect().height()); #endif } else { // fallback to plasma icon icon = QIcon::fromTheme(QStringLiteral("")); } //缩放模糊? QImage image = icon.pixmap(boundingRect().size().toSize()).toImage(); textureProvider->setTexture(window()->createTextureFromImage(image, QQuickWindow::TextureCanUseAtlas)); } void XWindowThumbnail::windowToTexture(WindowTextureProvider *textureProvider) { if (!m_damaged && textureProvider->texture()) { return; } if (m_pixmap == XCB_PIXMAP_NONE) { m_pixmap = pixmapForWindow(); } if (m_pixmap == XCB_PIXMAP_NONE) { // create above failed iconToTexture(textureProvider); setThumbnailAvailable(false); return; } bool fallbackToIcon = true; fallbackToIcon = !windowToTextureGLX(textureProvider); if (fallbackToIcon) { // if glx succeeded fallbackToIcon is false, thus we shouldn't try egl fallbackToIcon = !xcbWindowToTextureEGL(textureProvider); } if (fallbackToIcon) { // just for safety to not crash iconToTexture(textureProvider); } setThumbnailAvailable(!fallbackToIcon); } bool XWindowThumbnail::windowToTextureGLX(WindowTextureProvider *textureProvider) { const auto openglContext = static_cast(window()->rendererInterface()->getResource(window(), QSGRendererInterface::OpenGLContextResource)); if (openglContext) { if (!m_openGLFunctionsResolved) { resolveGLXFunctions(); } if (!m_bindTexImage || !m_releaseTexImage) { return false; } if (m_glxPixmap == XCB_PIXMAP_NONE) { xcb_connection_t *c = QX11Info::connection(); auto attrCookie = xcb_get_window_attributes_unchecked(c, m_winId); auto geometryCookie = xcb_get_geometry_unchecked(c, m_pixmap); QScopedPointer attr(xcb_get_window_attributes_reply(c, attrCookie, nullptr)); QScopedPointer geo(xcb_get_geometry_reply(c, geometryCookie, nullptr)); if (attr.isNull()) { return false; } if (geo.isNull()) { return false; } m_depth = geo->depth; m_visualid = attr->visual; if (!loadGLXTexture()) { return false; } #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) textureProvider->setTexture(window()->createTextureFromId(m_texture, QSize(geo->width, geo->height), QQuickWindow::TextureCanUseAtlas)); #else textureProvider->setTexture(QNativeInterface::QSGOpenGLTexture::fromNative(m_texture, window(), QSize(geo->width, geo->height), QQuickWindow::TextureCanUseAtlas)); #endif } openglContext->functions()->glBindTexture(GL_TEXTURE_2D, m_texture); bindGLXTexture(); return true; } return false; } void XWindowThumbnail::resolveGLXFunctions() { auto *context = static_cast(window()->rendererInterface()->getResource(window(), QSGRendererInterface::OpenGLContextResource)); QList extensions = QByteArray(glXQueryExtensionsString(QX11Info::display(), QX11Info::appScreen())).split(' '); if (extensions.contains(QByteArrayLiteral("GLX_EXT_texture_from_pixmap"))) { m_bindTexImage = context->getProcAddress(QByteArrayLiteral("glXBindTexImageEXT")); m_releaseTexImage = context->getProcAddress(QByteArrayLiteral("glXReleaseTexImageEXT")); } else { qWarning() << "couldn't resolve GLX_EXT_texture_from_pixmap functions"; } m_openGLFunctionsResolved = true; } xcb_pixmap_t XWindowThumbnail::pixmapForWindow() { if (!m_composite) { return XCB_PIXMAP_NONE; } xcb_connection_t *c = QX11Info::connection(); xcb_pixmap_t pix = xcb_generate_id(c); auto cookie = xcb_composite_name_window_pixmap_checked(c, m_winId, pix); QScopedPointer error(xcb_request_check(c, cookie)); if (error) { return XCB_PIXMAP_NONE; } return pix; } void XWindowThumbnail::setThumbnailAvailable(bool thumbnailAvailable) { if (m_thumbnailAvailable != thumbnailAvailable) { m_thumbnailAvailable = thumbnailAvailable; Q_EMIT thumbnailAvailableChanged(); } } bool XWindowThumbnail::loadGLXTexture() { GLXContext glxContext = glXGetCurrentContext(); if (!glxContext) { return false; } FbConfigInfo *info = nullptr; auto &hashTable = g_glxGlobalData->visualFbConfigHash; auto it = hashTable.constFind(m_visualid); if (it != hashTable.constEnd()) { info = *it; } else { info = getConfig(m_visualid); hashTable.insert(m_visualid, info); } if (!info) { return false; } glGenTextures(1, &m_texture); /* clang-format off */ const int attrs[] = { GLX_TEXTURE_FORMAT_EXT, info->textureFormat, GLX_MIPMAP_TEXTURE_EXT, false, GLX_TEXTURE_TARGET_EXT, GLX_TEXTURE_2D_EXT, XCB_NONE}; /* clang-format on */ m_glxPixmap = glXCreatePixmap(QX11Info::display(), info->fbConfig, m_pixmap, attrs); return true; } void XWindowThumbnail::bindGLXTexture() { Display *d = QX11Info::display(); ((glXReleaseTexImageEXT_func)(m_releaseTexImage))(d, m_glxPixmap, GLX_FRONT_LEFT_EXT); ((glXBindTexImageEXT_func)(m_bindTexImage))(d, m_glxPixmap, GLX_FRONT_LEFT_EXT, nullptr); resetDamaged(); } void XWindowThumbnail::resetDamaged() { m_damaged = false; if (m_damage == XCB_NONE) { return; } xcb_damage_subtract(QX11Info::connection(), m_damage, XCB_NONE, XCB_NONE); } bool XWindowThumbnail::xcbWindowToTextureEGL(WindowTextureProvider *textureProvider) { EGLContext context = eglGetCurrentContext(); if (context != EGL_NO_CONTEXT) { if (!m_eglFunctionsResolved) { resolveEGLFunctions(); } if (QByteArray((char *)glGetString(GL_RENDERER)).contains("llvmpipe")) { return false; } if (!m_eglCreateImageKHR || !m_eglDestroyImageKHR || !m_glEGLImageTargetTexture2DOES) { return false; } if (m_image == EGL_NO_IMAGE_KHR) { xcb_connection_t *c = QX11Info::connection(); auto geometryCookie = xcb_get_geometry_unchecked(c, m_pixmap); const EGLint attribs[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE}; m_image = ((eglCreateImageKHR_func)( m_eglCreateImageKHR))(eglGetCurrentDisplay(), EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR, (EGLClientBuffer)(uintptr_t)m_pixmap, attribs); if (m_image == EGL_NO_IMAGE_KHR) { qDebug() << "failed to create egl Image"; return false; } glGenTextures(1, &m_texture); QScopedPointer geo(xcb_get_geometry_reply(c, geometryCookie, nullptr)); QSize size; if (!geo.isNull()) { size.setWidth(geo->width); size.setHeight(geo->height); } #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) textureProvider->setTexture(window()->createTextureFromId(m_texture, size, QQuickWindow::TextureCanUseAtlas)); #else textureProvider->setTexture(QNativeInterface::QSGOpenGLTexture::fromNative(m_texture, window(), size, QQuickWindow::TextureCanUseAtlas)); #endif } auto *openglContext = static_cast(window()->rendererInterface()->getResource(window(), QSGRendererInterface::OpenGLContextResource)); openglContext->functions()->glBindTexture(GL_TEXTURE_2D, m_texture); bindEGLTexture(); return true; } return false; } void XWindowThumbnail::resolveEGLFunctions() { EGLDisplay display = eglGetCurrentDisplay(); if (display == EGL_NO_DISPLAY) { return; } auto *context = static_cast(window()->rendererInterface()->getResource(window(), QSGRendererInterface::OpenGLContextResource)); QList extensions = QByteArray(eglQueryString(display, EGL_EXTENSIONS)).split(' '); if (extensions.contains(QByteArrayLiteral("EGL_KHR_image")) // || (extensions.contains(QByteArrayLiteral("EGL_KHR_image_base")) // && extensions.contains(QByteArrayLiteral("EGL_KHR_image_pixmap")))) { if (context->hasExtension(QByteArrayLiteral("GL_OES_EGL_image"))) { qDebug() << "Have EGL texture from pixmap"; m_eglCreateImageKHR = context->getProcAddress(QByteArrayLiteral("eglCreateImageKHR")); m_eglDestroyImageKHR = context->getProcAddress(QByteArrayLiteral("eglDestroyImageKHR")); m_glEGLImageTargetTexture2DOES = context->getProcAddress(QByteArrayLiteral("glEGLImageTargetTexture2DOES")); } } m_eglFunctionsResolved = true; } void XWindowThumbnail::bindEGLTexture() { ((glEGLImageTargetTexture2DOES_func)(m_glEGLImageTargetTexture2DOES))(GL_TEXTURE_2D, (GLeglImageOES)m_image); resetDamaged(); } void XWindowThumbnail::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) { switch (change) { case ItemSceneChange: if (m_scene) { disconnect(m_scene.data(), &QWindow::visibleChanged, this, &XWindowThumbnail::sceneVisibilityChanged); } m_scene = data.window; if (m_scene) { connect(m_scene.data(), &QWindow::visibleChanged, this, &XWindowThumbnail::sceneVisibilityChanged); // restart the redirection, it might not have been active yet stopRedirecting(); if (startRedirecting()) { update(); } } break; case ItemEnabledHasChanged: Q_FALLTHROUGH(); case ItemVisibleHasChanged: if (data.boolValue) { if (startRedirecting()) { update(); } } else { stopRedirecting(); releaseResources(); } break; default: break; } } void XWindowThumbnail::sceneVisibilityChanged(bool visible) { if (visible) { if (startRedirecting()) { update(); } } else { stopRedirecting(); releaseResources(); } } void XWindowThumbnail::releaseResources() { QQuickWindow::RenderStage m_renderStage = QQuickWindow::NoStage; if (m_textureProvider) { window()->scheduleRenderJob(new DiscardTextureProviderRunnable(m_textureProvider), QQuickWindow::AfterSynchronizingStage); m_textureProvider = nullptr; } // only one (or none) should be set, but never both Q_ASSERT(m_glxPixmap == XCB_PIXMAP_NONE || m_image == EGL_NO_IMAGE_KHR); // data is deleted in the render thread (with relevant GLX calls) // note runnable may be called *after* this is deleted // but the pointer is held by the XWindowThumbnail which is in the main thread if (m_glxPixmap != XCB_PIXMAP_NONE) { window()->scheduleRenderJob(new DiscardGlxPixmapRunnable(m_texture, m_releaseTexImage, m_glxPixmap), m_renderStage); m_glxPixmap = XCB_PIXMAP_NONE; m_texture = 0; } if (m_image != EGL_NO_IMAGE_KHR) { window()->scheduleRenderJob(new XDiscardEglPixmapRunnable(m_texture, m_eglDestroyImageKHR, m_image), m_renderStage); m_image = EGL_NO_IMAGE_KHR; m_texture = 0; } } QSGTexture *WindowTextureProvider::texture() const { return m_texture.get(); } void WindowTextureProvider::setTexture(QSGTexture *texture) { m_texture.reset(texture); Q_EMIT textureChanged(); } ukui-quick/modules/window-thumbnail/screen-casting-request.cpp0000664000175000017500000000711515153755732023647 0ustar fengfeng/* SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez SPDX-FileCopyrightText: 2023 iaom SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "screen-casting-request.h" #include #include #include #include #include #include class ScreencastingSingleton : public QObject { Q_OBJECT public: ScreencastingSingleton(QObject *parent) : QObject(parent) { KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(this); if (!connection) { return; } KWayland::Client::Registry *registry = new KWayland::Client::Registry(this); connect(registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this, registry](const QByteArray &interfaceName, quint32 name, quint32 version) { if (interfaceName != "zkde_screencast_unstable_v1") return; m_screencasting = new Screencasting(registry, name, version, this); Q_EMIT created(m_screencasting); }); registry->create(connection); registry->setup(); } static ScreencastingSingleton *self() { static QPointer s_self; if (!s_self && QCoreApplication::instance()) s_self = new ScreencastingSingleton(QCoreApplication::instance()); return s_self; } void requestInterface(ScreenCastingRequest *item) { if (!m_screencasting) { connect(this, &ScreencastingSingleton::created, item, &ScreenCastingRequest::create, Qt::UniqueConnection); } else { item->create(m_screencasting); } } Q_SIGNALS: void created(Screencasting *screencasting); private: Screencasting *m_screencasting = nullptr; }; ScreenCastingRequest::ScreenCastingRequest(QObject *parent) :QObject(parent) { } ScreenCastingRequest::~ScreenCastingRequest() = default; quint32 ScreenCastingRequest::nodeId() const { return m_nodeId; } void ScreenCastingRequest::setNodeid(uint nodeId) { if (nodeId == m_nodeId) { return; } m_nodeId = nodeId; Q_EMIT nodeIdChanged(nodeId); } QString ScreenCastingRequest::uuid() const { return m_uuid; } void ScreenCastingRequest::setUuid(const QString &uuid) { if (m_uuid == uuid) { return; } Q_EMIT closeRunningStreams(); setNodeid(0); m_uuid = uuid; if (!m_uuid.isEmpty()) { ScreencastingSingleton::self()->requestInterface(this); } Q_EMIT uuidChanged(uuid); } void ScreenCastingRequest::create(Screencasting *screencasting) { auto stream = screencasting->createWindowStream(m_uuid, Screencasting::CursorMode::Hidden); stream->setObjectName(m_uuid); connect(stream, &ScreencastingStream::created, this, [stream, this](int nodeId) { if (stream->objectName() == m_uuid) { setNodeid(nodeId); } }); connect(stream, &ScreencastingStream::failed, this, [](const QString &error) { qWarning() << "error creating screencast" << error; }); connect(stream, &ScreencastingStream::closed, this, [this, stream] { if (stream->nodeId() == m_nodeId) { setNodeid(0); } }); connect(this, &ScreenCastingRequest::closeRunningStreams, stream, &QObject::deleteLater); } #include "screen-casting-request.moc" ukui-quick/modules/window-thumbnail/pipewire-core.cpp0000664000175000017500000000522615153755732022027 0ustar fengfeng/* SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez SPDX-FileContributor: Jan Grulich SPDX-FileContributor: iaom SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "pipewire-core.h" #include #include #include PipeWireCore::PipeWireCore() { pw_init(nullptr, nullptr); pwCoreEvents.version = PW_VERSION_CORE_EVENTS; pwCoreEvents.error = &PipeWireCore::onCoreError; } void PipeWireCore::onCoreError(void *data, uint32_t id, int seq, int res, const char *message) { Q_UNUSED(seq) qWarning() << "PipeWire remote error: " << message; if (id == PW_ID_CORE && res == -EPIPE) { PipeWireCore *pw = static_cast(data); Q_EMIT pw->pipewireFailed(QString::fromUtf8(message)); } } PipeWireCore::~PipeWireCore() { if (pwMainLoop) { pw_loop_leave(pwMainLoop); } if (pwCore) { pw_core_disconnect(pwCore); } if (pwContext) { pw_context_destroy(pwContext); } if (pwMainLoop) { pw_loop_destroy(pwMainLoop); } } bool PipeWireCore::init() { pwMainLoop = pw_loop_new(nullptr); pw_loop_enter(pwMainLoop); QSocketNotifier *notifier = new QSocketNotifier(pw_loop_get_fd(pwMainLoop), QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, [this] { int result = pw_loop_iterate(pwMainLoop, 0); if (result < 0) qWarning() << "pipewire_loop_iterate failed: " << spa_strerror(result); }); pwContext = pw_context_new(pwMainLoop, nullptr, 0); if (!pwContext) { qWarning() << "Failed to create PipeWire context"; m_error = tr("Failed to create PipeWire context"); return false; } pwCore = pw_context_connect(pwContext, nullptr, 0); if (!pwCore) { qWarning() << "Failed to connect PipeWire context"; m_error = tr("Failed to connect PipeWire context"); return false; } if (pw_loop_iterate(pwMainLoop, 0) < 0) { qWarning() << "Failed to start main PipeWire loop"; m_error = tr("Failed to start main PipeWire loop"); return false; } pw_core_add_listener(pwCore, &coreListener, &pwCoreEvents, this); return true; } QSharedPointer PipeWireCore::self() { static QWeakPointer global; QSharedPointer ret; if (global) { ret = global.toStrongRef(); } else { ret.reset(new PipeWireCore); if (ret->init()) { global = ret; } } return ret; } ukui-quick/modules/window-thumbnail/window-thumbnail-plugin.cpp0000664000175000017500000000511315153756415024033 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #include "window-thumbnail-plugin.h" #include #include // #include "pipewire-source-item.h" // #include "screen-casting.h" // #include "screen-casting-request.h" #include "x-window-thumbnail.h" #include "window-thumbnail-config.h" #include "window-thumbnail-mpris-model.h" #include "kywlcom-window-thumbnail-item.h" void WindowThumbnailPlugin::registerTypes(const char *uri) { Q_ASSERT(QLatin1String(uri) == QLatin1String(PLUGIN_IMPORT_URI)); // qmlRegisterType(uri, 1, 0, "PipeWireSourceItem"); // qmlRegisterType(uri, 1, 0, "ScreenCastingRequest"); qmlRegisterType(uri, 1, 0, "XWindowThumbnail"); qmlRegisterType(uri, 1, 0, "WindowThumbnailMprisModel"); qmlRegisterType(uri, 1, 0, "KywlcomWindowThumbnailItem"); // qmlRegisterUncreatableType(uri, 1, 0, "Screencasting", "Only enumeration variables are required"); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) qmlRegisterUncreatableType(uri, 1, 0, "MprisProperties", "Only enumeration variables are required"); #else qmlRegisterUncreatableMetaObject(MprisProperties::staticMetaObject, uri, 1, 0, "MprisProperties", "Types is a read-only interface used to access enumeration properties."); #endif qRegisterMetaType("MprisProperties::Operations"); qRegisterMetaType("MprisProperties::Properties"); } void WindowThumbnailPlugin::initializeEngine(QQmlEngine *engine, const char *uri) { Q_ASSERT(QLatin1String(uri) == QLatin1String(PLUGIN_IMPORT_URI)); engine->rootContext()->setContextProperty("windowThumbnailConfig", new WindowThumbnailConfig); } ukui-quick/modules/window-thumbnail/CMakeLists.txt0000664000175000017500000001131315153756415021302 0ustar fengfengcmake_minimum_required(VERSION 3.16) project(ukui-window-thumbnail LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) #find QT modules find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Gui Qml Quick DBus REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Qml Gui Quick DBus REQUIRED) #find kde modules #find_package(Qt5WaylandClient) if (QT_VERSION_MAJOR EQUAL "5") set(KF_VERSION_MAJOR "5") find_package(Qt${QT_VERSION_MAJOR} COMPONENTS X11Extras REQUIRED) find_package(KF5WindowSystem REQUIRED) find_package(KF5Wayland) find_package(PlasmaWaylandProtocols 1.6) elseif (QT_VERSION_MAJOR EQUAL "6") set(KF_VERSION_MAJOR "6") find_package(Qt6GuiPrivate REQUIRED) find_package(KF6WindowSystem REQUIRED) find_package(KWayland) find_package(PlasmaWaylandProtocols 1.16) endif() find_package(ECM REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) include(FindQtWaylandScanner) #find other modules find_package(PkgConfig REQUIRED) set(WINDOW_THUMBNAIL_PLUGIN_PC_PKGS libpipewire-0.3 x11 xcb xcb-damage xcb-composite egl xcb-render wayland-client kywc glx) foreach(external_lib IN ITEMS ${WINDOW_THUMBNAIL_PLUGIN_PC_PKGS}) pkg_check_modules(${external_lib} REQUIRED IMPORTED_TARGET ${external_lib}) if(${${external_lib}_FOUND}) include_directories(${${external_lib}_INCLUDE_DIRS}) link_directories(${${external_lib}_LIBRARY_DIRS}) list(APPEND WINDOW_THUMBNAIL_PLUGIN_EXTERNAL_LIBS PkgConfig::${external_lib}) endif() endforeach() include_directories(mpris) set(QRC_FILES res/res.qrc) set (PLUGIN_SRCS # screen-casting-request.cpp # screen-casting.cpp # pipewire-core.cpp # pipewire-source-stream.cpp # pipewire-source-item.cpp window-thumbnail-plugin.cpp x-window-thumbnail.cpp window-thumbnail-config.cpp window-thumbnail-config.h mpris/mpris-player-collecter.cpp mpris/mpris-player-collecter.h mpris/player-item.cpp mpris/player-item.h mpris/player-items-model.cpp mpris/player-items-model.h mpris/window-thumbnail-mpris-model.cpp mpris/window-thumbnail-mpris-model.h mpris/properties.h kywlcom-window-thumbnail-item.cpp kywlcom-window-thumbnail-item.h kywlcom-thumbnail.cpp kywlcom-thumbnail.h kywlcom-contex.cpp kywlcom-contex.h discard-egl-pixmap-runnable.h ) #generate wayland protocol files #ecm_add_qtwayland_client_protocol(PLUGIN_SRCS # PROTOCOL ${PLASMA_WAYLAND_PROTOCOLS_DIR}/screencast.xml # BASENAME zkde-screencast-unstable-v1 #) set_source_files_properties( mpris/org.freedesktop.DBus.Properties.xml mpris/org.mpris.MediaPlayer2.Player.xml mpris/org.mpris.MediaPlayer2.xml PROPERTIES NO_NAMESPACE ON) if(COMMAND qt_add_dbus_interface) qt_add_dbus_interface(PLUGIN_SRCS mpris/org.freedesktop.DBus.Properties.xml dbusproperties) qt_add_dbus_interface(PLUGIN_SRCS mpris/org.mpris.MediaPlayer2.xml mprisplayer2) qt_add_dbus_interface(PLUGIN_SRCS mpris/org.mpris.MediaPlayer2.Player.xml mprisplayer2player) else() qt5_add_dbus_interface(PLUGIN_SRCS mpris/org.freedesktop.DBus.Properties.xml dbusproperties) qt5_add_dbus_interface(PLUGIN_SRCS mpris/org.mpris.MediaPlayer2.xml mprisplayer2) qt5_add_dbus_interface(PLUGIN_SRCS mpris/org.mpris.MediaPlayer2.Player.xml mprisplayer2player) endif() add_library(${PROJECT_NAME} SHARED ${PLUGIN_SRCS} ${QRC_FILES}) target_include_directories(${PROJECT_NAME} PRIVATE ../../platform) set(PLUGIN_IMPORT_URI "org.ukui.windowThumbnail") target_compile_definitions(${PROJECT_NAME} PRIVATE PLUGIN_IMPORT_URI="${PLUGIN_IMPORT_URI}") target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Qml Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Quick Qt::GuiPrivate Qt${QT_VERSION_MAJOR}::DBus ${WINDOW_THUMBNAIL_PLUGIN_EXTERNAL_LIBS} KF${KF_VERSION_MAJOR}::WindowSystem ukui-quick::platform ) if (QT_VERSION_MAJOR EQUAL "5") target_link_libraries(${PROJECT_NAME} PRIVATE Qt::X11Extras Qt5::Gui_EGL KF5::WaylandClient) else() target_link_libraries(${PROJECT_NAME} PRIVATE Qt::GuiPrivate Plasma::KWaylandClient) endif() set(PLUGIN_INSTALL_PATH "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/qt${QT_VERSION_MAJOR}/qml/org/ukui/windowThumbnail") install(DIRECTORY "qml" DESTINATION "${PLUGIN_INSTALL_PATH}") install(FILES qmldir DESTINATION ${PLUGIN_INSTALL_PATH}) install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${PLUGIN_INSTALL_PATH}) ukui-quick/modules/window-thumbnail/window-thumbnail-plugin.h0000664000175000017500000000224615153755732023505 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef WINDOWTHUMBNAILPLUGIN_H #define WINDOWTHUMBNAILPLUGIN_H #include class WindowThumbnailPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) public: void registerTypes(const char *uri) override; void initializeEngine(QQmlEngine *engine, const char *uri) override; }; #endif // WINDOWTHUMBNAILPLUGIN_H ukui-quick/modules/window-thumbnail/res/0000775000175000017500000000000015153755732017335 5ustar fengfengukui-quick/modules/window-thumbnail/res/res.qrc0000664000175000017500000000014615153755732020636 0ustar fengfeng image/default-image.svg ukui-quick/modules/window-thumbnail/res/image/0000775000175000017500000000000015153755732020417 5ustar fengfengukui-quick/modules/window-thumbnail/res/image/default-image.svg0000664000175000017500000002113515153755732023646 0ustar fengfeng ukui-quick/modules/window-thumbnail/kywlcom-window-thumbnail-item.h0000664000175000017500000000631415153756415024627 0ustar fengfeng/* * * Copyright (C) 2024, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef UKUI_QUICK_KYWLCOM_WINDOW_THUMBNAIL_ITEM_H #define UKUI_QUICK_KYWLCOM_WINDOW_THUMBNAIL_ITEM_H #include #include #include #include #include #include #include #include #include "kywlcom-thumbnail.h" #include "kywlcom-contex.h" typedef struct _kywc_thumbnail kywc_thumbnail; class KywlcomWindowThumbnailItem : public QQuickItem { Q_OBJECT Q_PROPERTY(QString uuid READ uuid WRITE setUuid NOTIFY uuidChanged) Q_PROPERTY(qreal paintedWidth READ paintedWidth NOTIFY paintedSizeChanged) Q_PROPERTY(qreal paintedHeight READ paintedHeight NOTIFY paintedSizeChanged) Q_PROPERTY(bool fixHeight READ fixHeight WRITE setFixHeight NOTIFY fixHeightChanged) Q_PROPERTY(bool fixWidth READ fixWidth WRITE setFixWidth NOTIFY fixWidthChanged) public: explicit KywlcomWindowThumbnailItem(QQuickItem *parent = nullptr); ~KywlcomWindowThumbnailItem(); QString uuid(); void setUuid(QString &uuid); qreal paintedWidth() const; qreal paintedHeight() const; bool fixHeight() const; void setFixHeight(bool fixHeight); bool fixWidth() const; void setFixWidth(bool fixWidth); void componentComplete() override; void releaseResources() override; protected: QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) override; Q_SIGNALS: void uuidChanged(); void paintedSizeChanged(); void fixHeightChanged(); void fixWidthChanged(); private Q_SLOTS: void BufferImportDmabuf(); void thumbnailIsDeleted(); private: void itemChange(ItemChange change, const ItemChangeData &data) override; void createEglImage(Thumbnail *thumbnail); void imageFromMemfd(Thumbnail *thumbnail); void active(bool active); QString m_uuid; QString m_currentUuid; Context *m_context = nullptr; Thumbnail *m_thumbnail = nullptr; wl_display *m_display = nullptr; EGLImage m_image = EGL_NO_IMAGE_KHR; QImage m_qImage; uint32_t m_format; Thumbnail::BufferFlags m_thumFlags = Thumbnail::BufferFlag::Dmabuf; QOpenGLTexture *m_texture = nullptr; bool m_needsRecreateTexture = false; QSizeF m_paintedSize; bool m_fixHeight = false; bool m_fixWidth = false; bool m_active = false; bool m_thumbnailIsDeleted = false; uint8_t *map = nullptr; size_t dataSize = 0; }; #endif //UKUI_QUICK_KYWLCOM_WINDOW_THUMBNAIL_ITEM_H ukui-quick/modules/window-thumbnail/kywlcom-thumbnail.h0000664000175000017500000000332615153756415022366 0ustar fengfeng/* * * Copyright (C) 2024, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef UKUI_QUICK_KYWLCOM_THUMBNAIL_H #define UKUI_QUICK_KYWLCOM_THUMBNAIL_H #include #include #include class Thumbnail : public QObject { Q_OBJECT public: enum Type{ Output, Toplevel, Workspace, }; enum BufferFlag { Dmabuf = 1 << 0, Reused = 1 << 1, }; Q_DECLARE_FLAGS(BufferFlags, BufferFlag); explicit Thumbnail(QObject *parent = nullptr); ~Thumbnail(); void setup(kywc_context *ctx, Thumbnail::Type type, QString uuid, QString output_uuid, QString decoration); void destroy(); int32_t fd() const; uint32_t format() const; QSize size() const; uint32_t offset() const; uint32_t stride() const; uint64_t modifier() const; Thumbnail::BufferFlags flags() const; Q_SIGNALS: void bufferUpdate(); void deleted(); private: class Private; Private *d; }; #endif //UKUI_QUICK_KYWLCOM_THUMBNAIL_H ukui-quick/modules/window-thumbnail/kywlcom-contex.h0000664000175000017500000000333515153755732021704 0ustar fengfeng/* * * Copyright (C) 2024, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef UKUI_QUICK_KYWLCOM_CONTEX_H #define UKUI_QUICK_KYWLCOM_CONTEX_H #include #include "kywlcom-thumbnail.h" class Context : public QObject { Q_OBJECT public: enum class Capability { Output = 1 << 0, Toplevel = 1 << 1, Workspace = 1 << 2, Thumbnail = 1 << 3, }; Q_DECLARE_FLAGS(Capabilities, Capability) explicit Context(struct wl_display *display, Capabilities caps, QObject *parent = nullptr); ~Context(); void start(); void thumbnail_init(Thumbnail *thumbnail, Thumbnail::Type type, QString uuid, QString output_uuid, QString decoration); void dispatch(); bool valid(); Q_SIGNALS: void aboutToTeardown(); void created(); void destroyed(); void validChanged(bool); private Q_SLOTS: void onContextReady(); private: class Private; Private *d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(Context::Capabilities) #endif //UKUI_QUICK_KYWLCOM_CONTEX_H ukui-quick/modules/window-thumbnail/qml/0000775000175000017500000000000015153756415017334 5ustar fengfengukui-quick/modules/window-thumbnail/qml/MprisPlayerButton.qml0000664000175000017500000001073715153756415023522 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: qiqi * */ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 DtThemeBackground { useStyleTransparency: false backgroundColor: GlobalTheme.baseActive alpha: 0.65 radius: GlobalTheme.kRadiusMin border.width: 1 borderColor: GlobalTheme.kLineNormal property var playStatus signal skipBackwardClicked() signal playStatusClicked() signal skipForwardClicked() RowLayout { anchors.fill: parent spacing: 0 MouseArea { id: backward Layout.preferredHeight: 28 Layout.fillWidth: true hoverEnabled: true onClicked: skipBackwardClicked() Icon { height: 16 width: 16 anchors.centerIn: parent mode: (backward.containsPress || backward.containsMouse) ? Icon.ForceHighlight : Icon.AutoHighlight dtThemeHighlightColor: backward.containsPress ? GlobalTheme.kBrandClick : backward.containsMouse ? GlobalTheme.kBrandHover : GlobalTheme.kFontPrimary source: "media-skip-backward-symbolic" } } DtThemeBackground { useStyleTransparency: false Layout.preferredHeight: 16 Layout.preferredWidth: 1 backgroundColor: GlobalTheme.kDivider } MouseArea { id: playPause Layout.preferredHeight: 28 Layout.fillWidth: true hoverEnabled: true onClicked: playStatusClicked() Icon { height: 16 width: 16 anchors.centerIn: parent mode: (playPause.containsPress || playPause.containsMouse) ? Icon.ForceHighlight : Icon.AutoHighlight dtThemeHighlightColor: playPause.containsPress ? GlobalTheme.kBrandClick : playPause.containsMouse ? GlobalTheme.kBrandHover : GlobalTheme.kFontPrimary source: playStatus === "Playing" ? "media-playback-pause-symbolic" : "ukui-play-full-symbolic" } } DtThemeBackground { useStyleTransparency: false Layout.preferredHeight: 16 Layout.preferredWidth: 1 backgroundColor: GlobalTheme.kDivider } MouseArea { id: forward Layout.preferredHeight: 28 Layout.fillWidth: true hoverEnabled: true onClicked: skipForwardClicked() Icon { height: 16 width: 16 anchors.centerIn: parent mode: (forward.containsPress || forward.containsMouse) ? Icon.ForceHighlight : Icon.AutoHighlight dtThemeHighlightColor: forward.containsPress ? GlobalTheme.kBrandClick : forward.containsMouse ? GlobalTheme.kBrandHover : GlobalTheme.kFontPrimary source: "media-skip-forward-symbolic" } } } } ukui-quick/modules/window-thumbnail/qml/PipeWireThumbnail.qml0000664000175000017500000000151515153755732023442 0ustar fengfeng/* SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez SPDX-FileCopyrightText: 2023 iaom SPDX-License-Identifier: LGPL-2.0-or-later */ import QtQuick 2.14 import org.ukui.windowThumbnail 1.0 Item { property alias fixHeight: pipeWireSourceItem.fixHeight property alias fixWidth: pipeWireSourceItem.fixWidth width: fixHeight? pipeWireSourceItem.width : parent.width height: fixWidth? pipeWireSourceItem.height : parent.height PipeWireSourceItem { id: pipeWireSourceItem visible: waylandItem.nodeId > 0 nodeId: waylandItem.nodeId width: fixHeight? paintedWidth : parent.width height: fixWidth? paintedHeight : parent.height } ScreenCastingRequest { id: waylandItem uuid: {thumbnailSourceItem.winId} } } ukui-quick/modules/window-thumbnail/qml/WindowThumbnail.qml0000664000175000017500000000741515153756415023171 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: qiqi * */ import QtQuick 2.12 import Qt5Compat.GraphicalEffects import org.ukui.windowThumbnail 1.0 import org.ukui.quick.platform 1.0 Item { id: thumbnailSourceItem clip: true property string winId property bool fixHeight: false property bool fixWidth: false Loader { id: thumbnailLoader height: parent.height width: parent.width visible: windowThumbnailConfig.realTimeThumbnailEnable sourceComponent: { if(windowThumbnailConfig.kywlcomWindowThumbnailEnable) { return kywlcomWindowThumbnailComonent; } else if(windowThumbnailConfig.xThumbnailEnable) { return x11ThumbnailComponent; } } Component { id: x11ThumbnailComponent XWindowThumbnail { id: xWindowItem winId: Number.parseInt(thumbnailSourceItem.winId) fixHeight: thumbnailSourceItem.fixHeight fixWidth: thumbnailSourceItem.fixWidth width: parent.width height: parent.height // 预览图圆角 layer.enabled: true layer.effect: OpacityMask { maskSource: Item { width: xWindowItem.width height: xWindowItem.height Rectangle { x: (xWindowItem.width - xWindowItem.paintedWidth) / 2 y: (xWindowItem.height - xWindowItem.paintedHeight) / 2 width: xWindowItem.paintedWidth height: xWindowItem.paintedHeight radius: GlobalTheme.kRadiusMin } } } } } // Component { // id: piperWireThumbnailComponent // PipeWireThumbnail { // fixHeight: thumbnailSourceItem.fixHeight // fixWidth: thumbnailSourceItem.fixWidth // } // } Component { id: kywlcomWindowThumbnailComonent KywlcomWindowThumbnailItem { id: kywlcomItem uuid: thumbnailSourceItem.winId fixHeight: thumbnailSourceItem.fixHeight fixWidth: thumbnailSourceItem.fixWidth // 预览图圆角 layer.enabled: true layer.effect: OpacityMask { maskSource: Item { width: kywlcomItem.width height: kywlcomItem.height Rectangle { x: (kywlcomItem.width - kywlcomItem.paintedWidth) / 2 y: (kywlcomItem.height - kywlcomItem.paintedHeight) / 2 width: kywlcomItem.paintedWidth height: kywlcomItem.paintedHeight radius: GlobalTheme.kRadiusMin } } } } } } } ukui-quick/modules/window-thumbnail/qml/VideoPlayerThumbnail.qml0000664000175000017500000000360015153755732024136 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: qiqi * */ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 import org.ukui.windowThumbnail 1.0 WindowThumbnail { id: windowThumbnail property var playerData property var viewModel BlurItem { x: buttonBase.x y: buttonBase.y width: buttonBase.width height: buttonBase.height radius: 4 blurRadius: 40 samples: 81 source: windowThumbnail } MprisPlayerButton { id: buttonBase height: 28 width: 144 z: 10 anchors.bottom: parent.bottom anchors.bottomMargin: 8 anchors.horizontalCenter: parent.horizontalCenter playStatus: model.PlaybackStatus onSkipBackwardClicked: { playerData.operation(viewModel.index(index, 0), MprisProperties.Previous, []) } onPlayStatusClicked: { playerData.operation(viewModel.index(index, 0), MprisProperties.PlayPause, []) } onSkipForwardClicked: { playerData.operation(viewModel.index(index, 0), MprisProperties.Next, []) } } } ukui-quick/modules/window-thumbnail/qml/AudioPalyerThumbnail.qml0000664000175000017500000001342315153756415024134 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: qiqi * */ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 import Qt5Compat.GraphicalEffects import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 import org.ukui.windowThumbnail 1.0 Repeater { id: view property var playerData property int windowRadius property bool containsMouse: false property bool isOpenGL: true signal closeButtonClicked() signal favoriteButtonClicked() model: playerData RowLayout { id: musicBase width: 304 height: 96 Item { Layout.fillHeight: true Layout.preferredWidth: height Loader { id: artUrlImage anchors.margins: 4 anchors.fill: parent property var iconSource: model.MetaData["mpris:artUrl"] ? model.MetaData["mpris:artUrl"] : "qrc:/image/default-image.svg" sourceComponent: isOpenGL ? radiusIcon : icon } Component { id: radiusIcon Icon { source: iconSource layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: artUrlImage.width height: artUrlImage.height radius: windowRadius - 4 } } } } Component { id: icon Icon { source: iconSource } } } // 名称 + 歌词 + 按钮 Item { Layout.fillHeight: true Layout.fillWidth: true ColumnLayout { anchors.fill: parent anchors.leftMargin: 16 anchors.rightMargin: 12 anchors.bottomMargin: 8 anchors.topMargin: 8 spacing: 0 DtThemeText { Layout.fillWidth: true text: model.MetaData["xesam:title"] ? model.MetaData["xesam:title"] : "" textColor: GlobalTheme.kFontPrimary elide: Text.ElideRight verticalAlignment: Text.AlignVCenter } DtThemeText { Layout.fillWidth: true text: model.MetaData["xesam:artist"] ? model.MetaData["xesam:artist"] : "" textColor: GlobalTheme.kFontPrimary elide: Text.ElideRight verticalAlignment: Text.AlignVCenter } MprisPlayerButton { Layout.fillWidth: true Layout.preferredHeight: 28 playStatus: model.PlaybackStatus onSkipBackwardClicked: { playerData.operation(view.model.index(index, 0), MprisProperties.Previous, []) } onPlayStatusClicked: { playerData.operation(view.model.index(index, 0), MprisProperties.PlayPause, []) } onSkipForwardClicked: { playerData.operation(view.model.index(index, 0), MprisProperties.Next, []) } } } } // 关闭按钮 + 收藏按钮 Item { Layout.fillHeight: true Layout.preferredWidth: 32 + 4 MouseArea { id: closeButtonArea width: 32 height: width anchors.top: parent.top anchors.topMargin: 4 anchors.right: parent.right anchors.rightMargin: 4 hoverEnabled: true visible: view.containsMouse DtThemeBackground { backgroundColor: closeButtonArea.containsPress ? GlobalTheme.kErrorClick : GlobalTheme.kErrorNormal radius: GlobalTheme.kRadiusNormal anchors.fill: parent visible: parent.containsMouse } Icon { width: parent.width / 2 height: parent.height / 2 anchors.centerIn: parent mode: parent.containsMouse ? Icon.Highlight : Icon.AutoHighlight source: "window-close-symbolic" } onClicked: { closeButtonClicked(); } } //TODO:收藏按钮 MouseArea { width: 16 height: width anchors.left: parent.left anchors.leftMargin: 4 anchors.bottom: parent.bottom anchors.bottomMargin: 14 onClicked: favoriteButtonClicked() Icon { anchors.fill: parent //source: "" } } } } } ukui-quick/modules/window-thumbnail/screen-casting.cpp0000664000175000017500000000525015153755732022157 0ustar fengfeng/* SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez SPDX-FileCopyrightText: 2023 iaom SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "screen-casting.h" #include "qwayland-zkde-screencast-unstable-v1.h" #include #include #include #include #include using namespace KWayland::Client; class ScreencastingStreamPrivate : public QtWayland::zkde_screencast_stream_unstable_v1 { public: ScreencastingStreamPrivate(ScreencastingStream *q) : q(q) { } ~ScreencastingStreamPrivate() { close(); q->deleteLater(); } void zkde_screencast_stream_unstable_v1_created(uint32_t node) override { m_nodeId = node; Q_EMIT q->created(node); } void zkde_screencast_stream_unstable_v1_closed() override { Q_EMIT q->closed(); } void zkde_screencast_stream_unstable_v1_failed(const QString &error) override { Q_EMIT q->failed(error); } uint m_nodeId = 0; QPointer q; }; ScreencastingStream::ScreencastingStream(QObject *parent) : QObject(parent) , d(new ScreencastingStreamPrivate(this)) { } ScreencastingStream::~ScreencastingStream() = default; quint32 ScreencastingStream::nodeId() const { return d->m_nodeId; } class ScreencastingPrivate : public QtWayland::zkde_screencast_unstable_v1 { public: ScreencastingPrivate(Registry *registry, int id, int version, Screencasting *q) : QtWayland::zkde_screencast_unstable_v1(*registry, id, version) , q(q) { } ScreencastingPrivate(::zkde_screencast_unstable_v1 *screencasting, Screencasting *q) : QtWayland::zkde_screencast_unstable_v1(screencasting) , q(q) { } ~ScreencastingPrivate() { destroy(); } Screencasting *const q; }; Screencasting::Screencasting(QObject *parent) :QObject(parent) { } Screencasting::Screencasting(Registry *registry, int id, int version, QObject *parent) : QObject(parent) , d(new ScreencastingPrivate(registry, id, version, this)) { } Screencasting::~Screencasting() = default; ScreencastingStream *Screencasting::createWindowStream(const QString &uuid, CursorMode mode) { auto stream = new ScreencastingStream(this); stream->d->init(d->stream_window(uuid, mode)); return stream; } void Screencasting::setup(::zkde_screencast_unstable_v1 *screencasting) { d.reset(new ScreencastingPrivate(screencasting, this)); } void Screencasting::destroy() { d.reset(nullptr); } ukui-quick/modules/window-thumbnail/discard-egl-pixmap-runnable.h0000664000175000017500000000312015153755732024167 0ustar fengfeng/* * * Copyright (C) 2024, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef UKUI_QUICK_DISCARD_EGL_PIXMAP_RUNNABLE_H #define UKUI_QUICK_DISCARD_EGL_PIXMAP_RUNNABLE_H #include #include #include #include class DiscardEglPixmapRunnable : public QRunnable { public: DiscardEglPixmapRunnable(EGLImageKHR image, QOpenGLTexture *texture) : m_image(image) , m_texture(texture) { } void run() override { if (m_image != EGL_NO_IMAGE_KHR) { static auto eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); eglDestroyImageKHR(eglGetCurrentDisplay(), m_image); } delete m_texture; } private: const EGLImageKHR m_image; QOpenGLTexture *m_texture; }; #endif //UKUI_QUICK_DISCARD_EGL_PIXMAP_RUNNABLE_H ukui-quick/modules/window-thumbnail/pipewire-core.h0000664000175000017500000000166415153755732021476 0ustar fengfeng/* SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez SPDX-FileContributor: Jan Grulich SPDX-FileContributor: iaom SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #ifndef PIPEWIRECORE_H #define PIPEWIRECORE_H #include #include class PipeWireCore : public QObject { Q_OBJECT public: PipeWireCore(); static void onCoreError(void *data, uint32_t id, int seq, int res, const char *message); ~PipeWireCore(); bool init(); static QSharedPointer self(); struct pw_core *pwCore = nullptr; struct pw_context *pwContext = nullptr; struct pw_loop *pwMainLoop = nullptr; spa_hook coreListener; QString m_error; pw_core_events pwCoreEvents = {}; Q_SIGNALS: void pipewireFailed(const QString &message); }; #endif // PIPEWIRECORE_H ukui-quick/modules/window-thumbnail/pipewire-source-stream.h0000664000175000017500000000453715153755732023341 0ustar fengfeng/* SPDX-FileCopyrightText: 2018-2020 Red Hat Inc SPDX-FileCopyrightText: 2020-2021 Aleix Pol Gonzalez SPDX-FileContributor: Jan Grulich SPDX-FileContributor: iaom SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #ifndef PIPEWIRESOURCESTREAM_H #define PIPEWIRESOURCESTREAM_H #include #include #include #include #include #include #include #include #undef Status namespace KWin { class AbstractEglBackend; class GLTexture; } class PipeWireCore; typedef void *EGLDisplay; struct DmaBufPlane { int fd; /// The dmabuf file descriptor uint32_t offset; /// The offset from the start of buffer uint32_t stride; /// The distance from the start of a row to the next row in bytes uint64_t modifier = 0; /// The layout modifier }; class PipeWireSourceStream : public QObject { Q_OBJECT public: explicit PipeWireSourceStream(QObject *parent); ~PipeWireSourceStream(); static void onStreamParamChanged(void *data, uint32_t id, const struct spa_pod *format); static void onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message); uint framerate(); uint nodeId(); QString error() const { return m_error; } QSize size() const { return QSize(videoFormat.size.width, videoFormat.size.height); } bool createStream(uint nodeid); void stop(); void setActive(bool active); void handleFrame(struct pw_buffer *buffer); void process(); bool setAllowDmaBuf(bool allowed); Q_SIGNALS: void streamReady(); void startStreaming(); void stopStreaming(); void dmabufTextureReceived(const QVector &planes, uint32_t format); void imageTextureReceived(const QImage &image); private: void coreFailed(const QString &errorMessage); QSharedPointer pwCore; pw_stream *pwStream = nullptr; spa_hook streamListener; pw_stream_events pwStreamEvents = {}; uint32_t pwNodeId = 0; bool m_stopped = false; spa_video_info_raw videoFormat; QString m_error; bool m_allowDmaBuf = true; }; #endif // PIPEWIRESOURCESTREAM_H ukui-quick/modules/window-thumbnail/screen-casting-request.h0000664000175000017500000000231015153755732023304 0ustar fengfeng/* SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez SPDX-FileCopyrightText: 2023 iaom SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #ifndef SCREENCASTINGREQUEST_H #define SCREENCASTINGREQUEST_H #include "screen-casting.h" #include class ScreencastingStream; class ScreenCastingRequest : public QObject { Q_OBJECT Q_PROPERTY(QString uuid READ uuid WRITE setUuid NOTIFY uuidChanged) Q_PROPERTY(quint32 nodeId READ nodeId NOTIFY nodeIdChanged) public: explicit ScreenCastingRequest(QObject *parent = nullptr); ~ScreenCastingRequest(); void setUuid(const QString &uuid); QString uuid() const; quint32 nodeId() const; void create(Screencasting *screencasting); Q_SIGNALS: void nodeIdChanged(quint32 nodeId); void uuidChanged(const QString &uuid); void closeRunningStreams(); void cursorModeChanged(Screencasting::CursorMode cursorMode); private: void setNodeid(uint nodeId); ScreencastingStream *m_stream = nullptr; QString m_uuid; KWayland::Client::Output *m_output = nullptr; quint32 m_nodeId = 0; }; #endif // SCREENCASTINGREQUEST_H ukui-quick/modules/window-thumbnail/kywlcom-thumbnail.cpp0000664000175000017500000001240515153756415022717 0ustar fengfeng/* * * Copyright (C) 2024, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #include "kywlcom-thumbnail.h" #include class Thumbnail::Private { public: Private(Thumbnail *thumbnail); ~Private(); void setup(kywc_context *ctx, Thumbnail::Type type, QString uuid, QString output_uuid, QString decoration); int32_t fd; uint32_t format; QSize size; uint32_t offset; uint32_t stride; uint64_t modifier; Thumbnail::BufferFlags flags; Type type; QString source_uuid; QString output_uuid; kywc_thumbnail *k_thumbnail = nullptr; private: Thumbnail *q; static bool bufferHandle(kywc_thumbnail *thumbnail, const struct kywc_thumbnail_buffer *buffer, void *data); static void destroyHandle(kywc_thumbnail *thumbnail, void *data); static struct kywc_thumbnail_interface thumbnail_impl; }; Thumbnail::Private::Private(Thumbnail *thumbnail) : q(thumbnail) {} Thumbnail::Private::~Private() {} bool Thumbnail::Private::bufferHandle(kywc_thumbnail *thumbnail, const struct kywc_thumbnail_buffer *buffer, void *data) { Thumbnail *thum = (Thumbnail *)data; switch (thumbnail->type) { case KYWC_THUMBNAIL_TYPE_OUTPUT: thum->d->type = Thumbnail::Type::Output; break; case KYWC_THUMBNAIL_TYPE_TOPLEVEL: thum->d->type = Thumbnail::Type::Toplevel; break; case KYWC_THUMBNAIL_TYPE_WORKSPACE: thum->d->type = Thumbnail::Type::Workspace; break; } thum->d->source_uuid = QString(thumbnail->source_uuid); thum->d->output_uuid = QString(thumbnail->output_uuid); if (buffer->flags & KYWC_THUMBNAIL_BUFFER_IS_DMABUF) { thum->d->flags |= Thumbnail::BufferFlag::Dmabuf; } if (buffer->flags & KYWC_THUMBNAIL_BUFFER_IS_REUSED) { thum->d->flags |= Thumbnail::BufferFlag::Reused; } thum->d->size = QSize(buffer->width, buffer->height); thum->d->offset = buffer->offset; thum->d->stride = buffer->stride; thum->d->fd = buffer->fd; thum->d->format = buffer->format; thum->d->modifier = buffer->modifier; emit thum->bufferUpdate(); if (buffer->flags & KYWC_THUMBNAIL_BUFFER_IS_DMABUF) { return true; } else return false; } void Thumbnail::Private::destroyHandle(kywc_thumbnail *thumbnail, void *data) { Thumbnail *thum = (Thumbnail *)data; thum->d->k_thumbnail = nullptr; emit thum->deleted(); } struct kywc_thumbnail_interface Thumbnail::Private::thumbnail_impl { bufferHandle, destroyHandle, }; void Thumbnail::Private::setup(kywc_context *ctx, Thumbnail::Type type, QString uuid, QString output_uuid, QString decoration) { QByteArray qByteArray_uuid = uuid.toUtf8(); char *str = qByteArray_uuid.data(); kywc_thumbnail *thumbnail = NULL; switch (type) { case Thumbnail::Type::Output: thumbnail = kywc_thumbnail_create_from_output(ctx, str, &thumbnail_impl, this->q); break; case Thumbnail::Type::Toplevel: { bool without_decoration = false; if (decoration == QLatin1String("true")) { without_decoration = true; } thumbnail = kywc_thumbnail_create_from_toplevel(ctx, str, without_decoration, &thumbnail_impl, this->q); } break; case Thumbnail::Type::Workspace: { QByteArray qByteArray_uuid = output_uuid.toUtf8(); char *output = qByteArray_uuid.data(); thumbnail = kywc_thumbnail_create_from_workspace(ctx, str, output, &thumbnail_impl, this->q); } break; } k_thumbnail = thumbnail; } Thumbnail::Thumbnail(QObject *parent) : d(new Private(this)) {} Thumbnail::~Thumbnail() { if(d) { delete d; d = nullptr; } } void Thumbnail::setup(kywc_context *ctx, Thumbnail::Type type, QString uuid, QString output_uuid, QString decoration) { d->setup(ctx, type, uuid, output_uuid, decoration);; } int32_t Thumbnail::fd() const { return d->fd; } uint32_t Thumbnail::format() const { return d->format; } QSize Thumbnail::size() const { return d->size; } uint32_t Thumbnail::offset() const { return d->offset; } uint32_t Thumbnail::stride() const { return d->stride; } uint64_t Thumbnail::modifier() const { return d->modifier; } Thumbnail::BufferFlags Thumbnail::flags() const { return d->flags; } void Thumbnail::destroy() { if(d->k_thumbnail) { kywc_thumbnail_destroy(d->k_thumbnail); d->k_thumbnail = nullptr; } } ukui-quick/modules/window-thumbnail/x-window-thumbnail.h0000664000175000017500000001004715153755732022454 0ustar fengfeng/* SPDX-FileCopyrightText: 2013 Martin Gräßlin SPDX-FileCopyrightText: 2023 iaom SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef UKUI_TASK_MANAGER_WINDOW_THUMBNAIL_H #define UKUI_TASK_MANAGER_WINDOW_THUMBNAIL_H #include #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #else #include #endif #include #include #include class WindowTextureProvider; class XWindowThumbnail : public QQuickItem, public QAbstractNativeEventFilter { Q_OBJECT Q_PROPERTY(uint winId READ winId WRITE setWinId NOTIFY winIdChanged) Q_PROPERTY(qreal paintedWidth READ paintedWidth NOTIFY paintedSizeChanged) Q_PROPERTY(qreal paintedHeight READ paintedHeight NOTIFY paintedSizeChanged) Q_PROPERTY(bool thumbnailAvailable READ thumbnailAvailable NOTIFY thumbnailAvailableChanged) Q_PROPERTY(bool fixHeight READ fixHeight WRITE setFixHeight NOTIFY fixHeightChanged) Q_PROPERTY(bool fixWidth READ fixWidth WRITE setFixWidth NOTIFY fixWidthChanged) public: explicit XWindowThumbnail(QQuickItem *parent = nullptr); ~XWindowThumbnail() override; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override; #else bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override; #endif QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) override; qreal paintedWidth() const; qreal paintedHeight() const; bool thumbnailAvailable() const; uint32_t winId() const; void setWinId(uint32_t winId); bool fixHeight() const; void setFixHeight(bool fixHeight); bool fixWidth() const; void setFixWidth(bool fixWidth); Q_SIGNALS: void winIdChanged(); void fixHeightChanged(); void fixWidthChanged(); void paintedSizeChanged(); void thumbnailAvailableChanged(); protected: void itemChange(ItemChange change, const ItemChangeData &data) override; void releaseResources() override; private: void iconToTexture(WindowTextureProvider *textureProvider); void windowToTexture(WindowTextureProvider *textureProvider); bool windowToTextureGLX(WindowTextureProvider *textureProvider); bool xcbWindowToTextureEGL(WindowTextureProvider *textureProvider); void bindEGLTexture(); void resolveEGLFunctions(); void resolveGLXFunctions(); bool loadGLXTexture(); void bindGLXTexture(); xcb_pixmap_t pixmapForWindow(); void setThumbnailAvailable(bool thumbnailAvailable); bool startRedirecting(); void stopRedirecting(); void resetDamaged(); void sceneVisibilityChanged(bool visible); QPointer m_scene; uint32_t m_winId = 0; bool m_fixHeight = false; bool m_fixWidth = false; QSizeF m_paintedSize; bool m_thumbnailAvailable = false; bool m_xcb = false; bool m_composite = false; bool m_damaged = false; int m_depth = 0; bool m_redirecting = false; WindowTextureProvider *m_textureProvider = nullptr; uint8_t m_damageEventBase = 0; xcb_damage_damage_t m_damage; xcb_pixmap_t m_pixmap; bool m_openGLFunctionsResolved = false; bool m_eglFunctionsResolved = false; QFunctionPointer m_bindTexImage = nullptr; QFunctionPointer m_releaseTexImage = nullptr; xcb_pixmap_t m_glxPixmap; xcb_visualid_t m_visualid = 0; uint m_texture = 0; EGLImageKHR m_image; QFunctionPointer m_eglCreateImageKHR{}; QFunctionPointer m_eglDestroyImageKHR{}; QFunctionPointer m_glEGLImageTargetTexture2DOES{}; }; class WindowTextureProvider : public QSGTextureProvider { Q_OBJECT public: QSGTexture *texture() const override; void setTexture(QSGTexture *texture); private: std::unique_ptr m_texture; }; #endif //UKUI_TASK_MANAGER_WINDOW_THUMBNAIL_H ukui-quick/modules/window-thumbnail/pipewire-source-item.cpp0000664000175000017500000002344415153755732023335 0ustar fengfeng/* Render a PipeWire stream into a QtQuick scene as a standard Item SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez SPDX-FileContributor: iaom SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "pipewire-source-item.h" #include "pipewire-source-stream.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "discard-egl-pixmap-runnable.h" static void pwInit() { pw_init(nullptr, nullptr); } Q_COREAPP_STARTUP_FUNCTION(pwInit); PipeWireSourceItem::PipeWireSourceItem(QQuickItem *parent) : QQuickItem(parent) { setFlag(ItemHasContents, true); connect(this, &QQuickItem::visibleChanged, this, [this]() { if (m_stream) m_stream->setActive(isVisible()); }); } PipeWireSourceItem::~PipeWireSourceItem() { } void PipeWireSourceItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) { switch (change) { case ItemVisibleHasChanged: if (m_stream) m_stream->setActive(isVisible() && data.boolValue && isComponentComplete()); break; case ItemSceneChange: m_needsRecreateTexture = true; releaseResources(); break; default: break; } } void PipeWireSourceItem::releaseResources() { if (window()) { window()->scheduleRenderJob(new DiscardEglPixmapRunnable(m_image, m_texture.take()), QQuickWindow::NoStage); m_image = EGL_NO_IMAGE_KHR; } } void PipeWireSourceItem::setNodeId(uint nodeId) { if (nodeId == m_nodeId) return; m_nodeId = nodeId; if (m_nodeId == 0) { m_stream.reset(nullptr); m_createNextTexture = [] { return nullptr; }; } else { m_stream.reset(new PipeWireSourceStream(this)); m_stream->createStream(m_nodeId); if (!m_stream->error().isEmpty()) { m_stream.reset(nullptr); m_nodeId = 0; return; } m_stream->setActive(isVisible() && isComponentComplete()); connect(m_stream.data(), &PipeWireSourceStream::dmabufTextureReceived, this, &PipeWireSourceItem::updateTextureDmaBuf); connect(m_stream.data(), &PipeWireSourceStream::imageTextureReceived, this, &PipeWireSourceItem::updateTextureImage); } Q_EMIT nodeIdChanged(nodeId); } QSGNode *PipeWireSourceItem::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *) { if (Q_UNLIKELY(!m_createNextTexture)) { return node; } auto texture = m_createNextTexture(); if (!texture) { delete node; return nullptr; } if (m_needsRecreateTexture) { delete node; node = nullptr; m_needsRecreateTexture = false; } QSGImageNode *textureNode = static_cast(node); if (!textureNode) { textureNode = window()->createImageNode(); textureNode->setOwnsTexture(true); } textureNode->setTexture(texture); QSizeF scaleSize = textureNode->texture()->textureSize(); if(m_fixHeight) { scaleSize.setHeight(boundingRect().height()); } if(m_fixWidth) { scaleSize.setWidth(boundingRect().width()); } if(!m_fixHeight && !m_fixWidth) { scaleSize = boundingRect().size(); } const QSizeF size(texture->textureSize().scaled(scaleSize.toSize(), Qt::KeepAspectRatio)); if(size != m_paintedSize) { m_paintedSize = size; Q_EMIT paintedSizeChanged(); } QRect rect({0, 0}, size.toSize()); rect.moveCenter(boundingRect().toRect().center()); textureNode->setRect(rect); return textureNode; } QString PipeWireSourceItem::error() const { return m_stream->error(); } static EGLImage createImage(EGLDisplay display, const QVector &planes, uint32_t format, const QSize &size) { const bool hasModifiers = planes[0].modifier != DRM_FORMAT_MOD_INVALID; QVector attribs; attribs << EGL_WIDTH << size.width() << EGL_HEIGHT << size.height() << EGL_LINUX_DRM_FOURCC_EXT << EGLint(format) << EGL_DMA_BUF_PLANE0_FD_EXT << planes[0].fd << EGL_DMA_BUF_PLANE0_OFFSET_EXT << EGLint(planes[0].offset) << EGL_DMA_BUF_PLANE0_PITCH_EXT << EGLint(planes[0].stride); if (hasModifiers) { attribs << EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT << EGLint(planes[0].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT << EGLint(planes[0].modifier >> 32); } if (planes.count() > 1) { attribs << EGL_DMA_BUF_PLANE1_FD_EXT << planes[1].fd << EGL_DMA_BUF_PLANE1_OFFSET_EXT << EGLint(planes[1].offset) << EGL_DMA_BUF_PLANE1_PITCH_EXT << EGLint(planes[1].stride); if (hasModifiers) { attribs << EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT << EGLint(planes[1].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT << EGLint(planes[1].modifier >> 32); } } if (planes.count() > 2) { attribs << EGL_DMA_BUF_PLANE2_FD_EXT << planes[2].fd << EGL_DMA_BUF_PLANE2_OFFSET_EXT << EGLint(planes[2].offset) << EGL_DMA_BUF_PLANE2_PITCH_EXT << EGLint(planes[2].stride); if (hasModifiers) { attribs << EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT << EGLint(planes[2].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT << EGLint(planes[2].modifier >> 32); } } if (planes.count() > 3) { attribs << EGL_DMA_BUF_PLANE3_FD_EXT << planes[3].fd << EGL_DMA_BUF_PLANE3_OFFSET_EXT << EGLint(planes[3].offset) << EGL_DMA_BUF_PLANE3_PITCH_EXT << EGLint(planes[3].stride); if (hasModifiers) { attribs << EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT << EGLint(planes[3].modifier & 0xffffffff) << EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT << EGLint(planes[3].modifier >> 32); } } attribs << EGL_NONE; static auto eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR"); Q_ASSERT(eglCreateImageKHR); EGLImage ret = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer) nullptr, attribs.data()); if (ret == EGL_NO_IMAGE_KHR) { qWarning() << "invalid Image" << glGetError(); } // Q_ASSERT(ret); return ret; } void PipeWireSourceItem::updateTextureDmaBuf(const QVector &planes, uint32_t format) { static auto s_glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); if (!s_glEGLImageTargetTexture2DOES) { qWarning() << "glEGLImageTargetTexture2DOES is not available" << window(); return; } if (!window() || !window()->openglContext() || !m_stream) { qWarning() << "need a window and a context" << window(); return; } const EGLDisplay display = static_cast(QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("egldisplay")); if (m_image) { static auto eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); eglDestroyImageKHR(display, m_image); } const auto size = m_stream->size(); m_image = createImage(display, planes, format, size); if (m_image == EGL_NO_IMAGE_KHR) { QImage img(200, 200, QImage::Format_ARGB32_Premultiplied); img.fill(Qt::blue); updateTextureImage(img); return; } m_createNextTexture = [this, size, format] { if (!m_texture) { m_texture.reset(new QOpenGLTexture(QOpenGLTexture::Target2D)); bool created = m_texture->create(); Q_ASSERT(created); } m_texture->bind(); s_glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)m_image); m_texture->setWrapMode(QOpenGLTexture::ClampToEdge); m_texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear); m_texture->release(); m_texture->setSize(size.width(), size.height()); int textureId = m_texture->textureId(); QQuickWindow::CreateTextureOption textureOption = format == DRM_FORMAT_ARGB8888 ? QQuickWindow::TextureHasAlphaChannel : QQuickWindow::TextureIsOpaque; return window()->createTextureFromNativeObject(QQuickWindow::NativeObjectTexture, &textureId, 0 /*a vulkan thing?*/, size, textureOption); }; if (window()->isVisible()) { update(); } } void PipeWireSourceItem::updateTextureImage(const QImage &image) { if (!window()) { qWarning() << "pass"; return; } m_createNextTexture = [this, image] { return window()->createTextureFromImage(image, QQuickWindow::TextureIsOpaque); }; if (window()->isVisible()) update(); } void PipeWireSourceItem::componentComplete() { if (m_stream) m_stream->setActive(isVisible()); QQuickItem::componentComplete(); } bool PipeWireSourceItem::fixHeight() const { return m_fixHeight;; } void PipeWireSourceItem::setFixHeight(bool fixHeight) { if(m_fixHeight == fixHeight) { return; } m_fixHeight = fixHeight; Q_EMIT fixHeightChanged(); } bool PipeWireSourceItem::fixWidth() const { return m_fixWidth; } void PipeWireSourceItem::setFixWidth(bool fixWidth) { if(m_fixWidth == fixWidth) { return; } m_fixWidth = fixWidth; Q_EMIT fixWidthChanged(); } qreal PipeWireSourceItem::paintedWidth() const { return m_paintedSize.width(); } qreal PipeWireSourceItem::paintedHeight() const { return m_paintedSize.height(); }ukui-quick/modules/window-thumbnail/pipewire-source-stream.cpp0000664000175000017500000003276115153755732023674 0ustar fengfeng/* SPDX-FileCopyrightText: 2018-2020 Red Hat Inc SPDX-FileCopyrightText: 2020-2021 Aleix Pol Gonzalez SPDX-FileContributor: Jan Grulich SPDX-FileContributor: iaom SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "pipewire-source-stream.h" #include "pipewire-core.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef Status #if !PW_CHECK_VERSION(0, 3, 29) #define SPA_POD_PROP_FLAG_MANDATORY (1u << 3) #endif #if !PW_CHECK_VERSION(0, 3, 33) #define SPA_POD_PROP_FLAG_DONT_FIXATE (1u << 4) #endif static uint32_t SpaPixelFormatToDrmFormat(uint32_t spa_format) { switch (spa_format) { case SPA_VIDEO_FORMAT_RGBA: return DRM_FORMAT_ABGR8888; case SPA_VIDEO_FORMAT_RGBx: return DRM_FORMAT_XBGR8888; case SPA_VIDEO_FORMAT_BGRA: return DRM_FORMAT_ARGB8888; case SPA_VIDEO_FORMAT_BGRx: return DRM_FORMAT_XRGB8888; default: return DRM_FORMAT_INVALID; } } static std::vector queryDmaBufModifiers(EGLDisplay display, uint32_t format) { static auto eglQueryDmaBufModifiersEXT = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC)eglGetProcAddress("eglQueryDmaBufModifiersEXT"); static auto eglQueryDmaBufFormatsEXT = (PFNEGLQUERYDMABUFFORMATSEXTPROC)eglGetProcAddress("eglQueryDmaBufFormatsEXT"); if (!eglQueryDmaBufFormatsEXT || !eglQueryDmaBufModifiersEXT) { return {}; } uint32_t drm_format = SpaPixelFormatToDrmFormat(format); if (drm_format == DRM_FORMAT_INVALID) { qDebug() << "Failed to find matching DRM format." << format; return {}; } EGLint count = 0; EGLBoolean success = eglQueryDmaBufFormatsEXT(display, 0, nullptr, &count); if (!success || count == 0) { qWarning() << "Failed to query DMA-BUF format count."; return {}; } std::vector formats(count); if (!eglQueryDmaBufFormatsEXT(display, count, reinterpret_cast(formats.data()), &count)) { if (!success) qWarning() << "Failed to query DMA-BUF formats."; return {}; } if (std::find(formats.begin(), formats.end(), drm_format) == formats.end()) { qDebug() << "Format " << drm_format << " not supported for modifiers."; return {DRM_FORMAT_MOD_INVALID}; } success = eglQueryDmaBufModifiersEXT(display, drm_format, 0, nullptr, nullptr, &count); if (!success) { qWarning() << "Failed to query DMA-BUF modifier count."; return {}; } std::vector modifiers(count); if (count > 0) { if (!eglQueryDmaBufModifiersEXT(display, drm_format, count, modifiers.data(), nullptr, &count)) { qWarning() << "Failed to query DMA-BUF modifiers."; } } // Support modifier-less buffers modifiers.push_back(DRM_FORMAT_MOD_INVALID); return modifiers; } void PipeWireSourceStream::onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message) { PipeWireSourceStream *pw = static_cast(data); qDebug() << "state changed" << pw_stream_state_as_string(old) << "->" << pw_stream_state_as_string(state) << error_message; switch (state) { case PW_STREAM_STATE_ERROR: qWarning() << "Stream error: " << error_message; break; case PW_STREAM_STATE_PAUSED: Q_EMIT pw->streamReady(); break; case PW_STREAM_STATE_STREAMING: Q_EMIT pw->startStreaming(); break; case PW_STREAM_STATE_CONNECTING: break; case PW_STREAM_STATE_UNCONNECTED: if (!pw->m_stopped) { Q_EMIT pw->stopStreaming(); } break; } } static spa_pod *buildFormat(spa_pod_builder *builder, spa_video_format format, const std::vector &modifiers = {}) { spa_pod_frame f[2]; const spa_rectangle pw_min_screen_bounds{1, 1}; const spa_rectangle pw_max_screen_bounds{UINT32_MAX, UINT32_MAX}; spa_pod_builder_push_object(builder, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); spa_pod_builder_add(builder, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); if (modifiers.size()) { auto pw_version = QVersionNumber::fromString(pw_get_library_version()); // SPA_POD_PROP_FLAG_DONT_FIXATE can be used with PipeWire >= 0.3.33 if (pw_version >= QVersionNumber(0, 3, 33)) { spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE); } else { spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); } spa_pod_builder_push_choice(builder, &f[1], SPA_CHOICE_Enum, 0); // mofifiers from the array for (auto it = modifiers.begin(); it != modifiers.end(); it++) { spa_pod_builder_long(builder, *it); if (it == modifiers.begin()) { spa_pod_builder_long(builder, *it); } } spa_pod_builder_pop(builder, &f[1]); } spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&pw_min_screen_bounds, &pw_min_screen_bounds, &pw_max_screen_bounds), 0); return static_cast(spa_pod_builder_pop(builder, &f[0])); } void PipeWireSourceStream::onStreamParamChanged(void *data, uint32_t id, const struct spa_pod *format) { if (!format || id != SPA_PARAM_Format) { return; } PipeWireSourceStream *pw = static_cast(data); spa_format_video_raw_parse(format, &pw->videoFormat); const int32_t width = pw->videoFormat.size.width; const int32_t height = pw->videoFormat.size.height; const int bpp = pw->videoFormat.format == SPA_VIDEO_FORMAT_RGB || pw->videoFormat.format == SPA_VIDEO_FORMAT_BGR ? 3 : 4; const quint32 stride = SPA_ROUND_UP_N(width * bpp, 4); qDebug() << "Stream format changed"; const int32_t size = height * stride; uint8_t paramsBuffer[1024]; spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(paramsBuffer, sizeof(paramsBuffer)); const auto bufferTypes = pw->m_allowDmaBuf && spa_pod_find_prop(format, nullptr, SPA_FORMAT_VIDEO_modifier) ? (1 << SPA_DATA_DmaBuf) | (1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr) : (1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr); const spa_pod *param = (spa_pod *)spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(16, 2, 16), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(stride, stride, INT32_MAX), SPA_PARAM_BUFFERS_align, SPA_POD_Int(16), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(bufferTypes)); pw_stream_update_params(pw->pwStream, ¶m, 1); } static void onProcess(void *data) { PipeWireSourceStream *stream = static_cast(data); stream->process(); } PipeWireSourceStream::PipeWireSourceStream(QObject *parent) : QObject(parent) { pwStreamEvents.version = PW_VERSION_STREAM_EVENTS; pwStreamEvents.process = &onProcess; pwStreamEvents.state_changed = &PipeWireSourceStream::onStreamStateChanged; pwStreamEvents.param_changed = &PipeWireSourceStream::onStreamParamChanged; } PipeWireSourceStream::~PipeWireSourceStream() { m_stopped = true; if (pwStream) { pw_stream_destroy(pwStream); } } uint PipeWireSourceStream::framerate() { if (pwStream) { return videoFormat.max_framerate.num / videoFormat.max_framerate.denom; } return 0; } uint PipeWireSourceStream::nodeId() { return pwNodeId; } bool PipeWireSourceStream::createStream(uint nodeid) { pwCore = PipeWireCore::self(); if (!pwCore->m_error.isEmpty()) { m_error = pwCore->m_error; return false; } connect(pwCore.data(), &PipeWireCore::pipewireFailed, this, &PipeWireSourceStream::coreFailed); pwStream = pw_stream_new(pwCore->pwCore, "plasma-screencast", nullptr); pwNodeId = nodeid; pw_stream_add_listener(pwStream, &streamListener, &pwStreamEvents, this); uint8_t buffer[4096]; spa_pod_builder podBuilder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); const QVector formats = {SPA_VIDEO_FORMAT_RGBx, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGB, SPA_VIDEO_FORMAT_BGR}; QVector params; params.reserve(formats.size() * 2); const EGLDisplay display = static_cast(QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("egldisplay")); for (spa_video_format format : formats) { if (m_allowDmaBuf) { if (auto modifiers = queryDmaBufModifiers(display, format); modifiers.size() > 0) { params += buildFormat(&podBuilder, format, modifiers); } } params += buildFormat(&podBuilder, format, {}); } pw_stream_flags s = (pw_stream_flags)(PW_STREAM_FLAG_DONT_RECONNECT | PW_STREAM_FLAG_AUTOCONNECT); if (pw_stream_connect(pwStream, PW_DIRECTION_INPUT, pwNodeId, s, params.data(), params.size()) != 0) { qWarning() << "Could not connect to stream"; pw_stream_destroy(pwStream); return false; } return true; } void PipeWireSourceStream::handleFrame(struct pw_buffer *buffer) { spa_buffer *spaBuffer = buffer->buffer; if (spaBuffer->datas->chunk->size == 0) { return; } if (spaBuffer->datas->type == SPA_DATA_MemFd) { uint8_t *map = static_cast(mmap(nullptr, spaBuffer->datas->maxsize + spaBuffer->datas->mapoffset, PROT_READ, MAP_PRIVATE, spaBuffer->datas->fd, 0)); if (map == MAP_FAILED) { qWarning() << "Failed to mmap the memory: " << strerror(errno); return; } const QImage::Format format = spaBuffer->datas->chunk->stride / videoFormat.size.width == 3 ? QImage::Format_RGB888 : QImage::Format_ARGB32; QImage img(map, videoFormat.size.width, videoFormat.size.height, spaBuffer->datas->chunk->stride, format); Q_EMIT imageTextureReceived(img.copy()); munmap(map, spaBuffer->datas->maxsize + spaBuffer->datas->mapoffset); } else if (spaBuffer->datas->type == SPA_DATA_DmaBuf) { QVector planes; planes.reserve(spaBuffer->n_datas); for (uint i = 0; i < spaBuffer->n_datas; ++i) { const auto &data = spaBuffer->datas[i]; DmaBufPlane plane; plane.fd = data.fd; plane.stride = data.chunk->stride; plane.offset = data.chunk->offset; plane.modifier = DRM_FORMAT_MOD_INVALID; planes += plane; } Q_EMIT dmabufTextureReceived(planes, DRM_FORMAT_ARGB8888); } else if (spaBuffer->datas->type == SPA_DATA_MemPtr) { QImage img(static_cast(spaBuffer->datas->data), videoFormat.size.width, videoFormat.size.height, spaBuffer->datas->chunk->stride, QImage::Format_ARGB32); Q_EMIT imageTextureReceived(img); } else { qWarning() << "unsupported buffer type" << spaBuffer->datas->type; QImage errorImage(200, 200, QImage::Format_ARGB32_Premultiplied); errorImage.fill(Qt::red); Q_EMIT imageTextureReceived(errorImage); } } void PipeWireSourceStream::coreFailed(const QString &errorMessage) { m_error = errorMessage; Q_EMIT stopStreaming(); } void PipeWireSourceStream::process() { pw_buffer *buf = pw_stream_dequeue_buffer(pwStream); if (!buf) { return; } handleFrame(buf); pw_stream_queue_buffer(pwStream, buf); } void PipeWireSourceStream::stop() { if (!m_stopped) pw_stream_set_active(pwStream, false); m_stopped = true; delete this; } void PipeWireSourceStream::setActive(bool active) { Q_ASSERT(pwStream); pw_stream_set_active(pwStream, active); } ukui-quick/modules/window-thumbnail/kywlcom-contex.cpp0000664000175000017500000001131415153755732022233 0ustar fengfeng/* * * Copyright (C) 2024, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #include "kywlcom-contex.h" #include #include #include #include class Context::Private { public: explicit Private(Context *context); ~Private(); kywc_context *setup(uint32_t capability); kywc_context *k_context = nullptr; QSocketNotifier *notifier = nullptr; struct wl_display *display = nullptr; uint32_t capabilities = 0; bool valid = false; private: Context *q; static void createHandle(kywc_context *context, void *data); static void destroyHandle(kywc_context *context, void *data); static void newOutput(kywc_context *context, kywc_output *output, void *data); static void newToplevel(kywc_context *context, kywc_toplevel *toplevel, void *data); static void newWorkspace(kywc_context *context, kywc_workspace *workspace, void *data); static struct kywc_context_interface context_impl; }; Context::Private::Private(Context *context) : q(context) {} Context::Private::~Private() {} void Context::Private::createHandle(kywc_context *context, void *data) { auto *p = (Context::Private *)data; Q_EMIT p->q->created(); } void Context::Private::destroyHandle(kywc_context *context, void *data) { auto *p = (Context::Private *)data; Q_EMIT p->q->destroyed(); } void Context::Private::newOutput(kywc_context *context, kywc_output *output, void *data) { } void Context::Private::newToplevel(kywc_context *context, kywc_toplevel *toplevel, void *data) { } void Context::Private::newWorkspace(kywc_context *context, kywc_workspace *workspace, void *data) { } struct kywc_context_interface Context::Private::context_impl = { createHandle, destroyHandle, newOutput, newToplevel, newWorkspace, }; kywc_context *Context::Private::setup(uint32_t capabilities) { if (!display) k_context = kywc_context_create(nullptr, capabilities, &context_impl, this); else k_context = kywc_context_create_by_display(display, capabilities, &context_impl, this); return k_context; } Context::Context( struct wl_display *display, Capabilities caps, QObject *parent) : QObject{parent} , d(new Private(this)) { d->display = display; uint32_t capabilities = 0; if (caps & Context::Capability::Output) capabilities |= KYWC_CONTEXT_CAPABILITY_OUTPUT; if (caps & Context::Capability::Toplevel) capabilities |= KYWC_CONTEXT_CAPABILITY_TOPLEVEL; if (caps & Context::Capability::Workspace) capabilities |= KYWC_CONTEXT_CAPABILITY_WORKSPACE; if (caps & Context::Capability::Thumbnail) capabilities |= KYWC_CONTEXT_CAPABILITY_THUMBNAIL; d->capabilities = capabilities; } Context::~Context() { kywc_context_destroy(d->k_context); if(d) { delete d; d = nullptr; } } void Context::start() { kywc_context *ctx = nullptr; if (!d->display) { ctx = d->setup(d->capabilities); if (!ctx) { return; } d->notifier = new QSocketNotifier(kywc_context_get_fd(ctx), QSocketNotifier::Read, this); connect(d->notifier, &QSocketNotifier::activated, this, &Context::onContextReady); kywc_context_process(ctx); } else { ctx = d->setup(d->capabilities); if (!ctx) { return; } } d->valid = true; Q_EMIT validChanged(d->valid); } void Context::onContextReady() { if (kywc_context_process(d->k_context) < 0) { disconnect(d->notifier, &QSocketNotifier::activated, this, &Context::onContextReady); Q_EMIT aboutToTeardown(); d->valid = false; Q_EMIT validChanged(d->valid); } } void Context::dispatch() { kywc_context_dispatch(d->k_context); } void Context::thumbnail_init(Thumbnail *thumbnail, Thumbnail::Type type, QString uuid, QString output_uuid, QString decoration) { thumbnail->setup(d->k_context, type, std::move(uuid), std::move(output_uuid), std::move(decoration)); } bool Context::valid() { return d->valid; } ukui-quick/modules/window-thumbnail/mpris/0000775000175000017500000000000015153755732017676 5ustar fengfengukui-quick/modules/window-thumbnail/mpris/org.mpris.MediaPlayer2.Player.xml0000664000175000017500000001072015153755732026050 0ustar fengfeng ukui-quick/modules/window-thumbnail/mpris/properties.h0000664000175000017500000000365115153755732022250 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef UKUI_QUICK_PROPERTIES_H #define UKUI_QUICK_PROPERTIES_H #include class MprisProperties { Q_GADGET public: enum Properties { Pid = 0, Valid, IsCurrentMediaVideo, IsCurrentMediaAudio, //media player2 properties CanQuit, FullScreen, CanSetFullScreen, CanRaise, HasTrackList, Identity, DesktopEntry, SupportedUriSchemes, SupportedMimeTypes, //media player2 player properties PlaybackStatus, LoopStatus, Rate, Shuffle, MetaData, Volume, Position, MinimumRate, MaximumRate, CanGoNext, CanGoPrevious, CanPlay, CanPause, CanSeek, CanControl }; Q_ENUM(Properties) enum Operations { //media player2 methods Raise, Quit, //media player2 player methods Next, Previous, Pause, PlayPause, Stop, Play, Seek, SetPosition, OpenUri }; Q_ENUM(Operations) }; #endif //UKUI_QUICK_PROPERTIES_H ukui-quick/modules/window-thumbnail/mpris/mpris-player-collecter.cpp0000664000175000017500000001327115153755732025004 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #include "mpris-player-collecter.h" #include #include #include #include #include #include static MprisPlayerCollecter *globalInstance = nullptr; static std::once_flag flag; class MprisPlayerCollecterPrivate: public QObject { Q_OBJECT public: explicit MprisPlayerCollecterPrivate(QObject *parent = nullptr); void serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner); void removePlayer(const QString &serviceName); void addPlayer(const QString &serviceName); void serviceNameFetched(QDBusPendingCallWatcher *watcher); QDBusServiceWatcher *m_watcher = nullptr; QHash m_playerServices; QHash m_playerItems; MprisPlayerCollecter *q = nullptr; }; MprisPlayerCollecterPrivate::MprisPlayerCollecterPrivate(QObject *parent) : QObject(parent) { m_watcher = new QDBusServiceWatcher(QStringLiteral("org.mpris.MediaPlayer2*"), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); connect(m_watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &MprisPlayerCollecterPrivate::serviceOwnerChanged); QDBusPendingCall async = QDBusConnection::sessionBus().interface()->asyncCall(QStringLiteral("ListNames")); QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this); connect(callWatcher, &QDBusPendingCallWatcher::finished, this, &MprisPlayerCollecterPrivate::serviceNameFetched); q = qobject_cast(parent); } void MprisPlayerCollecterPrivate::serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner) { if (!serviceName.startsWith(QLatin1String("org.mpris.MediaPlayer2."))) { return; } // QString sourceName = serviceName.mid(23); if(!oldOwner.isEmpty()) { qDebug() << "MPRIS service" << serviceName << "just went offline"; removePlayer(serviceName); } if(!newOwner.isEmpty()) { qDebug() << "MPRIS service" << serviceName << "just came online"; addPlayer(serviceName); } } void MprisPlayerCollecterPrivate::removePlayer(const QString &serviceName) { uint pid = m_playerServices.take(serviceName); if(pid > 0) { Q_EMIT q->playerRemoved(serviceName, pid); auto player = m_playerItems.value(serviceName); player->disconnect(); player->deleteLater(); m_playerItems.remove(serviceName); } } void MprisPlayerCollecterPrivate::addPlayer(const QString &serviceName) { QDBusReply pidReply = QDBusConnection::sessionBus().interface()->servicePid(serviceName); if(pidReply.isValid()) { m_playerServices.insert(serviceName, pidReply.value()); Q_EMIT q->playerAdded(serviceName, pidReply.value()); } else { qWarning() << "Can not get pid for service:" << serviceName; } } void MprisPlayerCollecterPrivate::serviceNameFetched(QDBusPendingCallWatcher *watcher) { QDBusPendingReply propsReply = *watcher; watcher->deleteLater(); if (propsReply.isError()) { qWarning() << "MprisPlayerCollecter: Could not get list of available D-Bus services"; } else { for (const QString &serviceName: propsReply.value()) { if (serviceName.startsWith(QLatin1String("org.mpris.MediaPlayer2."))) { qDebug() << "Found MPRIS service" << serviceName; addPlayer(serviceName); } } } } MprisPlayerCollecter::MprisPlayerCollecter(QObject *parent) : QObject(parent), d(new MprisPlayerCollecterPrivate(this)) { } PlayerItem *MprisPlayerCollecter::item(uint pid) { //这里我们默认每个pid只对应一个service QHashIterator iter(d->m_playerServices); while (iter.hasNext()) { iter.next(); if(iter.value() == pid) { return item(iter.key()); } } return nullptr; } PlayerItem *MprisPlayerCollecter::item(const QString &service) { if(d->m_playerItems.contains(service)) { return d->m_playerItems.value(service); } else { auto item = new PlayerItem(service); d->m_playerItems.insert(service, item); connect(item, &PlayerItem::dataChanged, this, &MprisPlayerCollecter::dataChanged); return item; } } QStringList MprisPlayerCollecter::playerServices() { return d->m_playerServices.keys(); } MprisPlayerCollecter *MprisPlayerCollecter::self() { std::call_once(flag, [&] { globalInstance = new MprisPlayerCollecter(); }); return globalInstance; } uint MprisPlayerCollecter::pidOfService(const QString &service) { return d->m_playerServices.value(service); } #include "mpris-player-collecter.moc"ukui-quick/modules/window-thumbnail/mpris/org.freedesktop.DBus.Properties.xml0000664000175000017500000000207515153755732026514 0ustar fengfeng ukui-quick/modules/window-thumbnail/mpris/mpris-player-collecter.h0000664000175000017500000000310615153755732024445 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef UKUI_QUICK_MPRIS_PLAYER_COLLECTER_H #define UKUI_QUICK_MPRIS_PLAYER_COLLECTER_H #include #include "player-item.h" class MprisPlayerCollecterPrivate; class MprisPlayerCollecter : public QObject { Q_OBJECT public: static MprisPlayerCollecter *self(); PlayerItem *item(uint pid); PlayerItem *item(const QString &service); QStringList playerServices(); uint pidOfService(const QString &service); Q_SIGNALS: void playerAdded(const QString &service, uint pid); void playerRemoved(const QString &service, uint pid); void dataChanged(const QString &service, const QVector &properties); private: explicit MprisPlayerCollecter(QObject *parent = nullptr); MprisPlayerCollecterPrivate *d = nullptr; }; #endif //UKUI_QUICK_MPRIS_PLAYER_COLLECTER_H ukui-quick/modules/window-thumbnail/mpris/player-item.cpp0000664000175000017500000004150315153755732022635 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #include "player-item.h" #include #include #include #include #include #include "dbusproperties.h" #include "mprisplayer2player.h" #include "mprisplayer2.h" static const QString MPRIS2_PATH = QStringLiteral("/org/mpris/MediaPlayer2"); class MediaPlayer2Props { public: bool m_canQuit = false; bool m_fullScreen = false; bool m_canSetFullScreen = false; bool m_canRaise = false; bool m_hasTrackList = false; QString m_identity; QString m_desktopEntry; QStringList m_supportedUriSchemes; QStringList m_supportedMimeTypes; }; class MediaPlayer2PlayerProps { public: QString m_playbackStatus; QString m_loopStatus; double m_rate = 1; bool m_shuffle = false; QVariantMap m_metaData; double m_volume = 0; qint64 m_position = 0; double m_minimumRate = 0; double m_maximumRate = 0; bool m_canGoNext = true; bool m_canGoPrevious = true; bool m_canPlay = false; bool m_canPause = false; bool m_canSeek = false; bool m_canControl = false; }; class PlayerItemPrivate: public QObject { Q_OBJECT public: PlayerItemPrivate(const QString &serviceName, QObject *parent = nullptr); void refresh(); void updateMediaPlayer2Props(QDBusPendingCallWatcher *watcher); void updateMediaPlayer2PropsFromMap(const QVariantMap &map); void updateMediaPlayer2PlayerProps(QDBusPendingCallWatcher *watcher); void updateMediaPlayer2PlayerPropsFromMap(const QVariantMap &map); void propertiesChanged(const QString &interface, const QVariantMap &changedProperties, const QStringList &invalidatedProperties); void onSeeked(qint64 position); OrgFreedesktopDBusPropertiesInterface *m_propsIface = nullptr; OrgMprisMediaPlayer2Interface *m_mprisIface = nullptr; OrgMprisMediaPlayer2PlayerInterface *m_mprisPlayerIface = nullptr; QString m_serviceName; uint m_pid = 0; bool m_mp2PropsReceived = false; bool m_mp2PlayerPropsReceived = false; MediaPlayer2Props m_mp2Props; MediaPlayer2PlayerProps m_mp2PlayerProps; bool m_valid = false; PlayerItem *q = nullptr; }; PlayerItemPrivate::PlayerItemPrivate(const QString &serviceName, QObject *parent) : QObject(parent) { if(serviceName.isEmpty() || !serviceName.startsWith(QLatin1String("org.mpris.MediaPlayer2."))) { qWarning() << "Invalid mpris2 service: " << serviceName; return; } q = qobject_cast(parent); m_serviceName = serviceName; QDBusConnection conn = QDBusConnection::sessionBus(); QDBusReply pidReply = conn.interface()->servicePid(serviceName); if(pidReply.isValid()) { m_pid = pidReply.value(); } m_propsIface = new OrgFreedesktopDBusPropertiesInterface(serviceName, MPRIS2_PATH, conn, this); m_mprisIface = new OrgMprisMediaPlayer2Interface(serviceName, MPRIS2_PATH, conn, this); m_mprisPlayerIface = new OrgMprisMediaPlayer2PlayerInterface(serviceName, MPRIS2_PATH, conn, this); if(!m_propsIface->isValid() || !m_mprisIface->isValid() || !m_mprisPlayerIface->isValid()) { qWarning() << "Invalid mpris2 service: " << serviceName; return; } connect(m_propsIface, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, this, &PlayerItemPrivate::propertiesChanged); connect(m_mprisPlayerIface, &OrgMprisMediaPlayer2PlayerInterface::Seeked, this, &PlayerItemPrivate::onSeeked); refresh(); m_valid = true; } void PlayerItemPrivate::refresh() { //fetch MediaPlayer2 properties QDBusPendingCall async = m_propsIface->GetAll(OrgMprisMediaPlayer2Interface::staticInterfaceName()); auto watcher = new QDBusPendingCallWatcher(async, this); watcher->setProperty("fetch", true); connect(watcher, &QDBusPendingCallWatcher::finished, this, &PlayerItemPrivate::updateMediaPlayer2Props); //fetch MediaPlayer2.Player properties async = m_propsIface->GetAll(OrgMprisMediaPlayer2PlayerInterface::staticInterfaceName()); watcher = new QDBusPendingCallWatcher(async, this); watcher->setProperty("fetch", true); connect(watcher, &QDBusPendingCallWatcher::finished, this, &PlayerItemPrivate::updateMediaPlayer2PlayerProps); } void PlayerItemPrivate::updateMediaPlayer2Props(QDBusPendingCallWatcher *watcher) { QDBusPendingReply propsReply = *watcher; watcher->deleteLater(); //已收到更新信号后忽略主动查询返回 bool fetch = watcher->property("fetch").toBool(); if(m_mp2PropsReceived && fetch) { return; } if (propsReply.isError()) { qWarning() << m_serviceName << "update Media player2 prop error:" << "Error message:" << propsReply.error().name() << propsReply.error().message(); return; } updateMediaPlayer2PropsFromMap(propsReply.value()); if(!fetch) { m_mp2PropsReceived = true; } } void PlayerItemPrivate::updateMediaPlayer2PropsFromMap(const QVariantMap &map) { QVector updateProperties; if(map.find(QStringLiteral("CanQuit")) != map.constEnd()) { m_mp2Props.m_canQuit = map.value(QStringLiteral("CanQuit")).toBool(); updateProperties << MprisProperties::CanQuit; } if(map.find(QStringLiteral("Fullscreen")) != map.constEnd()) { m_mp2Props.m_fullScreen = map.value(QStringLiteral("Fullscreen")).toBool(); updateProperties << MprisProperties::FullScreen; } if(map.find(QStringLiteral("CanSetFullscreen")) != map.constEnd()) { m_mp2Props.m_canSetFullScreen = map.value(QStringLiteral("CanSetFullscreen")).toBool(); updateProperties << MprisProperties::CanSetFullScreen; } if(map.find(QStringLiteral("CanRaise")) != map.constEnd()) { m_mp2Props.m_canRaise = map.value(QStringLiteral("CanRaise")).toBool(); updateProperties << MprisProperties::CanRaise; } if(map.find(QStringLiteral("HasTrackList")) != map.constEnd()) { m_mp2Props.m_hasTrackList = map.value(QStringLiteral("HasTrackList")).toBool(); updateProperties << MprisProperties::HasTrackList; } if(map.find(QStringLiteral("Identity")) != map.constEnd()) { m_mp2Props.m_identity = map.value(QStringLiteral("Identity")).toString(); updateProperties << MprisProperties::Identity; } if(map.find(QStringLiteral("DesktopEntry")) != map.constEnd()) { m_mp2Props.m_desktopEntry = map.value(QStringLiteral("DesktopEntry")).toString(); updateProperties << MprisProperties::DesktopEntry; } if(map.find(QStringLiteral("SupportedUriSchemes")) != map.constEnd()) { m_mp2Props.m_supportedUriSchemes = map.value(QStringLiteral("SupportedUriSchemes")).toStringList(); updateProperties << MprisProperties::SupportedUriSchemes; } if(map.find(QStringLiteral("SupportedMimeTypes")) != map.constEnd()) { m_mp2Props.m_supportedMimeTypes = map.value(QStringLiteral("SupportedMimeTypes")).toStringList(); updateProperties << MprisProperties::SupportedMimeTypes; } if(!updateProperties.isEmpty()) { Q_EMIT q->dataChanged(m_serviceName, updateProperties); } } void PlayerItemPrivate::updateMediaPlayer2PlayerProps(QDBusPendingCallWatcher *watcher) { QDBusPendingReply propsReply = *watcher; watcher->deleteLater(); bool fetch = watcher->property("fetch").toBool(); if(m_mp2PlayerPropsReceived && fetch) { return; } if (propsReply.isError()) { qWarning() << m_serviceName << "update Media player2 player prop error:" << "Error message:" << propsReply.error().name() << propsReply.error().message(); return; } updateMediaPlayer2PlayerPropsFromMap(propsReply.value()); if(!fetch) { m_mp2PlayerPropsReceived = true; } } void PlayerItemPrivate::updateMediaPlayer2PlayerPropsFromMap(const QVariantMap &map) { QVector updateProperties; if(map.find(QStringLiteral("PlaybackStatus")) != map.constEnd()) { m_mp2PlayerProps.m_playbackStatus = map.value(QStringLiteral("PlaybackStatus")).toString(); updateProperties << MprisProperties::PlaybackStatus; } if(map.find(QStringLiteral("LoopStatus")) != map.constEnd()) { m_mp2PlayerProps.m_loopStatus = map.value(QStringLiteral("LoopStatus")).toString(); updateProperties << MprisProperties::LoopStatus; } if(map.find(QStringLiteral("Rate")) != map.constEnd()) { m_mp2PlayerProps.m_rate = map.value(QStringLiteral("Rate")).toDouble(); updateProperties << MprisProperties::Rate; } if(map.find(QStringLiteral("Shuffle")) != map.constEnd()) { m_mp2PlayerProps.m_shuffle = map.value(QStringLiteral("Shuffle")).toBool(); updateProperties << MprisProperties::Shuffle; } if(map.find(QStringLiteral("Metadata")) != map.constEnd()) { QDBusArgument arg = map.value(QStringLiteral("Metadata")).value(); m_mp2PlayerProps.m_metaData.clear(); arg >> m_mp2PlayerProps.m_metaData; updateProperties << MprisProperties::MetaData; updateProperties << MprisProperties::IsCurrentMediaVideo; updateProperties << MprisProperties::IsCurrentMediaAudio; } if(map.find(QStringLiteral("Volume")) != map.constEnd()) { m_mp2PlayerProps.m_volume = map.value(QStringLiteral("Volume")).toDouble(); updateProperties << MprisProperties::Volume; } if(map.find(QStringLiteral("Position")) != map.constEnd()) { m_mp2PlayerProps.m_position = map.value(QStringLiteral("Position")).toLongLong(); updateProperties << MprisProperties::Position; } if(map.find(QStringLiteral("MinimumRate")) != map.constEnd()) { m_mp2PlayerProps.m_minimumRate = map.value(QStringLiteral("MinimumRate")).toDouble(); updateProperties << MprisProperties::MinimumRate; } if(map.find(QStringLiteral("MaximumRate")) != map.constEnd()) { m_mp2PlayerProps.m_maximumRate = map.value(QStringLiteral("MaximumRate")).toDouble(); updateProperties << MprisProperties::MaximumRate; } if(map.find(QStringLiteral("CanGoNext")) != map.constEnd()) { m_mp2PlayerProps.m_canGoNext = map.value(QStringLiteral("CanGoNext")).toBool(); updateProperties << MprisProperties::CanGoNext; } if(map.find(QStringLiteral("CanGoPrevious")) != map.constEnd()) { m_mp2PlayerProps.m_canGoPrevious = map.value(QStringLiteral("CanGoPrevious")).toBool(); updateProperties << MprisProperties::CanGoPrevious; } if(map.find(QStringLiteral("CanPlay")) != map.constEnd()) { m_mp2PlayerProps.m_canPlay = map.value(QStringLiteral("CanPlay")).toBool(); updateProperties << MprisProperties::CanPlay; } if(map.find(QStringLiteral("CanPause")) != map.constEnd()) { m_mp2PlayerProps.m_canPause = map.value(QStringLiteral("CanPause")).toBool(); updateProperties << MprisProperties::CanPause; } if(map.find(QStringLiteral("CanSeek")) != map.constEnd()) { m_mp2PlayerProps.m_canSeek = map.value(QStringLiteral("CanSeek")).toBool(); updateProperties << MprisProperties::CanSeek; } if(map.find(QStringLiteral("CanControl")) != map.constEnd()) { m_mp2PlayerProps.m_canControl = map.value(QStringLiteral("CanControl")).toBool(); updateProperties << MprisProperties::CanControl; } if(!updateProperties.isEmpty()) { Q_EMIT q->dataChanged(m_serviceName, updateProperties); } } void PlayerItemPrivate::propertiesChanged(const QString &interface, const QVariantMap &changedProperties, const QStringList &invalidatedProperties) { if(interface == OrgMprisMediaPlayer2Interface::staticInterfaceName()) { if(invalidatedProperties.isEmpty()) { updateMediaPlayer2PropsFromMap(changedProperties); } else { QDBusPendingCall async = m_propsIface->GetAll(OrgMprisMediaPlayer2Interface::staticInterfaceName()); auto watcher = new QDBusPendingCallWatcher(async, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, &PlayerItemPrivate::updateMediaPlayer2Props); } } else if (interface == OrgMprisMediaPlayer2PlayerInterface::staticInterfaceName()) { if(invalidatedProperties.isEmpty()) { updateMediaPlayer2PlayerPropsFromMap(changedProperties); } else { QDBusPendingCall async = m_propsIface->GetAll(OrgMprisMediaPlayer2PlayerInterface::staticInterfaceName()); auto watcher = new QDBusPendingCallWatcher(async, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, &PlayerItemPrivate::updateMediaPlayer2PlayerProps); } } } void PlayerItemPrivate::onSeeked(qint64 position) { m_mp2PlayerProps.m_position = position; Q_EMIT q->dataChanged(m_serviceName, {MprisProperties::Position}); } PlayerItem::PlayerItem(const QString &service, QObject *parent) : QObject(parent), d(new PlayerItemPrivate(service, this)) { } uint PlayerItem::pid() { return d->m_pid; } bool PlayerItem::canQuit() { return d->m_mp2Props.m_canQuit; } bool PlayerItem::fullScreen() { return d->m_mp2Props.m_fullScreen; } bool PlayerItem::canSetFullScreen() { return d->m_mp2Props.m_canSetFullScreen; } bool PlayerItem::canRaise() { return d->m_mp2Props.m_canRaise; } bool PlayerItem::hasTrackList() { return d->m_mp2Props.m_hasTrackList; } QString PlayerItem::identity() { return d->m_mp2Props.m_identity; } QString PlayerItem::desktopEntry() { return d->m_mp2Props.m_desktopEntry; } QStringList PlayerItem::supportedUriSchemes() { return d->m_mp2Props.m_supportedUriSchemes; } QStringList PlayerItem::supportedMimeTypes() { return d->m_mp2Props.m_supportedMimeTypes; } void PlayerItem::raise() { d->m_mprisIface->Raise(); } void PlayerItem::quit() { d->m_mprisIface->Quit(); } QString PlayerItem::playbackStatus() { return d->m_mp2PlayerProps.m_playbackStatus; } QString PlayerItem::loopStatus() { return d->m_mp2PlayerProps.m_loopStatus; } double PlayerItem::rate() { return d->m_mp2PlayerProps.m_rate; } bool PlayerItem::shuffle() { return d->m_mp2PlayerProps.m_shuffle; } QVariantMap PlayerItem::metaData() { return d->m_mp2PlayerProps.m_metaData; } bool PlayerItem::isCurrentMediaVideo() { QUrl url(d->m_mp2PlayerProps.m_metaData.value("xesam:url").toString()); QMimeDatabase mdb; return mdb.mimeTypeForUrl(url).name().startsWith("video"); } bool PlayerItem::isCurrentMediaAudio() { QUrl url(d->m_mp2PlayerProps.m_metaData.value("xesam:url").toUrl()); QMimeDatabase mdb; return mdb.mimeTypeForUrl(url).name().startsWith("audio"); } double PlayerItem::volume() { return d->m_mp2PlayerProps.m_volume; } quint64 PlayerItem::position() { return d->m_mp2PlayerProps.m_position; } double PlayerItem::minimumRate() { return d->m_mp2PlayerProps.m_minimumRate; } double PlayerItem::maximumRate() { return d->m_mp2PlayerProps.m_maximumRate; } bool PlayerItem::canGoNext() { return d->m_mp2PlayerProps.m_canGoNext; } bool PlayerItem::canGoPrevious() { return d->m_mp2PlayerProps.m_canGoPrevious; } bool PlayerItem::canPlay() { return d->m_mp2PlayerProps.m_canPlay; } bool PlayerItem::canPause() { return d->m_mp2PlayerProps.m_canPause; } bool PlayerItem::canSeek() { return d->m_mp2PlayerProps.m_canSeek; } bool PlayerItem::canControl() { return d->m_mp2PlayerProps.m_canControl; } void PlayerItem::next() { d->m_mprisPlayerIface->Next(); } void PlayerItem::previous() { d->m_mprisPlayerIface->Previous(); } void PlayerItem::pause() { d->m_mprisPlayerIface->Pause(); } void PlayerItem::playPause() { d->m_mprisPlayerIface->PlayPause(); } void PlayerItem::stop() { d->m_mprisPlayerIface->Stop(); } void PlayerItem::play() { d->m_mprisPlayerIface->Play(); } void PlayerItem::seek(qint64 offset) { d->m_mprisPlayerIface->Seek(offset); } void PlayerItem::setPosition(const QString &trackID, qint64 position) { d->m_mprisPlayerIface->SetPosition(QDBusObjectPath(trackID), position); } void PlayerItem::openUri(const QString &uri) { d->m_mprisPlayerIface->OpenUri(uri); } bool PlayerItem::valid() { return d->m_valid; } #include "player-item.moc" ukui-quick/modules/window-thumbnail/mpris/window-thumbnail-mpris-model.h0000664000175000017500000000467715153755732025603 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef UKUI_QUICK_WINDOW_THUMBNAIL_MPRIS_MODEL_H #define UKUI_QUICK_WINDOW_THUMBNAIL_MPRIS_MODEL_H #include #include "properties.h" class WindowThumbnailMprisModelPrivate; class WindowThumbnailMprisModel : public QSortFilterProxyModel { Q_OBJECT Q_PROPERTY(QString winID READ winID WRITE setWinID) Q_PROPERTY(uint pid READ pid WRITE setPid) Q_PROPERTY(QString desktopEntry READ desktopEntry WRITE setDesktopEntry) Q_PROPERTY(int count READ rowCount NOTIFY countChanged) Q_PROPERTY(bool isCurrentMediaVideo READ isCurrentMediaVideo NOTIFY isCurrentMediaVideoChanged) Q_PROPERTY(bool isCurrentMediaAudio READ isCurrentMediaAudio NOTIFY isCurrentMediaAudioChanged) public: explicit WindowThumbnailMprisModel(QObject *parent = nullptr); ~WindowThumbnailMprisModel(); QString winID(); void setWinID(const QString &wid); QString desktopEntry(); void setDesktopEntry(const QString &desktopEntry); bool isCurrentMediaVideo(); bool isCurrentMediaAudio(); int count() const; uint pid(); void setPid(const uint &pid); public Q_SLOTS: void operation(const QModelIndex &index, MprisProperties::Operations operation, const QVariantList &args); Q_SIGNALS: void countChanged(); void isCurrentMediaVideoChanged(); void isCurrentMediaAudioChanged(); protected: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; private: void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()); WindowThumbnailMprisModelPrivate *d = nullptr; }; #endif //UKUI_QUICK_WINDOW_THUMBNAIL_MPRIS_MODEL_H ukui-quick/modules/window-thumbnail/mpris/player-item.h0000664000175000017500000000442215153755732022301 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef UKUI_QUICK_PLAYER_ITEM_H #define UKUI_QUICK_PLAYER_ITEM_H #include #include "properties.h" class PlayerItemPrivate; class PlayerItem : public QObject { Q_OBJECT public: explicit PlayerItem(const QString &service, QObject *parent = nullptr); uint pid(); bool valid(); //media player2 properties bool canQuit(); bool fullScreen(); bool canSetFullScreen(); bool canRaise(); bool hasTrackList(); QString identity(); QString desktopEntry(); QStringList supportedUriSchemes(); QStringList supportedMimeTypes(); //media player2 methods void raise(); void quit(); //media player2 player properties QString playbackStatus(); QString loopStatus(); double rate(); bool shuffle(); QVariantMap metaData(); bool isCurrentMediaVideo(); bool isCurrentMediaAudio(); double volume(); quint64 position(); double minimumRate(); double maximumRate(); bool canGoNext(); bool canGoPrevious(); bool canPlay(); bool canPause(); bool canSeek(); bool canControl(); //media player2 player methods void next(); void previous(); void pause(); void playPause(); void stop(); void play(); void seek(qint64 offset); void setPosition(const QString &trackID, qint64 position); void openUri(const QString &uri); Q_SIGNALS: void dataChanged(const QString &service, QVector); private: PlayerItemPrivate *d; }; #endif //UKUI_QUICK_PLAYER_ITEM_H ukui-quick/modules/window-thumbnail/mpris/player-items-model.h0000664000175000017500000000342415153755732023563 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef UKUI_QUICK_PLAYER_ITEMS_MODEL_H #define UKUI_QUICK_PLAYER_ITEMS_MODEL_H #include #include "properties.h" class MprisPlayerCollecter; class PlayerItemsModel : public QAbstractListModel { Q_OBJECT public: static PlayerItemsModel *self(); QHash roleNames() const override; QModelIndex index(int row, int column, const QModelIndex &parent) const override; int rowCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; void operation(const QModelIndex &index, MprisProperties::Operations operation, const QVariantList &args); private Q_SLOTS: void onPlayerAdded(const QString &service, uint pid); void onPlayerRemoved(const QString &service, uint pid); void onDataChanged(const QString &service, const QVector &properties); private: explicit PlayerItemsModel(QObject *parent = nullptr); QStringList m_services; }; #endif //UKUI_QUICK_PLAYER_ITEMS_MODEL_H ukui-quick/modules/window-thumbnail/mpris/window-thumbnail-mpris-model.cpp0000664000175000017500000001036015153755732026120 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #include "window-thumbnail-mpris-model.h" #include #include "window-manager.h" #include "player-items-model.h" #include "properties.h" class WindowThumbnailMprisModelPrivate { public: QString m_winID; uint m_pid; QString m_desktopEntry; PlayerItemsModel *m_sourceModel = nullptr; }; WindowThumbnailMprisModel::WindowThumbnailMprisModel(QObject *parent) : QSortFilterProxyModel(parent), d(new WindowThumbnailMprisModelPrivate) { d->m_sourceModel = PlayerItemsModel::self(); QSortFilterProxyModel::setSourceModel(d->m_sourceModel); connect(this, &WindowThumbnailMprisModel::rowsInserted, this, &WindowThumbnailMprisModel::countChanged); connect(this, &WindowThumbnailMprisModel::rowsRemoved, this, &WindowThumbnailMprisModel::countChanged); connect(this, &WindowThumbnailMprisModel::modelReset, this, &WindowThumbnailMprisModel::countChanged); connect(this, &WindowThumbnailMprisModel::dataChanged, this, &WindowThumbnailMprisModel::onDataChanged); } WindowThumbnailMprisModel::~WindowThumbnailMprisModel() { if(d) { delete d; d = nullptr; } } bool WindowThumbnailMprisModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { const QModelIndex sourceIndex = sourceModel()->index(sourceRow, 0, sourceParent); return sourceIndex.data(MprisProperties::Pid).toUInt() == d->m_pid && d->m_pid != 0 && sourceIndex.data(MprisProperties::Valid).toBool() && sourceIndex.data(MprisProperties::CanControl).toBool() && (sourceIndex.data(MprisProperties::CanGoNext).toBool() || sourceIndex.data(MprisProperties::CanGoPrevious).toBool() || sourceIndex.data(MprisProperties::CanPlay).toBool() || sourceIndex.data(MprisProperties::CanPause).toBool()); } QString WindowThumbnailMprisModel::winID() { return d->m_winID; } void WindowThumbnailMprisModel::setWinID(const QString &wid) { d->m_winID = wid; d->m_pid = UkuiQuick::WindowManager::pid(wid); invalidate(); Q_EMIT countChanged(); } QString WindowThumbnailMprisModel::desktopEntry() { return d->m_desktopEntry; } void WindowThumbnailMprisModel::setDesktopEntry(const QString &desktopEntry) { d->m_desktopEntry = desktopEntry; } void WindowThumbnailMprisModel::operation(const QModelIndex &index, MprisProperties::Operations operation, const QVariantList &args) { d->m_sourceModel->operation(QSortFilterProxyModel::mapToSource(index), operation, args); } int WindowThumbnailMprisModel::count() const { return QSortFilterProxyModel::rowCount({}); } uint WindowThumbnailMprisModel::pid() { return d->m_pid; } void WindowThumbnailMprisModel::setPid(const uint& pid) { d->m_pid = pid; invalidate(); Q_EMIT countChanged(); } bool WindowThumbnailMprisModel::isCurrentMediaVideo() { return data(index(0, 0, QModelIndex()), MprisProperties::IsCurrentMediaVideo).toBool(); } bool WindowThumbnailMprisModel::isCurrentMediaAudio() { return data(index(0, 0, QModelIndex()), MprisProperties::IsCurrentMediaAudio).toBool(); } void WindowThumbnailMprisModel::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { if (roles.contains(MprisProperties::IsCurrentMediaVideo)) { Q_EMIT isCurrentMediaVideoChanged(); } if (roles.contains(MprisProperties::IsCurrentMediaAudio)) { Q_EMIT isCurrentMediaAudioChanged(); } } ukui-quick/modules/window-thumbnail/mpris/org.mpris.MediaPlayer2.xml0000664000175000017500000000265615153755732024626 0ustar fengfeng ukui-quick/modules/window-thumbnail/mpris/player-items-model.cpp0000664000175000017500000001740715153755732024124 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #include #include #include #include "player-items-model.h" #include "player-item.h" #include "mpris-player-collecter.h" static PlayerItemsModel *globalInstance = nullptr; static std::once_flag flag; PlayerItemsModel *PlayerItemsModel::self() { std::call_once(flag, [&] { globalInstance = new PlayerItemsModel(); }); return globalInstance; } PlayerItemsModel::PlayerItemsModel(QObject *parent) : QAbstractListModel(parent) { m_services = MprisPlayerCollecter::self()->playerServices(); connect(MprisPlayerCollecter::self(), &MprisPlayerCollecter::playerAdded, this, &PlayerItemsModel::onPlayerAdded); connect(MprisPlayerCollecter::self(), &MprisPlayerCollecter::playerRemoved, this, &PlayerItemsModel::onPlayerRemoved); connect(MprisPlayerCollecter::self(), &MprisPlayerCollecter::dataChanged, this, &PlayerItemsModel::onDataChanged); } QHash PlayerItemsModel::roleNames() const { QHash roles; QMetaEnum e = QMetaEnum::fromType(); for (int i = 0; i < e.keyCount(); ++i) { roles.insert(e.value(i), e.key(i)); } return roles; } QModelIndex PlayerItemsModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || column != 0) { return {}; } return createIndex(row, column, nullptr); } int PlayerItemsModel::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : m_services.count(); } QVariant PlayerItemsModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= m_services.count()) { return {}; } if(role == MprisProperties::Pid) { return MprisPlayerCollecter::self()->pidOfService(m_services.at(index.row())); } PlayerItem *item = MprisPlayerCollecter::self()->item(m_services.at(index.row())); switch (role) { case MprisProperties::Valid: return item->valid(); case MprisProperties::CanQuit: return item->canQuit(); case MprisProperties::FullScreen: return item->fullScreen(); case MprisProperties::CanSetFullScreen: return item->canSetFullScreen(); case MprisProperties::CanRaise: return item->canRaise(); case MprisProperties::HasTrackList: return item->hasTrackList(); case MprisProperties::Identity: return item->identity(); case MprisProperties::DesktopEntry: return item->desktopEntry(); case MprisProperties::SupportedUriSchemes: return item->supportedUriSchemes(); case MprisProperties::SupportedMimeTypes: return item->supportedMimeTypes(); case MprisProperties::PlaybackStatus: return item->playbackStatus(); case MprisProperties::LoopStatus: return item->loopStatus(); case MprisProperties::Rate: return item->rate(); case MprisProperties::Shuffle: return item->shuffle(); case MprisProperties::MetaData: return item->metaData(); case MprisProperties::Volume: return item->volume(); case MprisProperties::Position: return item->position(); case MprisProperties::MinimumRate: return item->minimumRate(); case MprisProperties::MaximumRate: return item->maximumRate(); case MprisProperties::CanGoNext: return item->canGoNext(); case MprisProperties::CanGoPrevious: return item->canGoPrevious(); case MprisProperties::CanPlay: return item->canPlay(); case MprisProperties::CanPause: return item->canPause(); case MprisProperties::CanSeek: return item->canSeek(); case MprisProperties::CanControl: return item->canControl(); case MprisProperties::IsCurrentMediaVideo: return item->isCurrentMediaVideo(); case MprisProperties::IsCurrentMediaAudio: return item->isCurrentMediaAudio(); default: return {}; } Q_UNREACHABLE(); } void PlayerItemsModel::onPlayerAdded(const QString &service, uint pid) { Q_UNUSED(pid); beginInsertRows(QModelIndex(), m_services.size(), m_services.size()); m_services.append(service); endInsertRows(); } void PlayerItemsModel::onPlayerRemoved(const QString &service, uint pid) { Q_UNUSED(pid); int index = m_services.indexOf(service); beginRemoveRows(QModelIndex(), index, index); m_services.removeAll(service); endRemoveRows(); } void PlayerItemsModel::operation(const QModelIndex &index, MprisProperties::Operations operation, const QVariantList &args) { if (!index.isValid() || index.row() >= m_services.count()) { return; } switch (operation) { case MprisProperties::Raise: return MprisPlayerCollecter::self()->item(m_services.at(index.row()))->pause(); case MprisProperties::Quit: return MprisPlayerCollecter::self()->item(m_services.at(index.row()))->quit(); case MprisProperties::Next: return MprisPlayerCollecter::self()->item(m_services.at(index.row()))->next(); case MprisProperties::Previous: return MprisPlayerCollecter::self()->item(m_services.at(index.row()))->previous(); case MprisProperties::Pause: return MprisPlayerCollecter::self()->item(m_services.at(index.row()))->pause(); case MprisProperties::PlayPause: return MprisPlayerCollecter::self()->item(m_services.at(index.row()))->playPause(); case MprisProperties::Stop: return MprisPlayerCollecter::self()->item(m_services.at(index.row()))->stop(); case MprisProperties::Play: return MprisPlayerCollecter::self()->item(m_services.at(index.row()))->play(); case MprisProperties::Seek: if(args.isEmpty()) { qWarning() << "Seek without offset!"; return; } return MprisPlayerCollecter::self()->item(m_services.at(index.row()))->seek(args.at(0).toLongLong()); case MprisProperties::SetPosition: if(args.size() < 2) { qWarning() << "setPosition without enough args!"; return; } return MprisPlayerCollecter::self()->item(m_services.at(index.row()))->setPosition(args.at(0).toString(), args.at(1).toLongLong()); case MprisProperties::OpenUri: if(args.isEmpty()) { qWarning() << "OpenUri without uri!"; return; } return MprisPlayerCollecter::self()->item(m_services.at(index.row()))->openUri(args.at(0).toString()); } Q_UNREACHABLE(); } void PlayerItemsModel::onDataChanged(const QString &service, const QVector &properties) { int row = m_services.indexOf(service); if(row >= 0) { QModelIndex changedIndex = index(row ,0, {}); Q_EMIT dataChanged(changedIndex, changedIndex, properties); } } ukui-quick/modules/window-thumbnail/pipewire-source-item.h0000664000175000017500000000461315153755732022777 0ustar fengfeng/* Render a PipeWire stream into a QtQuick scene as a standard Item SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez SPDX-FileContributor: iaom SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #ifndef PIPEWIRESOURCEITEM_H #define PIPEWIRESOURCEITEM_H #include #include #include #include #include #include struct DmaBufPlane; class PipeWireSourceStream; class QSGTexture; class QOpenGLTexture; typedef void *EGLImage; class PipeWireSourceItem : public QQuickItem { Q_OBJECT Q_PROPERTY(uint nodeId READ nodeId WRITE setNodeId NOTIFY nodeIdChanged) Q_PROPERTY(qreal paintedWidth READ paintedWidth NOTIFY paintedSizeChanged) Q_PROPERTY(qreal paintedHeight READ paintedHeight NOTIFY paintedSizeChanged) Q_PROPERTY(bool fixHeight READ fixHeight WRITE setFixHeight NOTIFY fixHeightChanged) Q_PROPERTY(bool fixWidth READ fixWidth WRITE setFixWidth NOTIFY fixWidthChanged) public: PipeWireSourceItem(QQuickItem *parent = nullptr); ~PipeWireSourceItem() override; QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *data) override; Q_SCRIPTABLE QString error() const; void setNodeId(uint nodeId); uint nodeId() const { return m_nodeId; } void componentComplete() override; void releaseResources() override; bool fixHeight() const; void setFixHeight(bool fixHeight); bool fixWidth() const; void setFixWidth(bool fixWidth); qreal paintedWidth() const; qreal paintedHeight() const; Q_SIGNALS: void nodeIdChanged(uint nodeId); void fixHeightChanged(); void fixWidthChanged(); void paintedSizeChanged(); private: void itemChange(ItemChange change, const ItemChangeData &data) override; void updateTextureDmaBuf(const QVector &plane, uint32_t format); void updateTextureImage(const QImage &image); uint m_nodeId = 0; std::function m_createNextTexture; QScopedPointer m_stream; QScopedPointer m_texture; EGLImage m_image = nullptr; bool m_needsRecreateTexture = false; bool m_fixHeight = false; bool m_fixWidth = false; QSizeF m_paintedSize; }; #endif // PIPEWIRESOURCEITEM_H ukui-quick/modules/CMakeLists.txt0000664000175000017500000000014415153755732016013 0ustar fengfengcmake_minimum_required(VERSION 3.14) project(ukui-quick-modules) add_subdirectory(window-thumbnail)ukui-quick/cmake-extend/0000775000175000017500000000000015153755732014151 5ustar fengfengukui-quick/cmake-extend/CMakeLists.txt0000664000175000017500000000264415153755732016717 0ustar fengfengcmake_minimum_required(VERSION 3.14) project(cmake-extend) set(MODULES_CMAKE_EXTEND_INSTALL_DIR "/usr/share/cmake/ukui-quick-cmake-extend/modules/") set(CONFIG_CMAKE_EXTEND_INSTALL_DIR "/usr/share/cmake/ukui-quick-cmake-extend/cmake/") set(STYLE_CMAKE_EXTEND_INSTALL_DIR "/usr/share/cmake/ukui-quick-cmake-extend/style/") file(GLOB installCMakeExtendFiles ${CMAKE_CURRENT_SOURCE_DIR}/modules/*) install(FILES ${installCMakeExtendFiles} DESTINATION ${MODULES_CMAKE_EXTEND_INSTALL_DIR}) file(GLOB installCMakeExtendStyleFiles ${CMAKE_CURRENT_SOURCE_DIR}/style/*.css) install(FILES ${installCMakeExtendStyleFiles} DESTINATION ${STYLE_CMAKE_EXTEND_INSTALL_DIR}) include(CMakePackageConfigHelpers) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/ukui-quick-cmake-extend-config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-cmake-extend-config.cmake" INSTALL_DESTINATION ${CONFIG_CMAKE_EXTEND_INSTALL_DIR} PATH_VARS MODULES_CMAKE_EXTEND_INSTALL_DIR STYLE_CMAKE_EXTEND_INSTALL_DIR ) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-cmake-extend-config-version.cmake VERSION ${UKUI_QUICK_VERSION} COMPATIBILITY SameMajorVersion ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-cmake-extend-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-cmake-extend-config-version.cmake DESTINATION ${CONFIG_CMAKE_EXTEND_INSTALL_DIR}) ukui-quick/cmake-extend/modules/0000775000175000017500000000000015153755732015621 5ustar fengfengukui-quick/cmake-extend/modules/UkuiQDocAddQch.cmake0000664000175000017500000004532015153755732021360 0ustar fengfeng#[=======================================================================[.rst: 当前模块提供了 ‘ukui_qdoc_add_qch’ 函数用于自动化生成Qt风格的API文档。 以及ukui_install_qch_export 函数用于导出文档的CMake目标,用于查找和创建指向QCH文件的链接 ukui_qdoc_add_qch( NAME DOC_NAME VERSION [NAMESPACE ] QCH_INSTALL_DESTINATION TAGFILE_INSTALL_DESTINATION [CONFIG_TEMPLATE ] [STYLE_FILE ] [SOURCES [ [...]]] [SOURCE_DIRS

[ [...]]] [HEADERS [ [...]]] [HEADER_DIRS [ [...]]] [INCLUDE_DIRS [ [...]]] [IMAGES [ [...]]] [IMAGE_DIRS [ [...]]] [EXAMPLES [ [...]]] [EXAMPLE_DIRS [ [...]]] [EXCLUDE_FILES [ [...]]] [EXCLUDE_DIRS [ [...]]] [DEPEND_QCHS [ [...]]] ) ’NAME’ 用于显示的名称 ‘DOC_NAME’ 文档名称 ‘VERSION’ 版本号 ‘NAMESPACE’ 命名空间 ‘QCH_INSTALL_DESTINATION’ qch 文件安装路径 ‘TAGFILE_INSTALL_DESTINATION’ tags 文件安装路径 ‘CONFIG_TEMPLATE’ 配置模板文件,可参考QDOCConfig.qdocconf.in 编写 ‘STYLE_FILE’ 指定文档样式文件,如不显示指定,默认(default.css)和Qt文档风格一致 ‘SOURCES’ 源码文件,可以和SOURCE_DIRS 一起使用,指定源码文件也需要同步指定对应的头文件 ‘SOURCE_DIRS’ 源文件目录 ‘HEADERS’ 头文件,可以和HEADER_DIRS 一起使用 ‘HEADER_DIRS’ 头文件目录 ‘INCLUDE_DIRS’ 包含目录 ‘IMAGES’ 图片文件,文档注释中使用的图片文件 ‘IMAGE_DIRS’ 图片文件目录 ‘EXAMPLES’ 示例文件 ‘EXAMPLE_DIRS’ 示例文件目录 ‘EXCLUDE_FILES’ 扫描注释时排除的文件 ‘EXCLUDE_DIRS’ 排除目录 ‘DEPEND_QCHS’ 依赖的 qch 文件, 链接指定模块的文档 ‘DEPEND_DIRS’ 一般与DEPEND_QCHS一起使用, 如果 DEPEND_QCHS 指定的模块文档在默认目录下(/usr/share/qt5/doc/),无需显式指定当前变量; 如果依赖的模块文档不在默认目录下,需要通过DEPEND_DIRS 指定依赖的文档目录, 然后通过DEPEND_QCHS指定模块即可 ‘INDEX_FILES’ 文档索引文件,通过指定依赖的文档索引,来建立和文档的链接, 一般不和DEPEND_QCHS 一起使用。 #use example qdoc_add_qch(ukui-_quick-core-doc NAME "ukui-quick-core" DOC_NAME ukui-quick-core VERSION ${UKUI_QUICK_VERSION} NAMESPACE org.ukui. QCH_INSTALL_DESTINATION "/usr/share/qt5/doc" SOURCE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} HEADER_DIRS ${CMAKE_CURRENT_SOURCE_DIR}) :: ukui_install_qch_export( TARGETS [ [ [...]]] FILE DESTINATION [COMPONENT ] ) 'TARGETS' 指定要导出的文档目标 ‘FILE’ 指定生成的 CMake 文件名 ‘DESTINATION’ 指定 CMake 文件的安装目录 ‘COMPONENT’ 指定安装规则关联的组件名称 Example usage: ukui_install_qch_export( TARGETS MyLib_QCH FILE MyLibQCHTargets.cmake DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/cmake/MyLib" COMPONENT Devel ) #]=======================================================================] include(FeatureSummary) if (TARGET Qt5::Core) set(QT_MAJOR_VERSION 5) else () set(QT_MAJOR_VERSION 6) endif () function(ukui_qdoc_add_qch target_name) set(options VERBOSE) set(oneValueArgs NAME DOC_NAME VERSION NAMESPACE QCH_INSTALL_DESTINATION TAGFILE_INSTALL_DESTINATION CONFIG_TEMPLATE STYLE_FILE) set(multiValueArgs SOURCES SOURCE_DIRS HEADERS HEADER_DIRS INCLUDE_DIRS IMAGES IMAGE_DIRS EXAMPLES EXAMPLE_DIRS EXCLUDE_FILES EXCLUDE_DIRS DEPEND_QCHS DEPEND_DIRS INDEX_FILES) cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) #--------------------------------------------------------- # 检查必要参数 #--------------------------------------------------------- foreach (arg_name NAME DOC_NAME VERSION QCH_INSTALL_DESTINATION) if(NOT DEFINED ARGS_${arg_name}) message(FATAL_ERROR "${arg_name} needs to be defined when calling ukui_qdoc_add_qch") endif() endforeach () if(NOT DEFINED ARGS_SOURCE_DIRS AND NOT DEFINED ARGS_SOURCES) message(FATAL_ERROR "SOURCE_DIRS or SOURCES needs to be defined when calling ukui_qdoc_add_qch") endif() if(NOT DEFINED ARGS_HEADER_DIRS AND NOT DEFINED ARGS_HEADERS) message(FATAL_ERROR "HEADER_DIRS or HEADERS needs to be defined when calling ukui_qdoc_add_qch") endif() #--------------------------------------------------------- # 查找qdoc qhelpgenerator 工具 #--------------------------------------------------------- if (QT_VERSION_MAJOR EQUAL 5) find_program(QDoc_EXECUTABLE NAMES qdoc qdoc-qt5) set_package_properties(QDoc_EXECUTABLE PROPERTIES TYPE REQUIRED PURPOSE "Needed for API dox QCH file generation" DESCRIPTION "Part of Qt5 tools" ) find_program(QHelpGenerator_EXECUTABLE NAMES qhelpgenerator qhelpgenerator-qt5) set_package_properties(QHelpGenerator_EXECUTABLE PROPERTIES TYPE REQUIRED PURPOSE "Needed for API dox QCH file generation" DESCRIPTION "Part of Qt5 tools" ) else() find_package(Qt6 COMPONENTS ToolsTools CONFIG REQUIRED) set_package_properties(Qt6ToolsTools PROPERTIES TYPE REQUIRED PURPOSE "Needed for API dox QCH file generation" DESCRIPTION "qhelpgenerator from Qt6 tools" ) if(TARGET Qt6::qdoc) get_target_property(QDoc_EXECUTABLE Qt6::qdoc LOCATION) endif() if(TARGET Qt6::qhelpgenerator) get_target_property(QHelpGenerator_EXECUTABLE Qt6::qhelpgenerator LOCATION) endif() endif() set(missing_tools) if (NOT QDoc_EXECUTABLE AND NOT TARGET Qt6::qdoc) list(APPEND missing_tools "qdoc") endif() if (NOT QHelpGenerator_EXECUTABLE AND NOT TARGET Qt6::qhelpgenerator) list(APPEND missing_tools "qhelpgenerator") endif() if (missing_tools) message(WARNING "API dox QCH file will not be generated, tools missing: ${missing_tools}!") else() #--------------------------------------------------------- # 配置模板文件参数 #--------------------------------------------------------- set(QDOC_PROJECT_NAME "${ARGS_NAME}") string(REPLACE "-" "" qhp_project_name "${ARGS_NAME}") string(REPLACE " " "" qhp_project_name "${qhp_project_name}") set(QDOC_QHP_PROJECT_NAME "${qhp_project_name}") if (DEFINED ARGS_DOC_NAME) string(TOLOWER "${ARGS_DOC_NAME}" doc_name) string(REGEX REPLACE "[ _-]" "" doc_name "${doc_name}") else() string(TOLOWER "${qhp_project_name}" doc_name) string(REGEX REPLACE "[ _-]" "" doc_name "${doc_name}") endif() set(output_dir "${CMAKE_CURRENT_BINARY_DIR}/doc/${doc_name}") set(QDOC_OUTPUT_DIR "${output_dir}") set(QDOC_NAME "${doc_name}") set(QDOC_PROJECT_VERSION ${ARGS_VERSION}) set(QDOC_NAMESPACE ${ARGS_NAMESPACE}) #qch 安装路径 set(qch_install_destination ${ARGS_QCH_INSTALL_DESTINATION}) #tagfile 安装路径 if (DEFINED ARGS_TAGFILE_INSTALL_DESTINATION) set(tagfile_install_destination ${ARGS_TAGFILE_INSTALL_DESTINATION}) else () set(tagfile_install_destination "${qch_install_destination}/${doc_name}") endif () #index 安装路径 set(index_install_destination "${qch_install_destination}/${doc_name}") #其他模块引用当前index的路径 set(index_depend_path "${qch_install_destination}") #tags set(qdoc_output_tags_file "${output_dir}/${doc_name}.tags") set(QDOC_TAGS_FILE "${qdoc_output_tags_file}") if (DEFINED ARGS_CONFIG_TEMPLATE) set(qdoc_config_template_file ${ARGS_CONFIG_TEMPLATE}) else() set(qdoc_config_template_file "${UKUI_QUICK_CMAKE_EXTEND_DIR}/QDocConfig.qdocconf.in") endif() #style if (DEFINED ARGS_STYLE_FILE) set(qdoc_style_file ${ARGS_STYLE_FILE}) else() set(qdoc_style_file "${UKUI_QUICK_CMAKE_EXTEND_STYLE_DIR}default.css") endif() set(QDOC_HTML_STYLE_FILE "${qdoc_style_file}") get_filename_component(STYLE_NAME "${qdoc_style_file}" NAME) set(qdoc_config_file "${CMAKE_CURRENT_BINARY_DIR}/${doc_name}.qdocconf") #sources source_dirs set(QDOC_SOURCE_DIRS) foreach(source_dir IN LISTS ARGS_SOURCE_DIRS) if (NOT IS_ABSOLUTE ${source_dir}) set(source_dir "${CMAKE_CURRENT_SOURCE_DIR}/${source_dir}") endif() set(QDOC_SOURCE_DIRS "${QDOC_SOURCE_DIRS} \\\n\"${source_dir}\"") endforeach() set(QDOC_SOURCES) foreach(source IN LISTS ARGS_SOURCES) if (NOT IS_ABSOLUTE ${source}) set(source "${CMAKE_CURRENT_SOURCE_DIR}/${source}") endif() list(APPEND sources "${source}") endforeach() foreach(source IN LISTS sources) set(QDOC_SOURCES "${QDOC_SOURCES} \\\n\"${source}\"") endforeach() #headers header_dirs set(QDOC_HEADER_DIRS) foreach(header_dir IN LISTS ARGS_HEADER_DIRS) if (NOT IS_ABSOLUTE ${header_dir}) set(header_dir "${CMAKE_CURRENT_SOURCE_DIR}/${header_dir}") endif() set(QDOC_HEADER_DIRS "${QDOC_HEADER_DIRS} \\\n\"${header_dir}\"") endforeach() set(QDOC_HEADERS) foreach(header IN LISTS ARGS_HEADERS) if (NOT IS_ABSOLUTE ${header}) set(header "${CMAKE_CURRENT_SOURCE_DIR}/${header}") endif() list(APPEND headers "${header}") endforeach() foreach(header IN LISTS headers) set(QDOC_HEADERS "${QDOC_HEADERS} \\\n\"${header}\"") endforeach() #include_dirs set(QDOC_INCLUDE_DIRS) foreach(include_dir IN LISTS ARGS_INCLUDE_DIRS) if (NOT IS_ABSOLUTE ${include_dir}) set(include_dir "${CMAKE_CURRENT_SOURCE_DIR}/${include_dir}") endif() set(QDOC_INCLUDE_DIRS "${QDOC_INCLUDE_DIRS} \\\n\"${include_dir}\"") endforeach() #images set(QDOC_IMAGE_DIRS) foreach(image_dir IN LISTS ARGS_IMAGE_DIRS) if (NOT IS_ABSOLUTE ${image_dir}) set(image_dir "${CMAKE_CURRENT_SOURCE_DIR}/${image_dir}") endif() set(QDOC_IMAGE_DIRS "${QDOC_IMAGE_DIRS} \\\n\"${image_dir}\"") endforeach() set(QDOC_IMAGES) foreach(image IN LISTS ARGS_IMAGES) if (NOT IS_ABSOLUTE ${image}) set(image "${CMAKE_CURRENT_SOURCE_DIR}/${image}") endif() list(APPEND images "${image}") endforeach() foreach(image IN LISTS images) set(QDOC_IMAGES "${QDOC_IMAGES} \\\n\"${image}\"") endforeach() #example_dirs examples 示例目录 set(QDOC_EXAMPLE_DIRS) foreach(example_dir IN LISTS ARGS_EXAMPLE_DIRS) if (NOT IS_ABSOLUTE ${example_dir}) set(example_dir "${CMAKE_CURRENT_SOURCE_DIR}/${example_dir}") endif() set(QDOC_EXAMPLE_DIRS "${QDOC_EXAMPLE_DIRS} \\\n\"${example_dir}\"") endforeach() set(QDOC_EXAMPLES) foreach(example IN LISTS ARGS_EXAMPLES) if (NOT IS_ABSOLUTE ${example}) set(example "${CMAKE_CURRENT_SOURCE_DIR}/${example}") endif() list(APPEND examples "${example}") endforeach() foreach(example IN LISTS examples) set(QDOC_EXAMPLES "${QDOC_EXAMPLES} \\\n\"${example}\"") endforeach() #exclude_files exclude_dirs 需要排除的文件 set(QDOC_EXCLUDE_DIRS) foreach(exclude_dir IN LISTS ARGS_EXCLUDE_DIRS) if (NOT IS_ABSOLUTE ${exclude_dir}) set(exclude_dir "${CMAKE_CURRENT_SOURCE_DIR}/${exclude_dir}") endif() set(QDOC_EXCLUDE_DIRS "${QDOC_EXCLUDE_DIRS} \\\n\"${exclude_dir}\"") endforeach() set(QDOC_EXCLUDE_FILES) foreach(exclude_file IN LISTS ARGS_EXCLUDE_FILES) if (NOT IS_ABSOLUTE ${exclude_file}) set(exclude_file "${CMAKE_CURRENT_SOURCE_DIR}/${exclude_file}") endif() list(APPEND exclude_files "${exclude_file}") endforeach() foreach(exclude_file IN LISTS exclude_files) set(QDOC_EXCLUDE_FILES "${QDOC_EXCLUDE_FILES} \\\n\"${exclude_file}\"") endforeach() #depends_qchs set(QDOC_DEPEND_QCHS) foreach(depend_qch IN LISTS ARGS_DEPEND_QCHS) set(QDOC_DEPEND_QCHS "${QDOC_DEPEND_QCHS} \\\n${depend_qch}") endforeach() #depend_dirs 依赖的目录, 如果依赖文档没有安装在默认目录(/usr/share/qt5/doc/),通过当前变量指定 set(depend_dirs) #增加默认的依赖目录 list(APPEND ARGS_DEPEND_DIRS ${qch_install_destination}) #去重 list(REMOVE_DUPLICATES ARGS_DEPEND_DIRS) foreach(depend_dir IN LISTS ARGS_DEPEND_DIRS) if (NOT IS_ABSOLUTE ${depend_dir}) set(depend_dir "${CMAKE_CURRENT_SOURCE_DIR}/${depend_dir}") endif() list(APPEND depend_dirs "-indexdir" "${depend_dir}") endforeach() set(QDOC_INDEX_FILES) foreach(index_file IN LISTS ARGS_INDEX_FILES) set(QDOC_INDEX_FILES "${QDOC_INDEX_FILES} \\\n${index_file}") endforeach() #---------------------------------- # finally create qdoc config file configure_file( "${qdoc_config_template_file}" "${qdoc_config_file}" @ONLY ) set(qdoc_output_qhp_file "${output_dir}/${doc_name}.qhp") set(qdoc_output_qch_file "${output_dir}/${doc_name}.qch") set(qdoc_output_index_file "${output_dir}/${doc_name}.index") # 开启VERBOSE输出日志到终端,默认输出到文件 if(ARGS_VERBOSE) set(qdoc_log_command) set(qhelp_log_command) else() set(log_file "${CMAKE_CURRENT_BINARY_DIR}/qdoc_log.log") set(qdoc_log_command ">" ${log_file} "2>&1") set(qhelp_log_command ">>" ${log_file} "2>&1") endif() add_custom_command( OUTPUT ${qdoc_output_qhp_file} ${qdoc_output_qch_file} COMMENT "Generating ${_relative_qch_file}, ${_relative_tags_file}" COMMAND cmake -E remove_directory "${output_dir}" COMMAND cmake -E make_directory "${output_dir}" COMMAND ${QDoc_EXECUTABLE} "${qdoc_config_file}" ${depend_dirs} ${qdoc_log_command} COMMAND ${QHelpGenerator_EXECUTABLE} "${qdoc_output_qhp_file}" -o "${qdoc_output_qch_file}" ${qhelp_log_command} DEPENDS ${qdoc_config_file} ${depend_qchs} ) add_custom_target(${target_name} ALL DEPENDS ${qdoc_output_qhp_file} ${qdoc_output_qch_file}) #设置被引用的模块名和安装路径 set_target_properties(${target_name} PROPERTIES DEPEND_QCH_MODULE ${doc_name} DEPEND_QCH_PATH ${index_depend_path} DEPEND_QCH_INDEX "${index_install_destination}/${doc_name}.index" ) # setup installation install(FILES ${qdoc_output_qch_file} DESTINATION ${qch_install_destination} ) install(FILES ${qdoc_output_tags_file} DESTINATION ${tagfile_install_destination} ) install(FILES ${qdoc_output_index_file} DESTINATION ${index_install_destination} ) endif () endfunction() # Imitation ECM ecm_install_qch_export function(ukui_install_qch_export) set(options ) set(oneValueArgs FILE DESTINATION COMPONENT) set(multiValueArgs TARGETS) cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if(NOT DEFINED ARGS_FILE) message(FATAL_ERROR "FILE needs to be defined when calling ukui_qdoc_qch_export().") endif() if(NOT DEFINED ARGS_DESTINATION) message(FATAL_ERROR "DESTINATION needs to be defined when calling ukui_qdoc_qch_export().") endif() set(_content "# This file was generated by ukui_install_qch_export(). DO NOT EDIT! " ) foreach(_target IN LISTS ARGS_TARGETS) set(_target_usable TRUE) if (NOT TARGET ${_target}) message(STATUS "No such target ${_target} when calling ukui_install_qch_export().") set(_target_usable FALSE) else() get_target_property(depend_qch_module ${_target} DEPEND_QCH_MODULE) get_target_property(depend_qch_path ${_target} DEPEND_QCH_PATH) get_target_property(depend_qch_index ${_target} DEPEND_QCH_INDEX) if(NOT depend_qch_module) message(STATUS "No depend_qch_module for ${_target}.") set(_target_usable FALSE) endif() if(NOT depend_qch_path) message(STATUS "No depend_qch_path for ${_target}.") set(_target_usable FALSE) endif() if(NOT depend_qch_index) message(STATUS "No depend_qch_index for ${_target}.") set(_target_usable FALSE) endif() endif() if(_target_usable) set(_content "${_content} if (NOT TARGET ${_target}) add_custom_target(${_target}) set_target_properties(${_target} PROPERTIES DEPEND_QCH_MODULE \"${depend_qch_module}\" DEPEND_QCH_PATH \"${depend_qch_path}\" DEPEND_QCH_INDEX \"${depend_qch_index}\" ) endif() " ) else() message(STATUS "No target exported for ${_target}.") endif() endforeach() if (NOT IS_ABSOLUTE ${ARGS_FILE}) set(ARGS_FILE "${CMAKE_CURRENT_BINARY_DIR}/${ARGS_FILE}") endif() file(GENERATE OUTPUT "${ARGS_FILE}" CONTENT "${_content}" ) if (DEFINED ARGS_COMPONENT) set(_component COMPONENT ${ARGS_COMPONENT}) else() set(_component) endif() install( FILES "${ARGS_FILE}" DESTINATION "${ARGS_DESTINATION}" ${_component} ) endfunction()ukui-quick/cmake-extend/modules/QDocConfig.qdocconf.in0000664000175000017500000000644515153755732021731 0ustar fengfengproject = @QDOC_QHP_PROJECT_NAME@ description = @QDOC_PROJECT_NAME@ API 参考文档 url = qthelp://@QDOC_NAMESPACE@.@QDOC_NAME@/@QDOC_NAME@ version = @QDOC_PROJECT_VERSION@ #源码 sourcedirs += @QDOC_SOURCE_DIRS@ sources += @QDOC_SOURCES@ #头文件目录 headerdirs += @QDOC_HEADER_DIRS@ headers += @QDOC_HEADERS@ #图片 imagedirs += @QDOC_IMAGE_DIRS@ images += @QDOC_IMAGES@ #示例 exampledirs += @QDOC_EXAMPLE_DIRS@ examples += @QDOC_EXAMPLES@ #排除的文件 excludedirs += @QDOC_EXCLUDE_DIRS@ excludefiles += @QDOC_EXCLUDE_FILES@ #包含的文件 includepaths = @QDOC_INCLUDE_DIRS@ headers.fileextensions = "*.ch *.h *.h++ *.hh *.hpp *.hxx" sources.fileextensions = "*.c++ *.cc *.cpp *.cxx *.mm *.qml *.qdoc" examples.fileextensions = "*.cpp *.h *.js *.xq *.svg *.xml *.ui *.qhp *.qhcp *.qml *.css *.glsl" examples.imageextensions = "*.png *.jpg *.gif" #索引文件 indexes += @QDOC_INDEX_FILES@ depends += @QDOC_DEPEND_QCHS@ #输出格式 outputformats = HTML ########################## html 样式 HTML.stylesheets = @QDOC_HTML_STYLE_FILE@ #for including files into the qch file HTML.headerstyles = \ " \n" HTML.endheader = \ "\n" HTML.postheader = \ "\n" \ "
\n"\ "
\n" \ "
\n" \ "
\n"\ "
    \n"\ HTML.postpostheader = \ "
\n"\ "
\n" \ "
\n" \ "
\n" \ "
\n" \ "
\n" \ ############################################### #输出目录 outputdir = @QDOC_OUTPUT_DIR@ tagfile = @QDOC_TAGS_FILE@ outputencoding = UTF-8 sourceencoding = UTF-8 naturallanguage = zh-Hans #qhp qhp.projects = @QDOC_QHP_PROJECT_NAME@ qhp.extraFiles += style/@STYLE_NAME@ qhp.@QDOC_QHP_PROJECT_NAME@.file = @QDOC_NAME@.qhp qhp.@QDOC_QHP_PROJECT_NAME@.namespace = @QDOC_NAMESPACE@.@QDOC_NAME@ qhp.@QDOC_QHP_PROJECT_NAME@.virtualFolder = @QDOC_NAME@ qhp.@QDOC_QHP_PROJECT_NAME@.indexTitle = @QDOC_PROJECT_NAME@ qhp.@QDOC_QHP_PROJECT_NAME@.indexRoot = qhp.@QDOC_QHP_PROJECT_NAME@.subprojects = qmltypes classes examples qhp.@QDOC_QHP_PROJECT_NAME@.subprojects.qmltypes.title = QML Types qhp.@QDOC_QHP_PROJECT_NAME@.subprojects.qmltypes.indexTitle = @QDOC_PROJECT_NAME@ QML Types qhp.@QDOC_QHP_PROJECT_NAME@.subprojects.qmltypes.selectors = qmlclass qhp.@QDOC_QHP_PROJECT_NAME@.subprojects.qmltypes.sortPages = true qhp.@QDOC_QHP_PROJECT_NAME@.subprojects.classes.title = C++ Classes qhp.@QDOC_QHP_PROJECT_NAME@.subprojects.classes.indexTitle = @QDOC_PROJECT_NAME@ C++ Classes qhp.@QDOC_QHP_PROJECT_NAME@.subprojects.classes.selectors = class fake:headerfile qhp.@QDOC_QHP_PROJECT_NAME@.subprojects.classes.sortPages = true qhp.@QDOC_QHP_PROJECT_NAME@.subprojects.examples.title = Examples qhp.@QDOC_QHP_PROJECT_NAME@.subprojects.examples.indexTitle = @QDOC_PROJECT_NAME@ Examples and Tutorials qhp.@QDOC_QHP_PROJECT_NAME@.subprojects.examples.selectors = fake:example navigation.landingpage = "@QDOC_PROJECT_NAME@" navigation.cppclassespage = "@QDOC_PROJECT_NAME@ C++ Classes" navigation.qmltypespage = "@QDOC_PROJECT_NAME@ QML Types" syntaxhighlighting = trueukui-quick/cmake-extend/style/0000775000175000017500000000000015153755732015311 5ustar fengfengukui-quick/cmake-extend/style/default.css0000664000175000017500000003071515153755732017455 0ustar fengfeng /* * from qt offline.css */ body { font: normal 400 1rem/1.2 Arial; margin-top: 50px; font-family: Arial, Helvetica; text-align: left; margin-left: 5px; margin-right: 5px; background-color: #fff; } p { line-height: 20px } img { margin-left: 0px; max-width: 800px; height: auto; } .content .border img { box-shadow:3px 3px 8px 3px rgba(200,200,200,0.5) } .content .border .player { box-shadow:3px 3px 8px 3px rgba(200,200,200,0.5) } .content .indexboxcont li { font: normal bold 13px/1 Verdana } .content .normallist li { font: normal 13px/1 Verdana } .descr { margin-top: 35px; margin-bottom: 45px; margin-left: 5px; text-align: left; vertical-align: top; } .name { max-width: 75%; font-weight: 100; } tt { text-align: left } /* ----------- links ----------- */ a:link { color: #007330; text-decoration: none; text-align: left; } a.qa-mark:target:before { content: "***"; color: #ff0000; } a:hover { color: #44a51c; text-align: left; } a:visited { color: #007330; text-align: left; } a:visited:hover { color: #44a51c; text-align: left; } /* ----------- offline viewing: HTML links display an icon ----------- */ a[href*="http://"], a[href*="ftp://"], a[href*="https://"] { text-decoration: none; background-image: url(../images/ico_out.png); background-repeat: no-repeat; background-position: left; padding-left: 20px; text-align: left; } .flags { text-decoration: none; text-height: 24px; } .flags:target { background-color: #FFFFD6; } /* ------------------------------- NOTE styles ------------------------------- */ .admonition { padding: 5px 0 5px 40px; border: #ccc 1px solid; } .admonition.note, .admonition.important { background: #f2f2f2 3px 6px no-repeat url(../images/ico_note.png); } .admonition.warning { background: #f2f2f2 3px 6px no-repeat url(../images/ico_note_attention.png); } /* ------------------------------- Top navigation ------------------------------- */ .qtref { display: block; position: relative; height: 15px; z-index: 1; font-size: 11px; padding-right: 10px; float: right; } .naviNextPrevious { clear: both; display: block; position: relative; text-align: right; top: -30px; float: right; height: 20px; z-index: 1; padding-right: 10px; padding-top: 2px; vertical-align: top; margin: 0px; } .naviNextPrevious > a:first-child { background-image: url(../images/btn_prev.png); background-repeat: no-repeat; background-position: left; padding-left: 20px; height: 20px; padding-left: 20px; } .naviNextPrevious > a:last-child { background-image: url(../images/btn_next.png); background-repeat: no-repeat; background-position: right; padding-right: 20px; height: 20px; margin-left: 30px; } .naviSeparator { display: none } /* ----------- footer and license ----------- */ .footer { text-align: left; padding-top: 45px; padding-left: 5px; margin-top: 45px; margin-bottom: 45px; font-size: 10px; border-top: 1px solid #999; } .footer p { line-height: 14px; font-size: 11px; padding: 0; margin: 0; } .footer a[href*="http://"], a[href*="ftp://"], a[href*="https://"] { font-weight: bold; } .footerNavi { width: auto; text-align: right; margin-top: 50px; z-index: 1; } .navigationbar { display: block; position: relative; border-top: 1px solid #cecece; border-bottom: 1px solid #cecece; background-color: #F2F2F2; z-index: 1; height: 20px; padding-left: 7px; margin: 0px; padding-top: 2px; margin-left: -5px; margin-right: -5px; } .navigationbar .first { background: url(../images/home.png); background-position: left; background-repeat: no-repeat; padding-left: 20px; } .navigationbar ul { margin: 0px; padding: 0px; } .navigationbar ul li { list-style-type: none; padding-top: 2px; padding-left: 4px; margin: 0; height: 20px; } .navigationbar li { float: left } .navigationbar li a, .navigationbar td a { display: block; text-decoration: none; background: url(../images/arrow_bc.png); background-repeat: no-repeat; background-position: right; padding-right: 17px; } table.buildversion { float: right; margin-top: -18px !important; } .navigationbar table { border-radius: 0; border: 0 none; background-color: #F2F2F2; margin: 0; } .navigationbar table td { padding: 0; border: 0 none; } #buildversion { font-style: italic; float: right; margin-right: 5px; } #buildversion a { background: none; } /* /* table of content no display */ /* ----------- headers ----------- */ @media screen { .title { color: #313131; font-size: 20px; font-weight: normal; left: 0; padding-bottom: 15px; padding-left: 10px; padding-top: 15px; position: absolute; right: 0; top: 0; background-color: #E6E6E6; border-bottom: 1px #CCC solid; font-weight: bold; margin-left: 0px; margin-right: 0px; } .subtitle, .small-subtitle { display: block; clear: left; } } h1 { margin: 0 } h2, p.h2 { font: 500 16px/1.2 Arial; font-weight: 100; background-color: #F2F3F4; padding: 4px; margin-bottom: 15px; margin-top: 30px; border-top: #E0E0DE 1px solid; border-bottom: #E0E0DE 1px solid; max-width: 99%; } h2:target { background-color: #F2F3D4; } h3 { font: 500 14px/1.2 Arial; font-weight: 100; text-decoration: underline; margin-bottom: 15px; margin-top: 30px; } h3.fn, span.fn { border-width: 1px; border-style: solid; border-color: #E6E6E6; -moz-border-radius: 7px 7px 7px 7px; -webkit-border-radius: 7px 7px 7px 7px; border-radius: 7px 7px 7px 7px; background-color: #F6F6F6; word-spacing: 3px; padding: 5px 5px; text-decoration: none; font-weight: bold; max-width: 75%; font-size: 14px; margin: 0px; margin-top: 30px; } .fngroup h3.fngroupitem { margin-bottom: 5px; } h3.fn code { float: right; } h3.fn:target { background-color: #F6F6D6; } .name { color: #1A1A1A } .type { color: #808080 } @media print { .title { color: #0066CB; font-family: Arial, Helvetica; font-size: 32px; font-weight: normal; left: 0; position: absolute; right: 0; top: 0; } } /* ----------------- table styles ----------------- */ .table img { border: none; margin-left: 0px; -moz-box-shadow: 0px 0px 0px #fff; -webkit-box-shadow: 0px 0px 0px #fff; box-shadow: 0px 0px 0px #fff; } /* table with border alternative colours*/ table, pre, .LegaleseLeft { -moz-border-radius: 7px 7px 7px 7px; -webkit-border-radius: 7px 7px 7px 7px; border-radius: 7px 7px 7px 7px; background-color: #F6F6F6; border: 1px solid #E6E6E6; border-collapse: separate; margin-bottom: 25px; margin-left: 15px; font-size: 1rem; line-height: 1.2; } table tr.even { background-color: white; color: #66666E; } table tr.odd { background-color: #F6F6F6; color: #66666E; } table tr:target { background-color: #F6F6D6; } table thead { text-align: left; padding-left: 20px; background-color: #e1e0e0; border-left: none; border-right: none; } table thead th { padding-top: 5px; padding-left: 10px; padding-bottom: 5px; border-bottom: 2px solid #D1D1D1; padding-right: 10px; } table th { text-align: left; padding-left: 20px; } table td { padding: 3px 15px 3px 20px; border-bottom: #CCC dotted 1px; } table p { margin: 0px } .LegaleseLeft { font-family: monospace; white-space: pre-wrap; } /* table bodless & white*/ .borderless { border-radius: 0px 0px 0px 0px; background-color: #fff; border: 1px solid #fff; } .borderless tr { background-color: #FFF; color: #66666E; } .borderless td { border: none; border-bottom: #fff dotted 1px; } /* ----------- List ----------- */ ul { margin-top: 10px; } li { margin-bottom: 10px; padding-left: 8px; list-style: outside; text-align: left; } ul > li { list-style-type: square; } ol { margin: 10px; padding: 0; } ol.A > li { list-style-type: upper-alpha; } ol.a > li{ list-style-type: lower-alpha; } ol > li { margin-left: 30px; padding-left: 8px; list-style: decimal; } .centerAlign { text-align: left } .cpp, .LegaleseLeft { display: block; margin: 10px; overflow: auto; padding: 20px 20px 20px 20px; } .js { display: block; margin: 10px; overflow: auto; padding: 20px 20px 20px 20px; } .memItemLeft { padding-right: 3px } .memItemRight { padding: 3px 15px 3px 0 } .qml { display: block; margin: 10px; overflow: auto; padding: 20px 20px 20px 20px; } .qmldefault { padding-left: 5px; float: right; color: red; } .qmlreadonly { padding-left: 5px; float: right; color: #254117; } .rightAlign { padding: 3px 5px 3px 10px; text-align: right; } .qmldoc { margin-left: 15px } .flowList { padding: 25px } .flowList dd { display: inline-block; margin-left: 10px; width: 255px; line-height: 1.15em; overflow-x: hidden; text-overflow: ellipsis } .alphaChar { font-size: 2em; position: relative } /* ----------- Content table ----------- */ @media print { .toc { float: right; clear: right; padding-bottom: 10px; padding-top: 50px; width: 100%; background-image: url(../images/bgrContent.png); background-position: top; background-repeat: no-repeat; } } @media screen { .toc { float: right; clear: right; vertical-align: top; -moz-border-radius: 7px 7px 7px 7px; -webkit-border-radius: 7px 7px 7px 7px; border-radius: 7px 7px 7px 7px; background: #FFF url('../images/bgrContent.png'); background-position: top; background-repeat: repeat-x; border: 1px solid #E6E6E6; padding-left: 5px; padding-bottom: 10px; height: auto; width: 200px; text-align: left; margin-left: 20px; margin-top: 5px; } } .toc h3 { text-decoration: none } .toc h3 { font: 500 14px/1.2 Arial; font-weight: 100; padding: 0px; margin: 0px; padding-top: 5px; padding-left: 5px; } .toc ul { padding-left: 10px; padding-right: 5px; } .toc ul li { margin-left: 15px; list-style-image: url(../images/bullet_dn.png); marker-offset: 0px; margin-bottom: 8px; padding-left: 0px; } .toc .level1 { border: none } .toc .level2 { border: none; margin-left: 25px; } .level3 { border: none; margin-left: 30px; } .clearfix { clear: both } /* ----------- Landing page ----------- */ .col-group { white-space: nowrap; vertical-align: top; } .landing h2 { background-color: transparent; border: none; margin-bottom: 0px; font-size: 18px; } .landing a, .landing li { font-size: 13px; font-weight: bold !important; } .col-1 { display: inline-block; white-space: normal; width: 70%; height: 100%; float: left; } .col-2 { display: inline-block; white-space: normal; width: 20%; margin-left: 5%; position: relative; top: -20px; } .col-1 h1 { margin: 20px 0 0 0; } .col-1 h2 { font-size: 18px; font-weight: bold !important; } .landingicons { display: inline-block; width: 100%; } .icons1of3 { display: inline-block; width: 33.3333%; float: left; } .icons1of3 h2, .doc-column h2 { font-size: 15px; margin: 0px; padding: 0px; } div.multi-column { position: relative; } div.multi-column div { display: -moz-inline-box; display: inline-block; vertical-align: top; margin-top: 1em; margin-right: 4em; width: 24em; } .mainContent .video { width:40%; max-width:640px; margin: 15px 0 0 15px; position:relative; display:table } .mainContent .video > .vspan { padding-top:60%; display:block } .mainContent .video iframe { width:100%; height:100%; position:absolute; top:0; left:0 } ukui-quick/cmake-extend/ukui-quick-cmake-extend-config.cmake.in0000664000175000017500000000035515153755732023460 0ustar fengfeng@PACKAGE_INIT@ set(UKUI_QUICK_CMAKE_EXTEND_DIR "@PACKAGE_MODULES_CMAKE_EXTEND_INSTALL_DIR@") set(UKUI_QUICK_CMAKE_EXTEND_STYLE_DIR "@PACKAGE_STYLE_CMAKE_EXTEND_INSTALL_DIR@") list(APPEND CMAKE_MODULE_PATH ${UKUI_QUICK_CMAKE_EXTEND_DIR}) ukui-quick/platform/0000775000175000017500000000000015167355777013442 5ustar fengfengukui-quick/platform/xcb-window-manager.cpp0000664000175000017500000003061415153755732017631 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #include "xcb-window-manager.h" #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #include #else #include #endif #include #include namespace UkuiQuick { XcbWindowManager::XcbWindowManager(QObject *parent): AbstractWindowManager(parent) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) connect(KX11Extras::self(), &KX11Extras::windowAdded, this, &XcbWindowManager::windowAddedProxy); connect(KX11Extras::self(), &KX11Extras::windowRemoved, this, [=](WId wid) { m_windowProperties.remove(wid); Q_EMIT windowRemoved(QString::number(wid)); }); connect(KX11Extras::self(), &KX11Extras::currentDesktopChanged, this, &AbstractWindowManager::currentDesktopChanged); connect(KX11Extras::self(), QOverload::of(&KX11Extras::windowChanged) , this, &XcbWindowManager::windowChangedProxy); connect(KX11Extras::self(), &KX11Extras::activeWindowChanged, this, [=](WId wid) { Q_EMIT activeWindowChanged(QString::number(wid)); }); #else connect(KWindowSystem::self(), &KWindowSystem::windowAdded, this, &XcbWindowManager::windowAddedProxy); connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, this, [=](WId wid) { m_windowProperties.remove(wid); Q_EMIT windowRemoved(QString::number(wid)); }); connect(KWindowSystem::self(), &KWindowSystem::currentDesktopChanged, this, &AbstractWindowManager::currentDesktopChanged); connect(KWindowSystem::self(), QOverload::of(&KWindowSystem::windowChanged) , this, &XcbWindowManager::windowChangedProxy); connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, [=](WId wid) { Q_EMIT activeWindowChanged(QString::number(wid)); }); #endif } XcbWindowManager::~XcbWindowManager() { } QStringList XcbWindowManager::windows() { QStringList windows; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) for(const WId &wid : KX11Extras::windows()) { #else for(const WId &wid : KWindowSystem::windows()) { #endif windows.append(QString::number(wid)); } return windows; } QIcon XcbWindowManager::windowIcon(const QString &wid) { QIcon icon; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) icon.addPixmap(KX11Extras::icon(wid.toUInt(), 16, 16, false)); icon.addPixmap(KX11Extras::icon(wid.toUInt(), 22, 22, false)); icon.addPixmap(KX11Extras::icon(wid.toUInt(), 32, 32, false)); icon.addPixmap(KX11Extras::icon(wid.toUInt(), 48, 48, false)); icon.addPixmap(KX11Extras::icon(wid.toUInt(), 64, 64, false)); icon.addPixmap(KX11Extras::icon(wid.toUInt(), 128, 128, false)); #else icon.addPixmap(KWindowSystem::icon(wid.toUInt(), 16, 16, false)); icon.addPixmap(KWindowSystem::icon(wid.toUInt(), 22, 22, false)); icon.addPixmap(KWindowSystem::icon(wid.toUInt(), 32, 32, false)); icon.addPixmap(KWindowSystem::icon(wid.toUInt(), 48, 48, false)); icon.addPixmap(KWindowSystem::icon(wid.toUInt(), 64, 64, false)); icon.addPixmap(KWindowSystem::icon(wid.toUInt(), 128, 128, false)); #endif return icon; } QString XcbWindowManager::windowTitle(const QString &wid) { const KWindowInfo winfo{wid.toUInt(), NET::WMName}; return winfo.valid()? winfo.name() : QString{}; } bool XcbWindowManager::skipTaskBar(const QString &wid) { const KWindowInfo winfo(wid.toUInt(), NET::WMState); return winfo.valid() && winfo.hasState(NET::SkipTaskbar); } QString XcbWindowManager::windowGroup(const QString &wid) { KWindowInfo winfo(wid.toUInt(), NET::Properties(0), NET::WM2WindowClass); if(winfo.valid()) { return winfo.windowClassClass(); } return QString(); } bool XcbWindowManager::isMaximized(const QString &wid) { const KWindowInfo winfo(wid.toUInt(), NET::WMState); if(winfo.valid()) { return winfo.hasState(NET::MaxVert | NET::MaxHoriz); } return false; } void XcbWindowManager::maximizeWindow(const QString &wid) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) KX11Extras::setState(wid.toUInt(), NET::Max); #else KWindowSystem::setState(wid.toUInt(), NET::Max); #endif } bool XcbWindowManager::isMinimized(const QString &wid) { const KWindowInfo winfo(wid.toUInt(), NET::WMState); if(winfo.valid()) { return winfo.hasState(NET::Hidden); } return false; } void XcbWindowManager::minimizeWindow(const QString &wid) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) KX11Extras::minimizeWindow(wid.toUInt()); #else KWindowSystem::minimizeWindow(wid.toUInt()); #endif } bool XcbWindowManager::isKeepAbove(const QString &wid) { const KWindowInfo winfo(wid.toUInt(), NET::WMState); if(winfo.valid()) { return winfo.hasState(NET::KeepAbove); } return false; } void XcbWindowManager::keepAboveWindow(const QString &wid) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) if(isKeepAbove(wid)) { KX11Extras::setState(wid.toUInt(), NET::KeepAbove); } else { KX11Extras::clearState(wid.toUInt(), NET::KeepAbove); } #else if(isKeepAbove(wid)) { KWindowSystem::setState(wid.toUInt(), NET::KeepAbove); } else { KWindowSystem::clearState(wid.toUInt(), NET::KeepAbove); } #endif } bool XcbWindowManager::isOnAllDesktops(const QString &wid) { const KWindowInfo winfo(wid.toUInt(), NET::WMDesktop); if(winfo.valid()) { return winfo.onAllDesktops(); } return false; } bool XcbWindowManager::isOnCurrentDesktop(const QString &wid) { const KWindowInfo winfo(wid.toUInt(), NET::WMDesktop); if(winfo.valid()) { return winfo.isOnCurrentDesktop(); } return false; } void XcbWindowManager::activateWindow(const QString &wid) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) KX11Extras::activateWindow(wid.toUInt()); #else KWindowSystem::activateWindow(wid.toUInt()); #endif } QString XcbWindowManager::currentActiveWindow() { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) return QString::number(KX11Extras::activeWindow()); #else return QString::number(KWindowSystem::activeWindow()); #endif } void XcbWindowManager::closeWindow(const QString& wid) { NETRootInfo ri(QX11Info::connection(), NET::CloseWindow); ri.closeWindowRequest(wid.toUInt()); } void XcbWindowManager::restoreWindow(const QString &wid) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) KX11Extras::clearState(wid.toUInt(), NET::Max); #else KWindowSystem::clearState(wid.toUInt(), NET::Max); #endif } void XcbWindowManager::windowChangedProxy(WId wid, NET::Properties prop1, NET::Properties2 prop2) { if (prop1 == 0 && !(prop2 & (NET::WM2Activities | NET::WM2TransientFor))) { return; } const KWindowInfo info(wid,prop1,prop2); if(prop1.testFlag(NET::WMState) && info.valid()) { if(info.hasState(NET::State::SkipTaskbar) != m_windowProperties.value(wid).value(SkipTaskbar).toBool()) { Q_EMIT skipTaskbarChanged(QString::number(wid)); } if(info.hasState(NET::State::DemandsAttention) != m_windowProperties.value(wid).value(DemandsAttention).toBool()) { Q_EMIT demandsAttentionChanged(QString::number(wid)); } if((info.hasState(NET::State::MaxHoriz) && info.hasState(NET::State::MaxVert)) != m_windowProperties.value(wid).value(Maximized).toBool()) { Q_EMIT maximizedChanged(QString::number(wid)); } if(info.hasState(NET::State::FullScreen) != m_windowProperties.value(wid).value(Fullscreen).toBool()) { Q_EMIT fullscreenChanged(QString::number(wid)); } Q_EMIT windowStateChanged(QString::number(wid)); } if(prop1.testFlag(NET::WMDesktop)) { if(info.isOnCurrentDesktop() != m_windowProperties.value(wid).value(OnCurrentDesktop).toBool()) { Q_EMIT windowDesktopChanged(QString::number(wid)); } if(info.onAllDesktops() != m_windowProperties.value(wid).value(OnAllDesktops).toBool()) { Q_EMIT onAllDesktopsChanged(QString::number(wid)); } } updateWindowProperties(wid); if(prop1.testFlag(NET::WMIcon)) { Q_EMIT iconChanged(QString::number(wid)); } if(prop1.testFlag(NET::WMName) || prop1.testFlag(NET::WMVisibleName)) { Q_EMIT titleChanged(QString::number(wid)); } if(prop1.testFlag(NET::WMGeometry)) { Q_EMIT geometryChanged(QString::number(wid)); } } void XcbWindowManager::updateWindowProperties(WId wid) { QHash properties; const KWindowInfo winfo(wid, NET::WMState | NET::WMDesktop); properties.insert(WindowProperty::SkipTaskbar, winfo.valid() && winfo.hasState(NET::SkipTaskbar)); properties.insert(WindowProperty::DemandsAttention, winfo.valid() && winfo.hasState(NET::DemandsAttention)); properties.insert(WindowProperty::OnAllDesktops, winfo.valid() && winfo.onAllDesktops()); properties.insert(WindowProperty::OnCurrentDesktop, winfo.valid() && winfo.isOnCurrentDesktop()); properties.insert(WindowProperty::Maximized, winfo.valid() && winfo.hasState(NET::MaxHoriz) && winfo.hasState(NET::MaxVert)); properties.insert(WindowProperty::Fullscreen, winfo.valid() && winfo.hasState(NET::FullScreen)); m_windowProperties.insert(wid, properties); } bool XcbWindowManager::isDemandsAttention(const QString &wid) { const KWindowInfo winfo(wid.toUInt(), NET::WMState); if(winfo.valid()) { return winfo.hasState(NET::DemandsAttention); } return false; } quint32 XcbWindowManager::pid(const QString &wid) { const KWindowInfo winfo(wid.toUInt(), NET::WMPid); if(winfo.valid()) { return winfo.pid(); } return 0; } QString XcbWindowManager::appId(const QString &wid) { const KWindowInfo winfo(wid.toUInt(), {}, NET::WM2DesktopFileName); if(winfo.valid()) { return winfo.desktopFileName(); } return {}; } QRect XcbWindowManager::geometry(const QString &wid) { const KWindowInfo winfo(wid.toUInt(), NET::WMGeometry); if(winfo.valid()) { return winfo.geometry(); } return {}; } void XcbWindowManager::windowAddedProxy(WId wid) { updateWindowProperties(wid); Q_EMIT windowAdded(QString::number(wid)); } void XcbWindowManager::setStartupGeometry(const QString &wid, QQuickItem *item) { if(!item || !item->window()) { return; } NETWinInfo info(QX11Info::connection(), wid.toInt(), item->window()->winId(), NET::WMIconGeometry, {}, NET::Client); info.setIconGeometry(QRect(item->mapToGlobal({0, 0}).toPoint(), item->size().toSize())); } void XcbWindowManager::setMinimizedGeometry(const QString &wid, QQuickItem *item) { setStartupGeometry(wid, item); } void XcbWindowManager::unsetMinimizedGeometry(const QString &wid, QQuickItem *item) { } bool XcbWindowManager::showingDesktop() { return KWindowSystem::showingDesktop(); } void XcbWindowManager::setShowingDesktop(bool showing) { KWindowSystem::setShowingDesktop(showing); } bool XcbWindowManager::isFullscreen(const QString&wid) { const KWindowInfo winfo(wid.toUInt(), NET::WMState); if(winfo.valid()) { return winfo.hasState(NET::FullScreen); } return false; } bool XcbWindowManager::isMinimizable(const QString &wid) { const KWindowInfo winfo(wid.toUInt(), NET::Properties(0), NET::WM2AllowedActions); if(winfo.valid()) { return winfo.actionSupported(NET::ActionMax); } return false; } bool XcbWindowManager::isMaximizable(const QString &wid) { const KWindowInfo winfo(wid.toUInt(), NET::Properties(0), NET::WM2AllowedActions); if(winfo.valid()) { return winfo.actionSupported(NET::ActionMinimize); } return false; } }ukui-quick/platform/xatom-helper.cpp0000664000175000017500000001413315153755732016543 0ustar fengfeng/* * KWin Style UKUI * * Copyright (C) 2020, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: Yue Lan * */ #include "xatom-helper.h" #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #else #include #endif #include #include #include #include static XAtomHelper *global_instance = nullptr; XAtomHelper *XAtomHelper::getInstance() { if (!global_instance) global_instance = new XAtomHelper; return global_instance; } bool XAtomHelper::isFrameLessWindow(int winId) { auto hints = getInstance()->getWindowMotifHint(winId); if (hints.flags == MWM_HINTS_DECORATIONS && hints.functions == 1) { return true; } return false; } bool XAtomHelper::isWindowDecorateBorderOnly(int winId) { return isWindowMotifHintDecorateBorderOnly(getInstance()->getWindowMotifHint(winId)); } bool XAtomHelper::isWindowMotifHintDecorateBorderOnly(const MotifWmHints &hint) { bool isDeco = false; if (hint.flags & MWM_HINTS_DECORATIONS && hint.flags != MWM_HINTS_DECORATIONS) { if (hint.decorations == MWM_DECOR_BORDER) isDeco = true; } return isDeco; } bool XAtomHelper::isUKUICsdSupported() { // fixme: return false; } bool XAtomHelper::isUKUIDecorationWindow(int winId) { if (m_ukuiDecorationAtion == None) return false; Atom type; int format; ulong nitems; ulong bytes_after; uchar *data; bool isUKUIDecoration = false; XGetWindowProperty(QX11Info::display(), winId, m_ukuiDecorationAtion, 0, LONG_MAX, false, m_ukuiDecorationAtion, &type, &format, &nitems, &bytes_after, &data); if (type == m_ukuiDecorationAtion) { if (nitems == 1) { isUKUIDecoration = data[0]; } } return isUKUIDecoration; } UnityCorners XAtomHelper::getWindowBorderRadius(int winId) { UnityCorners corners; Atom type; int format; ulong nitems; ulong bytes_after; uchar *data; if (m_unityBorderRadiusAtom != None) { XGetWindowProperty(QX11Info::display(), winId, m_unityBorderRadiusAtom, 0, LONG_MAX, false, XA_CARDINAL, &type, &format, &nitems, &bytes_after, &data); if (type == XA_CARDINAL) { if (nitems == 4) { corners.topLeft = static_cast(data[0]); corners.topRight = static_cast(data[1 * sizeof(ulong)]); corners.bottomLeft = static_cast(data[2 * sizeof(ulong)]); corners.bottomRight = static_cast(data[3 * sizeof(ulong)]); } XFree(data); } } return corners; } void XAtomHelper::setWindowBorderRadius(int winId, const UnityCorners &data) { if (m_unityBorderRadiusAtom == None) return; ulong corners[4] = {data.topLeft, data.topRight, data.bottomLeft, data.bottomRight}; XChangeProperty(QX11Info::display(), winId, m_unityBorderRadiusAtom, XA_CARDINAL, 32, XCB_PROP_MODE_REPLACE, (const unsigned char *)&corners, sizeof(corners) / sizeof(corners[0])); } void XAtomHelper::setWindowBorderRadius(int winId, int topLeft, int topRight, int bottomLeft, int bottomRight) { if (m_unityBorderRadiusAtom == None) return; ulong corners[4] = {(ulong)topLeft, (ulong)topRight, (ulong)bottomLeft, (ulong)bottomRight}; XChangeProperty(QX11Info::display(), winId, m_unityBorderRadiusAtom, XA_CARDINAL, 32, XCB_PROP_MODE_REPLACE, (const unsigned char *)&corners, sizeof(corners) / sizeof(corners[0])); } void XAtomHelper::setUKUIDecoraiontHint(int winId, bool set) { if (m_ukuiDecorationAtion == None) return; XChangeProperty(QX11Info::display(), winId, m_ukuiDecorationAtion, m_ukuiDecorationAtion, 32, XCB_PROP_MODE_REPLACE, (const unsigned char *)&set, 1); } void XAtomHelper::setWindowMotifHint(int winId, const MotifWmHints &hints) { if (m_unityBorderRadiusAtom == None) return; XChangeProperty(QX11Info::display(), winId, m_motifWMHintsAtom, m_motifWMHintsAtom, 32, XCB_PROP_MODE_REPLACE, (const unsigned char *)&hints, sizeof(MotifWmHints) / sizeof(ulong)); } MotifWmHints XAtomHelper::getWindowMotifHint(int winId) { MotifWmHints hints; if (m_unityBorderRadiusAtom == None) return hints; uchar *data; Atom type; int format; ulong nitems; ulong bytes_after; XGetWindowProperty(QX11Info::display(), winId, m_motifWMHintsAtom, 0, sizeof(MotifWmHints) / sizeof(long), false, AnyPropertyType, &type, &format, &nitems, &bytes_after, &data); if (type == None) { return hints; } else { hints = *(MotifWmHints *)data; XFree(data); } return hints; } XAtomHelper::XAtomHelper(QObject *parent) : QObject(parent) { if (!QX11Info::isPlatformX11()) return; m_motifWMHintsAtom = XInternAtom(QX11Info::display(), "_MOTIF_WM_HINTS", true); m_unityBorderRadiusAtom = XInternAtom(QX11Info::display(), "_UNITY_GTK_BORDER_RADIUS", false); m_ukuiDecorationAtion = XInternAtom(QX11Info::display(), "_KWIN_UKUI_DECORAION", false); } Atom XAtomHelper::registerUKUICsdNetWmSupportAtom() { // fixme: return None; } void XAtomHelper::unregisterUKUICsdNetWmSupportAtom() { // fixme: } ukui-quick/platform/xcb-window-manager.h0000664000175000017500000000626515153755732017303 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef UKUI_PANEL_XCB_WINDOW_MANAGER_H #define UKUI_PANEL_XCB_WINDOW_MANAGER_H #include "abstract-window-manager.h" #include #include #include namespace UkuiQuick { class XcbWindowManager: public AbstractWindowManager { Q_OBJECT public: /** * 需要缓存的窗口属性 */ enum WindowProperty{ SkipTaskbar, DemandsAttention, OnAllDesktops, OnCurrentDesktop, Maximized, Fullscreen, }; Q_ENUM(WindowProperty) explicit XcbWindowManager(QObject *parent = nullptr); ~XcbWindowManager(); QStringList windows() override; QIcon windowIcon(const QString &wid) override; QString windowTitle(const QString &wid) override; bool skipTaskBar(const QString &wid) override; QString windowGroup(const QString &wid) override; bool isMaximizable(const QString &wid) override; bool isMaximized(const QString& wid) override; void maximizeWindow(const QString& wid) override; bool isMinimizable(const QString &wid) override; bool isMinimized(const QString& wid) override; void minimizeWindow(const QString& wid) override; bool isKeepAbove(const QString& wid) override; void keepAboveWindow(const QString& wid) override; bool isOnAllDesktops(const QString& wid) override; bool isOnCurrentDesktop(const QString& wid) override; void activateWindow(const QString& wid) override; QString currentActiveWindow() override; void closeWindow(const QString& wid) override; void restoreWindow(const QString& wid) override; bool isDemandsAttention(const QString& wid) override; quint32 pid(const QString& wid) override; QString appId(const QString& wid) override; QRect geometry(const QString& wid) override; void setStartupGeometry(const QString& wid, QQuickItem *item) override; void setMinimizedGeometry(const QString& wid, QQuickItem *item) override; void unsetMinimizedGeometry(const QString& wid, QQuickItem *item) override; bool showingDesktop() override; void setShowingDesktop(bool showing) override; bool isFullscreen(const QString& wid) override; private: void windowAddedProxy(WId wid); void windowChangedProxy(WId wid, NET::Properties prop1, NET::Properties2 prop2); void updateWindowProperties(WId wid); QHash> m_windowProperties; }; } #endif //UKUI_PANEL_XCB_WINDOW_MANAGER_H ukui-quick/platform/ukui-quick-platform-plugin.cpp0000664000175000017500000001446115153756415021346 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "ukui-quick-platform-plugin.h" #include "window-type.h" #include "windows/ukui-window.h" #include "windows/dialog.h" #include "ukui/ukui-theme-proxy.h" #include "ukui/settings.h" #include "ukui/app-launcher.h" #include "ukui/dt-theme-definition.h" #include "window-manager.h" #include "ukui/dt-theme.h" #include "ukui/application-icon-proxy.h" #include #include void UkuiQuickPlatformPlugin::registerTypes(const char *uri) { Q_ASSERT(QString(uri) == QLatin1String(PLUGIN_IMPORT_URI)); qmlRegisterModule(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR); qmlRegisterSingletonType(uri, 1, 0, "Settings", [] (QQmlEngine *, QJSEngine *) -> QObject* { return UkuiQuick::Settings::instance(); }); qmlRegisterSingletonType(uri, 1, 0, "AppLauncher", [] (QQmlEngine *, QJSEngine *) -> QObject* { return UkuiQuick::AppLauncher::instance(); }); qmlRegisterExtendedType(uri, 1, 0, "UKUIWindow"); qmlRegisterSingletonType(uri, 1, 0, "WindowManager", [] (QQmlEngine *, QJSEngine *) -> QObject* { return UkuiQuick::WindowManager::self(); }); qmlRegisterType(uri, 1, 0, "DtThemeDefinition"); qmlRegisterSingletonType(uri, 1, 0, "GlobalTheme", [] (QQmlEngine *engine, QJSEngine *) -> QObject* { return UkuiQuick::DtTheme::self(engine); }); qRegisterMetaType("GradientColor"); qmlRegisterType(uri, 1, 0, "GradientColor"); qRegisterMetaType("ShadowData"); qmlRegisterType(uri, 1, 0, "ShadowData"); qRegisterMetaType("DtColorType"); // qRegisterMetaType("SlideWindow"); // qmlRegisterType(uri, 1, 0, "SlideWindow"); // qRegisterMetaType("ApplicationIconProxy"); // qmlRegisterType(uri, 1, 0, "ApplicationIconProxy"); qmlRegisterUncreatableType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "ApplicationIconProxy", QStringLiteral("Used as grouped property")); qmlRegisterUncreatableType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "SlideWindow", QStringLiteral("Used as grouped property")); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) qmlRegisterUncreatableType(uri, 1, 0, "DtColorType", "DtColorType is a read-only interface used to access enumeration properties."); qmlRegisterUncreatableType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "Theme", "Accessing Theme through Attached Property."); qmlRegisterUncreatableType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "WindowType", "WindowType is a read-only interface used to access enumeration properties."); qmlRegisterUncreatableType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "Decoration", "Decoration is a read-only interface used to access enumeration properties."); qmlRegisterUncreatableType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "WindowState", "WindowState is a read-only interface used to access enumeration properties."); qmlRegisterUncreatableType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "WindowProxy", "WindowProxy is a read-only interface used to access enumeration properties."); #else qmlRegisterUncreatableMetaObject(UkuiQuick::DtColorType::staticMetaObject, uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "DtColorType", "DtColorType is a read-only interface used to access enumeration properties."); qmlRegisterUncreatableMetaObject(UkuiQuick::WindowType::staticMetaObject, uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "WindowType", "WindowType is a read-only interface used to access enumeration properties."); qmlRegisterUncreatableMetaObject(UkuiQuick::Decoration::staticMetaObject, uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "Decoration", "Decoration is a read-only interface used to access enumeration properties."); qmlRegisterUncreatableMetaObject(UkuiQuick::WindowState::staticMetaObject, uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "WindowState", "WindowState is a read-only interface used to access enumeration properties."); qmlRegisterUncreatableMetaObject(UkuiQuick::WindowProxy::staticMetaObject, uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "WindowProxy", "WindowProxy is a read-only interface used to access enumeration properties."); qmlRegisterUncreatableType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "Theme", "Accessing Theme through Attached Property."); // qmlRegisterUncreatableMetaObject(UkuiQuick::Theme::staticMetaObject, uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "Theme", // "Theme is a read-only interface used to access enumeration properties."); #endif } ukui-quick/platform/window-manager.cpp0000664000175000017500000001761515153755732017065 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #include "window-manager.h" #include #include "abstract-window-manager.h" #include "xcb-window-manager.h" #include "wayland-window-manager.h" namespace UkuiQuick { static WindowManager *g_windowManager = nullptr; static AbstractWindowManager *g_abstractWindowManager = nullptr; static std::once_flag flag_windowManager; static std::once_flag flag_abstractWindowManager; class WindowManagerPrivate { public: static AbstractWindowManager *interface(); }; AbstractWindowManager *WindowManagerPrivate::interface() { std::call_once(flag_abstractWindowManager, [ & ] { if(QString(getenv("XDG_SESSION_TYPE")) == "wayland") { g_abstractWindowManager = new WaylandWindowManager(); } else { g_abstractWindowManager = new XcbWindowManager(); } }); return g_abstractWindowManager; } WindowManager *WindowManager::self() { std::call_once(flag_windowManager, [ & ] { g_windowManager = new WindowManager(); }); return g_windowManager; } WindowManager::WindowManager(QObject *parent) : QObject(parent) { connect(WindowManagerPrivate::interface(), &AbstractWindowManager::windowAdded, this, &WindowManager::windowAdded, Qt::QueuedConnection); connect(WindowManagerPrivate::interface(), &AbstractWindowManager::windowRemoved, this, &WindowManager::windowRemoved, Qt::QueuedConnection); connect(WindowManagerPrivate::interface(), &AbstractWindowManager::currentDesktopChanged, this, &WindowManager::currentDesktopChanged, Qt::QueuedConnection); connect(WindowManagerPrivate::interface(), &AbstractWindowManager::titleChanged, this, &WindowManager::titleChanged, Qt::QueuedConnection); connect(WindowManagerPrivate::interface(), &AbstractWindowManager::iconChanged, this, &WindowManager::iconChanged, Qt::QueuedConnection); connect(WindowManagerPrivate::interface(), &AbstractWindowManager::skipTaskbarChanged, this, &WindowManager::skipTaskbarChanged, Qt::QueuedConnection); connect(WindowManagerPrivate::interface(), &AbstractWindowManager::onAllDesktopsChanged, this, &WindowManager::onAllDesktopsChanged, Qt::QueuedConnection); connect(WindowManagerPrivate::interface(), &AbstractWindowManager::windowDesktopChanged, this, &WindowManager::windowDesktopChanged, Qt::QueuedConnection); connect(WindowManagerPrivate::interface(), &AbstractWindowManager::demandsAttentionChanged, this, &WindowManager::demandsAttentionChanged, Qt::QueuedConnection); connect(WindowManagerPrivate::interface(), &AbstractWindowManager::geometryChanged, this, &WindowManager::geometryChanged, Qt::QueuedConnection); connect(WindowManagerPrivate::interface(), &AbstractWindowManager::activeWindowChanged, this, &WindowManager::activeWindowChanged, Qt::QueuedConnection); connect(WindowManagerPrivate::interface(), &AbstractWindowManager::windowStateChanged, this, &WindowManager::windowStateChanged, Qt::QueuedConnection); connect(WindowManagerPrivate::interface(), &AbstractWindowManager::maximizedChanged, this, &WindowManager::maximizedChanged, Qt::QueuedConnection); connect(WindowManagerPrivate::interface(), &AbstractWindowManager::fullscreenChanged, this, &WindowManager::fullscreenChanged, Qt::QueuedConnection); } QStringList WindowManager::windows() { return WindowManagerPrivate::interface()->windows(); } QIcon WindowManager::windowIcon(const QString &wid) { return WindowManagerPrivate::interface()->windowIcon(wid); } QString WindowManager::windowTitle(const QString &wid) { return WindowManagerPrivate::interface()->windowTitle(wid); } bool WindowManager::skipTaskBar(const QString &wid) { return WindowManagerPrivate::interface()->skipTaskBar(wid); } QString WindowManager::windowGroup(const QString &wid) { return WindowManagerPrivate::interface()->windowGroup(wid); } bool WindowManager::isMaximized(const QString &wid) { return WindowManagerPrivate::interface()->isMaximized(wid); } void WindowManager::maximizeWindow(const QString &wid) { WindowManagerPrivate::interface()->maximizeWindow(wid); } bool WindowManager::isMinimized(const QString &wid) { return WindowManagerPrivate::interface()->isMinimized(wid); } void WindowManager::minimizeWindow(const QString &wid) { WindowManagerPrivate::interface()->minimizeWindow(wid); } bool WindowManager::isKeepAbove(const QString &wid) { return WindowManagerPrivate::interface()->isKeepAbove(wid);; } void WindowManager::keepAboveWindow(const QString &wid) { WindowManagerPrivate::interface()->keepAboveWindow(wid); } bool WindowManager::isOnAllDesktops(const QString &wid) { return WindowManagerPrivate::interface()->isOnAllDesktops(wid); } bool WindowManager::isOnCurrentDesktop(const QString &wid) { return WindowManagerPrivate::interface()->isOnCurrentDesktop(wid); } void WindowManager::activateWindow(const QString &wid) { WindowManagerPrivate::interface()->activateWindow(wid); } QString WindowManager::currentActiveWindow() { return WindowManagerPrivate::interface()->currentActiveWindow();; } void WindowManager::closeWindow(const QString &wid) { WindowManagerPrivate::interface()->closeWindow(wid); } void WindowManager::restoreWindow(const QString &wid) { WindowManagerPrivate::interface()->restoreWindow(wid); } bool WindowManager::isDemandsAttention(const QString &wid) { return WindowManagerPrivate::interface()->isDemandsAttention(wid); } quint32 WindowManager::pid(const QString &wid) { return WindowManagerPrivate::interface()->pid(wid);; } QString WindowManager::appId(const QString &wid) { return WindowManagerPrivate::interface()->appId(wid); } bool WindowManager::isWaylandSession() { return QString(getenv("XDG_SESSION_TYPE")) == "wayland"; } QRect WindowManager::geometry(const QString &wid) { return WindowManagerPrivate::interface()->geometry(wid); } void WindowManager::setStartupGeometry(const QString &wid, QQuickItem *item) { WindowManagerPrivate::interface()->setStartupGeometry(wid, item); } void WindowManager::setMinimizedGeometry(const QString &wid, QQuickItem *item) { WindowManagerPrivate::interface()->setMinimizedGeometry(wid, item); } void WindowManager::unsetMinimizedGeometry(const QString &wid, QQuickItem *item) { WindowManagerPrivate::interface()->unsetMinimizedGeometry(wid, item); } void WindowManager::setMinimizedGeometry(const QStringList &widList, QQuickItem *item) { for (const auto &wid : widList) { WindowManagerPrivate::interface()->setMinimizedGeometry(wid, item); } } void WindowManager::activateWindowView(const QStringList &wids) { WindowManagerPrivate::interface()->activateWindowView(wids); } bool WindowManager::showingDesktop() { return WindowManagerPrivate::interface()->showingDesktop(); } void WindowManager::setShowingDesktop(bool showing) { WindowManagerPrivate::interface()->setShowingDesktop(showing); } bool WindowManager::isFullscreen(const QString&wid) { return WindowManagerPrivate::interface()->isFullscreen(wid); } bool WindowManager::isMaximizable(const QString &wid) { return WindowManagerPrivate::interface()->isMaximizable(wid); } bool WindowManager::isMinimizable(const QString &wid) { return WindowManagerPrivate::interface()->isMinimizable(wid); } }ukui-quick/platform/window-type.h0000664000175000017500000000471215153755732016073 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_WINDOW_TYPE_H #define UKUI_QUICK_WINDOW_TYPE_H #include namespace UkuiQuick { /** * usage: * windowType: WindowType.Tooltip */ class WindowType { Q_GADGET public: // copy from [KWindowSystem: netwm_def.h, KWayland: plasmashell.h] // 仅保留常用的层级 enum Type { Normal = 0, Desktop, Dock, Panel, SystemWindow, Notification, CriticalNotification, ScreenLockNotification, OnScreenDisplay, Dialog, ToolTip, Menu, PopupMenu, AppletPopup, Switcher, ScreenLock, WaterMark, InputPanel, Logout, Authentication }; Q_ENUM(Type) }; /** * @class Decoration * Window Decoration Components, only available on Wayland. * @see WindowProxy::setDecorationComponents */ class Decoration { Q_GADGET public: enum Component { RoundCorner = 0x1, Border = 0x2, Shadow = 0x4, }; Q_DECLARE_FLAGS(Components, Component) Q_FLAG(Components) }; class WindowState { Q_GADGET public: enum State { Minimizable = 0x1, Maximizable = 0x2, Closeable = 0x4, Fullscreenable = 0x8, Moveable = 0x10, Resizable = 0x20, Focusable = 0x40, Activatable = 0x80, KeepAbove = 0x100, KeepBelow = 0x200, }; Q_DECLARE_FLAGS(States, State) Q_FLAG(State) }; } Q_DECLARE_METATYPE(UkuiQuick::Decoration::Components) Q_DECLARE_OPERATORS_FOR_FLAGS(UkuiQuick::Decoration::Components) Q_DECLARE_METATYPE(UkuiQuick::WindowState::States) Q_DECLARE_OPERATORS_FOR_FLAGS(UkuiQuick::WindowState::States) #endif //UKUI_QUICK_WINDOW_TYPE_H ukui-quick/platform/xatom-helper.h0000664000175000017500000000607115153755732016212 0ustar fengfeng/* * KWin Style UKUI * * Copyright (C) 2020, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: Yue Lan * */ #ifndef XATOMHELPER_H #define XATOMHELPER_H #include struct UnityCorners { ulong topLeft = 0; ulong topRight = 0; ulong bottomLeft = 0; ulong bottomRight = 0; }; typedef struct { ulong flags = 0; ulong functions = 0; ulong decorations = 0; long input_mode = 0; ulong status = 0; } MotifWmHints, MwmHints; #define MWM_HINTS_FUNCTIONS (1L << 0) #define MWM_HINTS_DECORATIONS (1L << 1) #define MWM_HINTS_INPUT_MODE (1L << 2) #define MWM_HINTS_STATUS (1L << 3) #define MWM_FUNC_ALL (1L << 0) #define MWM_FUNC_RESIZE (1L << 1) #define MWM_FUNC_MOVE (1L << 2) #define MWM_FUNC_MINIMIZE (1L << 3) #define MWM_FUNC_MAXIMIZE (1L << 4) #define MWM_FUNC_CLOSE (1L << 5) #define MWM_DECOR_ALL (1L << 0) #define MWM_DECOR_BORDER (1L << 1) #define MWM_DECOR_RESIZEH (1L << 2) #define MWM_DECOR_TITLE (1L << 3) #define MWM_DECOR_MENU (1L << 4) #define MWM_DECOR_MINIMIZE (1L << 5) #define MWM_DECOR_MAXIMIZE (1L << 6) #define MWM_INPUT_MODELESS 0 #define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1 #define MWM_INPUT_SYSTEM_MODAL 2 #define MWM_INPUT_FULL_APPLICATION_MODAL 3 #define MWM_INPUT_APPLICATION_MODAL MWM_INPUT_PRIMARY_APPLICATION_MODAL #define MWM_TEAROFF_WINDOW (1L << 0) namespace UKUI { class Decoration; } class XAtomHelper : public QObject { friend class UKUI::Decoration; Q_OBJECT public: static XAtomHelper *getInstance(); static bool isFrameLessWindow(int winId); bool isWindowDecorateBorderOnly(int winId); bool isWindowMotifHintDecorateBorderOnly(const MotifWmHints &hint); bool isUKUICsdSupported(); bool isUKUIDecorationWindow(int winId); UnityCorners getWindowBorderRadius(int winId); void setWindowBorderRadius(int winId, const UnityCorners &data); void setWindowBorderRadius(int winId, int topLeft, int topRight, int bottomLeft, int bottomRight); void setUKUIDecoraiontHint(int winId, bool set = true); void setWindowMotifHint(int winId, const MotifWmHints &hints); MotifWmHints getWindowMotifHint(int winId); private: explicit XAtomHelper(QObject *parent = nullptr); unsigned long registerUKUICsdNetWmSupportAtom(); void unregisterUKUICsdNetWmSupportAtom(); unsigned long m_motifWMHintsAtom = 0l; unsigned long m_unityBorderRadiusAtom = 0l; unsigned long m_ukuiDecorationAtion = 0l; }; #endif // XATOMHELPER_H ukui-quick/platform/qmldir0000664000175000017500000000010115153755732014633 0ustar fengfengmodule org.ukui.quick.platform plugin ukui-quick-platform-plugin ukui-quick/platform/wm-impl-x11.h0000664000175000017500000000251315153755732015573 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_WM_IMPL_X11_H #define UKUI_QUICK_WM_IMPL_X11_H #include "wm-interface.h" namespace UkuiQuick { class WMImplX11 : public WMInterface { Q_OBJECT public: explicit WMImplX11(QWindow *window); void setWindowType(WindowType::Type type) override; void setSkipTaskBar(bool skip) override; void setSkipSwitcher(bool skip) override; void setRemoveTitleBar(bool remove) override; void setPanelAutoHide(bool autoHide) override; void setPanelTakesFocus(bool takesFocus) override; QScreen* currentScreen() override; }; } // Ukui #endif //UKUI_QUICK_WM_IMPL_X11_H ukui-quick/platform/windows/0000775000175000017500000000000015153756415015121 5ustar fengfengukui-quick/platform/windows/ukui-window.cpp0000664000175000017500000001141715153755732020114 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "ukui-window.h" #include #include #include namespace UkuiQuick { class UKUIWindow::Private { public: bool m_needSetupWhenExposed {true}; bool m_removeTitleBar {false}; bool m_skipTaskBar {false}; bool m_skipSwitcher {false}; bool m_enableBlurEffect {false}; WindowType::Type m_type {WindowType::Normal}; WindowProxy *windowProxy {nullptr}; Region *m_blurRegion {nullptr}; Region *m_maskRegion {nullptr}; }; UKUIWindow::UKUIWindow(QWindow *parent) : QQuickWindow(parent), d(new Private) { qRegisterMetaType(); d->windowProxy = new WindowProxy(this, WindowProxy::Operations()); d->m_blurRegion = new Region(this); d->m_maskRegion = new Region(this); connect(d->m_blurRegion, &Region::regionChanged, this, &UKUIWindow::blurRegionChanged); connect(d->m_maskRegion, &Region::regionChanged, this, &UKUIWindow::maskRegionChanged); } UKUIWindow::~UKUIWindow() { if (d) { delete d; d = nullptr; } } QPoint UKUIWindow::windowPosition() const { return d->windowProxy->position(); } void UKUIWindow::setWindowPosition(const QPoint &pos) { d->windowProxy->setPosition(pos); } bool UKUIWindow::enableBlurEffect() const { return d->m_enableBlurEffect; } void UKUIWindow::setEnableBlurEffect(bool enable) { if (d->m_enableBlurEffect == enable) { return; } d->m_enableBlurEffect = enable; d->windowProxy->setBlurRegion(enable, d->m_blurRegion->region()); emit enableBlurEffectChanged(); } Region* UKUIWindow::blurRegion() const { return d->m_blurRegion; } Region* UKUIWindow::maskRegion() const { return d->m_maskRegion; } WindowType::Type UKUIWindow::windowType() const { return d->m_type; } void UKUIWindow::setWindowType(WindowType::Type type) { if (type == d->m_type) { return; } d->m_type = type; d->windowProxy->setWindowType(d->m_type); emit windowTypeChanged(); } bool UKUIWindow::skipTaskBar() const { return d->m_skipTaskBar; } bool UKUIWindow::skipSwitcher() const { return d->m_skipSwitcher; } bool UKUIWindow::removeTitleBar() const { return d->m_removeTitleBar; } void UKUIWindow::setRemoveTitleBar(bool remove) { if (d->m_removeTitleBar == remove) { return; } d->m_removeTitleBar = remove; d->windowProxy->setRemoveTitleBar(remove); emit removeTitleBarChanged(); } void UKUIWindow::setSkipTaskBar(bool skip) { if (d->m_skipTaskBar == skip) { return; } d->m_skipTaskBar = skip; d->windowProxy->setSkipTaskBar(d->m_skipTaskBar); emit skipTaskBarChanged(); } void UKUIWindow::setSkipSwitcher(bool skip) { if (d->m_skipSwitcher == skip) { return; } d->m_skipSwitcher = skip; d->windowProxy->setSkipSwitcher(d->m_skipSwitcher); emit skipSwitcherChanged(); } void UKUIWindow::moveEvent(QMoveEvent *event) { QWindow::moveEvent(event); } void UKUIWindow::resizeEvent(QResizeEvent *event) { QQuickWindow::resizeEvent(event); } void UKUIWindow::blurRegionChanged() { if (d->m_enableBlurEffect) { d->windowProxy->setBlurRegion(d->m_enableBlurEffect, d->m_blurRegion->region()); } } void UKUIWindow::maskRegionChanged() { setMask(d->m_maskRegion->region()); } // ====== UKUIWindowExtension ====== // UKUIWindowExtension::UKUIWindowExtension(QObject *parent) : QObject(parent), m_window(qobject_cast(parent)) { } int UKUIWindowExtension::x() const { return m_window->windowPosition().x(); } int UKUIWindowExtension::y() const { return m_window->windowPosition().y(); } void UKUIWindowExtension::setX(int x) { QPoint wPos = m_window->windowPosition(); if (x == wPos.x()) { return; } wPos.setX(x); m_window->setWindowPosition(wPos); emit xChanged(x); } void UKUIWindowExtension::setY(int y) { QPoint wPos = m_window->windowPosition(); if (y == wPos.y()) { return; } wPos.setY(y); m_window->setWindowPosition(wPos); emit yChanged(y); } } // UkuiQuick ukui-quick/platform/windows/region.h0000664000175000017500000000313015153756415016552 0ustar fengfeng// // Created by iaom on 2025/9/12. // #ifndef REGION_H #define REGION_H #include #include #include namespace UkuiQuick { class RectRegionPrivate; class RectRegion : public QObject { Q_OBJECT Q_PROPERTY(int x READ x WRITE setX NOTIFY xChanged) Q_PROPERTY(int y READ y WRITE setY NOTIFY yChanged) Q_PROPERTY(int width READ width WRITE setWidth NOTIFY widthChanged) Q_PROPERTY(int height READ height WRITE setHeight NOTIFY heightChanged) Q_PROPERTY(int radius READ radius WRITE setRadius NOTIFY radiusChanged) public: explicit RectRegion(QObject *parent = nullptr); ~RectRegion(); void setX(int x); int x() const; void setY(int y); int y() const; void setWidth(int width); int width() const; void setHeight(int height); int height() const; void setRadius(int radius); int radius() const; Q_SIGNALS: void xChanged(); void yChanged(); void widthChanged(); void heightChanged(); void radiusChanged(); private: RectRegionPrivate *d = nullptr; }; class RegionPrivate; class Region : public QObject { Q_OBJECT Q_PROPERTY(QRegion region READ region NOTIFY regionChanged) public: explicit Region(QObject *parent = nullptr); Q_INVOKABLE void appendRect(UkuiQuick::RectRegion *rect); Q_INVOKABLE void removeRect(UkuiQuick::RectRegion *rect); Q_INVOKABLE void clear(); Q_INVOKABLE QRegion region() const; Q_SIGNALS: void regionChanged(); protected: bool event(QEvent *event) override; private: RegionPrivate *d = nullptr; }; } #endif //REGION_H ukui-quick/platform/windows/dialog.cpp0000664000175000017500000010455215153756415017073 0ustar fengfeng/* SPDX-FileCopyrightText: 2011 Marco Martin SPDX-FileCopyrightText: 2013 Sebastian Kügler SPDX-FileCopyrightText: 2014 Martin Gräßlin SPDX-FileCopyrightText: 2014 Vishesh Handa SPDX-FileCopyrightText: 2024 iaom SPDX-License-Identifier: GPL-2.0-or-later */ #include "dialog.h" #include #include #include #include #include "window-helper.h" namespace UkuiQuick { #define QWINDOWSIZE_MAX ((1<<24)-1) class SlideWindowPrivate { public: WindowProxy::SlideFromEdge m_fromEdge = WindowProxy::SlideFromEdge::NoEdge; int m_offset = -1; }; SlideWindow::SlideWindow(QObject* parent) : QObject(parent), d(new SlideWindowPrivate) { } SlideWindow::~SlideWindow() { if (d) { delete d; d = nullptr; } } WindowProxy::SlideFromEdge SlideWindow::fromEdge() const { return d->m_fromEdge; } void SlideWindow::setFromEdge(WindowProxy::SlideFromEdge fromEdge) { if (d->m_fromEdge == fromEdge) { return; } d->m_fromEdge = fromEdge; Q_EMIT changed(); } int SlideWindow::offset() const { return d->m_offset; } void SlideWindow::setOffset(int offset) { if (d->m_offset == offset) { return; } d->m_offset = offset; Q_EMIT changed(); } class DialogPrivate { public: explicit DialogPrivate(Dialog *dialog); /** * This function is an optimized version of updateMaximumHeight, * updateMaximumWidth,updateMinimumWidth and updateMinimumHeight. * It should be called when you need to call all 4 of these functions * AND you have called syncToMainItemSize before. */ void updateLayoutParameters(); void syncToMainItemSize(); void slotMainItemSizeChanged(); void slotWindowPositionChanged(); void updateMinimumWidth(); void updateMinimumHeight(); void updateMaximumWidth(); void updateMaximumHeight(); /** * Gets the maximum and minimum size hints for the window based on the contents. it doesn't actually resize anything */ void getSizeHints(QSize &min, QSize &max) const; /** * This function checks the current position of the dialog and repositions * it so that no part of it is not on the screen */ void repositionIfOffScreen(); QRect availableScreenGeometryForPosition(const QPoint &pos) const; void updateTheme(); void updateVisibility(bool visible); void applyType(); QPointF positionAdjustedForMainItem(const QPointF &point) const; bool mainItemContainsPosition(const QPointF &point) const; void updateBlurRegion(); void updateSlideWindowParams(); Dialog *q; QPointer m_mainItem; QPointer m_mainItemLayout; QPointer m_visualParent; bool m_componentComplete; UkuiQuick::Dialog::PopupLocation m_location; bool m_hideOnWindowDeactivate; bool m_visible; QPointer m_windowProxy; QRect m_cachedGeometry; WindowType::Type m_type; QRegion m_windowBlurRegion; bool m_enableWindowBlur; int m_margin; bool m_removeHeaderBar; Decoration::Components m_decorationComponents; std::unique_ptr m_slideWindow; }; DialogPrivate::DialogPrivate(Dialog *dialog) : q(dialog) , m_location(UkuiQuick::Dialog::PopupLocation::BottomEdge) , m_hideOnWindowDeactivate(false) , m_visible(false) , m_componentComplete(dialog->parent() == nullptr) , m_type(WindowType::Type::Normal) , m_enableWindowBlur(false) , m_margin(0) , m_removeHeaderBar{false} , m_slideWindow(std::make_unique()) { } void DialogPrivate::slotMainItemSizeChanged() { syncToMainItemSize(); } void DialogPrivate::slotWindowPositionChanged() { // Tooltips always have all the borders // floating windows have all borders if (!q->isVisible() || q->flags().testFlag(Qt::ToolTip) || m_location == UkuiQuick::Dialog::PopupLocation::Floating) { return; } updateTheme(); if (m_mainItem) { m_mainItem->setPosition(QPoint(0, 0)); m_mainItem->setSize(QSize(q->width(), q->height())); } } void DialogPrivate::updateMinimumWidth() { if(!m_mainItem || !m_mainItemLayout) { return; } if (!m_componentComplete) { return; } q->setMinimumWidth(0); int minimumWidth = m_mainItemLayout->property("minimumWidth").toInt(); if (q->screen()) { minimumWidth = qMin(q->screen()->availableGeometry().width(), minimumWidth); } q->contentItem()->setWidth(qMax(q->width(), minimumWidth)); q->setWidth(qMax(q->width(), minimumWidth)); updateLayoutParameters(); } void DialogPrivate::updateMinimumHeight() { if(!m_mainItem || !m_mainItemLayout) { return; } if (!m_componentComplete) { return; } q->setMinimumHeight(0); int minimumHeight = m_mainItemLayout->property("minimumHeight").toInt(); if (q->screen()) { minimumHeight = qMin(q->screen()->availableGeometry().height(), minimumHeight); } q->contentItem()->setHeight(qMax(q->height(), minimumHeight)); q->setHeight(qMax(q->height(), minimumHeight)); updateLayoutParameters(); } void DialogPrivate::updateMaximumWidth() { if(!m_mainItem || !m_mainItemLayout) { return; } if (!m_componentComplete) { return; } q->setMaximumWidth(QWINDOWSIZE_MAX); int maximumWidth = m_mainItemLayout->property("maximumWidth").toInt(); if (q->screen()) { maximumWidth = qMin(q->screen()->availableGeometry().width(), maximumWidth); } q->contentItem()->setWidth(qMin(q->width(), maximumWidth)); q->setWidth(qMin(q->width(), maximumWidth)); updateLayoutParameters(); } void DialogPrivate::updateMaximumHeight() { if(!m_mainItem || !m_mainItemLayout) { return; } if (!m_componentComplete) { return; } q->setMaximumHeight(QWINDOWSIZE_MAX); int maximumHeight = m_mainItemLayout->property("maximumHeight").toInt(); if (q->screen()) { maximumHeight = qMin(q->screen()->availableGeometry().height(), maximumHeight); } q->contentItem()->setHeight(qMin(q->height(), maximumHeight)); q->setHeight(qMin(q->height(), maximumHeight)); updateLayoutParameters(); } void DialogPrivate::updateLayoutParameters() { if (!m_componentComplete || !m_mainItem || !m_mainItemLayout) { return; } m_mainItem->disconnect(q); QSize min; QSize max(QWINDOWSIZE_MAX, QWINDOWSIZE_MAX); getSizeHints(min, max); q->setMinimumSize(min); q->setMaximumSize(max); const QSize finalSize(qBound(min.width(), q->width(), max.width()), qBound(min.height(), q->height(), max.height())); if (m_visualParent) { // it's important here that we're using re->size() as size, we don't want to do recursive resizeEvents const QRect geom(q->popupPosition(m_visualParent, finalSize), finalSize); q->adjustGeometry(geom); } else { q->resize(finalSize); } m_mainItem->setPosition(QPointF(0, 0)); m_mainItem->setSize(QSizeF(q->width(), q->height())); repositionIfOffScreen(); updateTheme(); QObject::connect(m_mainItem, SIGNAL(widthChanged()), q, SLOT(slotMainItemSizeChanged())); QObject::connect(m_mainItem, SIGNAL(heightChanged()), q, SLOT(slotMainItemSizeChanged())); } void DialogPrivate::syncToMainItemSize() { if(!m_mainItem) { return; } if (!m_componentComplete) { return; } if (m_mainItem->width() <= 0 || m_mainItem->height() <= 0) { qWarning() << "trying to show an empty dialog"; } QSize s = QSize(m_mainItem->width(), m_mainItem->height()); QSize min; QSize max(QWINDOWSIZE_MAX, QWINDOWSIZE_MAX); getSizeHints(min, max); q->setMinimumSize(min); q->setMaximumSize(max); s = QSize(qBound(min.width(), s.width(), max.width()), qBound(min.height(), s.height(), max.height())); q->contentItem()->setSize(s); if (m_visualParent) { const QRect geom(q->popupPosition(m_visualParent, s), s); if (geom == q->geometry()) { return; } q->adjustGeometry(geom); } else { q->resize(s); } m_mainItem->setPosition(QPointF(0, 0)); updateTheme(); } void DialogPrivate::getSizeHints(QSize &min, QSize &max) const { if (!m_componentComplete || !m_mainItem || !m_mainItemLayout) { return; } int minimumHeight = m_mainItemLayout->property("minimumHeight").toInt(); int maximumHeight = m_mainItemLayout->property("maximumHeight").toInt(); maximumHeight = maximumHeight > 0 ? qMax(minimumHeight, maximumHeight) : QWINDOWSIZE_MAX; int minimumWidth = m_mainItemLayout->property("minimumWidth").toInt(); int maximumWidth = m_mainItemLayout->property("maximumWidth").toInt(); maximumWidth = maximumWidth > 0 ? qMax(minimumWidth, maximumWidth) : QWINDOWSIZE_MAX; if (q->screen()) { minimumWidth = qMin(q->screen()->availableGeometry().width(), minimumWidth); minimumHeight = qMin(q->screen()->availableGeometry().height(), minimumHeight); maximumWidth = qMin(q->screen()->availableGeometry().width(), maximumWidth); maximumHeight = qMin(q->screen()->availableGeometry().height(), maximumHeight); } min = QSize(minimumWidth, minimumHeight); max = QSize(maximumWidth, maximumHeight); } void DialogPrivate::repositionIfOffScreen() { if (!m_componentComplete) { return; } const QRect avail = availableScreenGeometryForPosition(q->position()); int x = q->x(); int y = q->y(); if (x < avail.left()) { x = avail.left(); } else if (x + q->width() > avail.right()) { x = avail.right() - q->width() + 1; } if (y < avail.top()) { y = avail.top(); } else if (y + q->height() > avail.bottom()) { y = avail.bottom() - q->height() + 1; } if(m_windowProxy) { m_windowProxy->setGeometry(QRect(x, y, q->width(), q->height())); } } QRect DialogPrivate::availableScreenGeometryForPosition(const QPoint &pos) const { // FIXME: QWindow::screen() never ever changes if the window is moved across // virtual screens (normal two screens with X), this seems to be intentional // as it's explicitly mentioned in the docs. Until that's changed or some // more proper way of howto get the current QScreen for given QWindow is found, // we simply iterate over the virtual screens and pick the one our QWindow // says it's at. QRect avail; const auto screens = QGuiApplication::screens(); for (QScreen *screen : screens) { // we check geometry() but then take availableGeometry() // to reliably check in which screen a position is, we need the full // geometry, including areas for panels if (screen->geometry().contains(pos)) { avail = screen->availableGeometry(); break; } } /* * if the heuristic fails (because the topleft of the dialog is offscreen) * use at least our screen() * the screen should be correctly updated now on Qt 5.3+ so should be * more reliable anyways (could be tried to remove the whole for loop * above at this point) * * important: screen can be a nullptr... see bug 345173 */ if (avail.isEmpty() && q->screen()) { avail = q->screen()->availableGeometry(); } return avail; } void DialogPrivate::updateTheme() { } void DialogPrivate::updateVisibility(bool visible) { if (visible) { if (m_visualParent && m_visualParent->window()) { q->setTransientParent(m_visualParent->window()); } if (q->location() == UkuiQuick::Dialog::PopupLocation::FullScreen) { // We cache the original size of the item, to retrieve it // when the dialog is switched back from fullscreen. if (q->geometry() != q->screen()->availableGeometry()) { m_cachedGeometry = q->geometry(); } q->setGeometry(q->screen()->availableGeometry()); } else { if (!m_cachedGeometry.isNull()) { q->resize(m_cachedGeometry.size()); slotWindowPositionChanged(); if (m_visualParent) { q->setPosition(q->popupPosition(m_visualParent, q->size())); } m_cachedGeometry = QRect(); } if (m_mainItem) { syncToMainItemSize(); } if (m_mainItemLayout) { updateLayoutParameters(); } // if is a wayland window that was hidden, we need // to set its position again as there won't be any move event to sync QWindow::position and shellsurface::position if (q->type() != UkuiQuick::WindowType::OnScreenDisplay) { if(m_windowProxy) { m_windowProxy->setPosition(q->position()); } } } q->raise(); applyType(); } } void DialogPrivate::applyType() { if(m_windowProxy) { m_windowProxy->setWindowType(m_type); } } QPointF DialogPrivate::positionAdjustedForMainItem(const QPointF &point) const { if (!m_mainItem) { return point; } QRectF itemRect(m_mainItem->mapToScene(QPoint(0, 0)), QSizeF(m_mainItem->width(), m_mainItem->height())); return QPointF(qBound(itemRect.left(), point.x(), itemRect.right()), qBound(itemRect.top(), point.y(), itemRect.bottom())); } bool DialogPrivate::mainItemContainsPosition(const QPointF &point) const { if (!m_mainItem) { return false; } return QRectF(m_mainItem->mapToScene(QPoint(0, 0)), QSizeF(m_mainItem->width(), m_mainItem->height())).contains(point); } void DialogPrivate::updateBlurRegion() { m_windowProxy->setBlurRegion(m_enableWindowBlur, m_windowBlurRegion); } void DialogPrivate::updateSlideWindowParams() { if (m_windowProxy) { m_windowProxy->slideWindow(m_slideWindow->fromEdge(), m_slideWindow->offset()); } } Dialog::Dialog(QQuickItem *parent) : QQuickWindow(parent ? parent->window() : nullptr), d(new DialogPrivate(this)) { setColor(QColor(Qt::transparent)); setFlags(Qt::Dialog); connect(this, &QWindow::xChanged, [=]() { d->slotWindowPositionChanged(); }); connect(this, &QWindow::yChanged, [=]() { d->slotWindowPositionChanged(); }); // Given dialogs are skip task bar and don't have a decoration // minimizing them using e.g. "minimize all" should just close them connect(this, &QWindow::windowStateChanged, this, [this](Qt::WindowState newState) { if (newState == Qt::WindowMinimized) { setVisible(false); } }); connect(this, &QWindow::visibleChanged, this, &Dialog::visibleChangedProxy); connect(d->m_slideWindow.get(), &SlideWindow::changed, this, [&]() { d->updateSlideWindowParams(); }); // HACK: this property is invoked due to the initialization that gets done to contentItem() in the getter property("data"); d->m_windowProxy = new WindowProxy(this); } Dialog::~Dialog() { disconnect(this, nullptr, this, nullptr); } QQuickItem *Dialog::mainItem() const { return d->m_mainItem; } void Dialog::setMainItem(QQuickItem *mainItem) { if (d->m_mainItem != mainItem) { if (d->m_mainItem) { disconnect(d->m_mainItem, nullptr, this, nullptr); d->m_mainItem->setParentItem(nullptr); } if (d->m_mainItemLayout) { disconnect(d->m_mainItemLayout, nullptr, this, nullptr); } d->m_mainItem = mainItem; if (mainItem) { mainItem->setParentItem(contentItem()); connect(mainItem, &QQuickItem::widthChanged, this, [&](){ d->slotMainItemSizeChanged(); }); connect(mainItem, &QQuickItem::heightChanged, this, [&](){ d->slotMainItemSizeChanged(); }); d->slotMainItemSizeChanged(); // Extract the representation's Layout, if any QObject *layout = nullptr; setMinimumSize(QSize(0, 0)); setMaximumSize(QSize(QWINDOWSIZE_MAX, QWINDOWSIZE_MAX)); // Search a child that has the needed Layout properties // HACK: here we are not type safe, but is the only way to access to a pointer of Layout const auto lstChild = mainItem->children(); for (QObject *child : lstChild) { // find for the needed property of Layout: minimum/maximum/preferred sizes and fillWidth/fillHeight if (child->property("minimumWidth").isValid() && child->property("minimumHeight").isValid() && child->property("preferredWidth").isValid() && child->property("preferredHeight").isValid() && child->property("maximumWidth").isValid() && child->property("maximumHeight").isValid() && child->property("fillWidth").isValid() && child->property("fillHeight").isValid()) { layout = child; break; } } d->m_mainItemLayout = layout; if (layout) { // These connections are direct. They run on the GUI thread. // If the underlying QQuickItem is sane, these properties should be updated atomically in one cycle // of the GUI thread event loop, denying the chance for the event loop to run a QQuickItem::update() call in between. // So we avoid rendering a frame in between with inconsistent geometry properties which would cause flickering issues. connect(layout, SIGNAL(minimumWidthChanged()), this, SLOT(updateMinimumWidth())); connect(layout, SIGNAL(minimumHeightChanged()), this, SLOT(updateMinimumHeight())); connect(layout, SIGNAL(maximumWidthChanged()), this, SLOT(updateMaximumWidth())); connect(layout, SIGNAL(maximumHeightChanged()), this, SLOT(updateMaximumHeight())); d->updateLayoutParameters(); } } // if this is called in Component.onCompleted we have to wait a loop the item is added to a scene Q_EMIT mainItemChanged(); } } QQuickItem *Dialog::visualParent() const { return d->m_visualParent; } void Dialog::setVisualParent(QQuickItem *visualParent) { if (d->m_visualParent == visualParent) { return; } d->m_visualParent = visualParent; Q_EMIT visualParentChanged(); if (visualParent) { if (visualParent->window()) { setTransientParent(visualParent->window()); } d->syncToMainItemSize(); } } QPoint Dialog::popupPosition(QQuickItem *item, const QSize &size) { if (!item) { // If no item was specified try to align at the center of the parent view auto *parentItem = qobject_cast(parent()); if (parentItem) { QScreen *screen = parentItem->window()->screen(); switch (d->m_location) { case UkuiQuick::Dialog::PopupLocation::TopEdge: return {screen->availableGeometry().center().x() - size.width() / 2, screen->availableGeometry().y()}; case UkuiQuick::Dialog::PopupLocation::LeftEdge: return {screen->availableGeometry().x(), screen->availableGeometry().center().y() - size.height() / 2}; case UkuiQuick::Dialog::PopupLocation::RightEdge: return {screen->availableGeometry().right() - size.width(), screen->availableGeometry().center().y() - size.height() / 2}; case UkuiQuick::Dialog::PopupLocation::BottomEdge: return {screen->availableGeometry().center().x() - size.width() / 2, screen->availableGeometry().bottom() - size.height()}; // Default center in the screen default: return screen->geometry().center() - QPoint(size.width() / 2, size.height() / 2); } } else { return {}; } } QPointF pos = item->mapToScene(QPointF(0, 0)); if (item->window()) { pos = item->window()->mapToGlobal(pos.toPoint()); } else { return {}; } QRect parentGeometryBounds = item->mapRectToScene(item->boundingRect()).toRect(); if (item->window()) { parentGeometryBounds.moveTopLeft(item->window()->mapToGlobal(parentGeometryBounds.topLeft())); pos = parentGeometryBounds.topLeft(); } const QPoint topPoint(pos.x() + (item->mapRectToScene(item->boundingRect()).width() - size.width()) / 2, parentGeometryBounds.top() - size.height()); const QPoint bottomPoint(pos.x() + (item->mapRectToScene(item->boundingRect()).width() - size.width()) / 2, parentGeometryBounds.bottom()); const QPoint leftPoint(parentGeometryBounds.left() - size.width(), pos.y() + (item->mapRectToScene(item->boundingRect()).height() - size.height()) / 2); const QPoint rightPoint(parentGeometryBounds.right(), pos.y() + (item->mapRectToScene(item->boundingRect()).height() - size.height()) / 2); QPoint dialogPos; if (d->m_location == UkuiQuick::Dialog::PopupLocation::TopEdge) { dialogPos = bottomPoint; } else if (d->m_location == UkuiQuick::Dialog::PopupLocation::LeftEdge) { dialogPos = rightPoint; } else if (d->m_location == UkuiQuick::Dialog::PopupLocation::RightEdge) { dialogPos = leftPoint; } else { // Types::BottomEdge dialogPos = topPoint; } // find the correct screen for the item // we do not rely on item->window()->screen() because // QWindow::screen() is always only the screen where the window gets first created // not actually the current window. See QWindow::screen() documentation QRect avail = item->window()->screen()->availableGeometry(); if (dialogPos.x() < avail.left()) { // popup hits lhs if (d->m_location != UkuiQuick::Dialog::PopupLocation::LeftEdge || d->m_location == UkuiQuick::Dialog::PopupLocation::RightEdge) { // move it dialogPos.setX(avail.left()); } else { // swap edge dialogPos.setX(rightPoint.x()); } } if (dialogPos.x() + size.width() > avail.right()) { // popup hits rhs if (d->m_location == UkuiQuick::Dialog::PopupLocation::TopEdge || d->m_location == UkuiQuick::Dialog::PopupLocation::BottomEdge) { dialogPos.setX(qMax(avail.left(), (avail.right() - size.width() + 1))); } else { dialogPos.setX(leftPoint.x()); } } if (dialogPos.y() < avail.top()) { // hitting top if (d->m_location == UkuiQuick::Dialog::PopupLocation::LeftEdge || d->m_location ==UkuiQuick::Dialog::PopupLocation::RightEdge) { dialogPos.setY(avail.top()); } else { dialogPos.setY(bottomPoint.y()); } } if (dialogPos.y() + size.height() > avail.bottom()) { // hitting bottom if (d->m_location == UkuiQuick::Dialog::PopupLocation::TopEdge || d->m_location == UkuiQuick::Dialog::PopupLocation::BottomEdge) { dialogPos.setY(topPoint.y()); } else { dialogPos.setY(qMax(avail.top(), (avail.bottom() - size.height() + 1))); } } if(d->m_margin) { switch (d->m_location) { case TopEdge: dialogPos.setY(dialogPos.y() + d->m_margin); break; case BottomEdge: dialogPos.setY(dialogPos.y() - d->m_margin); break; case LeftEdge: dialogPos.setX(dialogPos.x() + d->m_margin); break; case RightEdge: dialogPos.setX(dialogPos.x() - d->m_margin); break; default: break; } } return dialogPos; } UkuiQuick::Dialog::PopupLocation Dialog::location() const { return d->m_location;; } void Dialog::setLocation(UkuiQuick::Dialog::PopupLocation location) { if (d->m_location == location) { return; } d->m_location = location; Q_EMIT locationChanged(); if (d->m_mainItem) { d->syncToMainItemSize(); } } void Dialog::adjustGeometry(const QRect &geom) { if(d->m_windowProxy) { d->m_windowProxy->setGeometry(geom); } } bool Dialog::hideOnWindowDeactivate() const { return d->m_hideOnWindowDeactivate; } void Dialog::setHideOnWindowDeactivate(bool hide) { if (d->m_hideOnWindowDeactivate == hide) { return; } d->m_hideOnWindowDeactivate = hide; Q_EMIT hideOnWindowDeactivateChanged(); } void Dialog::classBegin() { d->m_componentComplete = false; } void Dialog::componentComplete() { d->m_componentComplete = true; QQuickWindow::setVisible(d->m_visible); } void Dialog::resizeEvent(QResizeEvent *re) { QQuickWindow::resizeEvent(re); // it's a spontaneous event generated in qguiapplication.cpp QGuiApplicationPrivate::processWindowScreenChangedEvent // QWindowSystemInterfacePrivate::GeometryChangeEvent gce(window, QHighDpi::fromNativePixels(window->handle()->geometry(), window), QRect()); // This happens before the first show event when there is more than one screen, // right after the window has been created, the window is still 0x0, // but the resize event gets delivered with 0x0 again and executed with all the bad side effects // this seems to happen for every window when there are multiple screens, so something we have probably to watch out for in the future if (re->size().isEmpty() || re->size() == re->oldSize()) { return; } // A dialog can be resized even if no mainItem has ever been set if (!d->m_mainItem) { return; } d->m_mainItem->disconnect(this); d->m_mainItem->setPosition(QPointF(0, 0)); d->m_mainItem->setSize(QSize(re->size().width(), re->size().height())); connect(d->m_mainItem, &QQuickItem::widthChanged, this, [&](){ d->slotMainItemSizeChanged(); }); connect(d->m_mainItem, &QQuickItem::heightChanged, this, [&](){ d->slotMainItemSizeChanged(); }); } void Dialog::focusOutEvent(QFocusEvent *ev) { if (d->m_hideOnWindowDeactivate) { bool parentHasFocus = false; QWindow *parentWindow = transientParent(); while (parentWindow) { if (parentWindow->isActive() && !(parentWindow->flags() & Qt::WindowDoesNotAcceptFocus)) { parentHasFocus = true; break; } parentWindow = parentWindow->transientParent(); } const QWindow *focusWindow = QGuiApplication::focusWindow(); bool childHasFocus = focusWindow && ((focusWindow->isActive() && isAncestorOf(focusWindow)) || (focusWindow->type() & Qt::Popup) == Qt::Popup); if (!parentHasFocus && !childHasFocus) { setVisible(false); Q_EMIT windowDeactivated(); } } QQuickWindow::focusOutEvent(ev); } bool Dialog::event(QEvent *event) { if (event->type() == QEvent::Show) { d->updateVisibility(true); } /*Fitt's law: if the containment has margins, and the mouse cursor clicked * on the mouse edge, forward the click in the containment boundaries */ if (d->m_mainItem && !d->m_mainItem->size().isEmpty()) { switch (event->type()) { case QEvent::MouseMove: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { QMouseEvent *me = static_cast(event); // don't mess with position if the cursor is actually outside the view: // somebody is doing a click and drag that must not break when the cursor i outside if (geometry().contains(me->screenPos().toPoint()) && !d->mainItemContainsPosition(me->windowPos())) { QMouseEvent me2(me->type(), d->positionAdjustedForMainItem(me->windowPos()), d->positionAdjustedForMainItem(me->windowPos()), d->positionAdjustedForMainItem(me->windowPos()) + position(), me->button(), me->buttons(), me->modifiers()); if (isVisible()) { QCoreApplication::sendEvent(this, &me2); } return true; } break; } case QEvent::Wheel: { QWheelEvent *we = static_cast(event); const QPoint pos = we->position().toPoint(); if (!d->mainItemContainsPosition(pos)) { QWheelEvent we2(d->positionAdjustedForMainItem(pos), d->positionAdjustedForMainItem(pos) + position(), we->pixelDelta(), we->angleDelta(), we->buttons(), we->modifiers(), we->phase(), false /*inverted*/); if (isVisible()) { QCoreApplication::sendEvent(this, &we2); } return true; } break; } case QEvent::DragEnter: { QDragEnterEvent *de = static_cast(event); if (!d->mainItemContainsPosition(de->pos())) { QDragEnterEvent de2(d->positionAdjustedForMainItem(de->pos()).toPoint(), de->possibleActions(), de->mimeData(), de->mouseButtons(), de->keyboardModifiers()); if (isVisible()) { QCoreApplication::sendEvent(this, &de2); } return true; } break; } // DragLeave just works case QEvent::DragLeave: break; case QEvent::DragMove: { QDragMoveEvent *de = static_cast(event); if (!d->mainItemContainsPosition(de->pos())) { QDragMoveEvent de2(d->positionAdjustedForMainItem(de->pos()).toPoint(), de->possibleActions(), de->mimeData(), de->mouseButtons(), de->keyboardModifiers()); if (isVisible()) { QCoreApplication::sendEvent(this, &de2); } return true; } break; } case QEvent::Drop: { QDropEvent *de = static_cast(event); if (!d->mainItemContainsPosition(de->pos())) { QDropEvent de2(d->positionAdjustedForMainItem(de->pos()).toPoint(), de->possibleActions(), de->mimeData(), de->mouseButtons(), de->keyboardModifiers()); if (isVisible()) { QCoreApplication::sendEvent(this, &de2); } return true; } break; } default: break; } } return QQuickWindow::event(event); } void Dialog::setType(UkuiQuick::WindowType::Type type) { if (type == d->m_type) { return; } d->m_type = type; d->applyType(); Q_EMIT typeChanged(); } UkuiQuick::WindowType::Type Dialog::type() const { return d->m_type;; } bool Dialog::isVisible() const { if (d->m_componentComplete) { return QQuickWindow::isVisible(); } return d->m_visible; } void Dialog::setVisible(bool visible) { // only update real visibility when we have finished component completion // and all flags have been set d->m_visible = visible; if (d->m_componentComplete) { if (visible && d->m_visualParent && d->m_windowProxy) { d->m_windowProxy->setPosition(popupPosition(d->m_visualParent, size())); } // Bug 381242: Qt remembers minimize state and re-applies it when showing setWindowStates(windowStates() & ~Qt::WindowMinimized); QQuickWindow::setVisible(visible); // signal will be emitted and proxied from the QQuickWindow code } else { Q_EMIT visibleChangedProxy(); } } bool Dialog::enableWindowBlur() const { return d->m_enableWindowBlur; } void Dialog::setEnableWindowBlur(bool enable) { if (d->m_enableWindowBlur == enable) { return; } d->m_enableWindowBlur = enable; d->updateBlurRegion(); Q_EMIT enableWindowBlurChanged(); } QRegion Dialog::blurRegion() const { return d->m_windowBlurRegion; } void Dialog::setBlurRegion(const QRegion ®ion) { if (d->m_windowBlurRegion == region) { return; } d->m_windowBlurRegion = region; d->updateBlurRegion(); } int Dialog::margin() const { return d->m_margin; } void Dialog::setMargin(int margin) { if(d->m_margin != margin) { d->m_margin = margin; Q_EMIT marginChanged(); } } bool Dialog::removeHeaderBar() const { return d->m_removeHeaderBar; } void Dialog::setRemoveHeaderBar(bool remove) { if(d->m_removeHeaderBar != remove) { d->m_removeHeaderBar = remove; if(d->m_windowProxy) { d->m_windowProxy->setRemoveTitleBar(remove); } Q_EMIT removeHeaderBar(); } } void Dialog::updatePosition() { d->syncToMainItemSize(); } UkuiQuick::Decoration::Components Dialog::decorationComponents() const { return d->m_decorationComponents; } void Dialog::setDecorationComponents(UkuiQuick::Decoration::Components components) { d->m_decorationComponents = components; if (d->m_windowProxy) { d->m_windowProxy->setDecorationComponents(components); } Q_EMIT decorationComponentsChanged(); } void Dialog::setStates(uint32_t states, bool enable) { if (d->m_windowProxy) { d->m_windowProxy->setWindowStates(WindowState::States(states), enable); } } SlideWindow* Dialog::slideWindow() const { return d->m_slideWindow.get(); } } // UkuiQuick #include "moc_dialog.cpp"ukui-quick/platform/windows/dialog.h0000664000175000017500000001603215153756415016533 0ustar fengfeng/* SPDX-FileCopyrightText: 2011 Marco Martin SPDX-FileCopyrightText: 2013 Sebastian Kügler SPDX-FileCopyrightText: 2024 iaom SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef UKUI_QUICK_ITEMS_DIALOG_H #define UKUI_QUICK_ITEMS_DIALOG_H #include #include #include #include "window-helper.h" namespace UkuiQuick { class SlideWindowPrivate; class SlideWindow : public QObject { Q_OBJECT Q_PROPERTY(UkuiQuick::WindowProxy::SlideFromEdge fromEdge READ fromEdge WRITE setFromEdge NOTIFY changed) Q_PROPERTY(int offset READ offset WRITE setOffset NOTIFY changed) public: explicit SlideWindow(QObject* parent = nullptr); ~SlideWindow(); WindowProxy::SlideFromEdge fromEdge() const; void setFromEdge(WindowProxy::SlideFromEdge fromEdge); int offset() const; void setOffset(int offset); Q_SIGNALS: void changed(); private: SlideWindowPrivate *d = nullptr; }; class DialogPrivate; class Dialog : public QQuickWindow, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) /** * The main QML item that will be displayed in the Dialog */ Q_PROPERTY(QQuickItem *mainItem READ mainItem WRITE setMainItem NOTIFY mainItemChanged) /** * The main QML item that will be displayed in the Dialog */ Q_PROPERTY(QQuickItem *visualParent READ visualParent WRITE setVisualParent NOTIFY visualParentChanged) /** * Plasma Location of the dialog window. Useful if this dialog is a popup for a panel */ Q_PROPERTY(UkuiQuick::Dialog::PopupLocation location READ location WRITE setLocation NOTIFY locationChanged) /** * Type of the window */ Q_PROPERTY(UkuiQuick::WindowType::Type type READ type WRITE setType NOTIFY typeChanged) /** * Whether the dialog should be hidden when the dialog loses focus. * * The default value is @c false. **/ Q_PROPERTY(bool hideOnWindowDeactivate READ hideOnWindowDeactivate WRITE setHideOnWindowDeactivate NOTIFY hideOnWindowDeactivateChanged) Q_PROPERTY(bool enableWindowBlur READ enableWindowBlur WRITE setEnableWindowBlur NOTIFY enableWindowBlurChanged) Q_PROPERTY(QRegion blurRegion READ blurRegion WRITE setBlurRegion NOTIFY blurRegionChanged) /** * Margin between dialog and it's parent item */ Q_PROPERTY(int margin READ margin WRITE setMargin NOTIFY marginChanged) Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChangedProxy) Q_PROPERTY(bool removeHeaderBar READ removeHeaderBar WRITE setRemoveHeaderBar NOTIFY removeHeaderBarChanged) /** * Components of the decoration, only available on Wayland. * @see Decoration::Components */ Q_PROPERTY(UkuiQuick::Decoration::Components decorationComponents READ decorationComponents WRITE setDecorationComponents NOTIFY decorationComponentsChanged) /** * @brief This property holds the slide window params grouped property * * * Example usage: * @code * Dialog { * slideWindow.fromEdge: 4 * slideWindow.offset: 0 *} * @endcode * * @see SlideWindow */ Q_PROPERTY(SlideWindow *slideWindow READ slideWindow CONSTANT FINAL) Q_CLASSINFO("DefaultProperty", "mainItem") public: /** * The Location enumeration describes where on screen an element, such as an * Applet or its managing container, is positioned on the screen. **/ enum PopupLocation { Floating = 0, /**< Free floating. Neither geometry or z-ordering is described precisely by this value. */ Desktop, /**< On the planar desktop layer, extending across the full screen from edge to edge */ FullScreen, /**< Full screen */ TopEdge, /**< Along the top of the screen*/ BottomEdge, /**< Along the bottom of the screen*/ LeftEdge, /**< Along the left side of the screen */ RightEdge, /**< Along the right side of the screen */ }; Q_ENUM(PopupLocation) explicit Dialog(QQuickItem *parent = nullptr); ~Dialog(); QQuickItem *mainItem() const; void setMainItem(QQuickItem *mainItem); QQuickItem *visualParent() const; void setVisualParent(QQuickItem *visualParent); UkuiQuick::Dialog::PopupLocation location() const; void setLocation(UkuiQuick::Dialog::PopupLocation location); void setType(UkuiQuick::WindowType::Type type); UkuiQuick::WindowType::Type type() const; bool hideOnWindowDeactivate() const; void setHideOnWindowDeactivate(bool hide); bool isVisible() const; void setVisible(bool visible); bool enableWindowBlur() const; void setEnableWindowBlur(bool enable); QRegion blurRegion() const; void setBlurRegion(const QRegion& region); int margin() const; void setMargin(int margin); bool removeHeaderBar() const; void setRemoveHeaderBar(bool remove); /** * @returns The suggested screen position for the popup * @param item the item the popup has to be positioned relatively to. if null, the popup will be positioned in the center of the window * @param size the size that the popup will have, which influences the final position */ virtual QPoint popupPosition(QQuickItem *item, const QSize &size); Q_INVOKABLE void updatePosition(); UkuiQuick::Decoration::Components decorationComponents() const; void setDecorationComponents(UkuiQuick::Decoration::Components components); Q_INVOKABLE void setStates(uint32_t states, bool enable); SlideWindow *slideWindow() const; protected: virtual void adjustGeometry(const QRect &geom); void classBegin() override; void componentComplete() override; void resizeEvent(QResizeEvent *re) override; void focusOutEvent(QFocusEvent *ev) override; bool event(QEvent *event) override; Q_SIGNALS: void mainItemChanged(); void visualParentChanged(); void visibleChangedProxy(); // redeclaration of QQuickWindow::visibleChanged void typeChanged(); void locationChanged(); void hideOnWindowDeactivateChanged(); /** * Emitted when the @see hideOnWindowDeactivate property is @c true and this dialog lost focus to a * window that is neither a parent dialog to nor a child dialog of this dialog. */ void windowDeactivated(); void enableWindowBlurChanged(); void blurRegionChanged(); void marginChanged(); void removeHeaderBarChanged(); void decorationComponentsChanged(); private: friend class DialogPrivate; const QScopedPointer d; Q_PRIVATE_SLOT(d, void updateVisibility(bool visible)) Q_PRIVATE_SLOT(d, void updateMinimumWidth()) Q_PRIVATE_SLOT(d, void updateMinimumHeight()) Q_PRIVATE_SLOT(d, void updateMaximumWidth()) Q_PRIVATE_SLOT(d, void updateMaximumHeight()) Q_PRIVATE_SLOT(d, void updateLayoutParameters()) Q_PRIVATE_SLOT(d, void slotMainItemSizeChanged()) Q_PRIVATE_SLOT(d, void updateSlideWindowParams()) }; } // UkuiQuick #endif //UKUI_QUICK_ITEMS_DIALOG_H ukui-quick/platform/windows/region.cpp0000664000175000017500000000735515153756415017122 0ustar fengfeng// // Created by iaom on 2025/9/12. // #include "region.h" #include #include #include namespace UkuiQuick { class RectRegionPrivate { public: int m_x = 0; int m_y = 0; int m_width = 0; int m_height = 0; int m_radius = 0; }; RectRegion::RectRegion(QObject *parent) : QObject(parent), d(new RectRegionPrivate) { } RectRegion::~RectRegion() { if (d) { delete d; d = nullptr; } } void RectRegion::setX(int x) { if(d->m_x == x) { return; } d->m_x = x; Q_EMIT xChanged(); } int RectRegion::x() const { return d->m_x; } void RectRegion::setY(int y) { if(d->m_y == y) { return; } d->m_y = y; Q_EMIT yChanged(); } int RectRegion::y() const { return d->m_y; } void RectRegion::setWidth(int width) { if(d->m_width == width) { return; } d->m_width = width; Q_EMIT widthChanged(); } int RectRegion::width() const { return d->m_width; } void RectRegion::setHeight(int height) { if(d->m_height == height) { return; } d->m_height = height; Q_EMIT heightChanged(); } int RectRegion::height() const { return d->m_height; } void RectRegion::setRadius(int radius) { if(d->m_radius == radius) { return; } d->m_radius = radius; Q_EMIT radiusChanged(); } int RectRegion::radius() const { return d->m_radius; } class RegionPrivate : public QObject { Q_OBJECT public: RegionPrivate(Region *parent = nullptr); void onRectChanged(); void updateRegion(); QList m_rectRegions; QRegion m_region; bool m_requestUpdate = false; Region *q = nullptr; }; RegionPrivate::RegionPrivate(Region* parent) : QObject(parent), q(parent) { } void RegionPrivate::onRectChanged() { if (!m_requestUpdate) { m_requestUpdate = true;; QCoreApplication::postEvent(q, new QEvent(QEvent::UpdateRequest)); } } void RegionPrivate::updateRegion() { m_region = QRegion(); for (const auto rect : m_rectRegions) { QPainterPath path; path.addRoundedRect(rect->x(), rect->y(), rect->width(), rect->height(), rect->radius(), rect->radius()); m_region += QRegion(path.toFillPolygon().toPolygon()); } Q_EMIT q->regionChanged(); } Region::Region(QObject* parent) : QObject(parent), d(new RegionPrivate(this)) { } void Region::appendRect(UkuiQuick::RectRegion* rect) { if (!rect) { return; } d->m_rectRegions.append(rect); // 增加一个圆角矩形 QPainterPath path; path.addRoundedRect(rect->x(), rect->y(), rect->width(), rect->height(), rect->radius(), rect->radius()); d->m_region += QRegion(path.toFillPolygon().toPolygon()); connect(rect, &RectRegion::xChanged, d, &RegionPrivate::onRectChanged); connect(rect, &RectRegion::yChanged, d, &RegionPrivate::onRectChanged); connect(rect, &RectRegion::widthChanged, d, &RegionPrivate::onRectChanged); connect(rect, &RectRegion::heightChanged, d, &RegionPrivate::onRectChanged); connect(rect, &RectRegion::radiusChanged, d, &RegionPrivate::onRectChanged); Q_EMIT regionChanged(); } void Region::removeRect(UkuiQuick::RectRegion* rect) { if (!rect) { return; } rect->disconnect(d); d->m_rectRegions.removeOne(rect); Q_EMIT regionChanged(); } void Region::clear() { for (const UkuiQuick::RectRegion* rect : d->m_rectRegions) { rect->disconnect(d); } d->m_rectRegions.clear(); } QRegion Region::region() const { return d->m_region; } bool Region::event(QEvent* event) { if (event->type() == QEvent::UpdateRequest) { d->updateRegion(); d->m_requestUpdate = false; } return QObject::event(event); } } #include "region.moc" ukui-quick/platform/windows/ukui-window.h0000664000175000017500000000672215153755732017564 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_UKUI_WINDOW_H #define UKUI_QUICK_UKUI_WINDOW_H #include #include "window-helper.h" #include "region.h" namespace UkuiQuick { class Region; /** * @class UKUIWindow * * integration ukui-shell * * 集成ukui-shell * 1.设置窗口类型 * 2.设置位置 * 3.跳过任务栏,模糊特效等 * */ class UKUIWindow : public QQuickWindow { Q_OBJECT Q_PROPERTY(bool skipTaskBar READ skipTaskBar WRITE setSkipTaskBar NOTIFY skipTaskBarChanged) Q_PROPERTY(bool skipSwitcher READ skipSwitcher WRITE setSkipSwitcher NOTIFY skipSwitcherChanged) Q_PROPERTY(bool removeTitleBar READ removeTitleBar WRITE setRemoveTitleBar NOTIFY removeTitleBarChanged) Q_PROPERTY(UkuiQuick::WindowType::Type windowType READ windowType WRITE setWindowType NOTIFY windowTypeChanged) Q_PROPERTY(bool enableBlurEffect READ enableBlurEffect WRITE setEnableBlurEffect NOTIFY enableBlurEffectChanged) Q_PROPERTY(Region* blurRegion READ blurRegion CONSTANT FINAL) Q_PROPERTY(Region* maskRegion READ maskRegion CONSTANT FINAL) public: explicit UKUIWindow(QWindow *parent = nullptr); ~UKUIWindow() override; WindowType::Type windowType() const; void setWindowType(WindowType::Type type); bool skipTaskBar() const; bool skipSwitcher() const; bool removeTitleBar() const; void setSkipTaskBar(bool skip); void setSkipSwitcher(bool skip); void setRemoveTitleBar(bool remove); QPoint windowPosition() const; void setWindowPosition(const QPoint &pos); bool enableBlurEffect() const; void setEnableBlurEffect(bool enable); Region* blurRegion() const; Region* maskRegion() const; Q_SIGNALS: void windowTypeChanged(); void skipTaskBarChanged(); void skipSwitcherChanged(); void removeTitleBarChanged(); void enableBlurEffectChanged(); protected: void moveEvent(QMoveEvent *event) override; void resizeEvent(QResizeEvent *event) override; private Q_SLOTS: void blurRegionChanged(); void maskRegionChanged(); private: class Private; Private *d {nullptr}; }; /** * @class UKUIWindowExtension * * 向qml中注册UKUIWindow,并为UKUIWindow设置x,y属性,适配wayland环境 * * Usage: UKUIWindow { id: window x: 10 y: 10 } * */ class UKUIWindowExtension : public QObject { Q_OBJECT Q_PROPERTY(int x READ x WRITE setX NOTIFY xChanged) Q_PROPERTY(int y READ y WRITE setY NOTIFY yChanged) public: explicit UKUIWindowExtension(QObject *parent); int x() const; int y() const; void setX(int x); void setY(int y); Q_SIGNALS: void xChanged(int x); void yChanged(int y); private: UKUIWindow *m_window {nullptr}; }; } // UkuiQuick #endif //UKUI_QUICK_UKUI_WINDOW_H ukui-quick/platform/window-manager.h0000664000175000017500000000677615153755732016540 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef UKUI_PANEL_WINDOW_MANAGER_H #define UKUI_PANEL_WINDOW_MANAGER_H #include #include namespace UkuiQuick { class WindowManagerPrivate; class WindowManager : public QObject { Q_OBJECT Q_PROPERTY(bool isWaylandSession READ isWaylandSession CONSTANT) public: static WindowManager *self(); static QStringList windows(); static QIcon windowIcon(const QString &wid); static QString windowTitle(const QString &wid); static bool skipTaskBar(const QString &wid); static QString windowGroup(const QString &wid); static bool isMaximizable(const QString& wid); static bool isMaximized(const QString& wid); static void maximizeWindow(const QString& wid); static bool isMinimizable(const QString& wid); static bool isMinimized(const QString& wid); static void minimizeWindow(const QString& wid); static bool isKeepAbove(const QString& wid); static void keepAboveWindow(const QString& wid); static bool isOnAllDesktops(const QString& wid); static bool isOnCurrentDesktop(const QString& wid); static void activateWindow(const QString& wid); static QString currentActiveWindow(); static void closeWindow(const QString& wid); static void restoreWindow(const QString& wid); static bool isDemandsAttention(const QString& wid); static QString appId(const QString& wid); static bool isWaylandSession(); Q_INVOKABLE static quint32 pid(const QString&wid); Q_INVOKABLE static QRect geometry(const QString& wid); Q_INVOKABLE void setStartupGeometry(const QString& wid, QQuickItem *item); Q_INVOKABLE void setMinimizedGeometry(const QString& wid, QQuickItem *item); Q_INVOKABLE void setMinimizedGeometry(const QStringList& winds, QQuickItem *item); Q_INVOKABLE void unsetMinimizedGeometry(const QString& wid, QQuickItem *item); Q_INVOKABLE void activateWindowView(const QStringList &wids); Q_INVOKABLE static bool showingDesktop(); Q_INVOKABLE static void setShowingDesktop(bool showing); Q_INVOKABLE static bool isFullscreen(const QString& wid); Q_SIGNALS: void currentDesktopChanged(); void windowAdded(QString wid); void windowRemoved(QString wid); void titleChanged(QString wid); void iconChanged(QString wid); void skipTaskbarChanged(QString wid); void onAllDesktopsChanged(QString wid); void windowDesktopChanged(QString wid); void demandsAttentionChanged(QString wid); void geometryChanged(QString wid); void activeWindowChanged(QString id); void maximizedChanged(QString id); void fullscreenChanged(QString id); //xcb only void windowStateChanged(QString wid); private: WindowManager(QObject *parent = nullptr); }; } #endif //UKUI_PANEL_WINDOW_MANAGER_H ukui-quick/platform/window-helper-common.h0000664000175000017500000000341415153755732017655 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #ifndef WINDOW_HELPER_COMMON_H #define WINDOW_HELPER_COMMON_H #include #include "window-type.h" #include "window-helper.h" namespace UkuiQuick { static QMap ukui_surface_roleMap = { {WindowType::Normal, 0}, {WindowType::Desktop, 1}, {WindowType::Dock, 2}, {WindowType::Panel, 2}, {WindowType::OnScreenDisplay, 3}, {WindowType::Notification, 4}, {WindowType::ToolTip, 5}, {WindowType::CriticalNotification, 6}, {WindowType::AppletPopup, 7}, {WindowType::ScreenLock, 8}, {WindowType::WaterMark, 9}, {WindowType::SystemWindow,10}, {WindowType::InputPanel,11}, {WindowType::Logout,12}, {WindowType::ScreenLockNotification,13}, {WindowType::Switcher,14}, {WindowType::Authentication, 15} }; static QMap ukui_surface_slideEdge = { {WindowProxy::NoEdge, -1}, {WindowProxy::TopEdge, 1}, {WindowProxy::BottomEdge, 3}, {WindowProxy::LeftEdge, 0}, {WindowProxy::RightEdge, 2} }; } #endif //WINDOW_HELPER_COMMON_H ukui-quick/platform/CMakeLists.txt0000664000175000017500000002121415153756415016167 0ustar fengfengcmake_minimum_required(VERSION 3.16) project(platform) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 20) include(FeatureSummary) find_package(ECM NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Quick Qml Widgets DBus Concurrent WaylandClient REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Quick Qml Widgets DBus Concurrent WaylandClient REQUIRED) if (QT_VERSION_MAJOR EQUAL "5") set(KF_VERSION_MAJOR "5") find_package(Qt${QT_VERSION_MAJOR} COMPONENTS X11Extras XkbCommonSupport REQUIRED) find_package(KF5WindowSystem REQUIRED) find_package(KF5Wayland) set(PC_PKGS gsettings-qt x11 Qt5Xdg wayland-client) elseif (QT_VERSION_MAJOR EQUAL "6") set(KF_VERSION_MAJOR "6") find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Gui REQUIRED) find_package(Qt6GuiPrivate REQUIRED) find_package(Qt6WaylandClientPrivate REQUIRED) find_package(KF6WindowSystem REQUIRED) find_package(KWayland) set(PC_PKGS gsettings-qt6 x11 Qt6Xdg wayland-client) endif() find_package(PkgConfig REQUIRED) find_package(WaylandScanner) find_package(WaylandProtocols 1.15) set_package_properties(WaylandProtocols PROPERTIES TYPE REQUIRED) find_package(PlasmaWaylandProtocols 1.4.0 CONFIG) set_package_properties(PlasmaWaylandProtocols PROPERTIES TYPE REQUIRED) include(./cmake/FindWaylandScanner.cmake) set(EXTERNAL_LIBS "") foreach(external_lib IN ITEMS ${PC_PKGS}) pkg_check_modules(${external_lib} REQUIRED IMPORTED_TARGET ${external_lib}) if(${${external_lib}_FOUND}) include_directories(${${external_lib}_INCLUDE_DIRS}) link_directories(${${external_lib}_LIBRARY_DIRS}) list(APPEND EXTERNAL_LIBS PkgConfig::${external_lib}) endif() endforeach() set(UKUI_QUICK_PLATFORM_SRCS ukui/settings.cpp ukui/app-launcher.cpp ukui/ukui-theme-proxy.cpp ukui/screen-area-utils.cpp ukui/dt-theme.cpp ukui/dt-theme.h ukui/dt-theme-definition.h ukui/dt-theme-definition.cpp ukui/gradient-color.h ukui/gradient-color.cpp ukui/shadow-data.h ukui/shadow-data.cpp ukui/dbus-connector.cpp ukui/function-control.cpp wayland/registry.cpp wayland/wayland-pointer_p.h wayland/wayland-integration.cpp wayland/ukui-shell-v1.h wayland/ukui-shell-v1.cpp wayland/ukui-window-management.h wayland/ukui-window-management.cpp wayland/ukui-startup-manager.cpp wayland/ukui-startup-manager.h wayland/ukui-blur-manager.cpp wayland/ukui-blur-manager.h wayland/compositor.cpp wayland/compositor.h windows/dialog.cpp windows/region.cpp windows/ukui-window.cpp window-helper.cpp wm-interface.cpp wm-impl-x11.h wm-impl-x11.cpp wm-impl-wayland.cpp wm-impl-wayland.h abstract-window-manager.cpp wayland-window-manager.cpp wayland-window-manager.h window-manager.cpp xcb-window-manager.cpp xcb-window-manager.h xatom-helper.cpp startup-management.cpp ukui/application-icon-proxy.cpp ukui/application-icon-proxy.h glinfo-query.cpp glinfo-query.h ) set(HEADERS ukui/settings.h ukui/app-launcher.h ukui/ukui-theme-proxy.h ukui/screen-area-utils.h ukui/dbus-connector.h ukui/function-control.h window-type.h wm-interface.h window-helper.h wayland/registry.h windows/dialog.h windows/ukui-window.h windows/region.h window-manager.h abstract-window-manager.h startup-management.h ) include_directories(wayland) ukui_add_wayland_client_protocol(UKUI_QUICK_PLATFORM_SRCS PROTOCOL wayland/protocol/ukui-shell-v1.xml BASENAME ukui-shell PRIVATE_CODE ) ukui_add_wayland_client_protocol(UKUI_QUICK_PLATFORM_SRCS PROTOCOL wayland/protocol/ukui-window-management.xml BASENAME ukui-window-management PRIVATE_CODE ) ukui_add_wayland_client_protocol(UKUI_QUICK_PLATFORM_SRCS PROTOCOL ${PLASMA_WAYLAND_PROTOCOLS_DIR}/plasma-virtual-desktop.xml BASENAME plasma-virtual-desktop PRIVATE_CODE ) ukui_add_wayland_client_protocol(UKUI_QUICK_PLATFORM_SRCS PROTOCOL wayland/protocol//ukui-startup-v1.xml BASENAME ukui-startup-v1 PRIVATE_CODE ) ukui_add_wayland_client_protocol(UKUI_QUICK_PLATFORM_SRCS PROTOCOL wayland/protocol//ukui-blur-v1.xml BASENAME ukui-blur-v1 PRIVATE_CODE ) add_library(${PROJECT_NAME} SHARED ${UKUI_QUICK_PLATFORM_SRCS} ${HEADERS} ) add_library(${ROOT_PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::Qml Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::DBus Qt${QT_VERSION_MAJOR}::Concurrent Qt${QT_VERSION_MAJOR}::GuiPrivate Qt${QT_VERSION_MAJOR}::WaylandClientPrivate KF${KF_VERSION_MAJOR}::WindowSystem ${EXTERNAL_LIBS} ) if (QT_VERSION_MAJOR EQUAL "5") target_link_libraries(${PROJECT_NAME} PRIVATE Qt::X11Extras Qt5::XkbCommonSupportPrivate Qt5::Gui_EGL KF5::WaylandClient) else() target_link_libraries(${PROJECT_NAME} PRIVATE Qt::GuiPrivate Plasma::KWaylandClient) endif() set(HEADERS_INSTALL_DIR /usr/include/ukui-quick/platform) target_include_directories(${PROJECT_NAME} INTERFACE $) include(CMakePackageConfigHelpers) set(CMAKE_CONFIG_INSTALL_DIR "/usr/share/cmake/ukui-quick-platform") set(PC_INSTALL_DIR "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/pkgconfig") configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/ukui-quick-platform-config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-platform-config.cmake" INSTALL_DESTINATION ${CMAKE_CONFIG_INSTALL_DIR} ) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-platform-config-version.cmake VERSION ${UKUI_QUICK_VERSION} COMPATIBILITY SameMajorVersion ) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/ukui-quick-platform.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-platform.pc" INSTALL_DESTINATION ${PC_INSTALL_DIR}) set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${UKUI_QUICK_VERSION} SOVERSION ${VERSION_MAJOR} OUTPUT_NAME ${ROOT_PROJECT_NAME}-${PROJECT_NAME} ) install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME} PUBLIC_HEADER DESTINATION ${HEADERS_INSTALL_DIR} LIBRARY DESTINATION /usr/lib/${CMAKE_LIBRARY_ARCHITECTURE} ) install(EXPORT ${PROJECT_NAME} FILE ukui-quick-platform-targets.cmake NAMESPACE ${ROOT_PROJECT_NAME}:: DESTINATION ${CMAKE_CONFIG_INSTALL_DIR}) install(FILES ${HEADERS} DESTINATION ${HEADERS_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-platform.pc DESTINATION ${PC_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-platform-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/ukui-quick-platform-config-version.cmake DESTINATION ${CMAKE_CONFIG_INSTALL_DIR}) set(UKUI_QUICK_PLATFORM_PLUGIN_SRCS ukui-quick-platform-plugin.cpp ukui-quick-platform-plugin.h ) add_library(${ROOT_PROJECT_NAME}-${PROJECT_NAME}-plugin SHARED ${UKUI_QUICK_PLATFORM_PLUGIN_SRCS}) target_compile_definitions(${ROOT_PROJECT_NAME}-${PROJECT_NAME}-plugin PRIVATE PLUGIN_IMPORT_URI="org.ukui.quick.platform" PLUGIN_VERSION_MAJOR=${VERSION_MAJOR} PLUGIN_VERSION_MINOR=${VERSION_MINOR} ) target_link_libraries(${ROOT_PROJECT_NAME}-${PROJECT_NAME}-plugin PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Qml Qt${QT_VERSION_MAJOR}::Quick ${PROJECT_NAME} ) set(PLUGIN_INSTALL_ROOT_PATH "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/qt${QT_VERSION_MAJOR}/qml/org/ukui/quick/platform") install(FILES qmldir DESTINATION "${PLUGIN_INSTALL_ROOT_PATH}") install(TARGETS ${ROOT_PROJECT_NAME}-${PROJECT_NAME}-plugin LIBRARY DESTINATION "${PLUGIN_INSTALL_ROOT_PATH}") install(FILES ukui/DtThemeDefault.qml DESTINATION "/usr/share/ukui/ukui-quick-platform") enable_testing() if(BUILD_TEST) add_subdirectory(test) add_subdirectory(autotest) endif () ukui-quick/platform/wayland-window-manager.h0000664000175000017500000000733315153756415020162 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef UKUI_PANEL_WAYLAND_WINDOW_MANAGER_H #define UKUI_PANEL_WAYLAND_WINDOW_MANAGER_H #include "abstract-window-manager.h" #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #include #else #include #include #endif #include #include "registry.h" #include "ukui-window-management.h" namespace UkuiQuick { using namespace WaylandClient; class WaylandWindowManager : public AbstractWindowManager { Q_OBJECT public: explicit WaylandWindowManager(QObject *parent = nullptr); ~WaylandWindowManager(); QStringList windows() override; QIcon windowIcon(const QString &wid) override; QString windowTitle(const QString &wid) override; bool skipTaskBar(const QString &wid) override; QString windowGroup(const QString &wid) override; bool isMaximizable(const QString &wid) override; bool isMaximized(const QString& wid) override; void maximizeWindow(const QString& wid) override; bool isMinimizable(const QString &wid) override; bool isMinimized(const QString& wid) override; void minimizeWindow(const QString& wid) override; bool isKeepAbove(const QString& wid) override; void keepAboveWindow(const QString& wid) override; bool isOnAllDesktops(const QString& wid) override; bool isOnCurrentDesktop(const QString& wid) override; void activateWindow(const QString& wid) override; QString currentActiveWindow() override; void closeWindow(const QString& wid) override; void restoreWindow(const QString& wid) override; bool isDemandsAttention(const QString& wid) override; quint32 pid(const QString& wid) override; QString appId(const QString& wid) override; QRect geometry(const QString& wid) override; void setStartupGeometry(const QString& wid, QQuickItem *item) override; void setMinimizedGeometry(const QString& wid, QQuickItem *item) override; void unsetMinimizedGeometry(const QString& wid, QQuickItem *item) override; void activateWindowView(const QStringList &wids) override; bool showingDesktop() override; void setShowingDesktop(bool showing) override; bool isFullscreen(const QString& wid) override; private: void addWindow(UkuiWindow *window); void desktopCreated(const QString &id, quint32 position); void desktopRemoved(const QString &id); void setCurrentDesktop(const QString &id); UkuiQuick::WaylandClient::Registry *m_registry = nullptr; UkuiWindowManagement *m_windowManagement = nullptr; KWayland::Client::PlasmaVirtualDesktopManagement *m_virtualDesktopManagement = nullptr; KWayland::Client::ConnectionThread *m_connection = nullptr; QMap m_uuidToWindow; QList m_windows; QStringList m_desktops; QString m_currentDesktop; }; } #endif //UKUI_PANEL_WAYLAND_WINDOW_MANAGER_H ukui-quick/platform/wm-impl-x11.cpp0000664000175000017500000001274315153755732016134 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "wm-impl-x11.h" #include #include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #else #include #endif #include "xatom-helper.h" #include #include #include namespace UkuiQuick { WMImplX11::WMImplX11(QWindow *window) : WMInterface(window) { } void WMImplX11::setWindowType(WindowType::Type type) { NET::WindowType t; switch (type) { default: t = NET::Unknown; break; case WindowType::Normal: t = NET::Normal; break; case WindowType::Dock: case WindowType::Panel: t = NET::Dock; break; case WindowType::Desktop: t = NET::Desktop; break; case WindowType::Menu: t = NET::Menu; break; case WindowType::Dialog: t = NET::Dialog; window()->setFlags(window()->flags() | Qt::Dialog); break; case WindowType::PopupMenu: t = NET::PopupMenu; window()->setFlags(window()->flags() | Qt::Popup); break; case WindowType::ToolTip: t = NET::Tooltip; window()->setFlags(window()->flags() | Qt::ToolTip); break; case WindowType::Notification: t = NET::Notification; break; case WindowType::CriticalNotification: t = NET::CriticalNotification; break; case WindowType::SystemWindow: t = NET::AppletPopup; break; case WindowType::OnScreenDisplay: t = NET::OnScreenDisplay; break; case WindowType::AppletPopup: t = NET::AppletPopup; break; case WindowType::ScreenLockNotification: t = NET::CriticalNotification; break; } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) KX11Extras::setType(window()->winId(), t); #else KWindowSystem::setType(window()->winId(), t); #endif // thank kde. if (type == WindowType::OnScreenDisplay) { window()->setFlags((window()->flags() & ~Qt::Dialog) | Qt::Window); } bool onAllDesktop = (type == WindowType::Desktop || type == WindowType::Dock || type == WindowType::Panel || type == WindowType::SystemWindow || type == WindowType::Notification || type == WindowType::CriticalNotification || type == WindowType::OnScreenDisplay); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) KX11Extras::setOnAllDesktops(window()->winId(), onAllDesktop); #else KWindowSystem::setOnAllDesktops(window()->winId(), onAllDesktop); #endif } void WMImplX11::setSkipTaskBar(bool skip) { NET::States states; KWindowInfo windowInfo(window()->winId(), NET::WMState); if (windowInfo.valid()) { states = windowInfo.state(); } if (skip) { states |= NET::SkipTaskbar; } else { states &= ~NET::SkipTaskbar; } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) KX11Extras::setState(window()->winId(), states); #else KWindowSystem::setState(window()->winId(), states); #endif } void WMImplX11::setSkipSwitcher(bool skip) { NET::States states; KWindowInfo windowInfo(window()->winId(), NET::WMState); if (windowInfo.valid()) { states = windowInfo.state(); } if (skip) { states |= NET::SkipSwitcher | NET::SkipPager; } else { states &= ~(NET::SkipSwitcher | NET::SkipPager); } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) KX11Extras::setState(window()->winId(), states); #else KWindowSystem::setState(window()->winId(), states); #endif } void WMImplX11::setRemoveTitleBar(bool remove) { if (window()->flags().testFlag(Qt::FramelessWindowHint)) { return; } //x下暂不能取消 if (remove) { MotifWmHints hints; hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS; hints.functions = MWM_FUNC_ALL; hints.decorations = MWM_DECOR_BORDER; XAtomHelper::getInstance()->setWindowMotifHint(static_cast(window()->winId()), hints); } } void WMImplX11::setPanelAutoHide(bool autoHide) { Q_UNUSED(autoHide) } void WMImplX11::setPanelTakesFocus(bool takesFocus) { Q_UNUSED(takesFocus) // Qt::WindowFlags flags = window()->flags(); // if (takesFocus) { // flags &= ~Qt::WindowDoesNotAcceptFocus; // } else { // flags |= Qt::WindowDoesNotAcceptFocus; // } // // window()->setFlags(flags); } QScreen* WMImplX11::currentScreen() { QScreen* screen = qGuiApp->screenAt(QCursor::pos()); if (screen) { return screen; } return qGuiApp->primaryScreen(); } } ukui-quick/platform/window-helper.h0000664000175000017500000001603115153755732016366 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_ITEMS_WINDOW_HELPER_H #define UKUI_QUICK_ITEMS_WINDOW_HELPER_H #include #include #include #include #include "window-type.h" #include "wm-interface.h" namespace UkuiQuick { class WindowProxyPrivate; /** * @class WindowProxy * * usage: * new WindowProxy(this, WindowProxy::RemoveTitleBar, this); * * WindowProxy是一个事件过滤器,用于为窗口(Window)调用窗口管理器或wayland协议的相关接口,实现某些操作 * 其中的操作可分为: * 1.去除标题栏 * 2.跳过任务选择器和任务栏 * 3.设置窗口Geometry(大小和位置) * 4.添加边缘划出特效 * 5.添加自动从屏幕边缘隐藏窗口特效(一般情况下仅panel需要) * 6.为Window指定窗口类型(类型由窗口管理器或显示协议定义) * * 此外还提供了一些便利接口: * 1.判断是否wayland环境 * */ class WindowProxy : public QObject { Q_OBJECT public: enum Operation { SkipTaskBar = 0x0001, SkipSwitcher = 0x0002, RemoveTitleBar = 0x0004 }; Q_DECLARE_FLAGS(Operations, Operation) /** * 窗口在哪个屏幕边缘滑出 */ enum SlideFromEdge { NoEdge = 0, TopEdge, RightEdge, BottomEdge, LeftEdge, }; Q_ENUM(SlideFromEdge) explicit WindowProxy(QWindow *window, Operations operations = {SkipTaskBar | SkipSwitcher | RemoveTitleBar}); ~WindowProxy() override; bool eventFilter(QObject *watched, QEvent *event) override; /** * 是否wayland环境 */ static bool isWayland(); /** * 移除窗口管理器添加的标题栏 */ void setRemoveTitleBar(bool remove = true); /** * 不在taskBar(任务栏)上显示 */ void setSkipTaskBar(bool skip = true); /** * 不在多任务选择器中显示 */ void setSkipSwitcher(bool skip = true); /** * 设置窗口位置 */ void setPosition(const QPoint &point); void setGeometry(const QRect &rect); /** * 设置窗口类型 * 此处支持常用的几个窗口类型 * 每个类型在窗口管理器中的行为都不一样,使用前请先了解相关文档 * * @see WindowType::Type */ void setWindowType(WindowType::Type type); /** * 设置窗口为'自动隐藏'状态 * 设置为true时, 窗口管理器会将窗口所在的位置归还给可用区域,但是目前不会将该窗口隐藏,需要窗口自己实现 * 设置为false时,窗口管理器会将窗口所在的区域从可用区域中移除,普通应用将无法覆盖该区域. * * @warning 该接口仅对Panel和Dock层级有效(wayland) */ void setPanelAutoHide(bool autoHide); /** * 为窗口添加滑出特效 * @param location 窗口从哪个边缘滑出 * @param offset 窗口滑出位置与屏幕边缘的间距 */ static void slideWindow(QWindow *window, SlideFromEdge fromEdge, int offset = -1); void slideWindow(SlideFromEdge fromEdge, int offset = -1); /** * 为窗口添加毛玻璃特效 * @param window 需要添加毛玻璃特效的窗口 * @param enable true: 启用, false: 禁用 * @param region 窗口添加毛玻璃的区域, 空区域表示窗口整体添加毛玻璃 * 注意:此接口使用kde接口设置毛玻璃效果,在wlcom下会和使用ukui_blur_v1协议设置的毛玻璃效果有区别! **/ static void setBlurRegion(QWindow *window, bool enable = true, const QRegion ®ion = QRegion()); /** * 为窗口添加毛玻璃特效 * @param enable true: 启用, false: 禁用 * @param region 窗口添加毛玻璃的区域, 空区域表示窗口整体添加毛玻璃 * 注意:此接口在wayland环境下使用ukui_blur_v1协议设置毛玻璃效果,在x环境下使用kde接口设置毛玻璃效果! **/ void setBlurRegion(bool enable = true, const QRegion ®ion = QRegion()); /** * @brief 设置毛玻璃等级 * @param level 毛玻璃等级,默认12,范围为1 - 14 * @note 仅在wayland环境下生效 **/ void setBlurLevel(uint32_t level); static QScreen* currentScreen(); static bool useUkuiShellIntegration(QWindow *window); QPoint position() const; void setDecorationComponents(Decoration::Components components); void setWindowStates(WindowState::States states, bool enable = true); static WindowProxy* fromWindow(QWindow *window); QRect restoreGeometry() const; void setRestoreGeometry(const QRect &rect); void setBlurOpacity(const qreal opacity); private: WindowProxyPrivate *d = nullptr; }; /** * @class WindowProxy2 * * WindowProxy2 是一个事件过滤器,只负责在合适的时机调用后端的接口,为窗口设置窗口位置,类型和各种属性。 * 具体的后端接口如何定义,由WMImplWayland和WMImplX11实现。 * * @warning 警告:必须注意的一点,如果使用WindowProxy2,任何WindowProxy2提供了的功能接口都必须通过WindowProxy2进行设置,否则属性将被覆盖为默认值 * */ class WindowProxy2 : public WMInterface { Q_OBJECT public: explicit WindowProxy2(QWindow *window, WindowProxy::Operations operations = {WindowProxy::SkipTaskBar | WindowProxy::SkipSwitcher | WindowProxy::RemoveTitleBar}); ~WindowProxy2() override; bool eventFilter(QObject *watched, QEvent *event) override; QPoint position() const override; void setPosition(const QPoint &point) override; void setWindowType(WindowType::Type type) override; void setSkipTaskBar(bool skip) override; void setSkipSwitcher(bool skip) override; void setRemoveTitleBar(bool remove) override; void setPanelTakesFocus(bool takesFocus) override; void setPanelAutoHide(bool autoHide) override; void setSlideWindowArgs(WindowProxy::SlideFromEdge fromEdge, int offset); QScreen* currentScreen() override; void setDecorationComponents(Decoration::Components components) override; private: class Private; WindowProxy2::Private *d = nullptr; }; } // UkuiQuick // 定义flag的操作符,eg: |, & Q_DECLARE_OPERATORS_FOR_FLAGS(UkuiQuick::WindowProxy::Operations) Q_DECLARE_METATYPE(UkuiQuick::WindowType::Type) Q_DECLARE_METATYPE(UkuiQuick::WindowProxy::SlideFromEdge) #endif //UKUI_QUICK_ITEMS_WINDOW_HELPER_H ukui-quick/platform/window-helper.cpp0000664000175000017500000007357215167355777016750 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "window-helper.h" #include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #include #else #include #include #endif #include #include #include #include #include #include "xatom-helper.h" #include "ukui-shell-v1.h" //kde #include #include #include #include #include "wayland-integration_p.h" #include "window-helper-common.h" #include "ukui-blur-manager.h" namespace UkuiQuick { // ====== WindowProxyPrivate ====== // class WindowProxyPrivate { public: bool setupWaylandIntegration(); void removeHeaderBar(bool remove = true); void skipTaskBarOrSwitcher(); void setWindowTypeX11(); void setWindowTypeWayland(); void updateGeometry(); void updatePoint(); void setWindowType(); void setAutoHideState(); void exec(); void setDecorationComponents(); void setBlurBehind(); void setWindowStates(); void setRestoreGeometry(); // 代理操作 bool m_isWayland = false; bool m_panelAutoHide = false; bool m_removeTitleBar = false; // 窗口类型 WindowType::Type m_type = WindowType::Normal; WindowProxy::Operations m_operations; // old winId WId m_winId {0}; // 被代理的窗口 QWindow *m_window{nullptr}; QSize m_size; QPoint m_point; QRect m_restoreGeometry; QPointer m_surface; QPointer m_shellSurface; QPointer m_blurManager; QPointer m_blur; int m_slideOffset = -1; WindowProxy::SlideFromEdge m_slideFromEdge = WindowProxy::NoEdge; bool m_useUkuiShellIntegration = false; UkuiQuick::Decoration::Components m_decorationComponents; QRegion m_blurRegion; uint32_t m_blurLevel {12}; qreal m_blurOpacity {1.0}; bool m_enableBlur = false; QPair m_states; }; void WindowProxyPrivate::setRestoreGeometry() { if (!m_isWayland) { qWarning() << "setRestoreGeometry can be only set on wayland!"; return; } if (m_useUkuiShellIntegration) { m_window->setProperty("ukui_surface_restore_geometry", m_restoreGeometry); } else if (m_shellSurface) { m_shellSurface->setRestoreGeometry(m_restoreGeometry); } else { qWarning() << "Failed to set restore geometry: requires both ukui_shell integration and valid shell surface"; } } bool WindowProxyPrivate::setupWaylandIntegration() { if (!m_shellSurface) { WaylandClient::UkuiShell *interface = WaylandIntegration::self()->waylandUkuiShell(); if (!interface) { return false; } KWayland::Client::Surface *s = KWayland::Client::Surface::fromWindow(m_window); if (!s) { return false; } m_surface = s; m_shellSurface = interface->createSurface(s, m_window); } if (!m_blurManager) { m_blurManager = WaylandIntegration::self()->waylandBlurManager(); } return m_shellSurface != nullptr && m_blurManager != nullptr; } void WindowProxyPrivate::removeHeaderBar(bool remove) { // 无边框窗口不需要去除标题栏 if (m_window->flags().testFlag(Qt::FramelessWindowHint)) { qDebug() << "frameless window, no need to remove header bar" << m_window; return; } if(m_useUkuiShellIntegration) { m_window->setProperty("ukui_surface_no_titlebar", remove); return; } if (m_isWayland) { if (m_shellSurface) { m_shellSurface->setRemoveTitleBar(remove); } } else { //x下暂不能取消 if (remove && m_winId != m_window->winId()) { m_winId = m_window->winId(); MotifWmHints hints; hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS; hints.functions = MWM_FUNC_ALL; hints.decorations = MWM_DECOR_BORDER; XAtomHelper::getInstance()->setWindowMotifHint(static_cast(m_winId), hints); } } } void WindowProxyPrivate::setWindowTypeX11() { NET::WindowType t; switch (m_type) { default: t = NET::Unknown; break; case WindowType::Normal: t = NET::Normal; break; case WindowType::Dock: case WindowType::Panel: t = NET::Dock; break; case WindowType::Desktop: t = NET::Desktop; break; case WindowType::Menu: t = NET::Menu; break; case WindowType::Dialog: t = NET::Dialog; m_window->setFlags(m_window->flags() | Qt::Dialog); break; case WindowType::PopupMenu: t = NET::PopupMenu; m_window->setFlags(m_window->flags() | Qt::Popup); break; case WindowType::ToolTip: t = NET::Tooltip; m_window->setFlags(m_window->flags() | Qt::ToolTip); break; case WindowType::Notification: t = NET::Notification; break; case WindowType::CriticalNotification: t = NET::CriticalNotification; break; case WindowType::SystemWindow: t = NET::AppletPopup; break; case WindowType::OnScreenDisplay: t = NET::OnScreenDisplay; break; case WindowType::AppletPopup: t = NET::AppletPopup; break; case WindowType::ScreenLockNotification: t = NET::CriticalNotification; break; } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) KX11Extras::setType(m_window->winId(), t); #else KWindowSystem::setType(m_window->winId(), t); #endif bool onAllDesktop = false; if (m_type == WindowType::Dock || m_type == WindowType::Notification || m_type == WindowType::OnScreenDisplay || m_type == WindowType::CriticalNotification) { onAllDesktop = true; } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) KX11Extras::setOnAllDesktops(m_window->winId(), onAllDesktop); #else KWindowSystem::setOnAllDesktops(m_window->winId(), onAllDesktop); #endif } void WindowProxyPrivate::setWindowTypeWayland() { if (m_useUkuiShellIntegration) { m_window->setProperty("ukui_surface_role_v1", ukui_surface_roleMap.value(m_type)); return; } if (!m_shellSurface) { return; } using namespace WaylandClient; switch (m_type) { default: case WindowType::Normal: m_shellSurface->setRole(UkuiShellSurface::Role::Normal); break; case WindowType::Desktop: m_shellSurface->setRole(UkuiShellSurface::Role::Desktop); break; case WindowType::Dock: case WindowType::Panel: m_shellSurface->setRole(UkuiShellSurface::Role::Panel); break; case WindowType::SystemWindow: m_shellSurface->setRole(UkuiShellSurface::Role::SystemWindow); break; case WindowType::Notification: m_shellSurface->setRole(UkuiShellSurface::Role::Notification); break; case WindowType::CriticalNotification: m_shellSurface->setRole(UkuiShellSurface::Role::CriticalNotification); break; case WindowType::ScreenLockNotification: m_shellSurface->setRole(UkuiShellSurface::Role::ScreenLockNotification); break; case WindowType::OnScreenDisplay: m_shellSurface->setRole(UkuiShellSurface::Role::OnScreenDisplay); break; case WindowType::Menu: case WindowType::Dialog: m_window->setFlags(m_window->flags() | Qt::Dialog); case WindowType::ToolTip: m_shellSurface->setRole(UkuiShellSurface::Role::ToolTip); break; case WindowType::PopupMenu: m_window->setFlags(m_window->flags() | Qt::Popup); break; case WindowType::AppletPopup: m_shellSurface->setRole(UkuiShellSurface::Role::AppletPop); break; case WindowType::Switcher: m_shellSurface->setRole(UkuiShellSurface::Role::Switcher); break; } } void WindowProxyPrivate::updateGeometry() { if (m_size.isValid()) { m_window->resize(m_size); } updatePoint(); } void WindowProxyPrivate::updatePoint() { m_window->setPosition(m_point); if (m_shellSurface && !m_useUkuiShellIntegration) { m_shellSurface->setPosition(m_point); } } void WindowProxyPrivate::setWindowType() { if (m_isWayland) { setWindowTypeWayland(); } else { setWindowTypeX11(); } // thank kde. if (m_type == WindowType::OnScreenDisplay) { m_window->setFlags((m_window->flags() & ~Qt::Dialog) | Qt::Window); } } void WindowProxyPrivate::skipTaskBarOrSwitcher() { if(m_useUkuiShellIntegration) { m_window->setProperty("ukui_surface_skip_taskbar", m_operations.testFlag(WindowProxy::SkipTaskBar)); m_window->setProperty("ukui_surface_skip_switcher", m_operations.testFlag(WindowProxy::SkipSwitcher)); return; } if (m_isWayland) { if (m_shellSurface) { m_shellSurface->setSkipTaskbar(m_operations.testFlag(WindowProxy::SkipTaskBar)); m_shellSurface->setSkipSwitcher(m_operations.testFlag(WindowProxy::SkipSwitcher)); } } else { NET::States states; if (m_operations.testFlag(WindowProxy::SkipTaskBar)) { states |= NET::SkipTaskbar; } if (m_operations.testFlag(WindowProxy::SkipSwitcher)) { states |= (NET::SkipPager | NET::SkipSwitcher); } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) KX11Extras::setState(m_window->winId(), states); #else KWindowSystem::setState(m_window->winId(), states); #endif } } void WindowProxyPrivate::setAutoHideState() { if(m_useUkuiShellIntegration) { m_window->setProperty("ukui_surface_panel_auto_hide", m_panelAutoHide); } else if (m_shellSurface) { m_shellSurface->setPanelAutoHide(m_panelAutoHide); } } void WindowProxyPrivate::exec() { if (m_isWayland) { if (!setupWaylandIntegration()) { return; } } setWindowType(); setAutoHideState(); removeHeaderBar(m_operations.testFlag(WindowProxy::RemoveTitleBar)); skipTaskBarOrSwitcher(); updateGeometry(); setDecorationComponents(); setBlurBehind(); WindowProxy::slideWindow(m_window, m_slideFromEdge, m_slideOffset); setWindowStates(); } void WindowProxyPrivate::setDecorationComponents() { if (m_shellSurface) { m_shellSurface->setDecorationComponents(m_decorationComponents); } } void WindowProxyPrivate::setBlurBehind() { if (!m_enableBlur) { if (m_blur) { delete m_blur; m_blur = nullptr; } return; } if (!m_shellSurface) { return; } if (m_blurManager) { if (!m_blur) { m_blur = m_blurManager->getBlur(KWayland::Client::Surface::fromWindow(m_window), m_window); } if (m_blur) { m_blur->setRegion(m_blurRegion); m_blur->setLevel(m_blurLevel); m_blur->setOpacity(m_blurOpacity); if (m_surface) { m_surface->commit(KWayland::Client::Surface::CommitFlag::None); } } } } void WindowProxyPrivate::setWindowStates() { if (m_shellSurface) { m_shellSurface->setStates(m_states.first, m_states.second); } } // ====== WindowProxy ====== // WindowProxy::WindowProxy(QWindow *window, WindowProxy::Operations operations) : QObject(window), d(new WindowProxyPrivate) { Q_ASSERT(window); qRegisterMetaType(); d->m_window = window; d->m_operations = operations; d->m_isWayland = WindowProxy::isWayland(); d->m_useUkuiShellIntegration = useUkuiShellIntegration(d->m_window); for (auto child: window->children()) { auto* proxy = qobject_cast(child); if (proxy && proxy != this) { qWarning() << "WindowProxy::WindowProxy has been created for window" << window << ", you should use WindowProxy::fromWindow to get the instance."; return; } } if(d->m_useUkuiShellIntegration) { qDebug() << "UkuiQuick::WindowProxy using ukui_shell" << d->m_window << d->m_operations; //在wlcom中,此属性和FramelessWindowHint flag冲突,如果直接讲此属性设为false,那么再设置FramelessWindowHint将无效. if(d->m_operations.testFlag(WindowProxy::RemoveTitleBar)) { d->removeHeaderBar(true); } d->skipTaskBarOrSwitcher(); } else { d->m_window->installEventFilter(this); } } WindowProxy::~WindowProxy() { if (d) { delete d; d = nullptr; } } bool WindowProxy::eventFilter(QObject *watched, QEvent *event) { if (watched == d->m_window) { switch (event->type()) { case QEvent::PlatformSurface: { if (isWayland()) { const QPlatformSurfaceEvent *pSEvent = static_cast(event); if (pSEvent->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) auto waylandWindow = d->m_window->nativeInterface(); #else auto waylandWindow = dynamic_cast(d->m_window->handle()); #endif d->exec(); #if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0) connect(waylandWindow, &QtWaylandClient::QWaylandWindow::surfaceRoleCreated, this, [this]() { #elif QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) connect(waylandWindow, &QtWaylandClient::QWaylandWindow::surfaceCreated, this, [this]() { #else connect(waylandWindow, &QtWaylandClient::QWaylandWindow::wlSurfaceCreated, this, [this]() { #endif d->exec(); }); #if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0) connect(waylandWindow, &QtWaylandClient::QWaylandWindow::surfaceRoleDestroyed, this, [this]() { #elif QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) connect(waylandWindow, &QtWaylandClient::QWaylandWindow::surfaceDestroyed, this, [this]() { #else connect(waylandWindow, &QtWaylandClient::QWaylandWindow::wlSurfaceDestroyed, this, [this]() { #endif delete d->m_shellSurface; d->m_shellSurface = nullptr; delete d->m_blur; d->m_blur = nullptr; }); } } } case QEvent::Show: case QEvent::Expose: { if(!isWayland()) d->exec(); } default: break; } } return QObject::eventFilter(watched, event); } bool WindowProxy::isWayland() { return QGuiApplication::platformName().startsWith(QStringLiteral("wayland")); } // ==APIS== void WindowProxy::setRemoveTitleBar(bool remove) { if (remove) { d->m_operations |= RemoveTitleBar; } else { d->m_operations &= ~RemoveTitleBar; } if(d->m_useUkuiShellIntegration) { d->removeHeaderBar(remove); } } void WindowProxy::setSkipTaskBar(bool skip) { if (skip) { d->m_operations |= SkipTaskBar; } else { d->m_operations &= ~SkipTaskBar; } if(d->m_useUkuiShellIntegration) { d->skipTaskBarOrSwitcher(); } } void WindowProxy::setSkipSwitcher(bool skip) { if (skip) { d->m_operations |= SkipSwitcher; } else { d->m_operations &= ~SkipSwitcher; } if(d->m_useUkuiShellIntegration) { d->skipTaskBarOrSwitcher(); } } void WindowProxy::setPosition(const QPoint &point) { d->m_point = point; d->updatePoint(); } void WindowProxy::setGeometry(const QRect &rect) { d->m_size = rect.size(); d->m_point = rect.topLeft(); if(d->m_useUkuiShellIntegration) { d->m_window->setGeometry(rect); } else { d->updateGeometry(); } } void WindowProxy::setWindowType(WindowType::Type type) { d->m_type = type; d->setWindowType(); } void WindowProxy::setPanelAutoHide(bool autoHide) { d->m_panelAutoHide = autoHide; d->setAutoHideState(); } void WindowProxy::slideWindow(QWindow *window, WindowProxy::SlideFromEdge fromEdge, int offset) { if(useUkuiShellIntegration(window)) { window->setProperty("ukui_surface_slide", QVariant::fromValue(QPair(ukui_surface_slideEdge.value(fromEdge), offset))); return; } KWindowEffects::SlideFromLocation fromLocation; switch (fromEdge) { case NoEdge: fromLocation = KWindowEffects::SlideFromLocation::NoEdge; break; case TopEdge: fromLocation = KWindowEffects::SlideFromLocation::TopEdge; break; case RightEdge: fromLocation = KWindowEffects::SlideFromLocation::RightEdge; break; case BottomEdge: fromLocation = KWindowEffects::SlideFromLocation::BottomEdge; break; case LeftEdge: fromLocation = KWindowEffects::SlideFromLocation::LeftEdge; break; } KWindowEffects::slideWindow(window, fromLocation, offset); } void WindowProxy::slideWindow(WindowProxy::SlideFromEdge fromEdge, int offset) { if(d->m_useUkuiShellIntegration) { d->m_window->setProperty("ukui_surface_slide", QVariant::fromValue(QPair(ukui_surface_slideEdge.value(fromEdge), offset))); return; } d->m_slideFromEdge = fromEdge; d->m_slideOffset = offset; WindowProxy::slideWindow(d->m_window, fromEdge, offset); } void WindowProxy::setBlurRegion(QWindow *window, bool enable, const QRegion ®ion) { KWindowEffects::enableBlurBehind(window, enable, region); } void WindowProxy::setBlurRegion(bool enable, const QRegion ®ion) { d->m_enableBlur = enable; d->m_blurRegion = region; if (isWayland()) { if(d->m_useUkuiShellIntegration) { d->m_window->setProperty("ukui_surface_blur_v1", QVariant::fromValue(QPair>( d->m_blurRegion, d->m_enableBlur ? QVector{d->m_blurLevel, wl_fixed_from_double(d->m_blurOpacity)} : QVector{-1, -1}))); return; } d->setBlurBehind(); } else { KWindowEffects::enableBlurBehind(d->m_window, enable, region); } } void WindowProxy::setBlurLevel(uint32_t level) { if (d->m_blurLevel == level) { return; } d->m_blurLevel = level; if (isWayland()) { if(d->m_useUkuiShellIntegration) { d->m_window->setProperty("ukui_surface_blur_v1", QVariant::fromValue(QPair>( d->m_blurRegion, d->m_enableBlur ? QVector{d->m_blurLevel, wl_fixed_from_double(d->m_blurOpacity)} : QVector{-1, -1}))); return; } d->setBlurBehind(); } else { qWarning() << "Blur level can be only set on wayland!"; } } /*! /brief 设置毛玻璃透明度 \a opacity 毛玻璃透明度,范围 0.0 - 1.0 */ void WindowProxy::setBlurOpacity(const qreal opacity) { if (d->m_blurOpacity == opacity) { return; } d->m_blurOpacity = opacity; if (isWayland()) { if(d->m_useUkuiShellIntegration) { d->m_window->setProperty("ukui_surface_blur_v1", QVariant::fromValue(QPair>( d->m_blurRegion, d->m_enableBlur ? QVector{d->m_blurLevel, wl_fixed_from_double(d->m_blurOpacity)} : QVector{-1, -1}))); return; } d->setBlurBehind(); } else { qWarning() << "Blur opacity can be only set on wayland!"; } } QScreen* WindowProxy::currentScreen() { if(isWayland()) { auto shell = WaylandIntegration::self()->waylandUkuiShell(); shell->updateCurrentOutput(); WaylandIntegration::self()->sync(); if(shell->isCurrentOutputReady()) { QString name = shell->outputName(); for (auto screen: qGuiApp->screens()) { if (screen->name() == name) { return screen; } } } } else { QScreen * screen = qGuiApp->screenAt(QCursor::pos()); if(screen) { return screen; } } return qGuiApp->primaryScreen(); } bool WindowProxy::useUkuiShellIntegration(QWindow *window) { return QGuiApplication::platformName().startsWith(QStringLiteral("wayland")) && QGuiApplication::platformNativeInterface()->nativeResourceForWindow("ukui_shell", window); } QPoint WindowProxy::position() const { return d->m_point; } void WindowProxy::setDecorationComponents(Decoration::Components components) { if (!isWayland()) { qWarning() << "Decoration components can be only set on wayland!"; return; } if(d->m_useUkuiShellIntegration) { d->m_window->setProperty("ukui_surface_decoration_components", static_cast(components)); } else { d->m_decorationComponents = components; d->setDecorationComponents(); } } void WindowProxy::setWindowStates(WindowState::States states, bool enable) { if (isWayland()) { if(d->m_useUkuiShellIntegration) { QPair pair(states, enable? states : WindowState::States()); qDebug() << "===setWindowState===" << pair; d->m_window->setProperty("ukui_surface_state", QVariant::fromValue(pair)); } else { d->m_states = QPair(states, enable); d->setWindowStates(); } } else { const WindowState::States otherStates = states & (~(WindowState::KeepAbove | WindowState::KeepBelow)); if (otherStates != 0) { qWarning() << "Unsupported window states in X11 environment:" << static_cast(otherStates); } const WId winId = d->m_window->winId(); if (states.testFlag(WindowState::KeepAbove)) { if (enable) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) if (enable) { KX11Extras::setState(winId, NET::KeepAbove); } else { KX11Extras::clearState(winId, NET::KeepAbove); } #else if (enable) { KWindowSystem::setState(winId, NET::KeepAbove); } else { KWindowSystem::clearState(winId, NET::KeepAbove); } #endif } } if (states.testFlag(WindowState::KeepBelow)) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) if (enable) { KX11Extras::setState(winId, NET::KeepBelow); } else { KX11Extras::clearState(winId, NET::KeepBelow); } #else if (enable) { KWindowSystem::setState(winId, NET::KeepBelow); } else { KWindowSystem::clearState(winId, NET::KeepBelow); } #endif } } } WindowProxy* WindowProxy::fromWindow(QWindow* window) { for (auto child : window->children()) { if (auto *proxy = qobject_cast(child)) { return proxy; } } return new WindowProxy(window, Operations()); } QRect WindowProxy::restoreGeometry() const { return d->m_restoreGeometry; } void WindowProxy::setRestoreGeometry(const QRect& restoreGeomerty) { d->m_restoreGeometry = restoreGeomerty; d->setRestoreGeometry(); } // ======== WindowProxy2 =========== // class WindowProxy2::Private { public: WMInterface *m_wm {nullptr}; bool m_needResetProp = true; bool m_panelAutoHide = false; bool m_panelTakeFocus = false; WindowProxy::Operations m_operations; // 窗口类型 WindowType::Type m_type = WindowType::Normal; QPoint m_pos; KWindowEffects::SlideFromLocation m_fromLocation; int m_offset; UkuiQuick::Decoration::Components m_decorationComponents = UkuiQuick::Decoration::Border | UkuiQuick::Decoration::Shadow | UkuiQuick::Decoration::RoundCorner; void setupProp(); }; void WindowProxy2::Private::setupProp() { m_wm->setWindowType(m_type); m_wm->setSkipTaskBar(m_operations.testFlag(WindowProxy::SkipTaskBar)); m_wm->setSkipSwitcher(m_operations.testFlag(WindowProxy::SkipSwitcher)); m_wm->setRemoveTitleBar(m_operations.testFlag(WindowProxy::RemoveTitleBar)); m_wm->setPanelTakesFocus(m_panelTakeFocus); m_wm->setPanelAutoHide(m_panelAutoHide); m_wm->setDecorationComponents(m_decorationComponents); } WindowProxy2::WindowProxy2(QWindow *window, WindowProxy::Operations operations) : WMInterface(window), d(new WindowProxy2::Private) { if(!WindowProxy::useUkuiShellIntegration(window)) { window->installEventFilter(this); } d->m_operations = operations; d->m_wm = WManager::getWM(window); d->m_wm->setSkipTaskBar(d->m_operations.testFlag(WindowProxy::SkipTaskBar)); d->m_wm->setSkipSwitcher(d->m_operations.testFlag(WindowProxy::SkipSwitcher)); d->m_wm->setRemoveTitleBar(d->m_operations.testFlag(WindowProxy::RemoveTitleBar)); } WindowProxy2::~WindowProxy2() { if (d) { delete d; d = nullptr; } } bool WindowProxy2::eventFilter(QObject *watched, QEvent *event) { if (watched == window()) { switch (event->type()) { case QEvent::Expose: if (!d->m_needResetProp || !window()->isVisible()) { break; } d->m_needResetProp = false; case QEvent::Show: d->setupProp(); KWindowEffects::slideWindow(window(), d->m_fromLocation, d->m_offset); break; case QEvent::Hide: d->m_needResetProp = true; break; default: break; } } return QObject::eventFilter(watched, event); } QPoint WindowProxy2::position() const { return d->m_wm->position(); } void WindowProxy2::setPosition(const QPoint &point) { d->m_wm->setPosition(point); } void WindowProxy2::setWindowType(WindowType::Type type) { if (d->m_type == type) { return; } d->m_type = type; d->m_wm->setWindowType(type); } void WindowProxy2::setSkipTaskBar(bool skip) { if (skip) { d->m_operations |= WindowProxy::SkipTaskBar; } else { d->m_operations &= ~WindowProxy::SkipTaskBar; } d->m_wm->setSkipTaskBar( d->m_operations.testFlag(WindowProxy::SkipTaskBar)); } void WindowProxy2::setSkipSwitcher(bool skip) { if (skip) { d->m_operations |= WindowProxy::SkipSwitcher; } else { d->m_operations &= ~WindowProxy::SkipSwitcher; } d->m_wm->setSkipSwitcher( d->m_operations.testFlag(WindowProxy::SkipSwitcher)); } void WindowProxy2::setRemoveTitleBar(bool remove) { if (remove) { d->m_operations |= WindowProxy::RemoveTitleBar; } else { d->m_operations &= ~WindowProxy::RemoveTitleBar; } d->m_wm->setRemoveTitleBar( d->m_operations.testFlag(WindowProxy::RemoveTitleBar)); } void WindowProxy2::setPanelTakesFocus(bool takesFocus) { if (d->m_panelTakeFocus == takesFocus) { return; } d->m_panelTakeFocus = takesFocus; d->m_wm->setPanelTakesFocus(takesFocus); } void WindowProxy2::setPanelAutoHide(bool autoHide) { if (d->m_panelAutoHide == autoHide) { return; } d->m_panelAutoHide = autoHide; d->m_wm->setPanelTakesFocus(autoHide); } void WindowProxy2::setSlideWindowArgs(WindowProxy::SlideFromEdge fromEdge, int offset) { if (WindowProxy::useUkuiShellIntegration(window())) { window()->setProperty("ukui_surface_slide", QVariant::fromValue(QPair(ukui_surface_slideEdge.value(fromEdge), offset))); return; } KWindowEffects::SlideFromLocation fromLocation; switch (fromEdge) { case WindowProxy::NoEdge: fromLocation = KWindowEffects::SlideFromLocation::NoEdge; break; case WindowProxy::TopEdge: fromLocation = KWindowEffects::SlideFromLocation::TopEdge; break; case WindowProxy::RightEdge: fromLocation = KWindowEffects::SlideFromLocation::RightEdge; break; case WindowProxy::BottomEdge: fromLocation = KWindowEffects::SlideFromLocation::BottomEdge; break; case WindowProxy::LeftEdge: fromLocation = KWindowEffects::SlideFromLocation::LeftEdge; break; } d->m_fromLocation = fromLocation; d->m_offset = offset; KWindowEffects::slideWindow(window(), d->m_fromLocation, d->m_offset); } QScreen* WindowProxy2::currentScreen() { return d->m_wm->currentScreen(); } void WindowProxy2::setDecorationComponents(Decoration::Components components) { if (d->m_decorationComponents == components) { return; } d->m_decorationComponents = components; d->m_wm->setDecorationComponents(components); } } // UkuiQuick ukui-quick/platform/wayland/0000775000175000017500000000000015153756415015066 5ustar fengfengukui-quick/platform/wayland/ukui-window-management.cpp0000664000175000017500000007516515153755732022205 0ustar fengfeng#include "ukui-window-management.h" #include "wayland-ukui-window-management-client-protocol.h" #include "wayland-pointer_p.h" #include #include #include #include #include #include namespace UkuiQuick::WaylandClient { class Q_DECL_HIDDEN UkuiWindowManagement::Private { public: Private(UkuiWindowManagement *q); WaylandPointer wm; EventQueue *queue = nullptr; bool showingDesktop = false; QList windows; UkuiWindow *activeWindow = nullptr; QVector stackingOrder; QVector stackingOrderUuids; void setup(ukui_window_management *wm); private: static void showDesktopCallback(void *data, ukui_window_management *ukui_window_management, uint32_t state); static void windowCallback(void *data, struct ukui_window_management *ukui_window_management, const char *uuid); static void windowWithUuidCallback(void *data, struct ukui_window_management *ukui_window_management, uint32_t id, const char *uuid); static void stackingOrderUuidsCallback(void *data, struct ukui_window_management *ukui_window_management, const char *uuids); void setShowDesktop(bool set); void windowCreated(ukui_window *id, quint32 internalId, const char *uuid); void setStackingOrder(const QVector &ids); void setStackingOrder(const QVector &uuids); static struct ukui_window_management_listener s_listener; UkuiWindowManagement *q; }; class Q_DECL_HIDDEN UkuiWindow::Private { public: Private(ukui_window *window, quint32 internalId, const char *uuid, UkuiWindow *q); WaylandPointer window; quint32 internalId; ///< @deprecated QByteArray uuid; QString title; QString appId; quint32 desktop = 0; bool active = false; bool minimized = false; bool maximized = false; bool fullscreen = false; bool keepAbove = false; bool keepBelow = false; bool onAllDesktops = false; bool demandsAttention = false; bool closeable = false; bool minimizeable = false; bool maximizeable = false; bool fullscreenable = false; bool skipTaskbar = false; bool skipSwitcher = false; bool shadeable = false; bool shaded = false; bool movable = false; bool resizable = false; bool acceptFocus = false; bool modality = false; bool virtualDesktopChangeable = false; QIcon icon; UkuiWindowManagement *wm = nullptr; bool unmapped = false; QPointer parentWindow; QMetaObject::Connection parentWindowUnmappedConnection; QStringList ukuiVirtualDesktops; QStringList ukuiActivities; QRect geometry; quint32 pid = 0; QString applicationMenuServiceName; QString applicationMenuObjectPath; bool highlight = false; private: static void titleChangedCallback(void *data, ukui_window *window, const char *title); static void appIdChangedCallback(void *data, ukui_window *window, const char *app_id); static void pidChangedCallback(void *data, ukui_window *window, uint32_t pid); static void stateChangedCallback(void *data, ukui_window *window, uint32_t state); static void themedIconNameChangedCallback(void *data, ukui_window *window, const char *name); static void unmappedCallback(void *data, ukui_window *window); static void initialStateCallback(void *data, ukui_window *window); static void parentWindowCallback(void *data, ukui_window *window, ukui_window *parent); static void windowGeometryCallback(void *data, ukui_window *window, int32_t x, int32_t y, uint32_t width, uint32_t height); static void iconChangedCallback(void *data, ukui_window *ukui_window); static void virtualDesktopEnteredCallback(void *data, ukui_window *ukui_window, const char *id); static void virtualDesktopLeftCallback(void *data, ukui_window *ukui_window, const char *id); static void appmenuChangedCallback(void *data, ukui_window *ukui_window, const char *service_name, const char *object_path); static void activityEnteredCallback(void *data, ukui_window *ukui_window, const char *id); static void activityLeftCallback(void *data, ukui_window *ukui_window, const char *id); void setActive(bool set); void setMinimized(bool set); void setMaximized(bool set); void setFullscreen(bool set); void setKeepAbove(bool set); void setKeepBelow(bool set); void setOnAllDesktops(bool set); void setDemandsAttention(bool set); void setCloseable(bool set); void setMinimizeable(bool set); void setMaximizeable(bool set); void setFullscreenable(bool set); void setSkipTaskbar(bool skip); void setSkipSwitcher(bool skip); void setShadeable(bool set); void setShaded(bool set); void setMovable(bool set); void setResizable(bool set); void setVirtualDesktopChangeable(bool set); void setAcceptFocus(bool set); void setModality(bool set); void setParentWindow(UkuiWindow *parentWindow); void setPid(const quint32 pid); static Private *cast(void *data) { return reinterpret_cast(data); } UkuiWindow *q; static struct ukui_window_listener s_listener; }; UkuiWindowManagement::Private::Private(UkuiWindowManagement *q) : q(q) { } ukui_window_management_listener UkuiWindowManagement::Private::s_listener = { .show_desktop_changed = showDesktopCallback, .stacking_order_changed = stackingOrderUuidsCallback, .window_created = windowCallback, }; void UkuiWindowManagement::Private::setup(ukui_window_management *windowManagement) { Q_ASSERT(!wm); Q_ASSERT(windowManagement); wm.setup(windowManagement); ukui_window_management_add_listener(windowManagement, &s_listener, this); } void UkuiWindowManagement::Private::showDesktopCallback(void *data, ukui_window_management *ukui_window_management, uint32_t state) { auto wm = reinterpret_cast(data); Q_ASSERT(wm->wm == ukui_window_management); switch (state) { case UKUI_WINDOW_MANAGEMENT_SHOW_DESKTOP_ENABLED: wm->setShowDesktop(true); break; case UKUI_WINDOW_MANAGEMENT_SHOW_DESKTOP_DISABLED: wm->setShowDesktop(false); break; default: Q_UNREACHABLE(); break; } } void UkuiWindowManagement::Private::setShowDesktop(bool set) { if (showingDesktop == set) { return; } showingDesktop = set; Q_EMIT q->showingDesktopChanged(showingDesktop); } void UkuiWindowManagement::Private::windowCallback(void *data, ukui_window_management *interface, const char *_uuid) { QByteArray uuid(_uuid); auto wm = reinterpret_cast(data); Q_ASSERT(wm->wm == interface); QTimer *timer = new QTimer(); timer->setSingleShot(true); timer->setInterval(0); QObject::connect( timer, &QTimer::timeout, wm->q, [timer, wm, uuid] { // internalId 不用了,以uuid为唯一标识 wm->windowCreated(ukui_window_management_create_window(wm->wm, uuid), 0, uuid); timer->deleteLater(); }, Qt::QueuedConnection); timer->start(); } // not used void UkuiWindowManagement::Private::windowWithUuidCallback(void *data, ukui_window_management *interface, uint32_t id, const char *_uuid) { QByteArray uuid(_uuid); auto wm = reinterpret_cast(data); Q_ASSERT(wm->wm == interface); QTimer *timer = new QTimer(); timer->setSingleShot(true); timer->setInterval(0); QObject::connect( timer, &QTimer::timeout, wm->q, [timer, wm, id, uuid] { wm->windowCreated(ukui_window_management_create_window(wm->wm, uuid), id, uuid); timer->deleteLater(); }, Qt::QueuedConnection); timer->start(); } void UkuiWindowManagement::Private::windowCreated(ukui_window *id, quint32 internalId, const char *uuid) { if (queue) { queue->addProxy(id); } UkuiWindow *window = new UkuiWindow(q, id, internalId, uuid); window->d->wm = q; windows << window; const auto windowRemoved = [this, window] { windows.removeAll(window); if (activeWindow == window) { activeWindow = nullptr; Q_EMIT q->activeWindowChanged(); } }; QObject::connect(window, &QObject::destroyed, q, windowRemoved); QObject::connect(window, &UkuiWindow::unmapped, q, windowRemoved); QObject::connect(window, &UkuiWindow::activeChanged, q, [this, window] { if (window->d->unmapped) { return; } if (window->isActive()) { if (activeWindow == window) { return; } activeWindow = window; Q_EMIT q->activeWindowChanged(); } else { if (activeWindow == window) { activeWindow = nullptr; Q_EMIT q->activeWindowChanged(); } } }); } void UkuiWindowManagement::Private::stackingOrderUuidsCallback(void *data, ukui_window_management *interface, const char *uuids) { auto wm = reinterpret_cast(data); Q_ASSERT(wm->wm == interface); wm->setStackingOrder(QByteArray(uuids).split(';').toVector()); } void UkuiWindowManagement::Private::setStackingOrder(const QVector &ids) { if (stackingOrder == ids) { return; } stackingOrder = ids; Q_EMIT q->stackingOrderChanged(); } void UkuiWindowManagement::Private::setStackingOrder(const QVector &uuids) { if (stackingOrderUuids == uuids) { return; } stackingOrderUuids = uuids; Q_EMIT q->stackingOrderUuidsChanged(); } UkuiWindowManagement::UkuiWindowManagement(QObject *parent) : QObject(parent) , d(new Private(this)) { } UkuiWindowManagement::~UkuiWindowManagement() { release(); } void UkuiWindowManagement::destroy() { if (!d->wm) { return; } Q_EMIT interfaceAboutToBeDestroyed(); d->wm.destroy(); } void UkuiWindowManagement::release() { if (!d->wm) { return; } Q_EMIT interfaceAboutToBeReleased(); d->wm.release(); } void UkuiWindowManagement::setup(ukui_window_management *wm) { d->setup(wm); } void UkuiWindowManagement::setEventQueue(EventQueue *queue) { d->queue = queue; } EventQueue *UkuiWindowManagement::eventQueue() { return d->queue; } bool UkuiWindowManagement::isValid() const { return d->wm.isValid(); } UkuiWindowManagement::operator ukui_window_management *() { return d->wm; } UkuiWindowManagement::operator ukui_window_management *() const { return d->wm; } void UkuiWindowManagement::hideDesktop() { setShowingDesktop(false); } void UkuiWindowManagement::showDesktop() { setShowingDesktop(true); } void UkuiWindowManagement::setShowingDesktop(bool show) { ukui_window_management_show_desktop(d->wm, show ? UKUI_WINDOW_MANAGEMENT_SHOW_DESKTOP_ENABLED : UKUI_WINDOW_MANAGEMENT_SHOW_DESKTOP_DISABLED); } bool UkuiWindowManagement::isShowingDesktop() const { return d->showingDesktop; } QList UkuiWindowManagement::windows() const { return d->windows; } UkuiWindow *UkuiWindowManagement::activeWindow() const { return d->activeWindow; } QVector UkuiWindowManagement::stackingOrder() const { return d->stackingOrder; } QVector UkuiWindowManagement::stackingOrderUuids() const { return d->stackingOrderUuids; } ukui_window_listener UkuiWindow::Private::s_listener = { .title_changed = titleChangedCallback, .app_id_changed = appIdChangedCallback, .state_changed = stateChangedCallback, .themed_icon_name_changed = themedIconNameChangedCallback, .unmapped = unmappedCallback, .initial_state = initialStateCallback, .parent_window = parentWindowCallback, .geometry = windowGeometryCallback, .icon_changed = iconChangedCallback, .pid_changed = pidChangedCallback, .virtual_desktop_entered = virtualDesktopEnteredCallback, .virtual_desktop_left = virtualDesktopLeftCallback, .application_menu = appmenuChangedCallback, .activity_entered = activityEnteredCallback, .activity_left = activityLeftCallback}; void UkuiWindow::Private::appmenuChangedCallback(void *data, ukui_window *window, const char *service_name, const char *object_path) { Q_UNUSED(window) Private *p = cast(data); p->applicationMenuServiceName = QString::fromUtf8(service_name); p->applicationMenuObjectPath = QString::fromUtf8(object_path); Q_EMIT p->q->applicationMenuChanged(); } void UkuiWindow::Private::parentWindowCallback(void *data, ukui_window *window, ukui_window *parent) { Q_UNUSED(window) Private *p = cast(data); const auto windows = p->wm->windows(); auto it = std::find_if(windows.constBegin(), windows.constEnd(), [parent](const UkuiWindow *w) { return *w == parent; }); p->setParentWindow(it != windows.constEnd() ? *it : nullptr); } void UkuiWindow::Private::windowGeometryCallback(void *data, ukui_window *window, int32_t x, int32_t y, uint32_t width, uint32_t height) { Q_UNUSED(window) Private *p = cast(data); QRect geo(x, y, width, height); if (geo == p->geometry) { return; } p->geometry = geo; Q_EMIT p->q->geometryChanged(); } void UkuiWindow::Private::setParentWindow(UkuiWindow *parent) { const auto old = parentWindow; QObject::disconnect(parentWindowUnmappedConnection); if (parent && !parent->d->unmapped) { parentWindow = QPointer(parent); parentWindowUnmappedConnection = QObject::connect(parent, &UkuiWindow::unmapped, q, [this] { setParentWindow(nullptr); }); } else { parentWindow = QPointer(); parentWindowUnmappedConnection = QMetaObject::Connection(); } if (parentWindow.data() != old.data()) { Q_EMIT q->parentWindowChanged(); } } void UkuiWindow::Private::initialStateCallback(void *data, ukui_window *window) { Q_UNUSED(window) Private *p = cast(data); if (!p->unmapped) { Q_EMIT p->wm->windowCreated(p->q); } } void UkuiWindow::Private::titleChangedCallback(void *data, ukui_window *window, const char *title) { Q_UNUSED(window) Private *p = cast(data); const QString t = QString::fromUtf8(title); if (p->title == t) { return; } p->title = t; Q_EMIT p->q->titleChanged(); } void UkuiWindow::Private::appIdChangedCallback(void *data, ukui_window *window, const char *appId) { Q_UNUSED(window) Private *p = cast(data); const QString s = QString::fromUtf8(appId); if (s == p->appId) { return; } p->appId = s; Q_EMIT p->q->appIdChanged(); } void UkuiWindow::Private::pidChangedCallback(void *data, ukui_window *window, uint32_t pid) { Q_UNUSED(window) Private *p = cast(data); if (p->pid == static_cast(pid)) { return; } p->pid = pid; } void UkuiWindow::Private::unmappedCallback(void *data, ukui_window *window) { auto p = cast(data); Q_UNUSED(window); p->unmapped = true; Q_EMIT p->q->unmapped(); p->q->deleteLater(); } void UkuiWindow::Private::virtualDesktopEnteredCallback(void *data, ukui_window *window, const char *id) { auto p = cast(data); Q_UNUSED(window); const QString stringId(QString::fromUtf8(id)); p->ukuiVirtualDesktops << stringId; Q_EMIT p->q->ukuiVirtualDesktopEntered(stringId); if (p->ukuiVirtualDesktops.count() == 1) { Q_EMIT p->q->onAllDesktopsChanged(); } } void UkuiWindow::Private::virtualDesktopLeftCallback(void *data, ukui_window *window, const char *id) { auto p = cast(data); Q_UNUSED(window); const QString stringId(QString::fromUtf8(id)); p->ukuiVirtualDesktops.removeAll(stringId); Q_EMIT p->q->ukuiVirtualDesktopLeft(stringId); if (p->ukuiVirtualDesktops.isEmpty()) { Q_EMIT p->q->onAllDesktopsChanged(); } } void UkuiWindow::Private::activityEnteredCallback(void *data, ukui_window *window, const char *id) { auto p = cast(data); Q_UNUSED(window); const QString stringId(QString::fromUtf8(id)); p->ukuiActivities << stringId; Q_EMIT p->q->ukuiActivityEntered(stringId); } void UkuiWindow::Private::activityLeftCallback(void *data, ukui_window *window, const char *id) { auto p = cast(data); Q_UNUSED(window); const QString stringId(QString::fromUtf8(id)); p->ukuiActivities.removeAll(stringId); Q_EMIT p->q->ukuiActivityLeft(stringId); } void UkuiWindow::Private::stateChangedCallback(void *data, ukui_window *window, uint32_t state) { auto p = cast(data); Q_UNUSED(window); p->setActive(state & UKUI_WINDOW_STATE_ACTIVE); p->setMinimized(state & UKUI_WINDOW_STATE_MINIMIZED); p->setMaximized(state & UKUI_WINDOW_STATE_MAXIMIZED); p->setFullscreen(state & UKUI_WINDOW_STATE_FULLSCREEN); p->setKeepAbove(state & UKUI_WINDOW_STATE_KEEP_ABOVE); p->setKeepBelow(state & UKUI_WINDOW_STATE_KEEP_BELOW); p->setOnAllDesktops(state & UKUI_WINDOW_STATE_ON_ALL_DESKTOPS); p->setDemandsAttention(state & UKUI_WINDOW_STATE_DEMANDS_ATTENTION); p->setCloseable(state & UKUI_WINDOW_STATE_CLOSEABLE); p->setFullscreenable(state & UKUI_WINDOW_STATE_FULLSCREENABLE); p->setMaximizeable(state & UKUI_WINDOW_STATE_MAXIMIZABLE); p->setMinimizeable(state & UKUI_WINDOW_STATE_MINIMIZABLE); p->setSkipTaskbar(state & UKUI_WINDOW_STATE_SKIPTASKBAR); p->setSkipSwitcher(state & UKUI_WINDOW_STATE_SKIPSWITCHER); p->setShadeable(state & UKUI_WINDOW_STATE_SHADEABLE); p->setShaded(state & UKUI_WINDOW_STATE_SHADED); p->setMovable(state & UKUI_WINDOW_STATE_MOVABLE); p->setResizable(state & UKUI_WINDOW_STATE_RESIZABLE); p->setVirtualDesktopChangeable(state & UKUI_WINDOW_STATE_VIRTUAL_DESKTOP_CHANGEABLE); } void UkuiWindow::Private::themedIconNameChangedCallback(void *data, ukui_window *window, const char *name) { auto p = cast(data); Q_UNUSED(window); const QString themedName = QString::fromUtf8(name); if (!themedName.isEmpty()) { QIcon icon = QIcon::fromTheme(themedName); p->icon = icon; } else { p->icon = QIcon(); } Q_EMIT p->q->iconChanged(); } static int readData(int fd, QByteArray &data) { char buf[4096]; int retryCount = 0; int n; while (true) { n = read(fd, buf, 4096); if (n == -1 && (errno == EAGAIN) && ++retryCount < 1000) { usleep(1000); } else { break; } } if (n > 0) { data.append(buf, n); n = readData(fd, data); } return n; } void UkuiWindow::Private::iconChangedCallback(void *data, ukui_window *window) { auto p = cast(data); Q_UNUSED(window); int pipeFds[2]; if (pipe2(pipeFds, O_CLOEXEC | O_NONBLOCK) != 0) { return; } ukui_window_get_icon(p->window, pipeFds[1]); close(pipeFds[1]); const int pipeFd = pipeFds[0]; auto readIcon = [pipeFd]() -> QIcon { QByteArray content; if (readData(pipeFd, content) != 0) { close(pipeFd); return QIcon(); } close(pipeFd); if (qgetenv("XDG_SESSION_DESKTOP") == QString("kylin-wlcom")) { quint32 width, height; memcpy(&width, content.constData(), sizeof(width)); memcpy(&height, content.constData() + sizeof(width), sizeof(height)); if (content.size() - 8 != width * height * 4) { return QIcon(); } QImage image(width, height, QImage::Format_ARGB32); if (!image.bits()) { return QIcon(); } memcpy(image.bits(), content.constData() + 8, content.size() - 8); QIcon icon = QIcon(QPixmap::fromImage(image)); return icon; } else { QDataStream ds(content); QIcon icon; ds >> icon; return icon; } }; QFutureWatcher *watcher = new QFutureWatcher(p->q); QObject::connect(watcher, &QFutureWatcher::finished, p->q, [p, watcher] { watcher->deleteLater(); QIcon icon = watcher->result(); if (!icon.isNull()) { p->icon = icon; } else { p->icon = QIcon::fromTheme(QStringLiteral("wayland")); } Q_EMIT p->q->iconChanged(); }); watcher->setFuture(QtConcurrent::run(readIcon)); } void UkuiWindow::Private::setActive(bool set) { if (active == set) { return; } active = set; Q_EMIT q->activeChanged(); } void UkuiWindow::Private::setFullscreen(bool set) { if (fullscreen == set) { return; } fullscreen = set; Q_EMIT q->fullscreenChanged(); } void UkuiWindow::Private::setKeepAbove(bool set) { if (keepAbove == set) { return; } keepAbove = set; Q_EMIT q->keepAboveChanged(); } void UkuiWindow::Private::setKeepBelow(bool set) { if (keepBelow == set) { return; } keepBelow = set; Q_EMIT q->keepBelowChanged(); } void UkuiWindow::Private::setMaximized(bool set) { if (maximized == set) { return; } maximized = set; Q_EMIT q->maximizedChanged(); } void UkuiWindow::Private::setMinimized(bool set) { if (minimized == set) { return; } minimized = set; Q_EMIT q->minimizedChanged(); } void UkuiWindow::Private::setOnAllDesktops(bool set) { if (onAllDesktops == set) { return; } onAllDesktops = set; Q_EMIT q->onAllDesktopsChanged(); } void UkuiWindow::Private::setDemandsAttention(bool set) { if (demandsAttention == set) { return; } demandsAttention = set; Q_EMIT q->demandsAttentionChanged(); } void UkuiWindow::Private::setCloseable(bool set) { if (closeable == set) { return; } closeable = set; Q_EMIT q->closeableChanged(); } void UkuiWindow::Private::setFullscreenable(bool set) { if (fullscreenable == set) { return; } fullscreenable = set; Q_EMIT q->fullscreenableChanged(); } void UkuiWindow::Private::setMaximizeable(bool set) { if (maximizeable == set) { return; } maximizeable = set; Q_EMIT q->maximizeableChanged(); } void UkuiWindow::Private::setMinimizeable(bool set) { if (minimizeable == set) { return; } minimizeable = set; Q_EMIT q->minimizeableChanged(); } void UkuiWindow::Private::setSkipTaskbar(bool skip) { if (skipTaskbar == skip) { return; } skipTaskbar = skip; Q_EMIT q->skipTaskbarChanged(); } void UkuiWindow::Private::setSkipSwitcher(bool skip) { if (skipSwitcher == skip) { return; } skipSwitcher = skip; Q_EMIT q->skipSwitcherChanged(); } void UkuiWindow::Private::setShadeable(bool set) { if (shadeable == set) { return; } shadeable = set; Q_EMIT q->shadeableChanged(); } void UkuiWindow::Private::setShaded(bool set) { if (shaded == set) { return; } shaded = set; Q_EMIT q->shadedChanged(); } void UkuiWindow::Private::setMovable(bool set) { if (movable == set) { return; } movable = set; Q_EMIT q->movableChanged(); } void UkuiWindow::Private::setResizable(bool set) { if (resizable == set) { return; } resizable = set; Q_EMIT q->resizableChanged(); } void UkuiWindow::Private::setVirtualDesktopChangeable(bool set) { if (virtualDesktopChangeable == set) { return; } virtualDesktopChangeable = set; Q_EMIT q->virtualDesktopChangeableChanged(); } void UkuiWindow::Private::setAcceptFocus(bool set) { if (acceptFocus == set) { return; } acceptFocus = set; Q_EMIT q->acceptFocusChanged(); } void UkuiWindow::Private::setModality(bool set) { if (modality == set) { return; } modality = set; Q_EMIT q->modalityChanged(); } UkuiWindow::Private::Private(ukui_window *w, quint32 internalId, const char *uuid, UkuiWindow *q) : internalId(internalId) , uuid(uuid) , q(q) { Q_ASSERT(!this->uuid.isEmpty()); window.setup(w); ukui_window_add_listener(w, &s_listener, this); } UkuiWindow::UkuiWindow(UkuiWindowManagement *parent, ukui_window *window, quint32 internalId, const char *uuid) : QObject(parent) , d(new Private(window, internalId, uuid, this)) { } UkuiWindow::~UkuiWindow() { release(); } void UkuiWindow::destroy() { d->window.destroy(); } void UkuiWindow::release() { d->window.release(); } bool UkuiWindow::isValid() const { return d->window.isValid(); } UkuiWindow::operator ukui_window *() const { return d->window; } UkuiWindow::operator ukui_window *() { return d->window; } QString UkuiWindow::appId() const { return d->appId; } quint32 UkuiWindow::pid() const { return d->pid; } QString UkuiWindow::title() const { return d->title; } quint32 UkuiWindow::virtualDesktop() const { return d->desktop; } bool UkuiWindow::isActive() const { return d->active; } bool UkuiWindow::isFullscreen() const { return d->fullscreen; } bool UkuiWindow::isKeepAbove() const { return d->keepAbove; } bool UkuiWindow::isKeepBelow() const { return d->keepBelow; } bool UkuiWindow::isMaximized() const { return d->maximized; } bool UkuiWindow::isMinimized() const { return d->minimized; } bool UkuiWindow::isOnAllDesktops() const { if (ukui_window_get_version(d->window) < 8) { return d->onAllDesktops; } else { return d->ukuiVirtualDesktops.isEmpty(); } } bool UkuiWindow::isDemandingAttention() const { return d->demandsAttention; } bool UkuiWindow::isCloseable() const { return d->closeable; } bool UkuiWindow::isFullscreenable() const { return d->fullscreenable; } bool UkuiWindow::isMaximizeable() const { return d->maximizeable; } bool UkuiWindow::isMinimizeable() const { return d->minimizeable; } bool UkuiWindow::skipTaskbar() const { return d->skipTaskbar; } bool UkuiWindow::skipSwitcher() const { return d->skipSwitcher; } QIcon UkuiWindow::icon() const { return d->icon; } bool UkuiWindow::isShadeable() const { return d->shadeable; } bool UkuiWindow::isShaded() const { return d->shaded; } bool UkuiWindow::isResizable() const { return d->resizable; } bool UkuiWindow::isMovable() const { return d->movable; } bool UkuiWindow::isVirtualDesktopChangeable() const { return d->virtualDesktopChangeable; } QString UkuiWindow::applicationMenuObjectPath() const { return d->applicationMenuObjectPath; } QString UkuiWindow::applicationMenuServiceName() const { return d->applicationMenuServiceName; } void UkuiWindow::requestActivate() { ukui_window_set_state(d->window, UKUI_WINDOW_STATE_ACTIVE, UKUI_WINDOW_STATE_ACTIVE); } void UkuiWindow::requestClose() { ukui_window_close(d->window); } void UkuiWindow::requestMove() { ukui_window_request_move(d->window); } void UkuiWindow::requestResize() { ukui_window_request_resize(d->window); } void UkuiWindow::requestVirtualDesktop(quint32 desktop) { // not used } void UkuiWindow::requestToggleKeepAbove() { if (d->keepAbove) { ukui_window_set_state(d->window, UKUI_WINDOW_STATE_KEEP_ABOVE, 0); } else { ukui_window_set_state(d->window, UKUI_WINDOW_STATE_KEEP_ABOVE, UKUI_WINDOW_STATE_KEEP_ABOVE); } } void UkuiWindow::requestDemandAttention() { if (d->demandsAttention) { ukui_window_set_state(d->window, UKUI_WINDOW_STATE_DEMANDS_ATTENTION, 0); } else { ukui_window_set_state(d->window, UKUI_WINDOW_STATE_DEMANDS_ATTENTION, UKUI_WINDOW_STATE_DEMANDS_ATTENTION); } } void UkuiWindow::requestToggleKeepBelow() { if (d->keepBelow) { ukui_window_set_state(d->window, UKUI_WINDOW_STATE_KEEP_BELOW, 0); } else { ukui_window_set_state(d->window, UKUI_WINDOW_STATE_KEEP_BELOW, UKUI_WINDOW_STATE_KEEP_BELOW); } } void UkuiWindow::requestToggleMinimized() { if (d->minimized) { ukui_window_set_state(d->window, UKUI_WINDOW_STATE_MINIMIZED, 0); } else { ukui_window_set_state(d->window, UKUI_WINDOW_STATE_MINIMIZED, UKUI_WINDOW_STATE_MINIMIZED); } } void UkuiWindow::requestToggleMaximized() { if (d->maximized) { ukui_window_set_state(d->window, UKUI_WINDOW_STATE_MAXIMIZED, 0); } else { ukui_window_set_state(d->window, UKUI_WINDOW_STATE_MAXIMIZED, UKUI_WINDOW_STATE_MAXIMIZED); } } void UkuiWindow::setStartupGeometry(Surface *surface, const QRect &geometry) { ukui_window_set_startup_geometry(d->window, *surface, geometry.x(), geometry.y(), geometry.width(), geometry.height()); } void UkuiWindow::setMinimizedGeometry(Surface *panel, const QRect &geom) { ukui_window_set_minimized_geometry(d->window, *panel, geom.x(), geom.y(), geom.width(), geom.height()); } void UkuiWindow::unsetMinimizedGeometry(Surface *panel) { ukui_window_unset_minimized_geometry(d->window, *panel); } void UkuiWindow::requestToggleShaded() { if (d->shaded) { ukui_window_set_state(d->window, UKUI_WINDOW_STATE_SHADED, 0); } else { ukui_window_set_state(d->window, UKUI_WINDOW_STATE_SHADED, UKUI_WINDOW_STATE_SHADED); } } quint32 UkuiWindow::internalId() const { return d->internalId; } QByteArray UkuiWindow::uuid() const { return d->uuid; } QPointer UkuiWindow::parentWindow() const { return d->parentWindow; } QRect UkuiWindow::geometry() const { return d->geometry; } void UkuiWindow::requestEnterVirtualDesktop(const QString &id) { ukui_window_request_enter_virtual_desktop(d->window, id.toUtf8()); } void UkuiWindow::requestEnterNewVirtualDesktop() { ukui_window_request_enter_new_virtual_desktop(d->window); } void UkuiWindow::requestLeaveVirtualDesktop(const QString &id) { ukui_window_request_leave_virtual_desktop(d->window, id.toUtf8()); } QStringList UkuiWindow::ukuiVirtualDesktops() const { return d->ukuiVirtualDesktops; } void UkuiWindow::requestEnterActivity(const QString &id) { ukui_window_request_enter_activity(d->window, id.toUtf8()); } void UkuiWindow::requestLeaveActivity(const QString &id) { ukui_window_request_leave_activity(d->window, id.toUtf8()); } QStringList UkuiWindow::ukuiActivities() const { return d->ukuiActivities; } void UkuiWindow::sendToOutput(KWayland::Client::Output *output) const { if (ukui_window_get_version(d->window) >= UKUI_WINDOW_SEND_TO_OUTPUT_SINCE_VERSION) { ukui_window_send_to_output(d->window, *output); } } void UkuiWindow::setHighlight() { if (d->highlight) return; ukui_window_highlight(d->window); d->highlight = true; } void UkuiWindow::unsetHightlight() { if (!d->highlight) return; ukui_window_unset_highlight(d->window); d->highlight = false; } bool UkuiWindow::isHighlight() { return d->highlight; } }ukui-quick/platform/wayland/ukui-blur-manager.cpp0000664000175000017500000000721315153755732021125 0ustar fengfeng/* * * Copyright (C) 2025, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #include "ukui-blur-manager.h" #include #include "wayland-integration_p.h" #include "wayland-pointer_p.h" #include "wayland-ukui-blur-v1-client-protocol.h" #include namespace UkuiQuick::WaylandClient { class Q_DECL_HIDDEN UkuiBlurManager::Private { public: Private() = default; WaylandPointer manager; EventQueue *queue = nullptr; }; UkuiBlurManager::UkuiBlurManager(QObject* parent): QObject(parent), d(new Private) { } UkuiBlurManager::~UkuiBlurManager() { release(); } bool UkuiBlurManager::isValid() const { return d->manager.isValid(); } void UkuiBlurManager::setup(ukui_blur_manager_v1* manager) { Q_ASSERT(manager); Q_ASSERT(!d->manager); d->manager.setup(manager); } void UkuiBlurManager::release() { d->manager.release(); } void UkuiBlurManager::destroy() { d->manager.destroy(); } void UkuiBlurManager::setEventQueue(EventQueue* queue) { d->queue = queue; } EventQueue* UkuiBlurManager::eventQueue() { return d->queue; } UkuiBlur* UkuiBlurManager::getBlur(KWayland::Client::Surface *surface, QObject *parent) { if (!isValid() || !surface) { return nullptr; } auto blur = new UkuiBlur(parent); auto w = ukui_blur_manager_v1_get_blur(d->manager, *surface); if (d->queue) { d->queue->addProxy(w); } blur->setup(w); return blur; } class UkuiBlur::Private { public: WaylandPointer blur; }; UkuiBlur::UkuiBlur(QObject* parent): QObject(parent) , d(new Private) { } UkuiBlur::~UkuiBlur() { release(); } void UkuiBlur::setup(ukui_blur_surface_v1* blur) { Q_ASSERT(blur); Q_ASSERT(!d->blur); d->blur.setup(blur); } void UkuiBlur::release() { d->blur.release(); } void UkuiBlur::destroy() { d->blur.destroy(); } bool UkuiBlur::isValid() const { return d->blur.isValid(); } void UkuiBlur::setRegion(QRegion region) { auto wlRegion = WaylandIntegration::self()->waylandCompositor()->createRegion(); for (const QRect &rect : region) { wl_region_add(wlRegion, rect.x(), rect.y(), rect.width(), rect.height()); } ukui_blur_surface_v1_set_region(d->blur, wlRegion); } void UkuiBlur::setLevel(uint32_t level) { ukui_blur_surface_v1_set_level(d->blur, level); } void UkuiBlur::setOpacity(const qreal opacity) const { if (ukui_blur_surface_v1_get_version(d->blur) >= UKUI_BLUR_SURFACE_V1_SET_OPACITY_SINCE_VERSION) { ukui_blur_surface_v1_set_opacity(d->blur, wl_fixed_from_double(opacity)); } else { qWarning() << "UkuiBlur::setOpacity: Blur surface protocol version too low, opacity setting not supported. " << "Current version:" << ukui_blur_surface_v1_get_version(d->blur) << ", required version:" << UKUI_BLUR_SURFACE_V1_SET_OPACITY_SINCE_VERSION; } } } ukui-quick/platform/wayland/ukui-startup-manager.h0000664000175000017500000000340115153755732021323 0ustar fengfeng/* * * Copyright (C) 2025, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef UKUI_STARTUP_MANAGER_H #define UKUI_STARTUP_MANAGER_H #include #include #include #include #include struct ukui_startup_management_v1; using namespace KWayland::Client; namespace UkuiQuick::WaylandClient { class UkuiStartupManagement : public QObject { Q_OBJECT public: explicit UkuiStartupManagement(QObject *parent = nullptr); ~UkuiStartupManagement() override; bool isValid() const; void release(); void destroy(); void setup(ukui_startup_management_v1 *shell) const; void setEventQueue(EventQueue *queue) const; EventQueue *eventQueue() const; void setStartupGeometry(uint32_t pid, int32_t x, int32_t y, uint32_t width, uint32_t height); Q_SIGNALS: void interfaceAboutToBeReleased(); void interfaceAboutToBeDestroyed(); void removed(); private: class Private; QScopedPointer d; }; } #endif //UKUI_STARTUP_MANAGER_H ukui-quick/platform/wayland/ukui-startup-manager.cpp0000664000175000017500000000502615153755732021663 0ustar fengfeng/* * * Copyright (C) 2025, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #include "ukui-startup-manager.h" #include "wayland-pointer_p.h" #include #include "wayland-ukui-startup-v1-client-protocol.h" namespace UkuiQuick::WaylandClient { class Q_DECL_HIDDEN UkuiStartupManagement::Private { public: Private(UkuiStartupManagement *q); WaylandPointer manager; EventQueue *queue = nullptr; UkuiStartupManagement *q; }; UkuiStartupManagement::Private::Private(UkuiStartupManagement* q): q(q) { } UkuiStartupManagement::UkuiStartupManagement(QObject* parent): QObject(parent) , d(new Private(this)) { } UkuiStartupManagement::~UkuiStartupManagement() { release(); } bool UkuiStartupManagement::isValid() const { return d->manager.isValid(); } void UkuiStartupManagement::release() { if (!d->manager) { return; } Q_EMIT interfaceAboutToBeReleased(); d->manager.release(); } void UkuiStartupManagement::destroy() { if (!d->manager) { return; } Q_EMIT interfaceAboutToBeDestroyed(); d->manager.destroy(); } void UkuiStartupManagement::setup(ukui_startup_management_v1* shell) const { Q_ASSERT(!d->manager); Q_ASSERT(shell); d->manager.setup(shell); } void UkuiStartupManagement::setEventQueue(EventQueue* queue) const { d->queue = queue; } EventQueue* UkuiStartupManagement::eventQueue() const { return d->queue; } void UkuiStartupManagement::setStartupGeometry(uint32_t pid, int32_t x, int32_t y, uint32_t width, uint32_t height) { if (!d->manager) { return; } if (width <= 0 || height <= 0) { return; } ukui_startup_management_v1_set_startup_geometry(d->manager, pid, x, y, width, height); } } ukui-quick/platform/wayland/compositor.cpp0000664000175000017500000000372215153755732017775 0ustar fengfeng/* SPDX-FileCopyrightText: 2014 Martin Gräßlin SPDX-FileCopyrightText: 2025 iaom SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "compositor.h" #include #include "wayland-pointer_p.h" #include #include namespace UkuiQuick::WaylandClient { class Q_DECL_HIDDEN Compositor::Private { public: Private() = default; WaylandPointer compositor; EventQueue *queue = nullptr; }; Compositor::Compositor(QObject* parent): QObject(parent) , d(new Private) { } Compositor::~Compositor() { release(); } Compositor* Compositor::fromApplication(QObject* parent) { QPlatformNativeInterface *native = qGuiApp->platformNativeInterface(); if (!native) { return nullptr; } wl_compositor *compositor = static_cast(native->nativeResourceForIntegration(QByteArrayLiteral("compositor"))); if (!compositor) { return nullptr; } auto *c = new Compositor(parent); c->d->compositor.setup(compositor, true); return c; } bool Compositor::isValid() const { return d->compositor.isValid(); } void Compositor::setup(wl_compositor* compositor) { Q_ASSERT(compositor); Q_ASSERT(!d->compositor); d->compositor.setup(compositor); } void Compositor::release() { d->compositor.release(); } void Compositor::destroy() { d->compositor.destroy(); } void Compositor::setEventQueue(EventQueue* queue) { d->queue = queue; } EventQueue* Compositor::eventQueue() { return d->queue; } wl_surface* Compositor::createSurface() { if (!isValid()) { return nullptr; } return wl_compositor_create_surface(d->compositor); } wl_region* Compositor::createRegion() { if (!isValid()) { return nullptr; } return wl_compositor_create_region(d->compositor); } } ukui-quick/platform/wayland/wayland-integration.cpp0000664000175000017500000000626215153755732021561 0ustar fengfeng/* SPDX-FileCopyrightText: 2020 Vlad Zahorodnii Based on WaylandIntegration from kwayland-integration SPDX-FileCopyrightText: 2014 Martin Gräßlin SPDX-FileCopyrightText: 2015 Marco Martin SPDX-FileCopyrightText: 2024 iaom SPDX-License-Identifier: GPL-2.0-or-later */ #include "wayland-integration_p.h" #include #include #include #include "ukui-shell-v1.h" #include "compositor.h" #include "ukui-blur-manager.h" #include "registry.h" class WaylandIntegrationSingleton { public: WaylandIntegration self; }; Q_GLOBAL_STATIC(WaylandIntegrationSingleton, privateWaylandIntegrationSelf) WaylandIntegration::WaylandIntegration(QObject *parent) : QObject(parent) { setupKWaylandIntegration(); } WaylandIntegration::~WaylandIntegration() {} UkuiQuick::WaylandClient::Registry *WaylandIntegration::registry() const { return m_registry; } UkuiQuick::WaylandClient::UkuiShell *WaylandIntegration::waylandUkuiShell() { if (!m_waylandUkuiShell && m_registry) { const UkuiQuick::WaylandClient::Registry::AnnouncedInterface interface = m_registry->interface(UkuiQuick::WaylandClient::Registry::Interface::UkuiShell); if (interface.name == 0) { qWarning() << "The compositor does not support the ukui shell protocol"; return nullptr; } m_waylandUkuiShell = m_registry->createUkuiShell(interface.name, interface.version, qGuiApp); connect(m_waylandUkuiShell, &UkuiQuick::WaylandClient::UkuiShell::removed, this, [this]() { m_waylandUkuiShell->deleteLater(); }); } return m_waylandUkuiShell; } UkuiQuick::WaylandClient::Compositor *WaylandIntegration::waylandCompositor() const { return m_compositor; } UkuiQuick::WaylandClient::UkuiBlurManager* WaylandIntegration::waylandBlurManager() { if (!m_blurManager && m_registry) { const UkuiQuick::WaylandClient::Registry::AnnouncedInterface wmInterface = m_registry->interface(UkuiQuick::WaylandClient::Registry::Interface::UkuiBlurManager); if (wmInterface.name == 0) { return nullptr; } m_blurManager = m_registry->createUkuiBlurManager(wmInterface.name, wmInterface.version, qGuiApp); connect(m_blurManager, &UkuiQuick::WaylandClient::UkuiBlurManager::removed, this, [this]() { m_blurManager->deleteLater(); }); } return m_blurManager; } WaylandIntegration *WaylandIntegration::self() { return &privateWaylandIntegrationSelf()->self; } void WaylandIntegration::sync() { wl_display_roundtrip(m_connection->display()); } void WaylandIntegration::setupKWaylandIntegration() { m_connection = KWayland::Client::ConnectionThread::fromApplication(this); if (!m_connection) { qWarning() << "Failed getting Wayland connection from QPA"; return; } m_registry = new UkuiQuick::WaylandClient::Registry(qGuiApp); m_registry->create(m_connection); m_compositor = UkuiQuick::WaylandClient::Compositor::fromApplication(this); m_registry->setup(); m_connection->roundtrip(); } ukui-quick/platform/wayland/protocol/0000775000175000017500000000000015153756415016727 5ustar fengfengukui-quick/platform/wayland/protocol/ukui-blur-v1.xml0000664000175000017500000001010215153755732021707 0ustar fengfeng This protocol provides a way to improve visuals of translucent surfaces by blurring background behind them. Warning! The protocol described in this file is currently in the testing phase. Backward compatible changes may be added together with the corresponding interface version bump. Backward incompatible changes can only be done by creating a new major version of the extension. Informs the server that the client will no longer be using this protocol object. Existing objects created by this object are not affected. Instantiate an interface extension for the given wl_surface to blur background behind it. If the given wl_surface already has a ukui_blur_surface_v1 object associated, the blur_exists protocol error will be raised. The blur object provides a way to specify a region behind a surface that should be blurred by the compositor. If the wl_surface associated with the ukui_blur_surface_v1 object has been destroyed, this object becomes inert. Informs the server that the client will no longer be using this protocol object. The blur region will be unset on the next commit. This request sets the region of the surface that will have its background blurred. The blur region is specified in the surface-local coordinates. The initial value for the blur region is empty. Setting the pending blur region has copy semantics, and the wl_region object can be destroyed immediately. A NULL wl_region causes the blur region to be set to infinite. The blur region is double-buffered state, and will be applied on the next wl_surface.commit. The blur algorithm is subject to compositor policies. Change blur algorithm level in compositor. The range of level is 1 to 15. The compositor default blur algorithm level is 5. The blur level will be applied on the next wl_surface.commit. The opacity of blur, value between 0 and 1.0. The mixture of original background and blur. Default value is 1.0. The blur opacity will be applied on the next wl_surface.commit. ukui-quick/platform/wayland/protocol/ukui-window-management.xml0000664000175000017500000004177015153755732024057 0ustar fengfeng This interface manages application windows. It provides requests to show and hide the desktop and emits an event every time a window is created so that the client can use it to manage the window. Tell the compositor to show/hide the desktop. This event will be sent whenever the show desktop mode changes. E.g. when it is entered or left. On binding the interface the current state is sent. This event will be sent when stacking order changed and on bind This event will be sent immediately after a window is mapped. Manages and control an application window. Set window state. Can set multiple states at once. Values for state argument are described by ukui_window_management.state and can be used together in a bitfield. The flags bitfield describes which flags are supposed to be set, the state bitfield the value for the set flags. The state argument is not a boolean value, but a bitfield, so it is possible to set multiple states at once. Only the states that are set in the flags bitfield will be changed. For example: If flags was 0x14(keep_above|maximized) and state was 0x04(maximized), the window would be maximized, but not keep above. If flags was 0x04(maximized) and state was 0x14(keep_above|maximized), the window would be maximized but not change keep above state. Sets the geometry of the taskbar/desktop entry for this window. The geometry is relative to a panel/desktop in particular. Sets the geometry of the taskbar entry for this window. The geometry is relative to a panel in particular. Remove the task geometry information for a particular panel. Close this window. Request an interactive move for this window. The pointer will move to the center of the window and move with the pointer. When mouse button is released, the interactive move ends. Request an interactive resize for this window. The pointer will move to window right bottom corner and resize with the pointer. When mouse button is released, the interactive resize ends. Removes the resource bound for this ukui_window. The compositor will write the window icon into the provided file descriptor. The data is a serialized QIcon with QDataStream. This event will be sent as soon as the window title is changed. This event will be sent as soon as the application identifier is changed. This event will be sent as soon as the window state changes. Values for state argument are described by ukui_window_management.state. It contains the whole new state of the window. This event will be sent whenever the themed icon name changes. May be null. This event will be sent immediately after the window is closed and its surface is unmapped. This event will be sent immediately after all initial state been sent to the client. If the Plasma window is already unmapped, the unmapped event will be sent before the initial_state event. This event will be sent whenever the parent window of this ukui_window changes. The passed parent is another ukui_window and this ukui_window is a transient window to the parent window. If the parent argument is null, this ukui_window does not have a parent window. This event will be sent whenever the window geometry of this ukui_window changes. The coordinates are in absolute coordinates of the windowing system. This event will be sent whenever the icon of the window changes, but there is no themed icon name. Common examples are Xwayland windows which have a pixmap based icon. The client can request the icon using get_icon. This event will be sent when the compositor has set the process id this window belongs to. This should be set once before the initial_state is sent. Make the window enter a virtual desktop. A window can enter more than one virtual desktop. if the id is empty or invalid, no action will be performed. RFC: do this with an empty id to request_enter_virtual_desktop? Make the window enter a new virtual desktop. If the server consents the request, it will create a new virtual desktop and assign the window to it. Make the window exit a virtual desktop. If it exits all desktops it will be considered on all of them. This event will be sent when the window has entered a new virtual desktop. The window can be on more than one desktop, or none: then is considered on all of them. This event will be sent when the window left a virtual desktop. If the window leaves all desktops, it can be considered on all. If the window gets manually added on all desktops, the server has to send virtual_desktop_left for every previous desktop it was in for the window to be really considered on all desktops. This event will be sent after the application menu for the window has changed. Make the window enter an activity. A window can enter more activity. If the id is empty or invalid, no action will be performed. Make the window exit a an activity. If it exits all activities it will be considered on all of them. This event will be sent when the window has entered an activity. The window can be on more than one activity, or none: then is considered on all of them. This event will be sent when the window left an activity. If the window leaves all activities, it will be considered on all. If the window gets manually added on all activities, the server has to send activity_left for every previous activity it was in for the window to be really considered on all activities. Requests this window to be displayed in a specific output. This event will be sent when the X11 resource name of the window has changed. This is only set for XWayland windows. Tell the compositor to highlight this window. Tell the compositor to unset highlight window. The activation manager interface provides a way to get notified when an application is about to be activated. Destroy the activation manager object. The activation objects introduced by this manager object will be unaffected. Will be issued when an app is set to be activated. It offers an instance of org_ukui_activation that will tell us the app_id and the extent of the activation. Notify the compositor that the org_ukui_activation object will no longer be used. ukui-quick/platform/wayland/protocol/ukui-startup-v1.xml0000664000175000017500000000175615153755732022464 0ustar fengfeng This interface is a manager that allows to set app startup info. Sets the startup geometry of the taskbar/desktop entry for app. Coordinates are global. ukui-quick/platform/wayland/protocol/ukui-shell-v1.xml0000664000175000017500000003455015153755732022067 0ustar fengfeng This interface is used by UKUI Wayland shells to communicate with the compositor and can only be bound one time. Create a shell surface for an existing surface. Only one shell surface can be associated with a given surface. Should create ukui_surface_v1 before create xdg_surface and surface map. Request get the output under cursor per seat. Send current_output and done event after this request. Create a shell decoration for an existing surface. Only one shell decoration can be associated with a given surface. This request needs be called before surface map. Emitted after bind ukui_shell or receive get_current_output request. This event is sent after all information have been sent on binding to the ukui shell object. An interface that may be implemented by a wl_surface, for implementations that provide the shell user interface. It provides requests to set surface roles, state or set the position in global coordinates. On the server side the object is automatically destroyed when the related wl_surface is destroyed. On client side, ukui_surface_v1.destroy() must be called before destroying the wl_surface object. The ukui_surface_v1 interface is removed from the wl_surface object that was turned into a shell surface with the ukui_shell_v1.get_surface request. Move the surface to new coordinates. Coordinates are global. Setting this will say the surface prefers to not be listed in the taskbar. This request needs be called before surface map. Setting this will say the surface prefers to not be listed in the switcher. This request needs be called before surface map. Assign a role to a shell surface. The compositor handles surfaces depending on their role. This request needs be called before surface map. Set surface state. Can set multiple states at once. Values for state argument are described by ukui_surface.state and can be used together in a bitfield. The flags bitfield describes which flags are supposed to be set, the state bitfield the value for the set flags. The state argument is not a boolean value, but a bitfield, so it is possible to set multiple states at once. Only the states that are set in the flags bitfield will be changed. The keep_above and keep_below can only used for the normal window. For example: If flags was 0x12(movable|maximizable) and state was 0x02(maximizable), the window would be maximizable, but not movable. Setting this bit will indicate that the panel area not to be calculate in output usable area. Request the initial position of this surface to be under the current cursor position. This request needs be called before surface map. This request makes the created surface take an explicit keyboard grab. This keyboard grab will be dismissed when the client destroys the surface. Grab all keyboard when seat is set to null. Setting server decoration icon by icon name. Set icon name to null will use app_id to find icon. This request needs be called before surface map. This event informs the client that the position of the surface is about to change. This request makes the created surface to be activated. There is no guarantee the surface will be actually activated, and behaviour may be compositor-dependentption. Sets the startup geometry of the taskbar/desktop entry for this surface. Coordinates are global. One established way of doing this is through the UKUI_SURFACE_STARTUP_GEOMETRY environment variable of a newly launched child process. The child process should unset the environment variable again right after reading it out in order to avoid propagating it to other child processes. This request needs be called before surface map. This request is for showing tile flyout on the client side decoration. The parameters represent a mouse region, tile flyout will autohide when mouse leave the region. Coordinates are relative to the surface. Use default seat when seat is set to null. This request needs be called after surface map. This event will be sent as soon as the surface state changes. Values for state argument are described by ukui_surface_v1.surface_state. It contains the whole new state of the window. Sets the restore geometry for the surface when it exits maximized state. Coordinates are global. Clients may use this to ensure the surface restores to a contextually appropriate position.For example: - If the taskbar moves from bottom to top of screen, - the client should call this request to update its restore geometry, - so that after maximize → restore, it appears near the new taskbar location. It provides requests to set surface need to draw corner/shadow/border or not. On the server side the object is automatically destroyed when the related wl_surface is destroyed. On client side, ukui_decoration_v1.destroy() must be called before destroying the wl_surface object. Setting this will say the surface prefers not to show titlebar. This request needs be called before surface map. The flags bitfield describes which components of decoration are supposed to be set and can be set together in a bitfield. Draw decoration will be based on the flags. This flags needs be called before surface map and become immutable when surface is mapped. This request is only for xdg_popup now. ukui-quick/platform/wayland/compositor.h0000664000175000017500000000310715153755732017437 0ustar fengfeng/* * * Copyright (C) 2025, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef COMPOSITOR_H #define COMPOSITOR_H #include #include struct wl_compositor; struct wl_region; struct wl_surface; using namespace KWayland::Client; namespace UkuiQuick::WaylandClient { class Compositor : public QObject { Q_OBJECT public: explicit Compositor(QObject *parent = nullptr); ~Compositor() override; static Compositor *fromApplication(QObject *parent = nullptr); bool isValid() const; void setup(wl_compositor *compositor); void release(); void destroy(); void setEventQueue(EventQueue *queue); EventQueue *eventQueue(); wl_surface *createSurface(); wl_region *createRegion(); Q_SIGNALS: void removed(); private: class Private; QScopedPointer d; }; } #endif //COMPOSITOR_H ukui-quick/platform/wayland/ukui-blur-manager.h0000664000175000017500000000725615153755732020601 0ustar fengfeng/* * * Copyright (C) 2025, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef UKUI_BLUR_MANAGER_H #define UKUI_BLUR_MANAGER_H #include #include #include struct ukui_blur_manager_v1; struct ukui_blur_surface_v1; using namespace KWayland::Client; namespace UkuiQuick::WaylandClient { class UkuiBlur; class UkuiBlurManager : public QObject { Q_OBJECT public: /** * Creates a new UkuiBlurManager. * Note: after constructing the UkuiBlurManager it is not yet valid and one needs * to call setup. In order to get a ready to use UkuiBlurManager prefer using * Registry::createUkuiBlurManager. **/ explicit UkuiBlurManager(QObject* parent = nullptr); ~UkuiBlurManager() override; /** * @returns @c true if managing a ukui_blur_manager_v1. **/ bool isValid() const; /** * Setup this UkuiBlurManager to manage the @p manager. * When using Registry::createUkuiBlurManager there is no need to call this * method. **/ void setup(ukui_blur_manager_v1 *manager); /** * Releases the ukui_blur_manager_v1 interface. * After the interface has been released the UkuiBlurManager instance is no * longer valid and can be setup with another ukui_blur_manager_v1 interface. **/ void release(); /** * Destroys the data held by this UkuiBlurManager. * This method is supposed to be used when the connection to the Wayland * server goes away. If the connection is not valid anymore, it's not * possible to call release anymore as that calls into the Wayland * connection and the call would fail. This method cleans up the data, so * that the instance can be deleted or set up to a new ukui_blur_manager_v1 interface * once there is a new connection available. * * It is suggested to connect this method to ConnectionThread::connectionDied: * @code * connect(connection, &ConnectionThread::connectionDied, compositor, &BlurManager::destroy); * @endcode * * @see release **/ void destroy(); /** * Sets the @p queue to use for creating a Blur. **/ void setEventQueue(EventQueue *queue); /** * @returns The event queue to use for creating a Blur. **/ EventQueue *eventQueue(); UkuiBlur *getBlur(KWayland::Client::Surface *surface, QObject *parent = nullptr); Q_SIGNALS: void removed(); private: class Private; QScopedPointer d; }; class UkuiBlur : public QObject { Q_OBJECT public: ~UkuiBlur() override; void setup(ukui_blur_surface_v1 *blur); void release(); void destroy(); bool isValid() const; void setRegion(QRegion region); void setLevel(uint32_t level); void setOpacity(const qreal opacity) const; private: friend class UkuiBlurManager; explicit UkuiBlur(QObject *parent = nullptr); class Private; QScopedPointer d; }; } #endif //UKUI_BLUR_MANAGER_H ukui-quick/platform/wayland/ukui-window-management.h0000664000175000017500000001367715153755732021652 0ustar fengfeng/* * libkysdk-waylandhelper's Library * * Copyright (C) 2023, KylinSoft Co., Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see . * * Authors: Zhen Sun * */ #ifndef UKUIWINDOWMANAGEMENT_H #define UKUIWINDOWMANAGEMENT_H #include #include #include #include #include #include #include struct ukui_activation; struct ukui_activation_feedback; struct ukui_window; struct ukui_window_management; using namespace KWayland::Client; namespace UkuiQuick::WaylandClient { class UkuiWindow; class UkuiWindowManagement : public QObject { Q_OBJECT public: explicit UkuiWindowManagement(QObject *parent = nullptr); ~UkuiWindowManagement() override; bool isValid() const; void release(); void destroy(); void setup(ukui_window_management *wm); void setEventQueue(EventQueue *queue); EventQueue *eventQueue(); operator ukui_window_management *(); operator ukui_window_management *() const; bool isShowingDesktop() const; void setShowingDesktop(bool show); void showDesktop(); void hideDesktop(); QList windows() const; UkuiWindow *activeWindow() const; QVector stackingOrder() const; QVector stackingOrderUuids() const; Q_SIGNALS: void interfaceAboutToBeReleased(); void interfaceAboutToBeDestroyed(); void showingDesktopChanged(bool); void windowCreated(UkuiWindow *window); void activeWindowChanged(); void removed(); void stackingOrderChanged(); void stackingOrderUuidsChanged(); public: class Private; private: QScopedPointer d; }; class UkuiWindow : public QObject { Q_OBJECT public: ~UkuiWindow() override; void release(); void destroy(); bool isValid() const; operator ukui_window *(); operator ukui_window *() const; QString title() const; QString appId() const; quint32 virtualDesktop() const; bool isActive() const; bool isFullscreen() const; bool isKeepAbove() const; bool isKeepBelow() const; bool isMinimized() const; bool isMaximized() const; bool isOnAllDesktops() const; bool isDemandingAttention() const; bool isCloseable() const; bool isMaximizeable() const; bool isMinimizeable() const; bool isFullscreenable() const; bool skipTaskbar() const; bool skipSwitcher() const; QIcon icon() const; bool isShadeable() const; bool isShaded() const; bool isMovable() const; bool isResizable() const; bool isVirtualDesktopChangeable() const; quint32 pid() const; void requestActivate(); void requestClose(); void requestMove(); void requestResize(); void requestVirtualDesktop(quint32 desktop); void requestToggleKeepAbove(); void requestDemandAttention(); void requestToggleKeepBelow(); void requestToggleMinimized(); void requestToggleMaximized(); void setStartupGeometry(Surface *surface, const QRect &geometry); void setMinimizedGeometry(Surface *panel, const QRect &geom); void unsetMinimizedGeometry(Surface *panel); void requestToggleShaded(); quint32 internalId() const; QByteArray uuid() const; QPointer parentWindow() const; QRect geometry() const; void requestEnterVirtualDesktop(const QString &id); void requestEnterNewVirtualDesktop(); void requestLeaveVirtualDesktop(const QString &id); QStringList ukuiVirtualDesktops() const; void requestEnterActivity(const QString &id); void requestLeaveActivity(const QString &id); QStringList ukuiActivities() const; QString applicationMenuServiceName() const; QString applicationMenuObjectPath() const; void sendToOutput(Output *output) const; void setHighlight(); void unsetHightlight(); bool isHighlight(); Q_SIGNALS: void titleChanged(); void appIdChanged(); void virtualDesktopChanged(); void activeChanged(); void fullscreenChanged(); void keepAboveChanged(); void keepBelowChanged(); void minimizedChanged(); void maximizedChanged(); void onAllDesktopsChanged(); void demandsAttentionChanged(); void closeableChanged(); void minimizeableChanged(); void maximizeableChanged(); void fullscreenableChanged(); void skipTaskbarChanged(); void skipSwitcherChanged(); void iconChanged(); void shadeableChanged(); void shadedChanged(); void movableChanged(); void resizableChanged(); void modalityChanged(); void acceptFocusChanged(); void virtualDesktopChangeableChanged(); void unmapped(); void parentWindowChanged(); void geometryChanged(); void ukuiVirtualDesktopEntered(const QString &id); void ukuiVirtualDesktopLeft(const QString &id); void ukuiActivityEntered(const QString &id); void ukuiActivityLeft(const QString &id); void applicationMenuChanged(); private: friend class UkuiWindowManagement; explicit UkuiWindow(UkuiWindowManagement *parent, ukui_window *activation, quint32 internalId, const char *uuid); class Private; QScopedPointer d; }; } Q_DECLARE_METATYPE(UkuiQuick::WaylandClient::UkuiWindow *) #endif // UKUIWINDOWMANAGEMENT_H ukui-quick/platform/wayland/registry.h0000664000175000017500000003424215153756415017114 0ustar fengfeng/* SPDX-FileCopyrightText: 2014 Martin Gräßlin SPDX-FileCopyrightText: 2018 David Edmundson SPDX-FileCopyrightText: 2024 iaom SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #ifndef UKUI_QUICK_REGISTRY_H #define UKUI_QUICK_REGISTRY_H #include #include #include #include struct wl_registry; struct ukui_shell_v1; struct ukui_window_management; struct org_kde_plasma_virtual_desktop_management; struct ukui_startup_management_v1; struct ukui_blur_manager_v1; struct wl_compositor; namespace KWayland::Client { class PlasmaVirtualDesktopManagement; } namespace UkuiQuick::WaylandClient { class UkuiShell; class UkuiWindowManagement; class UkuiStartupManagement; class Compositor; class UkuiBlurManager; /** * @short Wrapper for the wl_registry interface. * * The purpose of this class is to manage the wl_registry interface. * This class supports some well-known interfaces and can create a * wrapper class for those. * * The main purpose is to emit signals whenever a new interface is * added or an existing interface is removed. For the well known interfaces * dedicated signals are emitted allowing a user to connect directly to the * signal announcing the interface it is interested in. * * To create and setup the Registry one needs to call create with either a * wl_display from an existing Wayland connection or a ConnectionThread instance: * * @code * ConnectionThread *connection; // existing connection * Registry registry; * registry.create(connection); * registry.setup(); * @endcode * * The interfaces are announced in an asynchronous way by the Wayland server. * To initiate the announcing of the interfaces one needs to call setup. **/ class Registry : public QObject { Q_OBJECT public: /** * The well-known interfaces this Registry supports. * For each of the enum values the Registry is able to create a Wrapper * object. **/ enum class Interface { Unknown, ///< Refers to an Unknown interface UkuiShell, UkuiWindowManagement, PlasmaVirtualDesktopManagement, ///< Refers to org_kde_plasma_virtual_desktop_management interface UkuiStartupManagement, UkuiBlurManager, Compositor }; explicit Registry(QObject *parent = nullptr); ~Registry() override; /** * Releases the wl_registry interface. * After the interface has been released the Registry instance is no * longer valid and can be setup with another wl_registry interface. **/ void release(); /** * Destroys the data held by this Registry. * This method is supposed to be used when the connection to the Wayland * server goes away. If the connection is not valid anymore, it's not * possible to call release anymore as that calls into the Wayland * connection and the call would fail. This method cleans up the data, so * that the instance can be deleted or set up to a new wl_registry interface * once there is a new connection available. * * It is suggested to connect this method to ConnectionThread::connectionDied: * @code * connect(connection, &ConnectionThread::connectionDied, registry, &Registry::destroy); * @endcode * * @see release **/ void destroy(); /** * Gets the registry from the @p display. **/ void create(wl_display *display); /** * Gets the registry from the @p connection. **/ void create(KWayland::Client::ConnectionThread *connection); /** * Finalizes the setup of the Registry. * After calling this method the interfaces will be announced in an asynchronous way. * The Registry must have been created when calling this method. * @see create **/ void setup(); /** * Sets the @p queue to use for this Registry. * * The EventQueue should be set before the Registry gets setup. * The EventQueue gets automatically added to all interfaces created by * this Registry. So that all objects are in the same EventQueue. * * @param queue The event queue to use for this Registry. **/ void setEventQueue(KWayland::Client::EventQueue *queue); /** * @returns The EventQueue used by this Registry **/ KWayland::Client::EventQueue *eventQueue(); /** * @returns @c true if managing a wl_registry. **/ bool isValid() const; /** * @returns @c true if the Registry has an @p interface. **/ bool hasInterface(Interface interface) const; /** * Representation of one announced interface. **/ struct AnnouncedInterface { /** * The name of the announced interface. **/ quint32 name; /** * The maximum supported version of the announced interface. **/ quint32 version; }; /** * Provides name and version for the @p interface. * * The first value of the returned pair is the "name", the second value is the "version". * If the @p interface has not been announced, both values are set to 0. * If there @p interface has been announced multiple times, the last announced is returned. * In case one is interested in all announced interfaces, one should prefer @link interfaces(Interface) @endlink. * * The returned information can be passed into the bind or create methods. * * @param interface The well-known interface for which the name and version should be retrieved * @returns name and version of the given interface * @since 5.5 **/ AnnouncedInterface interface(Interface interface) const; /** * Provides all pairs of name and version for the well-known @p interface. * * If the @p interface has not been announced, an empty vector is returned. * * The returned information can be passed into the bind or create methods. * * @param interface The well-known interface for which the name and version should be retrieved * @returns All pairs of name and version of the given interface * @since 5.5 **/ QVector interfaces(Interface interface) const; ukui_shell_v1 *bindUkuiShell(uint32_t name, uint32_t version) const; ukui_window_management *bindUkuiWindowManagement(uint32_t name, uint32_t version) const; /** * Binds the org_kde_plasma_virtual_desktop_management with @p name and @p version. * If the @p name does not exist or is not for the Plasma Virtual desktop interface, * @c null will be returned. * * Prefer using createPlasmaShell instead. * @see createPlasmaShell **/ org_kde_plasma_virtual_desktop_management *bindPlasmaVirtualDesktopManagement(uint32_t name, uint32_t version) const; /** * Binds the ukui_startup_management_v1 interface with @p name and @p version. * If the @p name does not exist or is not for the ukui startup interface, * @c null will be returned. * * @see createUkuiStartupManager **/ ukui_startup_management_v1 *bindUkuiStartupManagement(uint32_t name, uint32_t version) const; /** * Binds the wl_compositor with @p name and @p version. * If the @p name does not exist or is not for the compositor interface, * @c null will be returned. * * Prefer using createCompositor instead. * @see createCompositor **/ wl_compositor *bindCompositor(uint32_t name, uint32_t version) const; ukui_blur_manager_v1 *bindUkuiBlurManager(uint32_t name, uint32_t version) const; UkuiShell *createUkuiShell(quint32 name, quint32 version, QObject *parent = nullptr); UkuiWindowManagement *createUkuiWindowManagement(quint32 name, quint32 version, QObject *parent = nullptr); /** * Creates a PlasmaVirtualDesktopManagement and sets it up to manage the interface identified by * @p name and @p version. * * Note: in case @p name is invalid or isn't for the org_kde_plasma_virtual_desktop_management interface, * the returned VirtualDesktop will not be valid. Therefore it's recommended to call * isValid on the created instance. * * @param name The name of the org_kde_plasma_virtual_desktop_management interface to bind * @param version The version or the org_kde_plasma_virtual_desktop_management interface to use * @param parent The parent for PlasmaShell * * @returns The created PlasmaShell. **/ KWayland::Client::PlasmaVirtualDesktopManagement *createPlasmaVirtualDesktopManagement(quint32 name, quint32 version, QObject *parent = nullptr); UkuiStartupManagement *createUkuiStartupManagement(uint32_t name, uint32_t version, QObject *parent = nullptr); /** * Creates a Compositor and sets it up to manage the interface identified by * @p name and @p version. * * Note: in case @p name is invalid or isn't for the wl_compositor interface, * the returned Compositor will not be valid. Therefore it's recommended to call * isValid on the created instance. * * @param name The name of the wl_compositor interface to bind * @param version The version or the wl_compositor interface to use * @param parent The parent for Compositor * * @returns The created Compositor. **/ Compositor *createCompositor(quint32 name, quint32 version, QObject *parent = nullptr); UkuiBlurManager *createUkuiBlurManager(quint32 name, uint32_t version, QObject *parent = nullptr); /** * cast operator to the low-level Wayland @c wl_registry **/ operator wl_registry *(); /** * cast operator to the low-level Wayland @c wl_registry **/ operator wl_registry *() const; /** * @returns access to the low-level Wayland @c wl_registry **/ wl_registry *registry(); Q_SIGNALS: /** * @name Interface announced signals. **/ /** * Emitted whenever a ukui_window_management_interface interface gets announced. * @param name The name for the announced interface * @param version The maximum supported version of the announced interface **/ void ukuiWindowManagementAnnounced(quint32 name, quint32 version); /** * Emitted whenever a ukui_shell_interface interface gets announced. * @param name The name for the announced interface * @param version The maximum supported version of the announced interface **/ void ukuiShellAnnounced(quint32 name, quint32 version); /** * Emitted whenever a org_kde_plasma_virtual_desktop_management interface gets announced. * @param name The name for the announced interface * @param version The maximum supported version of the announced interface **/ void plasmaVirtualDesktopManagementAnnounced(quint32 name, quint32 version); /** * Emitted whenever a ukui_shell_interface interface gets announced. * @param name The name for the announced interface * @param version The maximum supported version of the announced interface **/ void ukuiStartupManagementAnnounced(quint32 name, quint32 version); /** * Emitted whenever a wl_compositor interface gets announced. * @param name The name for the announced interface * @param version The maximum supported version of the announced interface **/ void compositorAnnounced(quint32 name, quint32 version); void ukuiBlurManagerAnnounced(quint32 name, quint32 version); ///@} /** * @name Interface removed signals. **/ /** ** Generic announced signal which gets emitted whenever an interface gets * announced. * * This signal is emitted before the dedicated signals are handled. If one * wants to know about one of the well-known interfaces use the dedicated * signals instead. Especially the bind methods might fail before the dedicated * signals are emitted. * * @param interface The interface (e.g. wl_compositor) which is announced * @param name The name for the announced interface * @param version The maximum supported version of the announced interface **/ void interfaceAnnounced(QByteArray interface, quint32 name, quint32 version); /** * Emitted when the Wayland display is done flushing the initial interface * callbacks, announcing wl_display properties. This can be used to compress * events. Note that this signal is emitted only after announcing interfaces, * such as outputs, but not after receiving callbacks of interface properties, * such as the output's geometry, modes, etc.. * This signal is emitted from the wl_display_sync callback. **/ void interfacesAnnounced(); /** * Generic removal signal which gets emitted whenever an interface gets removed. * * This signal is emitted after the dedicated signals are handled. * * @param name The name for the removed interface **/ void interfaceRemoved(quint32 name); /** * Emitted whenever a ukui_shell interface gets removed. * @param name The name for the removed interface **/ void ukuiShellRemoved(quint32 name); /** * Emitted whenever a ukui_window_management interface gets removed. * @param name The name for the removed interface * @since 5.4 **/ void ukuiWindowManagementRemoved(quint32 name); /** * Emitted whenever a org_kde_plasma_virtual_desktop_management interface gets removed. * @param name The name for the removed interface * @since 5.52 **/ void plasmaVirtualDesktopManagementRemoved(quint32 name); /** * Emitted whenever a ukui_startup_management_v1 interface gets removed. * @param name The name for the removed interface **/ void ukuiStartupManagementRemoved(quint32 name); /** * Emitted whenever a wl_compositor interface gets removed. * @param name The name for the removed interface **/ void compositorRemoved(quint32 name); void ukuiBlurManagerRemoved(quint32 name); Q_SIGNALS: /* * Emitted when the registry has been destroyed rather than released */ void registryDestroyed(); private: class Private; QScopedPointer d; }; } #endif //UKUI_QUICK_REGISTRY_H ukui-quick/platform/wayland/wayland-integration_p.h0000664000175000017500000000263715153755732021547 0ustar fengfeng/* SPDX-FileCopyrightText: 2020 Vlad Zahorodnii SPDX-FileCopyrightText: 2024 iaom SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef WAYLAND_INTEGRATION_P_H #define WAYLAND_INTEGRATION_P_H #include #include #include "ukui-window-management.h" namespace KWayland::Client { class ConnectionThread; } namespace UkuiQuick::WaylandClient { class UkuiShell; class Registry; class UkuiBlurManager; class Compositor; } class WaylandIntegration : public QObject { Q_OBJECT public: explicit WaylandIntegration(QObject *parent = nullptr); ~WaylandIntegration() override; UkuiQuick::WaylandClient::Registry *registry() const; UkuiQuick::WaylandClient::UkuiShell *waylandUkuiShell(); UkuiQuick::WaylandClient::Compositor *waylandCompositor() const; UkuiQuick::WaylandClient::UkuiBlurManager *waylandBlurManager(); static WaylandIntegration *self(); void sync(); private: void setupKWaylandIntegration(); QPointer m_registry; QPointer m_waylandUkuiShell; QPointer m_connection; QPointer m_compositor; QPointer m_blurManager; Q_DISABLE_COPY(WaylandIntegration) }; #endif // WAYLAND_INTEGRATION_P_H ukui-quick/platform/wayland/registry.cpp0000664000175000017500000003251115153756415017444 0ustar fengfeng/* SPDX-FileCopyrightText: 2014 Martin Gräßlin SPDX-FileCopyrightText: 2018 David Edmundson SPDX-FileCopyrightText: 2024 iaom SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "registry.h" // Qt #include // wayland #include #include #include #include #include #include #include #include "wayland-pointer_p.h" #include "ukui-shell-v1.h" #include "ukui-window-management.h" #include "ukui-startup-manager.h" #include "compositor.h" #include "ukui-blur-manager.h" /***** * How to add another interface: * * define a new enum value in Registry::Interface * * define the bind method * * define the create method * * define the Announced signal * * define the Removed signal * * add a block to s_interfaces * * add the BIND macro for the new bind * * add the CREATE macro for the new create * * extend registry unit test to verify that it works ****/ namespace UkuiQuick::WaylandClient { namespace { struct SupportedInterfaceData { quint32 maxVersion; QByteArray name; const wl_interface *interface; void (Registry::*announcedSignal)(quint32, quint32); void (Registry::*removedSignal)(quint32); }; // clang-format off static const QMap s_interfaces = { {Registry::Interface::Compositor, { 4, QByteArrayLiteral("wl_compositor"), &wl_compositor_interface, &Registry::compositorAnnounced, &Registry::compositorRemoved }}, {Registry::Interface::UkuiShell, { 3, QByteArrayLiteral("ukui_shell_v1"), &ukui_shell_v1_interface, &Registry::ukuiShellAnnounced, &Registry::ukuiShellRemoved }}, {Registry::Interface::UkuiWindowManagement, { 1, QByteArrayLiteral("ukui_window_management"), &ukui_window_management_interface, &Registry::ukuiWindowManagementAnnounced, &Registry::ukuiWindowManagementRemoved }}, {Registry::Interface::PlasmaVirtualDesktopManagement, { 2, QByteArrayLiteral("org_kde_plasma_virtual_desktop_management"), &org_kde_plasma_virtual_desktop_management_interface, &Registry::plasmaVirtualDesktopManagementAnnounced, &Registry::plasmaVirtualDesktopManagementRemoved }}, {Registry::Interface::UkuiStartupManagement, { 1, QByteArrayLiteral("ukui_startup_management_v1"), &ukui_startup_management_v1_interface, &Registry::ukuiStartupManagementAnnounced, &Registry::ukuiStartupManagementRemoved }}, {Registry::Interface::UkuiBlurManager, { 2, QByteArrayLiteral("ukui_blur_manager_v1"), &ukui_blur_manager_v1_interface, &Registry::ukuiBlurManagerAnnounced, &Registry::ukuiBlurManagerRemoved }}, }; static quint32 maxVersion(const Registry::Interface &interface) { auto it = s_interfaces.find(interface); if (it != s_interfaces.end()) { return it.value().maxVersion; } return 0; } } class Q_DECL_HIDDEN Registry::Private { public: explicit Private(Registry *q); void setup(); bool hasInterface(Interface interface) const; AnnouncedInterface interface(Interface interface) const; QVector interfaces(Interface interface) const; Interface interfaceForName(quint32 name) const; template T *bind(Interface interface, uint32_t name, uint32_t version) const; template T *create(quint32 name, quint32 version, QObject *parent, WL *(Registry::*bindMethod)(uint32_t, uint32_t) const); WaylandPointer registry; static const struct wl_callback_listener s_callbackListener; WaylandPointer callback; KWayland::Client::EventQueue *queue = nullptr; private: void handleAnnounce(uint32_t name, const char *interface, uint32_t version); void handleRemove(uint32_t name); void handleGlobalSync(); static void globalAnnounce(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version); static void globalRemove(void *data, struct wl_registry *registry, uint32_t name); static void globalSync(void *data, struct wl_callback *callback, uint32_t serial); Registry *q; struct InterfaceData { Interface interface; uint32_t name; uint32_t version; }; QList m_interfaces; static const struct wl_registry_listener s_registryListener; }; Registry::Private::Private(Registry *q) : q(q) { } void Registry::Private::setup() { wl_registry_add_listener(registry, &s_registryListener, this); wl_callback_add_listener(callback, &s_callbackListener, this); } Registry::Registry(QObject *parent) : QObject(parent) , d(new Private(this)) { } Registry::~Registry() { release(); } void Registry::release() { d->registry.release(); d->callback.release(); } void Registry::destroy() { Q_EMIT registryDestroyed(); d->registry.destroy(); d->callback.destroy(); } void Registry::create(wl_display *display) { Q_ASSERT(display); Q_ASSERT(!isValid()); d->registry.setup(wl_display_get_registry(display)); d->callback.setup(wl_display_sync(display)); if (d->queue) { d->queue->addProxy(d->registry); d->queue->addProxy(d->callback); } } void Registry::create(ConnectionThread *connection) { create(connection->display()); connect(connection, &ConnectionThread::connectionDied, this, &Registry::destroy); } void Registry::setup() { Q_ASSERT(isValid()); d->setup(); } void Registry::setEventQueue(EventQueue *queue) { d->queue = queue; if (!queue) { return; } if (d->registry) { d->queue->addProxy(d->registry); } if (d->callback) { d->queue->addProxy(d->callback); } } KWayland::Client::EventQueue *Registry::eventQueue() { return d->queue; } #ifndef K_DOXYGEN const struct wl_registry_listener Registry::Private::s_registryListener = {globalAnnounce, globalRemove}; const struct wl_callback_listener Registry::Private::s_callbackListener = {globalSync}; #endif void Registry::Private::globalAnnounce(void *data, wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { auto r = reinterpret_cast(data); Q_ASSERT(registry == r->registry); r->handleAnnounce(name, interface, version); } void Registry::Private::globalRemove(void *data, wl_registry *registry, uint32_t name) { auto r = reinterpret_cast(data); Q_ASSERT(registry == r->registry); r->handleRemove(name); } void Registry::Private::globalSync(void *data, wl_callback *callback, uint32_t serial) { Q_UNUSED(serial) auto r = reinterpret_cast(data); Q_ASSERT(r->callback == callback); r->handleGlobalSync(); r->callback.release(); } void Registry::Private::handleGlobalSync() { Q_EMIT q->interfacesAnnounced(); } namespace { static Registry::Interface nameToInterface(const char *interface) { for (auto it = s_interfaces.constBegin(); it != s_interfaces.constEnd(); ++it) { if (qstrcmp(interface, it.value().name) == 0) { return it.key(); } } return Registry::Interface::Unknown; } } void Registry::Private::handleAnnounce(uint32_t name, const char *interface, uint32_t version) { Interface i = nameToInterface(interface); Q_EMIT q->interfaceAnnounced(QByteArray(interface), name, version); if (i == Interface::Unknown) { // qDebug() << "Unknown interface announced: " << interface << "/" << name << "/" << version; return; } qDebug() << "Wayland Interface: " << interface << "/" << name << "/" << version; m_interfaces.append({i, name, version}); auto it = s_interfaces.constFind(i); if (it != s_interfaces.end()) { Q_EMIT(q->*it.value().announcedSignal)(name, version); } } void Registry::Private::handleRemove(uint32_t name) { auto it = std::find_if(m_interfaces.begin(), m_interfaces.end(), [name](const InterfaceData &data) { return data.name == name; }); if (it != m_interfaces.end()) { InterfaceData data = *(it); m_interfaces.erase(it); auto sit = s_interfaces.find(data.interface); if (sit != s_interfaces.end()) { Q_EMIT(q->*sit.value().removedSignal)(data.name); } } Q_EMIT q->interfaceRemoved(name); } bool Registry::Private::hasInterface(Registry::Interface interface) const { auto it = std::find_if(m_interfaces.constBegin(), m_interfaces.constEnd(), [interface](const InterfaceData &data) { return data.interface == interface; }); return it != m_interfaces.constEnd(); } QVector Registry::Private::interfaces(Interface interface) const { QVector retVal; for (auto it = m_interfaces.constBegin(); it != m_interfaces.constEnd(); ++it) { const auto &data = *it; if (data.interface == interface) { retVal << AnnouncedInterface{data.name, data.version}; } } return retVal; } Registry::AnnouncedInterface Registry::Private::interface(Interface interface) const { const auto all = interfaces(interface); if (!all.isEmpty()) { return all.last(); } return AnnouncedInterface{0, 0}; } Registry::Interface Registry::Private::interfaceForName(quint32 name) const { auto it = std::find_if(m_interfaces.constBegin(), m_interfaces.constEnd(), [name](const InterfaceData &data) { return data.name == name; }); if (it == m_interfaces.constEnd()) { return Interface::Unknown; } return (*it).interface; } bool Registry::hasInterface(Registry::Interface interface) const { return d->hasInterface(interface); } QVector Registry::interfaces(Interface interface) const { return d->interfaces(interface); } Registry::AnnouncedInterface Registry::interface(Interface interface) const { return d->interface(interface); } // clang-format off #define BIND2(__NAME__, __INAME__, __WL__) \ __WL__ *Registry::bind##__NAME__(uint32_t name, uint32_t version) const \ { \ return d->bind<__WL__>(Interface::__INAME__, name, qMin(maxVersion(Interface::__INAME__), version)); \ } #define BIND(__NAME__, __WL__) BIND2(__NAME__, __NAME__, __WL__) BIND(UkuiShell, ukui_shell_v1) BIND(UkuiWindowManagement, ukui_window_management) BIND(PlasmaVirtualDesktopManagement, org_kde_plasma_virtual_desktop_management) BIND(UkuiStartupManagement, ukui_startup_management_v1) BIND(Compositor, wl_compositor) BIND(UkuiBlurManager, ukui_blur_manager_v1) #undef BIND #undef BIND2 template T *Registry::Private::create(quint32 name, quint32 version, QObject *parent, WL *(Registry::*bindMethod)(uint32_t, uint32_t) const) { T *t = new T(parent); t->setEventQueue(queue); t->setup((q->*bindMethod)(name, version)); QObject::connect(q, &Registry::interfaceRemoved, t, [t, name](quint32 removed) { if (name == removed) { Q_EMIT t->removed(); } }); QObject::connect(q, &Registry::registryDestroyed, t, &T::destroy); return t; } #define CREATE2(__NAME__, __BINDNAME__) \ __NAME__ *Registry::create##__NAME__(quint32 name, quint32 version, QObject *parent) \ { \ return d->create<__NAME__>(name, version, parent, &Registry::bind##__BINDNAME__); \ } #define CREATE(__NAME__) CREATE2(__NAME__, __NAME__) CREATE(UkuiShell) CREATE(UkuiWindowManagement) CREATE(PlasmaVirtualDesktopManagement) CREATE(UkuiStartupManagement) CREATE(Compositor) CREATE(UkuiBlurManager) #undef CREATE #undef CREATE2 namespace { static const wl_interface *wlInterface(Registry::Interface interface) { auto it = s_interfaces.find(interface); if (it != s_interfaces.end()) { return it.value().interface; } return nullptr; } } template T *Registry::Private::bind(Registry::Interface interface, uint32_t name, uint32_t version) const { auto it = std::find_if(m_interfaces.constBegin(), m_interfaces.constEnd(), [=](const InterfaceData &data) { return data.interface == interface && data.name == name && data.version >= version; }); if (it == m_interfaces.constEnd()) { qDebug() << "Don't have interface " << int(interface) << "with name " << name << "and minimum version" << version; return nullptr; } auto t = reinterpret_cast(wl_registry_bind(registry, name, wlInterface(interface), version)); if (queue) { queue->addProxy(t); } return t; } bool Registry::isValid() const { return d->registry.isValid(); } wl_registry *Registry::registry() { return d->registry; } Registry::operator wl_registry *() const { return d->registry; } Registry::operator wl_registry *() { return d->registry; } } ukui-quick/platform/wayland/ukui-shell-v1.cpp0000664000175000017500000002607215153755732020210 0ustar fengfeng/* * * Copyright (C) 2025, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #include "ukui-shell-v1.h" #include "wayland-pointer_p.h" #include namespace UkuiQuick::WaylandClient { class Q_DECL_HIDDEN UkuiShell::Private { public: Private(UkuiShell *q); WaylandPointer shell; EventQueue *queue = nullptr; UkuiShell *q; QString m_seatName; QString m_outputName; bool m_currentOutputReady = false; void init_listener(); static struct ukui_shell_v1_listener s_listener; static void currentOutputCallback(void *data, struct ukui_shell_v1 *ukui_shell, const char *output_name, const char *seat_name); static void doneCallback(void *data, struct ukui_shell_v1 *ukui_shell); }; ukui_shell_v1_listener UkuiShell::Private::s_listener = { .current_output = currentOutputCallback, .done = doneCallback}; class Q_DECL_HIDDEN UkuiShellSurface::Private { public: Private(UkuiShellSurface *q); ~Private(); void setup(ukui_surface_v1 *surface); WaylandPointer surface; QSize size; QPointer parentSurface; UkuiShellSurface::Role role; ukui_decoration_v1 *decoration; static UkuiShellSurface *get(Surface *surface); private: UkuiShellSurface *q; static QVector s_surfaces; }; QVector UkuiShellSurface::Private::s_surfaces; UkuiShell::UkuiShell(QObject *parent) : QObject(parent) , d(new Private(this)) { } UkuiShell::~UkuiShell() { release(); } void UkuiShell::destroy() { if (!d->shell) { return; } Q_EMIT interfaceAboutToBeDestroyed(); d->shell.destroy(); } void UkuiShell::release() { if (!d->shell) { return; } Q_EMIT interfaceAboutToBeReleased(); d->shell.release(); } void UkuiShell::setup(ukui_shell_v1 *shell) { Q_ASSERT(!d->shell); Q_ASSERT(shell); d->shell.setup(shell); d->init_listener(); } void UkuiShell::setEventQueue(EventQueue *queue) { d->queue = queue; } EventQueue *UkuiShell::eventQueue() { return d->queue; } QString UkuiShell::seatName() { if (d->m_currentOutputReady) return d->m_seatName; else return QString(); } QString UkuiShell::outputName() { if (d->m_currentOutputReady) return d->m_outputName; else return QString(); } bool UkuiShell::isCurrentOutputReady() { return d->m_currentOutputReady; } UkuiShellSurface *UkuiShell::createSurface(wl_surface *surface, QObject *parent) { Q_ASSERT(isValid()); auto kwS = Surface::get(surface); if (kwS) { if (auto s = UkuiShellSurface::Private::get(kwS)) { return s; } } UkuiShellSurface *s = new UkuiShellSurface(ukui_shell_v1_create_decoration(d->shell, surface), parent); connect(this, &UkuiShell::interfaceAboutToBeReleased, s, &UkuiShellSurface::release); connect(this, &UkuiShell::interfaceAboutToBeDestroyed, s, &UkuiShellSurface::destroy); auto w = ukui_shell_v1_create_surface(d->shell, surface); if (d->queue) { d->queue->addProxy(w); } s->setup(w); s->d->parentSurface = QPointer(kwS); return s; } UkuiShellSurface *UkuiShell::createSurface(Surface *surface, QObject *parent) { return createSurface(*surface, parent); } void UkuiShell::updateCurrentOutput() { ukui_shell_v1_get_current_output(d->shell); } bool UkuiShell::isValid() const { return d->shell.isValid(); } UkuiShell::operator ukui_shell_v1 *() { return d->shell; } UkuiShell::operator ukui_shell_v1 *() const { return d->shell; } UkuiShellSurface::Private::Private(UkuiShellSurface *q) : role(UkuiShellSurface::Role::Normal) , q(q) { s_surfaces << this; } UkuiShellSurface::Private::~Private() { s_surfaces.removeAll(this); } UkuiShellSurface *UkuiShellSurface::Private::get(Surface *surface) { if (!surface) { return nullptr; } for (auto it = s_surfaces.constBegin(); it != s_surfaces.constEnd(); ++it) { if ((*it)->parentSurface == surface) { return (*it)->q; } } return nullptr; } void UkuiShellSurface::Private::setup(ukui_surface_v1 *s) { Q_ASSERT(s); Q_ASSERT(!surface); surface.setup(s); } UkuiShellSurface::UkuiShellSurface(ukui_decoration_v1 *decoration, QObject *parent) : QObject(parent) , d(new Private(this)) { d->decoration = decoration; } UkuiShellSurface::~UkuiShellSurface() { release(); if (d->decoration) { ukui_decoration_v1_destroy(d->decoration); d->decoration = nullptr; } } void UkuiShellSurface::release() { d->surface.release(); } void UkuiShellSurface::destroy() { d->surface.destroy(); } void UkuiShellSurface::setup(ukui_surface_v1 *surface) { d->setup(surface); } UkuiShellSurface *UkuiShellSurface::get(Surface *surface) { if (auto s = UkuiShellSurface::Private::get(surface)) { return s; } return nullptr; } bool UkuiShellSurface::isValid() const { return d->surface.isValid(); } UkuiShellSurface::operator ukui_surface_v1 *() { return d->surface; } UkuiShellSurface::operator ukui_surface_v1 *() const { return d->surface; } void UkuiShellSurface::setPosition(const QPoint &point) { Q_ASSERT(isValid()); ukui_surface_v1_set_position(d->surface, point.x(), point.y()); } void UkuiShellSurface::setRole(UkuiShellSurface::Role role) { Q_ASSERT(isValid()); uint32_t wlRole = UKUI_SURFACE_V1_ROLE_NORMAL; switch (role) { case Role::Normal: wlRole = UKUI_SURFACE_V1_ROLE_NORMAL; break; case Role::Desktop: wlRole = UKUI_SURFACE_V1_ROLE_DESKTOP; break; case Role::Panel: wlRole = UKUI_SURFACE_V1_ROLE_PANEL; break; case Role::OnScreenDisplay: wlRole = UKUI_SURFACE_V1_ROLE_ONSCREENDISPLAY; break; case Role::Notification: wlRole = UKUI_SURFACE_V1_ROLE_NOTIFICATION; break; case Role::ToolTip: wlRole = UKUI_SURFACE_V1_ROLE_TOOLTIP; break; case Role::CriticalNotification: wlRole = UKUI_SURFACE_V1_ROLE_CRITICALNOTIFICATION; break; case Role::AppletPop: wlRole = UKUI_SURFACE_V1_ROLE_APPLETPOPUP; break; case Role::ScreenLock: wlRole = UKUI_SURFACE_V1_ROLE_SCREENLOCK; break; case Role::Watermark: wlRole = UKUI_SURFACE_V1_ROLE_WATERMARK; break; case Role::SystemWindow: wlRole = UKUI_SURFACE_V1_ROLE_SYSTEMWINDOW; break; case Role::InputPanel: wlRole = UKUI_SURFACE_V1_ROLE_INPUTPANEL; break; case Role::Logout: wlRole = UKUI_SURFACE_V1_ROLE_LOGOUT; break; case Role::ScreenLockNotification: wlRole = UKUI_SURFACE_V1_ROLE_SCREENLOCKNOTIFICATION; break; case Role::Switcher: wlRole = UKUI_SURFACE_V1_ROLE_SWITCHER; break; case Role::Authentication: wlRole = UKUI_SURFACE_V1_ROLE_AUTHENTICATION; break; default: Q_UNREACHABLE(); break; } ukui_surface_v1_set_role(d->surface, wlRole); d->role = role; } UkuiShellSurface::Role UkuiShellSurface::role() const { return d->role; } void UkuiShellSurface::setSkipTaskbar(bool skip) { ukui_surface_v1_set_skip_taskbar(d->surface, skip); } void UkuiShellSurface::setSkipSwitcher(bool skip) { ukui_surface_v1_set_skip_switcher(d->surface, skip); } void UkuiShellSurface::setPanelAutoHide(bool autoHide) { ukui_surface_v1_set_panel_auto_hide(d->surface, autoHide); } void UkuiShellSurface::setGrabKeyboard(wl_seat *seat) { ukui_surface_v1_grab_keyboard(d->surface, seat); } void UkuiShellSurface::setOpenUnderCursor() { ukui_surface_v1_open_under_cursor(d->surface, 0, 0); } void UkuiShellSurface::setOpenUnderCursor(int x, int y) { ukui_surface_v1_open_under_cursor(d->surface, x, y); } void UkuiShellSurface::setIconName(const QString &iconName) { if (iconName.isEmpty()) { ukui_surface_v1_set_icon(d->surface, NULL); } else { ukui_surface_v1_set_icon(d->surface, iconName.toStdString().c_str()); } } void UkuiShellSurface::setStates(WindowState::States states, bool enable) { ukui_surface_v1_set_state(d->surface, states, enable ? static_cast(states) : 0x00); } void UkuiShellSurface::setPanelTakesFocus(bool takesFocus) { ukui_surface_v1_set_state(d->surface, WindowState::Focusable, takesFocus? WindowState::Focusable : 0x00); } void UkuiShellSurface::setRemoveTitleBar(bool remove) { if (d->surface && d->decoration && d->decoration) { ukui_decoration_v1_set_no_titlebar(d->decoration, remove); } else { qDebug() << "ukui_surface_v1 is not initialized, ignoring remove titleBar request."; } } void UkuiShellSurface::setDecorationComponents(Decoration::Components components) { if (d->surface && d->decoration && d->decoration) { ukui_decoration_v1_set_decoration_components(d->decoration, components); } else { qDebug() << "ukui_surface_v1 is not initialized, ignoring set decoration components request."; } } void UkuiShellSurface::setRestoreGeometry(const QRect& restoreGeometry) { if (d->surface) { ukui_surface_v1_set_restore_geometry(d->surface, restoreGeometry.x(), restoreGeometry.y(), restoreGeometry.width(), restoreGeometry.height()); } else { qDebug() << "ukui_surface_v1 is not initialized, ignoring set restore geometry request."; } } UkuiShell::Private::Private(UkuiShell *q) : q(q) { } void UkuiShell::Private::init_listener() { ukui_shell_v1_add_listener(shell, &s_listener, this); } void UkuiShell::Private::currentOutputCallback(void *data, ukui_shell_v1 *ukui_shell, const char *output_name, const char *seat_name) { auto ukuiShell = reinterpret_cast(data); if (ukuiShell->shell != ukui_shell) return; ukuiShell->m_outputName = QString::fromUtf8(output_name); ukuiShell->m_seatName = QString::fromUtf8(seat_name); } void UkuiShell::Private::doneCallback(void *data, ukui_shell_v1 *ukui_shell) { auto ukuiShell = reinterpret_cast(data); if (ukuiShell->shell != ukui_shell) return; ukuiShell->m_currentOutputReady = true; Q_EMIT ukuiShell->q->currentOutputReady(); } }ukui-quick/platform/wayland/wayland-pointer_p.h0000664000175000017500000000336315153755732020701 0ustar fengfeng/* SPDX-FileCopyrightText: 2014 Martin Gräßlin SPDX-FileCopyrightText: 2024 iaom SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #ifndef WAYLAND_POINTER_P_H #define WAYLAND_POINTER_P_H struct wl_proxy; namespace UkuiQuick::WaylandClient { template class WaylandPointer { public: WaylandPointer() = default; WaylandPointer(Pointer *p) : m_pointer(p) { } WaylandPointer(const WaylandPointer &other) = delete; virtual ~WaylandPointer() { release(); } void setup(Pointer *pointer, bool foreign = false) { Q_ASSERT(pointer); Q_ASSERT(!m_pointer); m_pointer = pointer; m_foreign = foreign; } void release() { if (!m_pointer) { return; } if (!m_foreign) { deleter(m_pointer); } m_pointer = nullptr; } void destroy() { if (!m_pointer) { return; } if (!m_foreign) { free(m_pointer); } m_pointer = nullptr; } bool isValid() const { return m_pointer != nullptr; } operator Pointer *() { return m_pointer; } operator Pointer *() const { return m_pointer; } operator wl_proxy *() { return reinterpret_cast(m_pointer); } Pointer *operator->() { return m_pointer; } operator bool() { return isValid(); } operator bool() const { return isValid(); } private: Pointer *m_pointer = nullptr; bool m_foreign = false; }; } #endif ukui-quick/platform/wayland/ukui-shell-v1.h0000664000175000017500000000720115153755732017646 0ustar fengfeng/* * * Copyright (C) 2025, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef UKUI_SHELL_V1_H #define UKUI_SHELL_V1_H #include "wayland-ukui-shell-client-protocol.h" #include #include #include #include #include "window-type.h" struct wl_surface; struct ukui_surface; struct wl_seat; using namespace KWayland::Client; namespace UkuiQuick::WaylandClient { class UkuiShellSurface; class UkuiShell : public QObject { Q_OBJECT public: explicit UkuiShell(QObject *parent = nullptr); ~UkuiShell() override; bool isValid() const; void release(); void destroy(); void setup(ukui_shell_v1 *shell); void setEventQueue(EventQueue *queue); EventQueue *eventQueue(); QString seatName(); QString outputName(); bool isCurrentOutputReady(); UkuiShellSurface *createSurface(wl_surface *surface, QObject *parent = nullptr); UkuiShellSurface *createSurface(Surface *surface, QObject *parent = nullptr); void updateCurrentOutput(); operator ukui_shell_v1 *(); operator ukui_shell_v1 *() const; Q_SIGNALS: void interfaceAboutToBeReleased(); void interfaceAboutToBeDestroyed(); void removed(); void currentOutputReady(); private: class Private; QScopedPointer d; }; class UkuiShellSurface : public QObject { Q_OBJECT public: explicit UkuiShellSurface(ukui_decoration_v1 *decoration, QObject *parent); ~UkuiShellSurface() override; void release(); void destroy(); void setup(ukui_surface_v1 *surface); static UkuiShellSurface *get(Surface *surf); bool isValid() const; operator ukui_surface_v1 *(); operator ukui_surface_v1 *() const; enum Role { Normal, Desktop, Panel, OnScreenDisplay, Notification, ToolTip, CriticalNotification, AppletPop, ScreenLock, Watermark, SystemWindow, InputPanel, Logout, ScreenLockNotification, Switcher, Authentication, }; void setRole(Role role); Role role() const; void setPosition(const QPoint &point); void setSkipTaskbar(bool skip); void setSkipSwitcher(bool skip); void setPanelAutoHide(bool autoHide); void setGrabKeyboard(wl_seat *seat); void setOpenUnderCursor(); void setOpenUnderCursor(int x, int y); void setIconName(const QString &iconName); void setStates(WindowState::States states, bool enable); void setPanelTakesFocus(bool takesFocus); void setRemoveTitleBar(bool remove); void setDecorationComponents(Decoration::Components components); void setRestoreGeometry(const QRect& restoreGeometry); Q_SIGNALS: void autoHidePanelHidden(); void autoHidePanelShown(); private: friend class UkuiShell; class Private; QScopedPointer d; }; } #endif // UKUI_SHELL_V1_H ukui-quick/platform/autotest/0000775000175000017500000000000015153756415015277 5ustar fengfengukui-quick/platform/autotest/CMakeLists.txt0000664000175000017500000000215115153756415020036 0ustar fengfengset(CMAKE_AUTOMOC on) find_package(QT NAMES Qt6 Qt5 COMPONENTS Gui Test DBus Qml Widgets REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Gui Test DBus Qml Widgets REQUIRED) find_package(PkgConfig REQUIRED) add_executable(settings ../ukui/settings.cpp test-settings.cpp) include_directories(../ukui) target_link_libraries(settings PRIVATE Qt${QT_VERSION_MAJOR}::Test Qt${QT_VERSION_MAJOR}::DBus Qt${QT_VERSION_MAJOR}::Gui) add_test(NAME settings COMMAND settings_test) add_executable(test-theme ../ukui/ukui-theme-proxy.cpp test-theme.cpp) include_directories(../ukui) pkg_check_modules(gsettings-qt REQUIRED IMPORTED_TARGET gsettings-qt) if(${gsettings-qt_FOUND}) include_directories(${gsettings-qt_INCLUDE_DIRS}) link_directories(${gsettings-qt_LIBRARY_DIRS}) list(APPEND EXTERNAL_LIBS PkgConfig::gsettings-qt) endif() target_link_libraries(test-theme PRIVATE Qt${QT_VERSION_MAJOR}::Test Qt${QT_VERSION_MAJOR}::DBus Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Qml Qt${QT_VERSION_MAJOR}::Widgets gsettings-qt) add_test(NAME test-theme COMMAND test-theme) ukui-quick/platform/autotest/test-theme.cpp0000664000175000017500000007117715153755732020100 0ustar fengfeng/* * * Copyright (C) 2024, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: amingamingaming * */ #include #include "ukui-theme-proxy.h" #include #include #include #include using namespace UkuiQuick; #define CONTROL_CENTER_SETTING "org.ukui.control-center.personalise" #define UKUI_STYLE_SETTING "org.ukui.style" #define UKUI_STYLE_NAME_KEY "styleName" #define UKUI_STYLE_THEME_COLOR_KEY "themeColor" #define UKUI_STYLE_SYSTEM_FONT_KEY "systemFont" #define UKUI_STYLE_SYSTEM_FONT_SIZE "systemFontSize" #define UKUI_STYLE_ICON_THEME_NAME_KEY "iconThemeName" #define UKUI_STYLE_WINDOW_RADIUS_KEY "windowRadius" #define CONTROL_CENTER_TRANSPARENCY_KEY "transparency" class ukuiThemeProxy_test : public QObject { Q_OBJECT private slots: void test_font(); void test_palette(); void test_fontSize(); void test_fontFamily(); void test_themeName(); void test_themeColor(); void test_isDarkTheme(); void test_themeTransparency(); void test_layoutDirection(); void test_windowRadius(); void test_maxRadius(); void test_normalRadius(); void test_minRadius(); void test_window(); void test_windowText(); void test_base(); void test_text(); void test_alternateBase(); void test_button(); void test_buttonText(); void test_light(); void test_midLight(); void test_dark(); void test_mid(); void test_shadow(); void test_highlight(); void test_highlightedText(); void test_separator(); void test_brightText(); void test_link(); void test_linkVisited(); void test_toolTipBase(); void test_toolTipText(); void test_placeholderText(); void test_colorWithThemeTransparency(); void test_colorWithThemeTransparency_data(); void test_colorWithCustomTransparency(); void test_colorWithCustomTransparency_data(); }; void ukuiThemeProxy_test::test_font() { QCOMPARE(Theme::instance()->font(), QGuiApplication::font()); } void ukuiThemeProxy_test::test_palette() { QCOMPARE(Theme::instance()->palette(), QGuiApplication::palette()); } void ukuiThemeProxy_test::test_fontSize() { QCOMPARE(Theme::instance()->font().pointSize(), QGuiApplication::font().pointSize()); } void ukuiThemeProxy_test::test_fontFamily() { QCOMPARE(Theme::instance()->font().family(), QGuiApplication::font().family()); } void ukuiThemeProxy_test::test_themeName() { auto settings = new QGSettings(UKUI_STYLE_SETTING, QByteArray(), Theme::instance()); QString themeName = settings->get(UKUI_STYLE_NAME_KEY).toString(); QCOMPARE(Theme::instance()->themeName(), themeName); } void ukuiThemeProxy_test::test_themeColor() { auto settings = new QGSettings(UKUI_STYLE_SETTING, QByteArray(), Theme::instance()); QString themeColor = settings->get(UKUI_STYLE_THEME_COLOR_KEY).toString(); QCOMPARE(Theme::instance()->themeColor(), themeColor); } void ukuiThemeProxy_test::test_isDarkTheme() { auto settings = new QGSettings(UKUI_STYLE_SETTING, QByteArray(), Theme::instance()); QString themeName = settings->get(UKUI_STYLE_NAME_KEY).toString(); QCOMPARE(Theme::instance()->isDarkTheme(), themeName == "ukui-dark"); } void ukuiThemeProxy_test::test_themeTransparency() { auto settings = new QGSettings(CONTROL_CENTER_SETTING, QByteArray(), Theme::instance()); auto themeTransparency = settings->get(CONTROL_CENTER_TRANSPARENCY_KEY).toReal(); QCOMPARE(Theme::instance()->themeTransparency(), themeTransparency); } void ukuiThemeProxy_test::test_layoutDirection() { QCOMPARE(Theme::instance()->layoutDirection(), QGuiApplication::layoutDirection()); } void ukuiThemeProxy_test::test_windowRadius() { auto settings = new QGSettings(UKUI_STYLE_SETTING, QByteArray(), Theme::instance()); auto radius = settings->get(UKUI_STYLE_WINDOW_RADIUS_KEY).toInt(); QCOMPARE(Theme::instance()->windowRadius(), radius); } void ukuiThemeProxy_test::test_maxRadius() { auto maxRadius = qApp->style()->property("maxRadius").toInt(); QCOMPARE(Theme::instance()->maxRadius(), maxRadius); } void ukuiThemeProxy_test::test_normalRadius() { auto normalRadius = qApp->style()->property("normalRadius").toInt(); QCOMPARE(Theme::instance()->normalRadius(), normalRadius); } void ukuiThemeProxy_test::test_minRadius() { auto minRadius = qApp->style()->property("minRadius").toInt(); QCOMPARE(Theme::instance()->minRadius(), minRadius); } void ukuiThemeProxy_test::test_window() { QCOMPARE(Theme::instance()->window(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::Window)); QCOMPARE(Theme::instance()->window(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::Window)); QCOMPARE(Theme::instance()->window(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::Window)); } void ukuiThemeProxy_test::test_windowText() { QCOMPARE(Theme::instance()->windowText(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::WindowText)); QCOMPARE(Theme::instance()->windowText(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::WindowText)); QCOMPARE(Theme::instance()->windowText(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::WindowText)); } void ukuiThemeProxy_test::test_base() { QCOMPARE(Theme::instance()->base(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::Base)); QCOMPARE(Theme::instance()->base(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::Base)); QCOMPARE(Theme::instance()->base(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::Base)); } void ukuiThemeProxy_test::test_text() { QCOMPARE(Theme::instance()->text(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::Text)); QCOMPARE(Theme::instance()->text(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::Text)); QCOMPARE(Theme::instance()->text(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::Text)); } void ukuiThemeProxy_test::test_alternateBase() { QCOMPARE(Theme::instance()->alternateBase(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::AlternateBase)); QCOMPARE(Theme::instance()->alternateBase(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::AlternateBase)); QCOMPARE(Theme::instance()->alternateBase(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::AlternateBase)); } void ukuiThemeProxy_test::test_button() { QCOMPARE(Theme::instance()->button(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::Button)); QCOMPARE(Theme::instance()->button(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::Button)); QCOMPARE(Theme::instance()->button(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::Button)); } void ukuiThemeProxy_test::test_buttonText() { QCOMPARE(Theme::instance()->buttonText(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::ButtonText)); QCOMPARE(Theme::instance()->buttonText(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::ButtonText)); QCOMPARE(Theme::instance()->buttonText(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::ButtonText)); } void ukuiThemeProxy_test::test_light() { QCOMPARE(Theme::instance()->light(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::Light)); QCOMPARE(Theme::instance()->light(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::Light)); QCOMPARE(Theme::instance()->light(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::Light)); } void ukuiThemeProxy_test::test_midLight() { QCOMPARE(Theme::instance()->midLight(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::Midlight)); QCOMPARE(Theme::instance()->midLight(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::Midlight)); QCOMPARE(Theme::instance()->midLight(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::Midlight)); } void ukuiThemeProxy_test::test_dark() { QCOMPARE(Theme::instance()->dark(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::Dark)); QCOMPARE(Theme::instance()->dark(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::Dark)); QCOMPARE(Theme::instance()->dark(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::Dark)); } void ukuiThemeProxy_test::test_mid() { QCOMPARE(Theme::instance()->mid(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::Mid)); QCOMPARE(Theme::instance()->mid(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::Mid)); QCOMPARE(Theme::instance()->mid(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::Mid)); } void ukuiThemeProxy_test::test_shadow() { QCOMPARE(Theme::instance()->shadow(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::Shadow)); QCOMPARE(Theme::instance()->shadow(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::Shadow)); QCOMPARE(Theme::instance()->shadow(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::Shadow)); } void ukuiThemeProxy_test::test_highlight() { QCOMPARE(Theme::instance()->highlight(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::Highlight)); QCOMPARE(Theme::instance()->highlight(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::Highlight)); QCOMPARE(Theme::instance()->highlight(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::Highlight)); } void ukuiThemeProxy_test::test_highlightedText() { QCOMPARE(Theme::instance()->highlightedText(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::HighlightedText)); QCOMPARE(Theme::instance()->highlightedText(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::HighlightedText)); QCOMPARE(Theme::instance()->highlightedText(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::HighlightedText)); } void ukuiThemeProxy_test::test_separator() { QCOMPARE(Theme::instance()->separator(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::Window)); QCOMPARE(Theme::instance()->separator(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::Window)); QCOMPARE(Theme::instance()->separator(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::Window)); } void ukuiThemeProxy_test::test_brightText() { QCOMPARE(Theme::instance()->brightText(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::BrightText)); QCOMPARE(Theme::instance()->brightText(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::BrightText)); QCOMPARE(Theme::instance()->brightText(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::BrightText)); } void ukuiThemeProxy_test::test_link() { QCOMPARE(Theme::instance()->link(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::Link)); QCOMPARE(Theme::instance()->link(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::Link)); QCOMPARE(Theme::instance()->link(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::Link)); } void ukuiThemeProxy_test::test_linkVisited() { QCOMPARE(Theme::instance()->linkVisited(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::LinkVisited)); QCOMPARE(Theme::instance()->linkVisited(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::LinkVisited)); QCOMPARE(Theme::instance()->linkVisited(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::LinkVisited)); } void ukuiThemeProxy_test::test_toolTipBase() { QCOMPARE(Theme::instance()->toolTipBase(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::ToolTipBase)); QCOMPARE(Theme::instance()->toolTipBase(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::ToolTipBase)); QCOMPARE(Theme::instance()->toolTipBase(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::ToolTipBase)); } void ukuiThemeProxy_test::test_toolTipText() { QCOMPARE(Theme::instance()->toolTipText(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::ToolTipText)); QCOMPARE(Theme::instance()->toolTipText(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::ToolTipText)); QCOMPARE(Theme::instance()->toolTipText(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::ToolTipText)); } void ukuiThemeProxy_test::test_placeholderText() { QCOMPARE(Theme::instance()->placeholderText(Theme::Active), QGuiApplication::palette().color(QPalette::Active, QPalette::PlaceholderText)); QCOMPARE(Theme::instance()->placeholderText(Theme::Disabled), QGuiApplication::palette().color(QPalette::Disabled, QPalette::PlaceholderText)); QCOMPARE(Theme::instance()->placeholderText(Theme::Inactive), QGuiApplication::palette().color(QPalette::Inactive, QPalette::PlaceholderText)); } void ukuiThemeProxy_test::test_colorWithThemeTransparency() { QFETCH(Theme::ColorRole, ColorRole); QFETCH(Theme::ColorGroup, ColorGroup); QFETCH(QColor, result); QCOMPARE(Theme::instance()->colorWithThemeTransparency(ColorRole, ColorGroup), result); } void ukuiThemeProxy_test::test_colorWithThemeTransparency_data() { auto settings = new QGSettings(CONTROL_CENTER_SETTING, QByteArray(), Theme::instance()); auto themeTransparency = settings->get(CONTROL_CENTER_TRANSPARENCY_KEY).toReal(); QTest::addColumn("ColorRole"); QTest::addColumn("ColorGroup"); QTest::addColumn("result"); QColor colortest = QGuiApplication::palette().color(QPalette::Active, QPalette::Window); colortest.setAlphaF(themeTransparency); QTest::newRow("test0ActiveWindow")<colorWithCustomTransparency(ColorRole, ColorGroup, alphaF), result); } void ukuiThemeProxy_test::test_colorWithCustomTransparency_data() { QTest::addColumn("ColorRole"); QTest::addColumn("ColorGroup"); QTest::addColumn("alphaF"); QTest::addColumn("result"); qreal alphaF = 0.15; QColor colortest = QGuiApplication::palette().color(QPalette::Active, QPalette::Window); colortest.setAlphaF(alphaF); QTest::newRow("test0ActiveWindow")<. * * * * Authors: amingamingaming * */ #include #include "settings.h" #include #include #include using namespace UkuiQuick; static const QString STATUS_MANAGER_SERVICE = QStringLiteral("com.kylin.statusmanager.interface"); static const QString STATUS_MANAGER_PATH = QStringLiteral("/"); static const QString STATUS_MANAGER_INTERFACE = QStringLiteral("com.kylin.statusmanager.interface"); const QString USD_SERVICE = QStringLiteral("org.ukui.SettingsDaemon"); const QString USD_GLOBAL_SIGNAL_PATH = QStringLiteral("/GlobalSignal"); const QString USD_GLOBAL_SIGNAL_INTERFACE = QStringLiteral("org.ukui.SettingsDaemon.GlobalSignal"); class settings_test : public QObject { Q_OBJECT private slots: void test_liteAnimation(); void test_liteFunction(); void test_tabletMode(); void test_platformName(); }; void settings_test::test_liteAnimation() { QDBusInterface usdGlobalSignalIface(USD_SERVICE, USD_GLOBAL_SIGNAL_PATH, USD_GLOBAL_SIGNAL_INTERFACE, QDBusConnection::sessionBus(), this); QList argumentList; QDBusPendingReply replyAppList = usdGlobalSignalIface.callWithArgumentList(QDBus::Block, "getUKUILiteAnimation", argumentList); if (!replyAppList.isValid() || replyAppList.isError()) { return; } QString test = replyAppList.value(); QCOMPARE(Settings::instance()->liteAnimation(), test); } void settings_test::test_liteFunction() { QDBusInterface usdGlobalSignalIface(USD_SERVICE, USD_GLOBAL_SIGNAL_PATH, USD_GLOBAL_SIGNAL_INTERFACE, QDBusConnection::sessionBus(), this); QList argumentList; QDBusPendingReply replyAppList = usdGlobalSignalIface.callWithArgumentList(QDBus::Block, "getUKUILiteFunction", argumentList); if (!replyAppList.isValid() || replyAppList.isError()) { return; } QString test = replyAppList.value(); QCOMPARE(Settings::instance()->liteFunction(), "normal"); } void settings_test::test_tabletMode() { QDBusInterface statusManagerIface(STATUS_MANAGER_SERVICE, STATUS_MANAGER_PATH, STATUS_MANAGER_INTERFACE, QDBusConnection::sessionBus(), this); QList argumentList; QDBusPendingReply replyAppList = statusManagerIface.callWithArgumentList(QDBus::Block, "getCurrentTabletMode", argumentList); if (!replyAppList.isValid() || replyAppList.isError()) { return; } bool test = replyAppList.value(); QCOMPARE(Settings::instance()->tabletMode(), test); } void settings_test::test_platformName() { QCOMPARE(Settings::instance()->platformName(), QGuiApplication::platformName()); } QTEST_MAIN(settings_test) #include "test-settings.moc" ukui-quick/platform/abstract-window-manager.h0000664000175000017500000000665715153755732020337 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef UKUI_PANEL_ABSTRACT_WINDOW_MANAGER_H #define UKUI_PANEL_ABSTRACT_WINDOW_MANAGER_H #include #include namespace UkuiQuick { class AbstractWindowManager : public QObject { Q_OBJECT public: explicit AbstractWindowManager(QObject* parent=nullptr): QObject(parent){} virtual ~AbstractWindowManager() = default; virtual QStringList windows() = 0; virtual QIcon windowIcon(const QString &wid) = 0; virtual QString windowTitle(const QString &wid) = 0; virtual bool skipTaskBar(const QString &wid) = 0; virtual QString windowGroup(const QString &wid) = 0; virtual bool isMaximizable(const QString& wid) = 0; virtual bool isMaximized(const QString& wid) = 0; virtual void maximizeWindow(const QString& wid) = 0; virtual bool isMinimizable(const QString& wid) = 0; virtual bool isMinimized(const QString& wid) = 0; virtual void minimizeWindow(const QString& wid) = 0; virtual bool isKeepAbove(const QString& wid) = 0; virtual void keepAboveWindow(const QString& wid) = 0; virtual bool isOnAllDesktops(const QString& wid) = 0; virtual bool isOnCurrentDesktop(const QString& wid) = 0; virtual void activateWindow(const QString& wid) = 0; virtual QString currentActiveWindow() = 0; virtual void closeWindow(const QString& wid) = 0; virtual void restoreWindow(const QString& wid) = 0; virtual bool isDemandsAttention(const QString& wid) = 0; virtual quint32 pid(const QString& wid) = 0; virtual QString appId(const QString& wid) = 0; virtual QRect geometry(const QString& wid) = 0; virtual void setStartupGeometry(const QString& wid, QQuickItem *item) = 0; virtual void setMinimizedGeometry(const QString& wid, QQuickItem *item) = 0; virtual void unsetMinimizedGeometry(const QString& wid, QQuickItem *item) = 0; virtual void activateWindowView(const QStringList &wids); virtual bool showingDesktop() = 0; virtual void setShowingDesktop(bool showing) = 0; virtual bool isFullscreen(const QString& wid) = 0; Q_SIGNALS: void currentDesktopChanged(); void windowDesktopChanged(QString wid); void windowAdded(QString wid); void windowRemoved(QString wid); void titleChanged(QString wid); void iconChanged(QString wid); void skipTaskbarChanged(QString wid); void onAllDesktopsChanged(QString wid); void demandsAttentionChanged(QString wid); void geometryChanged(QString wid); void activeWindowChanged(QString id); void maximizedChanged(QString wid); void fullscreenChanged(QString wid); //xcb only void windowStateChanged(QString wid); }; } #endif //UKUI_PANEL_ABSTRACT_WINDOW_MANAGER_H ukui-quick/platform/wm-impl-wayland.h0000664000175000017500000000355015153755732016623 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_WM_IMPL_WAYLAND_H #define UKUI_QUICK_WM_IMPL_WAYLAND_H #include "wm-interface.h" #include "wayland-integration_p.h" #include "ukui-shell-v1.h" namespace UkuiQuick { class WMImplWayland : public WMInterface { Q_OBJECT public: explicit WMImplWayland(QWindow *window); QPoint position() const override; void setPosition(const QPoint &point) override; void setWindowType(WindowType::Type type) override; void setSkipTaskBar(bool skip) override; void setSkipSwitcher(bool skip) override; void setRemoveTitleBar(bool remove) override; void setPanelAutoHide(bool autoHide) override; void setPanelTakesFocus(bool takesFocus) override; QScreen* currentScreen() override; void setDecorationComponents(Decoration::Components components) override; protected: bool eventFilter(QObject *watched, QEvent *event) override; private: void initSurface(); inline void destroySurface(); private: QPoint m_pos; std::unique_ptr m_shellSurface; bool m_useUkuiShellIntegration = false; }; } // UkuiQuick #endif //UKUI_QUICK_WM_IMPL_WAYLAND_H ukui-quick/platform/abstract-window-manager.cpp0000664000175000017500000000276015153755732020661 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #include "abstract-window-manager.h" #include #include #include namespace UkuiQuick { static const QString HIGHLIGHT_WINDOW_SERVICE = QStringLiteral("org.kde.KWin.HighlightWindow"); static const QString HIGHLIGHT_WINDOW_PATH = QStringLiteral("/org/kde/KWin/HighlightWindow"); static const QString &HIGHLIGHT_WINDOW_INTERFACE = HIGHLIGHT_WINDOW_SERVICE; void AbstractWindowManager::activateWindowView(const QStringList &wids) { auto message = QDBusMessage::createMethodCall(HIGHLIGHT_WINDOW_SERVICE, HIGHLIGHT_WINDOW_PATH, HIGHLIGHT_WINDOW_INTERFACE, QStringLiteral("highlightWindows")); message << wids; QDBusConnection::sessionBus().asyncCall(message); } }ukui-quick/platform/ukui-quick-platform.pc.in0000664000175000017500000000047315153755732020276 0ustar fengfengprefix=/usr exec_prefix=${prefix} libdir=${prefix}/lib/@CMAKE_LIBRARY_ARCHITECTURE@ includedir=${prefix}/include/ukui-quick/platform Name: ukui-quick-platform Description: ukui-quick-platform header files URL: https://www.ukui.org/ Version: @VERSION@ Cflags: -I${includedir} Libs: -L${libdir} -lukui-quick-platformukui-quick/platform/glinfo-query.cpp0000664000175000017500000002455115153755732016564 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: sundagao * */ #include #include #include #include #include #include #include #include "glinfo-query.h" /*************************** * EGLInfoQuery **************************/ namespace UkuiQuick { class EGLInfoQuery { public: using EglPlatformDisplayFunc = PFNEGLGETPLATFORMDISPLAYEXTPROC; enum Platform { X11, Wayland }; EGLInfoQuery(const QString &env, Platform platform = Platform::Wayland); ~EGLInfoQuery(); QString glRenderDevice(); bool isValid(); private: bool m_isValid = false; EGLDisplay m_eglDisplay = EGL_NO_DISPLAY; EGLContext m_eglContext = EGL_NO_CONTEXT; EglPlatformDisplayFunc m_eglPlatformDisplayFunc = nullptr; }; EGLInfoQuery::EGLInfoQuery(const QString &env, Platform platform) { void *nativeDisplay = nullptr; EGLenum eglPlatform = EGL_PLATFORM_WAYLAND_EXT; if (platform == Platform::Wayland) { nativeDisplay = reinterpret_cast(wl_display_connect(nullptr)); eglPlatform = EGL_PLATFORM_WAYLAND_EXT; } else if (platform == Platform::X11) { nativeDisplay = reinterpret_cast(XOpenDisplay(nullptr)); eglPlatform = EGL_PLATFORM_X11_EXT; } //获取egl display m_eglPlatformDisplayFunc = reinterpret_cast( eglGetProcAddress(reinterpret_cast("eglGetPlatformDisplayEXT"))); if (nativeDisplay != EGL_DEFAULT_DISPLAY && m_eglPlatformDisplayFunc) { m_eglDisplay = m_eglPlatformDisplayFunc(eglPlatform, nativeDisplay, nullptr); } else { m_eglDisplay = eglGetDisplay(reinterpret_cast(nativeDisplay)); } if (m_eglDisplay == EGL_NO_DISPLAY) { qWarning() << "[" << env << "] : Get egl display failed ."; return; } if (!eglInitialize(m_eglDisplay, nullptr, nullptr)) { qWarning() << "[" << env << "] : Init egl failed ."; eglTerminate(m_eglDisplay); return; } // Bind the GL API (try desktop GL first) if (!eglBindAPI(EGL_OPENGL_API)) { qWarning() << "[" << env << "] : eglBindAPI(EGL_OPENGL_API) failed, trying EGL_OPENGL_ES_API ."; if (!eglBindAPI(EGL_OPENGL_ES_API)) { qWarning() << "[" << env << "] : eglBindAPI(EGL_OPENGL_ES_API) also failed ."; eglTerminate(m_eglDisplay); return; } } EGLint config_attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE }; EGLConfig config; EGLint num_configs; if (!eglChooseConfig(m_eglDisplay, config_attribs, &config, 1, &num_configs) || num_configs < 1) { qWarning() << "[" << env << "] : egl choose config failed ."; eglTerminate(m_eglDisplay); return; } // Create context: try desktop GL 3.0+, then fallback to 2.0 / ES EGLint ctx_attribs_gl3[] = { EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 0, EGL_NONE }; EGLint ctx_attribs_gl2[] = { EGL_CONTEXT_MAJOR_VERSION, 2, EGL_NONE }; EGLint ctx_attribs_es2[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; m_eglContext = eglCreateContext(m_eglDisplay, config, EGL_NO_CONTEXT, ctx_attribs_gl3); if (m_eglContext == EGL_NO_CONTEXT) { m_eglContext = eglCreateContext(m_eglDisplay, config, EGL_NO_CONTEXT, ctx_attribs_gl2); } if (m_eglContext == EGL_NO_CONTEXT) { m_eglContext = eglCreateContext(m_eglDisplay, config, EGL_NO_CONTEXT, ctx_attribs_es2); } if (m_eglContext == EGL_NO_CONTEXT) { qWarning() << "[" << env << "] : create egl conttext failed ."; eglTerminate(m_eglDisplay); return; } if (!eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, m_eglContext)) { qWarning() << "[" << env << "] : Active egl conttext failed ."; eglDestroyContext(m_eglDisplay, m_eglContext); eglTerminate(m_eglDisplay); return ; } m_isValid = true; } EGLInfoQuery::~EGLInfoQuery() { if (m_isValid) { eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext(m_eglDisplay, m_eglContext); eglTerminate(m_eglDisplay); } } /*! 获取当前的渲染设备 */ QString EGLInfoQuery::glRenderDevice() { return QString::fromUtf8(reinterpret_cast(glGetString(GL_RENDERER))); } /*! 表示 EGLInfoQuery 是否可以有效查询 renderer 信息。 为 true 表示提供的是有效值。 */ bool EGLInfoQuery::isValid() { return m_isValid; } /*************************** * GLXInfoQuery **************************/ class GLXInfoQuery { public: // 函数指针, 用于查询对应glx 扩展对应属性的值。在准备好glx 上下文后调用有效 using QueryInteger = PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC; GLXInfoQuery(); ~GLXInfoQuery(); QString glRenderDevice(); QVariant isHardwareRendering(); bool isValid(); private: bool m_isVaild = false; QueryInteger m_queryInteger{nullptr}; Display* m_display{nullptr}; GLXContext m_glxContext{nullptr}; GLXFBConfig* m_glxFbConfigs{nullptr}; }; GLXInfoQuery::GLXInfoQuery() { m_display = XOpenDisplay(nullptr); if (!m_display) { qWarning() << "Open display failed ."; return; } // 选择FBConfig int fbAttribs[] = { GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, GLX_DOUBLEBUFFER, False, None }; int numConfigs = 0; int screen = DefaultScreen(m_display); m_glxFbConfigs = glXChooseFBConfig(m_display, screen, fbAttribs, &numConfigs); if (!m_glxFbConfigs || numConfigs == 0) { qWarning() << "Get glx fb config failed ."; return; } // 创建上下文 m_glxContext = glXCreateNewContext(m_display, m_glxFbConfigs[0], GLX_RGBA_TYPE, nullptr, True); if (!m_glxContext) { XFree(m_glxFbConfigs); qWarning() << "Create glx context failed ."; return; } Window m_window = XCreateSimpleWindow(m_display, RootWindow(m_display, screen), 0, 0, 1, 1, 0, 0, 0); // 激活上下文 if (!glXMakeCurrent(m_display, m_window, m_glxContext)) { qWarning() << "Active glx context failed ."; return; } m_isVaild = true; #ifdef GLX_MESA_query_renderer m_queryInteger = reinterpret_cast(glXGetProcAddressARB( reinterpret_cast("glXQueryCurrentRendererIntegerMESA"))); #endif } GLXInfoQuery::~GLXInfoQuery() { if (m_isVaild) { if (m_glxContext) { glXDestroyContext(m_display, m_glxContext); } XSync(m_display, true); XCloseDisplay(m_display); } } /*! 获取当前的渲染设备 */ QString GLXInfoQuery::glRenderDevice() { return QString::fromUtf8(reinterpret_cast(glGetString(GL_RENDERER))); } /*! 是否支持硬件渲染, 需要先检查是否有效,再使用返回值。 */ QVariant GLXInfoQuery::isHardwareRendering() { unsigned int isHardWareAccelerated = 0; if (m_queryInteger && m_queryInteger(GLX_RENDERER_ACCELERATED_MESA, &isHardWareAccelerated)) { return QVariant(static_cast(isHardWareAccelerated)); } return QVariant(QVariant::Invalid); } /*! 表示 GLXInfoQuery 是否可以有效查询 renderer 信息。 为 true 表示提供的是有效值。 */ bool GLXInfoQuery::isValid() { return m_isVaild; } /*************************** * GLInfoQuery **************************/ GLInfoQuery::GLInfoQuery() { QByteArray env = qgetenv("XDG_SESSION_TYPE"); if (env == "wayland") { m_eglQuery.reset(new EGLInfoQuery(QString::fromUtf8(env), EGLInfoQuery::Platform::Wayland)); } else { env = qgetenv("DISPLAY"); if (!env.isEmpty()) { m_glxQuery.reset(new GLXInfoQuery()); if (!(m_glxQuery && m_glxQuery->isValid())) { m_eglQuery.reset(new EGLInfoQuery(QString::fromUtf8(env), EGLInfoQuery::Platform::X11)); } } } } GLInfoQuery::~GLInfoQuery() { } QString GLInfoQuery::glRenderDevice() { if (m_eglQuery && m_eglQuery->isValid()) { return m_eglQuery->glRenderDevice(); } if (m_glxQuery && m_glxQuery->isValid()) { return m_glxQuery->glRenderDevice(); } qWarning() << "GLInfoQuery::glRenderDevice() failed."; return QString(); } bool GLInfoQuery::isHardwareRendering() { //dbus QDBusReply registeredReply = QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("com.kylin.Wlcom")); if (registeredReply.isValid() && registeredReply.value()) { QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("com.kylin.Wlcom"), QStringLiteral("/com/kylin/Wlcom/Renderer"), QStringLiteral("com.kylin.Wlcom.Renderer"), QStringLiteral("IsHardwareRendering")); QDBusReply reply = QDBusConnection::sessionBus().call(msg); if(reply.isValid()) { return reply.value(); } } //glx if (m_glxQuery && m_glxQuery->isValid()) { QVariant isHardwareRendering = m_glxQuery->isHardwareRendering(); if (isHardwareRendering.isValid()) { return isHardwareRendering.toBool(); } } qWarning() << "GLInfoQuery::isHardwareRendering: failed to retrieve from dbus and glx, return true"; return true; } }ukui-quick/platform/ukui/0000775000175000017500000000000015167355777014417 5ustar fengfengukui-quick/platform/ukui/ukui-theme-proxy.cpp0000664000175000017500000002753415153755732020360 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "ukui-theme-proxy.h" #include #include #include #include #define CONTROL_CENTER_SETTING "org.ukui.control-center.personalise" #define CONTROL_CENTER_TRANSPARENCY_KEY "transparency" #define UKUI_STYLE_SETTING "org.ukui.style" #define UKUI_STYLE_NAME_KEY "styleName" #define UKUI_WIDGET_THEME_NAME "widgetThemeName" #define UKUI_STYLE_THEME_COLOR_KEY "themeColor" #define UKUI_STYLE_SYSTEM_FONT_KEY "systemFont" #define UKUI_STYLE_SYSTEM_FONT_SIZE "systemFontSize" #define UKUI_STYLE_ICON_THEME_NAME_KEY "iconThemeName" #define UKUI_STYLE_WINDOW_RADIUS_KEY "windowRadius" namespace UkuiQuick { class ThemePrivate { public: explicit ThemePrivate(Theme *parent); void initTransparency(); void initStyleSetting(); void initThemeRadius(); void initLayoutDirection(); Theme *q {nullptr}; QFont font; QPalette palette; Qt::LayoutDirection layoutDirection; qreal transparency {1.0}; QString themeName {"ukui-light"}; QString themeColor {"daybreakBlue"}; int maxRadius = 0; int normalRadius = 0; int minRadius = 0; int windowRadius = 0; }; ThemePrivate::ThemePrivate(Theme *parent) : q(parent), font(QGuiApplication::font()), palette(QGuiApplication::palette()), layoutDirection(QGuiApplication::layoutDirection()) { initTransparency(); initStyleSetting(); initThemeRadius(); } void ThemePrivate::initTransparency() { const QByteArray id(CONTROL_CENTER_SETTING); if (QGSettings::isSchemaInstalled(id)) { auto settings = new QGSettings(id, QByteArray(), q); QStringList keys = settings->keys(); if (keys.contains(CONTROL_CENTER_TRANSPARENCY_KEY)) { transparency = settings->get(CONTROL_CENTER_TRANSPARENCY_KEY).toReal(); } QObject::connect(settings, &QGSettings::changed, q, [this, settings] (const QString &key) { if (key == CONTROL_CENTER_TRANSPARENCY_KEY) { transparency = settings->get(key).toReal(); Q_EMIT q->themeTransparencyChanged(); } }); } } void ThemePrivate::initStyleSetting() { const QByteArray id(UKUI_STYLE_SETTING); if (QGSettings::isSchemaInstalled(id)) { auto settings = new QGSettings(id, QByteArray(), q); QStringList keys = settings->keys(); if (keys.contains(UKUI_STYLE_NAME_KEY)) { themeName = settings->get(UKUI_STYLE_NAME_KEY).toString(); } if (keys.contains(UKUI_STYLE_THEME_COLOR_KEY)) { themeColor = settings->get(UKUI_STYLE_THEME_COLOR_KEY).toString(); } if (keys.contains(UKUI_STYLE_WINDOW_RADIUS_KEY)) { windowRadius = settings->get(UKUI_STYLE_WINDOW_RADIUS_KEY).toInt(); } QObject::connect(settings, &QGSettings::changed, q, [this, settings] (const QString &key) { if (key == UKUI_STYLE_NAME_KEY || key == UKUI_WIDGET_THEME_NAME) { themeName = settings->get(UKUI_STYLE_NAME_KEY).toString(); initThemeRadius(); Q_EMIT q->themeRadiusChanged(); Q_EMIT q->themeNameChanged(); Q_EMIT q->paletteChanged(); return; } if (key == UKUI_STYLE_THEME_COLOR_KEY) { themeColor = settings->get(key).toString(); Q_EMIT q->themeColorChanged(); Q_EMIT q->paletteChanged(); return; } if (key == UKUI_STYLE_ICON_THEME_NAME_KEY) { Q_EMIT q->iconThemeChanged(); } if (key == UKUI_STYLE_WINDOW_RADIUS_KEY) { windowRadius = settings->get(UKUI_STYLE_WINDOW_RADIUS_KEY).toInt(); Q_EMIT q->windowRadiusChanged(); } }); } } void ThemePrivate::initThemeRadius() { // QApplication程序 if (qobject_cast(qApp)) { maxRadius = qApp->style()->property("maxRadius").toInt(); normalRadius = qApp->style()->property("normalRadius").toInt(); minRadius = qApp->style()->property("minRadius").toInt(); } else { maxRadius = 8; normalRadius = 6; minRadius = 4; } } // ====== Theme ====== // Theme *Theme::qmlAttachedProperties(QObject *object) { Q_UNUSED(object) return Theme::instance(); } Theme *Theme::instance() { static Theme theme; return &theme; } Theme::Theme(QObject *parent) : QObject(parent), d(new ThemePrivate(this)) { qRegisterMetaType("QFont"); qRegisterMetaType("QPalette"); qRegisterMetaType("Theme::ColorRole"); qRegisterMetaType("Theme::ColorGroup"); connect(qGuiApp, &QGuiApplication::fontChanged, this, [this] (const QFont &font) { d->font = font; Q_EMIT fontChanged(); }); connect(qGuiApp, &QGuiApplication::paletteChanged, this, [this] (const QPalette &palette) { d->palette = palette; Q_EMIT paletteChanged(); }); connect(qGuiApp, &QGuiApplication::layoutDirectionChanged, this, [this] (const Qt::LayoutDirection &layoutDirection) { d->layoutDirection = layoutDirection; Q_EMIT layoutDirectionChanged(); }); } Theme::~Theme() { if (d) { delete d; d = nullptr; } } QFont Theme::font() const { return d->font; } QPalette Theme::palette() const { return d->palette; } QColor Theme::color(Theme::ColorRole role, Theme::ColorGroup group) const { switch (role) { default: case Window: return window(group); case WindowText: return windowText(group); case Base: return base(group); case Text: return text(group); case AlternateBase: return alternateBase(group); case Button: return button(group); case ButtonText: return buttonText(group); case Light: return light(group); case MidLight: return midLight(group); case Dark: return dark(group); case Mid: return mid(group); case Shadow: return shadow(group); case Highlight: return highlight(group); case HighlightedText: return highlightedText(group); case BrightText: return brightText(group); case Link: return link(group); case LinkVisited: return linkVisited(group); case ToolTipBase: return toolTipBase(group); case ToolTipText: return toolTipText(group); case PlaceholderText: return placeholderText(group); } } QColor Theme::color(Theme::ColorRole role, Theme::ColorGroup group, qreal alpha) const { if (alpha < 0 || alpha > 1) { return colorWithThemeTransparency(role, group); } return colorWithCustomTransparency(role, group, alpha); } QColor Theme::colorWithThemeTransparency(Theme::ColorRole role, Theme::ColorGroup group) const { QColor c = Theme::color(role, group); c.setAlphaF(d->transparency); return c; } QColor Theme::colorWithCustomTransparency(Theme::ColorRole role, Theme::ColorGroup group, qreal alphaF) const { QColor c = Theme::color(role, group); c.setAlphaF(alphaF < 0 ? 0 : alphaF > 1 ? 1 : alphaF); return c; } QColor Theme::window(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::Window); } QColor Theme::windowText(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::WindowText); } QColor Theme::text(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::Text); } QColor Theme::base(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::Base); } QColor Theme::alternateBase(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::AlternateBase); } QColor Theme::button(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::Button); } QColor Theme::buttonText(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::ButtonText); } QColor Theme::light(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::Light); } QColor Theme::midLight(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::Midlight); } QColor Theme::dark(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::Dark); } QColor Theme::mid(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::Mid); } QColor Theme::shadow(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::Shadow); } QColor Theme::highlight(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::Highlight); } QColor Theme::highlightedText(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::HighlightedText); } QColor Theme::separator(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::Window); } QColor Theme::brightText(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::BrightText); } QColor Theme::link(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::Link); } QColor Theme::linkVisited(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::LinkVisited); } QColor Theme::toolTipBase(ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::ToolTipBase); } QColor Theme::toolTipText(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::ToolTipText); } QColor Theme::placeholderText(Theme::ColorGroup group) const { return d->palette.color(switchColorGroup(group), QPalette::PlaceholderText); } inline QPalette::ColorGroup Theme::switchColorGroup(Theme::ColorGroup group) const { switch (group) { default: case Active: return QPalette::Active; case Disabled: return QPalette::Disabled; case Inactive: return QPalette::Inactive; } } qreal Theme::fontSize() const { return d->font.pointSize(); } QString Theme::fontFamily() const { return d->font.family(); } QString Theme::themeName() const { return d->themeName; } QString Theme::themeColor() const { return d->themeColor; } bool Theme::isDarkTheme() const { return d->themeName == "ukui-dark"; } qreal Theme::themeTransparency() const { return d->transparency; } int Theme::maxRadius() const { return d->maxRadius; } int Theme::normalRadius() const { return d->normalRadius; } int Theme::minRadius() const { return d->minRadius; } int Theme::windowRadius() const { return d->windowRadius; } Qt::LayoutDirection Theme::layoutDirection() const { return d->layoutDirection; } } // UkuiQuick ukui-quick/platform/ukui/dbus-connector.cpp0000664000175000017500000000562715153756415020047 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #include "dbus-connector.h" #include #include #include #include namespace UkuiQuick { class DbusConnectorPrivate { public: DbusConnectorPrivate(const QString& service , const QString& path , const QString& interface , QDBusConnection conn) : m_service(service) , m_path(path) , m_interface(interface) , m_conn(std::move(conn)) { } QString m_service; QString m_path; QString m_interface; QDBusConnection m_conn; }; DbusConnector::DbusConnector(const QString &service, const QString& path, const QString& interface, QDBusConnection conn, QObject* parent) : QThread(parent), d(new DbusConnectorPrivate(service, path, interface, conn)) { } DbusConnector::~DbusConnector() { quit(); wait(); if (d) { delete d; d = nullptr; } } void DbusConnector::run() { auto createAndValidateInterface = [this]() -> QDBusInterface* { auto dbus(new QDBusInterface(d->m_service, d->m_path, d->m_interface, d->m_conn)); if (dbus->isValid()) { qWarning() << "DbusConnector: dbus connection success!"; dbus->moveToThread(QCoreApplication::instance()->thread()); return dbus; } qWarning() << "DBus connection failed:" << dbus->lastError().message(); delete dbus; return nullptr; }; qDebug() << "DbusConnector begin init dbus interface" << d->m_service << d->m_path << d->m_interface; if (auto dbus = createAndValidateInterface()) { Q_EMIT ready(dbus); return; } auto watcher = new QDBusServiceWatcher(d->m_service, d->m_conn, QDBusServiceWatcher::WatchForRegistration); connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, [this, createAndValidateInterface]() { qDebug() << "DbusConnector: service registered, begin init dbus interface" << d->m_service << d->m_path << d->m_interface; if (auto dbus = createAndValidateInterface()) { Q_EMIT ready(dbus); } sender()->deleteLater(); this->quit(); }); exec(); } }ukui-quick/platform/ukui/settings.cpp0000664000175000017500000002155015153755732016754 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #include "settings.h" #include "glinfo-query.h" #include #include #include #include #include #include #include #include static UkuiQuick::Settings *g_settings = nullptr; static std::once_flag onceFlag; static std::once_flag rendererFlag; const QString USD_SERVICE = QStringLiteral("org.ukui.SettingsDaemon"); const QString USD_GLOBAL_SIGNAL_PATH = QStringLiteral("/GlobalSignal"); const QString USD_GLOBAL_SIGNAL_INTERFACE = QStringLiteral("org.ukui.SettingsDaemon.GlobalSignal"); const QString UKUI_LITE_CHANGED = QStringLiteral("UKUILiteChanged"); static const QString STATUS_MANAGER_SERVICE = QStringLiteral("com.kylin.statusmanager.interface"); static const QString STATUS_MANAGER_PATH = QStringLiteral("/"); static const QString STATUS_MANAGER_INTERFACE = QStringLiteral("com.kylin.statusmanager.interface"); static const QString MODE_CHANGE_SIGNAL = QStringLiteral("mode_change_signal"); static const QStringList SPECIAL_RENDERER_DEVICES {"ZX C-1190", "Phytium FTG340"}; namespace UkuiQuick { class SettingsPrivate : public QObject { Q_OBJECT public: explicit SettingsPrivate(QObject *parent = nullptr); void updateLiteLevel(const QVariantMap &map); void updateTabletMode(bool tabletMode); void refresh(); QString m_liteAnimation = QStringLiteral("normal"); QString m_liteFunction = QStringLiteral("normal"); bool m_isTabletMode = false; static bool s_verticalSync; static bool s_isHardWareRendering; QDBusInterface *m_usdGlobalSignalIface = nullptr; QDBusInterface *m_statusManagerIface = nullptr; Settings *q = nullptr; }; bool SettingsPrivate::s_isHardWareRendering = true; bool SettingsPrivate::s_verticalSync = true; SettingsPrivate::SettingsPrivate(QObject *parent) : QObject(parent) { q = qobject_cast(parent); refresh(); } void SettingsPrivate::updateLiteLevel(const QVariantMap &map) { if(map.find(QStringLiteral("ukui-lite-animation")) != map.constEnd()) { m_liteAnimation = map.value(QStringLiteral("ukui-lite-animation")).toString(); Q_EMIT q->liteAnimationChanged(); } if(map.find(QStringLiteral("ukui-lite-function")) != map.constEnd()) { m_liteFunction = map.value(QStringLiteral("ukui-lite-function")).toString(); Q_EMIT q->liteFunctionChanged(); } } void SettingsPrivate::updateTabletMode(bool tabletMode) { if(m_isTabletMode != tabletMode) { m_isTabletMode = tabletMode; Q_EMIT q->tabletModeChanged(); } } void SettingsPrivate::refresh() { //ukui lite if(!m_usdGlobalSignalIface) { m_usdGlobalSignalIface = new QDBusInterface(USD_SERVICE, USD_GLOBAL_SIGNAL_PATH, USD_GLOBAL_SIGNAL_INTERFACE, QDBusConnection::sessionBus(), this); } if(!m_usdGlobalSignalIface->isValid()) { qWarning() << "Init USD interface error:" << m_usdGlobalSignalIface->lastError(); } else { QDBusPendingCall animationCall = m_usdGlobalSignalIface->asyncCall(QStringLiteral("getUKUILiteLevel")); connect(new QDBusPendingCallWatcher(animationCall, this), &QDBusPendingCallWatcher::finished, [&](QDBusPendingCallWatcher *call = nullptr){ QDBusPendingReply reply = *call; if (reply.isError()) { qWarning() << "USD interface error" << reply.error(); } else { updateLiteLevel(reply.value()); } call->deleteLater(); }); if(!QDBusConnection::sessionBus().connect(USD_SERVICE, USD_GLOBAL_SIGNAL_PATH, USD_GLOBAL_SIGNAL_INTERFACE, UKUI_LITE_CHANGED, this, SLOT(updateLiteLevel(const QVariantMap &map)))) { qWarning() << "USD interface error, connect " << UKUI_LITE_CHANGED << "failed!"; } } //tablet mode if(!m_statusManagerIface) { m_statusManagerIface = new QDBusInterface(STATUS_MANAGER_SERVICE, STATUS_MANAGER_PATH, STATUS_MANAGER_INTERFACE, QDBusConnection::sessionBus(), this); } if(!m_statusManagerIface->isValid()) { qWarning() << "Init kylin status manager interface error:" << m_statusManagerIface->lastError(); } else { QDBusPendingCall tabletCall = m_statusManagerIface->asyncCall(QStringLiteral("get_current_tabletmode")); connect(new QDBusPendingCallWatcher(tabletCall, this), &QDBusPendingCallWatcher::finished, [&](QDBusPendingCallWatcher *call = nullptr){ QDBusPendingReply reply = *call; if (reply.isError()) { qWarning() << "Kylin status manager interface error" << reply.error(); } else { updateTabletMode(reply.value()); } call->deleteLater(); }); if(!QDBusConnection::sessionBus().connect(STATUS_MANAGER_SERVICE, STATUS_MANAGER_PATH, STATUS_MANAGER_INTERFACE, MODE_CHANGE_SIGNAL, this, SLOT(updateTabletMode(bool)))) { qWarning() << "Kylin status manager interface error, connect " << MODE_CHANGE_SIGNAL << "failed!"; } } } Settings *Settings::instance() { std::call_once(onceFlag, [ & ] { g_settings = new Settings(); }); return g_settings; } UkuiQuick::Settings::Settings(QObject *parent) : QObject(parent), d(new SettingsPrivate(this)) { refresh(); } QString UkuiQuick::Settings::liteAnimation() { return d->m_liteAnimation; } QString UkuiQuick::Settings::liteFunction() { return d->m_liteFunction; } bool Settings::tabletMode() { return d->m_isTabletMode; } void Settings::refresh() { d->refresh(); } QString Settings::platformName() { return QGuiApplication::platformName(); } /*! 获取渲染设备信息,是否支持垂直同步和硬件加速。 程序启动之前会初始化完成对应信息,需要在 QGuiAppliction 实例化之后的操作不要放在当前函 首选配置文件获取禁用垂直同步的渲染设备列表,没有配置文件则使用默认列表 SPECIAL_RENDERER_DEVICES 配置文件:/etc/ukui/ukui-quick/disable-vertical-sync.json 格式: \code { "RendererDevice":["111111111", "llvmpipe (LLVM 17.0.6, 256 bits)"] } \endcode */ bool Settings::getRendererInfo() { auto getConfig = [](const QString &filepath) -> QJsonObject { QFile file(filepath); if (!file.exists() || !file.open(QIODevice::ReadOnly)) { return QJsonObject(); } QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); file.close(); if (error.error != QJsonParseError::NoError) { return QJsonObject(); } if (!doc.isObject()) { return QJsonObject(); } return doc.object(); }; GLInfoQuery glInfoQuery; const QString renderDevice = glInfoQuery.glRenderDevice(); auto config = getConfig(QStringLiteral("/etc/ukui/ukui-quick/disable-vertical-sync.json")); if (!config.isEmpty() && config.contains(QStringLiteral("RendererDevice"))) { const QStringList& specialRendererDevice = config.value(QStringLiteral("RendererDevice")).toVariant().toStringList(); SettingsPrivate::s_verticalSync = !specialRendererDevice.contains(renderDevice); qWarning() << "The rendering device comes from the configuration file. RendererDevice : " << specialRendererDevice; } else { SettingsPrivate::s_verticalSync = !SPECIAL_RENDERER_DEVICES.contains(renderDevice); } SettingsPrivate::s_isHardWareRendering = glInfoQuery.isHardwareRendering(); return true; }; bool Settings::verticalSync() { std::call_once(rendererFlag, [ & ] { Settings::getRendererInfo(); }); return SettingsPrivate::s_verticalSync; } bool Settings::isHardWareRendering() { std::call_once(rendererFlag, [ & ] { Settings::getRendererInfo(); }); return SettingsPrivate::s_isHardWareRendering; } } #include "settings.moc"ukui-quick/platform/ukui/DtThemeDefault.qml0000775000175000017500000005007715153755732017773 0ustar fengfengimport QtQml 2.15 import QtQuick 2.15 import org.ukui.quick.platform 1.0 DtThemeDefinition { id: dtTheme //QPalette 颜色 property color windowTextActive : kFontPrimary property color windowTextInactive : kFontSecondaryDisable property color windowTextDisable : kFontPrimaryDisable property color buttonActive : kGray3 property color buttonInactive : kGray3 property color buttonDisable : Qt.rgba(0, 0, 0, 0.45) property color lightActive : kGray0 property color lightInactive : kGray0 property color lightDisable : Qt.rgba(255/255, 255/255, 255/255, 0.45) property color midLightActive : kGray7 property color midLightInactive : kGray7 property color midLightDisable : Qt.rgba(204/255, 204/255, 204/255, 0.42) property color darkActive : kBlack property color darkInactive : kBlack property color darkDisable : kBlack property color midActive : kGray13 property color midInactive : kGray13 property color midDisable : Qt.rgba(102/255, 102/255, 102/255, 0.4) property color textActive : kFontPrimary property color textInactive : kFontPrimary property color textDisable : kFontPrimaryDisable property color brightTextActive : kFontStrong property color brightTextInactive : kFontStrong property color brightTextDisable : Qt.rgba(0, 0, 0, 0.45) property color buttonTextActive : kFontPrimary property color buttonTextInactive : kFontPrimary property color buttonTextDisable : kFontPrimaryDisable property color baseActive : kGray0 property color baseInactive : kGray1 property color baseDisable : kGray1 property color windowActive : kGray2 property color windowInactive : kGray3 property color windowDisable : kGray3 property color shadowActive : Qt.rgba(0, 0, 0, 0.25) property color shadowInactive : Qt.rgba(0, 0, 0, 0.18) property color shadowDisable : Qt.rgba(0, 0, 0, 0.11) property color highlightActive : kBrandNormal property color highlightInactive : kBrandNormal property color highlightDisable : kBrandDisable property color highlightedTextActive : kFontWhite property color highlightedTextInactive : kFontWhite property color highlightedTextDisable : kFontWhiteDisable property color linkActive : Qt.rgba(46/255, 95/255, 209/255, 1) property color linkInactive : Qt.rgba(46/255, 95/255, 209/255, 1) property color linkDisable : Qt.rgba(46/255, 95/255, 209/255, 0.4) property color linkVisitedActive : Qt.rgba(120/255, 48/255, 191/255, 1) property color linkVisitedInactive : Qt.rgba(120/255, 48/255, 191/255, 1) property color linkVisitedDisable : Qt.rgba(120/255, 48/255, 191/255, 0.4) property color alternateBaseActive : Qt.rgba(0, 0, 0, 0.03) property color alternateBaseInactive : Qt.rgba(0, 0, 0, 0.01) property color alternateBaseDisable : Qt.rgba(0, 0, 0, 0.01) property color noRoleActive : kGray1 property color noRoleInactive : kGray1 property color noRoleDisable : Qt.rgba(250/255, 250/255, 250/255, 0.42) property color toolTipBaseActive : kGray0 property color toolTipBaseInactive : kGray0 property color toolTipBaseDisable : Qt.rgba(255/255, 255/255, 255/255, 0.4) property color toolTipTextActive : kFontPrimary property color toolTipTextInactive : kFontPrimary property color toolTipTextDisable : kFontPrimaryDisable property color placeholderTextActive : kFontPlaceholderText property color placeholderTextInactive : kFontPlaceholderText property color placeholderTextDisable : kFontPlaceholderTextDisable //中性色 property color kWhite : Qt.rgba(255/255, 255/255, 255/255, 1) property color kBlack : Qt.rgba(0/255, 0/255, 0/255, 1) property color kGray0 : Qt.rgba(255/255, 255/255, 255/255, 1) property color kGray1 : Qt.rgba(250/255, 250/255, 250/255, 1) property color kGray2 : Qt.rgba(245/255, 245/255, 245/255, 1) property color kGray3 : Qt.rgba(240/255, 240/255, 240/255, 1) property color kGray4 : Qt.rgba(235/255, 235/255, 235/255, 1) property color kGray5 : Qt.rgba(229/255, 229/255, 229/255, 1) property color kGray6 : Qt.rgba(224/255, 224/255, 224/255, 1) property color kGray7 : Qt.rgba(214/255, 214/255, 214/255, 1) property color kGray8 : Qt.rgba(199/255, 199/255, 199/255, 1) property color kGray9 : Qt.rgba(184/255, 184/255, 184/255, 1) property color kGray10 : Qt.rgba(163/255, 163/255, 163/255, 1) property color kGray11 : Qt.rgba(143/255, 143/255, 143/255, 1) property color kGray12 : Qt.rgba(122/255, 122/255, 122/255, 1) property color kGray13 : Qt.rgba(102/255, 102/255, 102/255, 1) property color kGray14 : Qt.rgba(82/255, 82/255, 82/255, 1) property color kGray15 : Qt.rgba(61/255, 61/255, 61/255, 1) property color kGray16 : Qt.rgba(41/255, 41/255, 41/255, 1) property color kGray17 : Qt.rgba(26/255, 26/255, 26/255, 1) property color kGrayAlpha0 : Qt.rgba(0, 0, 0, 0) property color kGrayAlpha1 : Qt.rgba(0, 0, 0, 0.04) property color kGrayAlpha2 : Qt.rgba(0, 0, 0, 0.06) property color kGrayAlpha3 : Qt.rgba(0, 0, 0, 0.8) property color kGrayAlpha4 : Qt.rgba(0, 0, 0, 0.12) property color kGrayAlpha5 : Qt.rgba(0, 0, 0, 0.16) property color kGrayAlpha6 : Qt.rgba(0, 0, 0, 0.24) property color kGrayAlpha7 : Qt.rgba(0, 0, 0, 0.3) property color kGrayAlpha8 : Qt.rgba(0, 0, 0, 0.38) property color kGrayAlpha9 : Qt.rgba(0, 0, 0, 0.42) property color kGrayAlpha10 : Qt.rgba(0, 0, 0, 0.55) property color kGrayAlpha11 : Qt.rgba(0, 0, 0, 0.9) property color kGrayAlpha12 : Qt.rgba(0, 0, 0, 0.02) property color kGrayAlpha13 : Qt.rgba(0, 0, 0, 0.75) //文本颜色 property color kFontStrong : Qt.rgba(0, 0, 0, 1) property color kFontPrimary : Qt.rgba(0, 0, 0, 0.88) property color kFontPrimaryDisable : Qt.rgba(0, 0, 0, 0.28) property color kFontSecondary : Qt.rgba(0, 0, 0, 0.4) property color kFontSecondaryDisable : Qt.rgba(0, 0, 0, 0.15) property color kFontWhite : Qt.rgba(255/255, 255/255, 255/255, 0.98) property color kFontWhiteDisable : Qt.rgba(255/255, 255/255, 255/255, 0.68) property color kFontWhiteSecondary : Qt.rgba(255/255, 255/255, 255/255, 0.65) property color kFontWhiteSecondaryDisable : Qt.rgba(255/255, 255/255, 255/255, 0.24) //功能色 property color kBrandNormal : Qt.rgba(54/255, 118/255, 245/255, 1) property var kBrandHover : GradientColor { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0.1) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0.1) gradientBackground : Qt.rgba(54/255, 118/255, 245/255, 1) } property var kBrandClick : GradientColor { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0.2) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0.2) gradientBackground : Qt.rgba(54/255, 118/255, 245/255, 1) } property color kBrandFocus : kGrayAlpha13 property color kSuccessNormal : kGreen1 property var kSuccessHover : GradientColor { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0.1) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0.1) gradientBackground : Qt.rgba(24/255, 168/255, 13/255, 1) } property var kSuccessClick : GradientColor { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0.2) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0.2) gradientBackground : Qt.rgba(24/255, 168/255, 13/255, 1) } property color kWarningNormal : kOrange1 property var kWarningHover : GradientColor { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0.1) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0.1) gradientBackground : Qt.rgba(255/255, 128/255, 31/255, 1) } property var kWarningClick : GradientColor { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0.2) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0.2) gradientBackground : Qt.rgba(255/255, 128/255, 31/255, 1) } property color kErrorNormal : kRed1 property var kErrorHover : GradientColor { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0.1) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0.1) gradientBackground : Qt.rgba(245/255, 73/255, 73/255, 1) } property var kErrorClick : GradientColor { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0.2) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0.2) gradientBackground : Qt.rgba(245/255, 73/255, 73/255, 1) } property color kBrand1 : Qt.rgba(55/255, 144/255, 250/255, 1) property color kBrand2 : Qt.rgba(137/255, 38/255, 235/255, 1) property color kBrand3 : Qt.rgba(228/255, 74/255, 232/255, 1) property color kBrand4 : Qt.rgba(242/255, 181/255, 39/255, 1) property color kBrand5 : Qt.rgba(20, 166/255, 33/255, 1) property color kBrand6 : Qt.rgba(240/255, 73/255, 67/255, 1) property color kBrand7 : Qt.rgba(250/255, 125/255, 15/255, 1) //其他颜色 property color kContainHover : kGray2 property color kContainClick : kGray4 property color kContainGeneralNormal : kGray0 property color kContainSecondaryNormal : kGray1 property color kContainSecondaryAlphaNormal : kGrayAlpha12 property color kComponentNormal : kGray3 property color kComponentHover : kGray4 property color kComponentClick : kGray6 property color kComponentDisable : Qt.rgba(245/255, 245/255, 245/255, 0.45) property color kComponentAlphaNormal : kGrayAlpha1 property color kComponentAlphaHover : kGrayAlpha3 property color kComponentAlphaClick : kGrayAlpha4 property color kComponentAlphaDisable : Qt.rgba(0, 0, 0, 0.02) property color kLineWindowActive : Qt.rgba(255/255, 255/255, 255/255, 0.7) property color kLineComponentNormal : kGrayAlpha2 property color kLineComponentHover : kGrayAlpha3 property color kLineComponentClick : kGrayAlpha4 property color kLineComponentDisable : Qt.rgba(0, 0, 0, 0.03) property color kLineBrandNormal : Qt.rgba(54/255, 118/255, 245/255, 0.55) property var kLineBrandHover : { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0.1) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0.1) gradientBackground : Qt.rgba(54/255, 118/255, 245/255, 0.55) } property var kLineBrandClick : { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0.2) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0.2) gradientBackground : Qt.rgba(54/255, 118/255, 245/255, 0.55) } property color kLineBrandDisable : Qt.rgba(54/255, 118/255, 245/255, 0.23) property color kModalMask : KGrayAlpha6 //2025/01/02根据主题补充token property color kSuccessDisable: Qt.rgba(24/255, 168/255, 13/255, 0.45) property color kErrorDisable: Qt.rgba(245/255, 73/255, 73/255, 0.45) property color kWarningDisable: Qt.rgba(255/255, 128/255, 31/255, 0.45) property color kLineNormal: Qt.rgba(0, 0, 0, 0.06) property color kContainGeneralAlphaNormal: kGrayAlpha0 property color kLineWindowInactive: Qt.rgba(255/255, 255/255, 255/255, 0.4) property color kContainAlphaClick: kGrayAlpha3 property color kContainAlphaHover: kGrayAlpha1 property color kBrandDisable: Qt.rgba(54/255, 118/255, 245/255, 0.4) property color kLineDisable: Qt.rgba(0, 0, 0, 0.03) property color kDivider: kGrayAlpha3 //2025/03/03根据5.0主题新增token,默认值为V11主题颜色 property color kBrand8: Qt.rgba(54/255, 118/255, 245/255, 1) property color kBrand9: Qt.rgba(6/255, 192/255, 199/255, 1) property color kLineSelectboxNormal: Qt.rgba(0/255, 0/255, 0/255, 0.2) property color kLineSelectboxHover: Qt.rgba(0/255, 0/255, 0/255, 0.24) property color kLineSelectboxClick: Qt.rgba(0/255, 0/255, 0/255, 0.28) property color kLineSelectboxSelected: Qt.rgba(0/255, 0/255, 0/255, 0) property color kLineSelectboxDisable: Qt.rgba(0/255, 0/255, 0/255, 0.08) property color kFontWhitePlaceholderTextDisable: Qt.rgba(255/255, 255/255, 255/255, 0.13) property color kContainGeneralInactive: kGray1 property color kFontPlaceholderTextDisable: Qt.rgba(0/255, 0/255, 0/255, 0.12) property color kFontWhitePlaceholderText: Qt.rgba(255/255, 255/255, 255/255, 0.35) property color kLineInputDisable: Qt.rgba(0/255, 0/255, 0/255, 0.04) property color kLineDock: Qt.rgba(255/255, 255/255, 255/255, 0.55) property color kLineMenu: Qt.rgba(255/255, 255/255, 255/255, 0.7) property color kLineTable: Qt.rgba(0/255, 0/255, 0/255, 0.12) property color kLineInputNormal: Qt.rgba(0/255, 0/255, 0/255, 0.08) property color kLineInputHover: Qt.rgba(0/255, 0/255, 0/255, 0.12) property color kLineInputClick: kBrand8 property color kOrange1: Qt.rgba(255/255, 128/255, 31/255, 1) property color kGreen1: Qt.rgba(24/255, 168/255, 13/255, 1) property color kRed1: Qt.rgba(235/255, 70/255, 70/255, 1) property color kDividerWhite: Qt.rgba(255/255, 255/255, 255/255, 0.12) property color kLineSuccessNormal: Qt.rgba(24/255, 168/255, 13/255, 0.55) property var kLineSuccessHover : GradientColor { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0.1) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0.1) gradientBackground : Qt.rgba(24/255, 168/255, 13/255, 0.55) } property var kLineSuccessClick : GradientColor { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0.2) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0.2) gradientBackground : Qt.rgba(24/255, 168/255, 13/255, 0.55) } property color kLineSuccessDisable: Qt.rgba(24/255, 168/255, 13/255, 0.23) property color kLineErrorNormal: Qt.rgba(245/255, 73/255, 73/255, 0.55) property var kLineErrorHover : GradientColor { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0.1) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0.1) gradientBackground : Qt.rgba(245/255, 73/255, 73/255, 0.55) } property var kLineErrorClick : GradientColor { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0.2) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0.2) gradientBackground : Qt.rgba(245/255, 73/255, 73/255, 0.55) } property color kLineWarningNormal: Qt.rgba(255/255, 128/255, 31/255, 0.55) property var kLineWarningHover : GradientColor { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0.1) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0.1) gradientBackground : Qt.rgba(255/255, 128/255, 31/255, 0.55) } property var kLineWarningClick : GradientColor { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0.2) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0.2) gradientBackground : Qt.rgba(255/255, 128/255, 31/255, 0.55) } property color kLineWarningDisable: Qt.rgba(255/255, 128/255, 31/255, 0.23) property var kShadowPrimaryActive : ShadowData { xOffset: 0 yOffset: 0 blurRadius: 4 spread: 0 shadowColor: Qt.rgba(0/255, 0/255, 0/255, 0.2) isMultiShadow: true childShadow: ShadowData { xOffset: 0 yOffset: 18 blurRadius: 38 spread: 0 shadowColor: Qt.rgba(0/255, 0/255, 0/255, 0.3) isMultiShadow: false } } property var kShadowPrimaryInactive : ShadowData { xOffset: 0 yOffset: 0 blurRadius: 2 spread: 0 shadowColor: Qt.rgba(0/255, 0/255, 0/255, 0.2) isMultiShadow: true childShadow: ShadowData { xOffset: 0 yOffset: 8 blurRadius: 16 spread: 0 shadowColor: Qt.rgba(0/255, 0/255, 0/255, 0.18) isMultiShadow: false } } property var kShadowSecondaryActive : ShadowData { xOffset: 0 yOffset: 0 blurRadius: 2 spread: 0 shadowColor: Qt.rgba(0/255, 0/255, 0/255, 0.1) isMultiShadow: true childShadow: ShadowData { xOffset: 0 yOffset: 12 blurRadius: 26 spread: 0 shadowColor: Qt.rgba(0/255, 0/255, 0/255, 0.25) isMultiShadow: false } } property var kShadowSecondaryInactive : ShadowData { xOffset: 0 yOffset: 0 blurRadius: 2 spread: 0 shadowColor: Qt.rgba(0/255, 0/255, 0/255, 0.1) isMultiShadow: true childShadow: ShadowData { xOffset: 0 yOffset: 6 blurRadius: 13 spread: 0 shadowColor: Qt.rgba(0/255, 0/255, 0/255, 0.18) isMultiShadow: false } } property var kShadowMenu : ShadowData { xOffset: 0 yOffset: 0 blurRadius: 1 spread: 0 shadowColor: Qt.rgba(0/255, 0/255, 0/255, 0.06) isMultiShadow: true childShadow: ShadowData { xOffset: 0 yOffset: 6 blurRadius: 14 spread: 0 shadowColor: Qt.rgba(0/255, 0/255, 0/255, 0.2) isMultiShadow: false } } property var kShadowComponent : ShadowData { xOffset: 0 yOffset: 1 blurRadius: 2 spread: 0 shadowColor: Qt.rgba(0/255, 0/255, 0/255, 0.1) isMultiShadow: false } property var kShadowMin : ShadowData { xOffset: 0 yOffset: 2 blurRadius: 6 spread: 0 shadowColor: Qt.rgba(0/255, 0/255, 0/255, 0.15) isMultiShadow: false } //2025.05.14添加 property color kComponentSelectedNormal : kGray3 property color kComponentSelectedHover: kGray4 property color kComponentSelectedClick: kGray6 property color kComponentSelectedDisable : Qt.rgba(224/255, 224/255, 224/255, 0.45) property color kComponentSelectedAlphaNormal: kGrayAlpha3 property color kComponentSelectedAlphaHover: kGrayAlpha4 property color kComponentSelectedAlphaClick: kGrayAlpha5 property color kComponentSelectedAlphaDisable: Qt.rgba(0, 0, 0, 0.04) property color kComponentInput: kWhite property color kComponentInputAlpha: Qt.rgba(255/255, 255/255, 255/255, 0.5) //2025.06.25添加 property color kSelectAlphaWhiteDisable : Qt.rgba(255/255, 255/255, 255/255, 0.45) property var kSelectAlphaWhiteHover : GradientColor { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0.1) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0.1) gradientBackground : Qt.rgba(255/255, 255/255, 255/255, 1) } property var kSelectAlphaWhiteClick : GradientColor { gradientDeg : 0 gradientStart : Qt.rgba(0/255, 0/255, 0/255, 0.2) gradientEnd : Qt.rgba(0/255, 0/255, 0/255, 0.2) gradientBackground : Qt.rgba(255/255, 255/255, 255/255, 1) } property color kMenu : kGray1 property color kLineErrorDisable : Qt.rgba(245/255, 73/255, 73/255, 0.23) property color kContainSecondaryInactive : kGray2 property color kSelectAlphaWhite : kGray0 property color kFontPlaceholderText : Qt.rgba(0, 0, 0, 0.28) //描边 property int normalLine : 1 property int focusLine : 2 //圆角 property int kRadiusMin : 4 property int kRadiusNormal : 6 property int kRadiusMax : 8 property int kRadiusMenu : 8 property int kRadiusWindow : 12 //间距 property int kMarginMin : 4 property int kMarginNormal : 8 property int kMarginBig : 16 property int kMarginWindow : 24 property int kMarginComponent : 40 //边距 property int kPaddingMinLeft : 8 property int kPaddingMinTop : 2 property int kPaddingMinRight : 8 property int kPaddingMinBottom : 2 property int kPaddingNormalLeft : 16 property int kPaddingNormalTop : 2 property int kPaddingNormalRight : 16 property int kPaddingNormalBottom : 2 property int kPadding8Left : 8 property int kPadding8Top : 8 property int kPadding8Right : 8 property int kPadding8Bottom : 8 property int kPaddingWindowLeft : 24 property int kPaddingWindowTop : 16 property int kPaddingWindowRight : 24 property int kPaddingWindowBottom : 24 } ukui-quick/platform/ukui/screen-area-utils.h0000664000175000017500000000244515153755732020106 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: youdiansaodongxi * */ #ifndef SCREENAREAUTILS_H #define SCREENAREAUTILS_H #include #include #include namespace UkuiQuick { class ScreenAreaUtilsPrivate; class ScreenAreaUtils : public QObject { Q_OBJECT public: static ScreenAreaUtils *instance(); ~ScreenAreaUtils(); QRect getAvailableGeometry(QScreen* screen); Q_SIGNALS: void availableGeometryChanged(); private: explicit ScreenAreaUtils(QObject *parent = nullptr); void initPanelSetting(); ScreenAreaUtilsPrivate *d = nullptr; }; } #endif // SCREENAREAUTILS_H ukui-quick/platform/ukui/dt-theme.cpp0000664000175000017500000017705715153756415016640 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #include "dt-theme.h" #include #include #include #include #include #include #include #include #include "ukui-theme-proxy.h" static QHash g_dtThemes; namespace UkuiQuick { #define COLOR_TOKEN_GETTER(token) \ GradientColor* DtTheme::token() const { return &(d->m_##token); } #define INT_TOKEN_GETTER(token) \ int DtTheme::token() const { return d->m_##token; } #define SHADOW_TOKEN_GETTER(token) \ ShadowData* DtTheme::token() const { return &(d->m_##token); } #define COLOR_TOKEN_SETTER(token) \ void DtTheme::token##Set(QVariant value) \ { \ GradientColor* gra = value.value();\ if(gra == nullptr) { \ QColor color = value.value(); \ if(d->m_##token != color) { \ d->m_##token.setGradientDeg(0); \ d->m_##token.setGradientStart(QColor(0,0,0,0)); \ d->m_##token.setGradientEnd(QColor(0,0,0,0)); \ d->m_##token.setGradientBackground(QColor(0,0,0,0)); \ d->m_##token.setPureColor(color); \ d->m_##token.setColorType(DtColorType::Pure);\ Q_EMIT token##Changed(); \ } \ } else { \ if(d->m_##token != gra) { \ d->m_##token.setGradientDeg(gra->gradientDeg());\ d->m_##token.setGradientStart(gra->gradientStart());\ d->m_##token.setGradientEnd(gra->gradientEnd());\ d->m_##token.setGradientBackground(gra->gradientBackground());\ d->m_##token.setPureColor(QColor(0,0,0,0)); \ d->m_##token.setColorType(DtColorType::Gradient);\ Q_EMIT token##Changed(); \ } \ } \ } #define INT_TOKEN_SETTER(token) \ void DtTheme::token##Set(QVariant value) \ { \ if(d->m_##token != value.value()) { \ d->m_##token = value.value(); \ Q_EMIT token##Changed(); \ } \ } #define SHADOW_TOKEN_SETTER(token) \ void DtTheme::token##Set(QVariant value) \ { \ ShadowData* data = value.value();\ if(d->m_##token != *data) { \ d->m_##token.setXOffset(data->xOffset()); \ d->m_##token.setYOffset(data->yOffset()); \ d->m_##token.setBlurRadius(data->blurRadius()); \ d->m_##token.setSpread(data->spread()); \ d->m_##token.setIsMultiShadow(data->isMultiShadow());\ d->m_##token.setShadowColor(data->shadowColor());\ d->m_##token.setChildShadow(data->childShadow());\ Q_EMIT token##Changed(); \ } \ } #define UKUI_STYLE_SETTING "org.ukui.style" #define UKUI_STYLE_NAME_KEY "widgetThemeName" #define UKUI_STYLE_COLOR_KEY "styleName" #define UKUI_STYLE_FONT_SIZE_KEY "systemFontSize" #define UKUI_STYLE_FONT_KEY "systemFont" #define UKUI_STYLE_ICON_THEME_NAME_KEY "iconThemeName" #define UKUI_STYLE_THEME_COLOR "themeColor" #define UKUI_STYLE_WINDOW_RADIUS "windowRadius" #define CONTROL_CENTER_SETTING "org.ukui.control-center.personalise" #define CONTROL_CENTER_TRANSPARENCY_KEY "transparency" class ThemeGlobalConfigPrivate; class ThemeGlobalConfigPrivate { public: QString m_themeName = "default"; QString m_styleColor = "Light"; qreal m_transparency = 1.0; qreal m_fontSize = 11.0; QString m_fontFamily = "Noto Sans CJK SC"; Qt::LayoutDirection layoutDirection = QGuiApplication::layoutDirection(); QString m_iconThemeName = "ukui-icon-theme-default"; QString m_themeColor = "KBrand1"; QString m_windowRadius = "bigRadius"; }; ThemeGlobalConfig* ThemeGlobalConfig::instance() { static ThemeGlobalConfig* instance = nullptr; if(!instance) { instance = new ThemeGlobalConfig(); } return instance; } ThemeGlobalConfig::~ThemeGlobalConfig() { if (d) { delete d; d = nullptr; } } qreal ThemeGlobalConfig::transparency() const { return d->m_transparency; } QString ThemeGlobalConfig::themeName() const { return d->m_themeName; } QString ThemeGlobalConfig::styleColor() const { return d->m_styleColor; } QString ThemeGlobalConfig::themeColor() const { return d->m_themeColor; } QString ThemeGlobalConfig::windowRadius() const { return d->m_windowRadius; } QString ThemeGlobalConfig::iconThemeName() const { return d->m_iconThemeName; } Qt::LayoutDirection ThemeGlobalConfig::layoutDirection() const { return d->layoutDirection; } bool ThemeGlobalConfig::isDarkTheme() const { return d->m_styleColor == "Dark"; } qreal ThemeGlobalConfig::fontSize() const { return d->m_fontSize; } QString ThemeGlobalConfig::fontFamily() const { return d->m_fontFamily; } ThemeGlobalConfig::ThemeGlobalConfig() : d(new ThemeGlobalConfigPrivate()) { QByteArray id(CONTROL_CENTER_SETTING); if (QGSettings::isSchemaInstalled(id)) { auto settings = new QGSettings(id, QByteArray(), this); QStringList keys = settings->keys(); if (keys.contains(CONTROL_CENTER_TRANSPARENCY_KEY)) { d->m_transparency = settings->get(CONTROL_CENTER_TRANSPARENCY_KEY).toReal(); } connect(settings, &QGSettings::changed, this, [this, settings] (const QString &key) { if (key == CONTROL_CENTER_TRANSPARENCY_KEY) { d->m_transparency = settings->get(key).toReal(); Q_EMIT transparencyChanged(); } }); } id = UKUI_STYLE_SETTING; if (QGSettings::isSchemaInstalled(id)) { auto settings = new QGSettings(id, QByteArray(), this); QStringList keys = settings->keys(); if (keys.contains(UKUI_STYLE_NAME_KEY)) { d->m_themeName = settings->get(UKUI_STYLE_NAME_KEY).toString(); } if (keys.contains(UKUI_STYLE_COLOR_KEY)) { auto color = settings->get(UKUI_STYLE_COLOR_KEY).toString(); if(color == "ukui-light" || color == "ukui-white") { d->m_styleColor = "Light"; } else if (color == "ukui-dark" || color == "ukui-black") { d->m_styleColor = "Dark"; } } if (keys.contains(UKUI_STYLE_FONT_SIZE_KEY)) { d->m_fontSize = settings->get(UKUI_STYLE_FONT_SIZE_KEY).toReal(); } if (keys.contains(UKUI_STYLE_FONT_KEY)) { d->m_fontFamily = settings->get(UKUI_STYLE_FONT_KEY).toString(); } if (keys.contains(UKUI_STYLE_THEME_COLOR)) { d->m_themeColor = settings->get(UKUI_STYLE_THEME_COLOR).toString(); } if (keys.contains(UKUI_STYLE_WINDOW_RADIUS)) { d->m_windowRadius = settings->get(UKUI_STYLE_WINDOW_RADIUS).toString(); } if (keys.contains(UKUI_STYLE_ICON_THEME_NAME_KEY)) { d->m_iconThemeName = settings->get(UKUI_STYLE_ICON_THEME_NAME_KEY).toString(); } QObject::connect(settings, &QGSettings::changed, this, [this, settings] (const QString &key) { if (key == UKUI_STYLE_NAME_KEY) { if(d->m_themeName != settings->get(key).toString()) { d->m_themeName = settings->get(key).toString(); Q_EMIT themeNameChanged(); } } else if (key == UKUI_STYLE_COLOR_KEY) { auto color = settings->get(UKUI_STYLE_COLOR_KEY).toString(); if(color == "ukui-light" || color == "ukui-white") { d->m_styleColor = "Light"; } else if (color == "ukui-dark" || color == "ukui-black") { d->m_styleColor = "Dark"; } Q_EMIT styleColorChanged(); } else if (key == UKUI_STYLE_FONT_SIZE_KEY) { d->m_fontSize = settings->get(key).toReal(); Q_EMIT fontSizeChanged(); } else if (key == UKUI_STYLE_FONT_KEY) { d->m_fontFamily = settings->get(key).toString(); Q_EMIT fontFamilyChanged(); } else if (key == UKUI_STYLE_ICON_THEME_NAME_KEY) { d->m_iconThemeName = settings->get(key).toString(); Q_EMIT iconThemeNameChanged(); } else if (key == UKUI_STYLE_THEME_COLOR) { d->m_themeColor = settings->get(UKUI_STYLE_THEME_COLOR).toString(); Q_EMIT themeColorChanged(); } else if (key == UKUI_STYLE_WINDOW_RADIUS) { d->m_windowRadius = settings->get(UKUI_STYLE_WINDOW_RADIUS).toString(); Q_EMIT windowRadiusChanged(); } }); } connect(qGuiApp, &QGuiApplication::layoutDirectionChanged, this, [this] (const Qt::LayoutDirection &layoutDirection) { d->layoutDirection = layoutDirection; Q_EMIT layoutDirectionChanged(); }); } class DtThemePrivate { public: DtThemePrivate(DtTheme *q) : q(q) {}; DESIGN_TOKENS QQmlEngine* m_engine = nullptr; DtThemeDefinition *m_themeDefinition = nullptr; DtTheme *q = nullptr; QMap> m_valueSet; }; DtTheme::DtTheme(QQmlEngine* engine, QObject *parent) : QObject(parent), d(new DtThemePrivate(this)) { d->m_engine = engine; //加载默认主题文件 addFuncPointer(); //根据DT主题名称和颜色读取对应的qml文件 setTheme(); onWindowRadiusChanged(); connect(ThemeGlobalConfig::instance(), &ThemeGlobalConfig::transparencyChanged, this, &DtTheme::transparencyChanged); connect(ThemeGlobalConfig::instance(), &ThemeGlobalConfig::themeNameChanged, this, &DtTheme::setTheme); connect(ThemeGlobalConfig::instance(), &ThemeGlobalConfig::themeColorChanged, this, &DtTheme::onThemeColorChanged); connect(ThemeGlobalConfig::instance(), &ThemeGlobalConfig::styleColorChanged, this, &DtTheme::setTheme); connect(ThemeGlobalConfig::instance(), &ThemeGlobalConfig::windowRadiusChanged, this, &DtTheme::onWindowRadiusChanged); connect(ThemeGlobalConfig::instance(), &ThemeGlobalConfig::fontSizeChanged, this, &DtTheme::fontSizeChanged); connect(ThemeGlobalConfig::instance(), &ThemeGlobalConfig::fontFamilyChanged, this, &DtTheme::fontFamilyChanged); connect(ThemeGlobalConfig::instance(), &ThemeGlobalConfig::iconThemeNameChanged, this, &DtTheme::iconThemeNameChanged); connect(ThemeGlobalConfig::instance(), &ThemeGlobalConfig::layoutDirectionChanged, this, &DtTheme::layoutDirectionChanged); } DtTheme* DtTheme::self(QQmlEngine* engine) { if(!engine) { return nullptr; } const QHash::iterator it = g_dtThemes.find(engine); if(it == g_dtThemes.end()) { return g_dtThemes.insert(engine, new DtTheme(engine)).value(); } return it.value(); } DtTheme::~DtTheme() { g_dtThemes.remove(d->m_engine); if(d) { delete d; d = nullptr; } } void DtTheme::proportySet() { if(!d->m_themeDefinition) { return; } const QMetaObject* metaObject = d->m_themeDefinition->metaObject(); QStringList properties; for(int i = metaObject->propertyOffset(); i < metaObject->propertyCount(); ++i) properties << QString::fromLatin1(metaObject->property(i).name()); for (auto token : properties) { if(QVariant value = d->m_themeDefinition->property(token.toLocal8Bit().data()); value.isValid()) { if(d->m_valueSet.contains(token)) { std::function valueSet = d->m_valueSet[token]; valueSet(value); } } } } void DtTheme::setTheme() { //根据DT主题名称和颜色读取对应的qml文件 QString path= QString("/usr/share/config/themeconfig/token/k%1%2.qml").arg(ThemeGlobalConfig::instance()->themeName()).arg(ThemeGlobalConfig::instance()->styleColor()); if(!QFile::exists(path)) { qWarning() << "No Theme file found, using default theme"; path = QStringLiteral("/usr/share/ukui/ukui-quick-platform/DtThemeDefault.qml"); } if(QFile::exists(path)) { QQmlComponent component(d->m_engine); component.loadUrl(QUrl(path)); if (!component.isError()) { auto result = component.create(); if (auto themeDefinition = qobject_cast(result)) { d->m_themeDefinition = themeDefinition; //TODO token详细的分类,分组更新 proportySet(); if (d->m_themeDefinition) { delete d->m_themeDefinition; d->m_themeDefinition = nullptr; } onThemeColorChanged(); return; } } const auto errors = component.errors(); for (const auto&error: errors) { qWarning() << error.toString(); } qWarning() << "Invalid Theme file, using default theme."; } } void DtTheme::addFuncPointer() { d->m_valueSet.clear(); d->m_valueSet.insert("windowTextActive", std::bind(&DtTheme::windowTextActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("windowTextInactive", std::bind(&DtTheme::windowTextInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("windowTextDisable", std::bind(&DtTheme::windowTextDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("buttonActive", std::bind(&DtTheme::buttonActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("buttonInactive", std::bind(&DtTheme::buttonInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("buttonDisable", std::bind(&DtTheme::buttonDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("lightActive", std::bind(&DtTheme::lightActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("lightInactive", std::bind(&DtTheme::lightInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("lightDisable", std::bind(&DtTheme::lightDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("midLightActive", std::bind(&DtTheme::midLightActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("midLightInactive", std::bind(&DtTheme::midLightInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("midLightDisable", std::bind(&DtTheme::midLightDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("darkActive", std::bind(&DtTheme::darkActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("darkInactive", std::bind(&DtTheme::darkInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("darkDisable", std::bind(&DtTheme::darkDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("midActive", std::bind(&DtTheme::midActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("midInactive", std::bind(&DtTheme::midInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("midDisable", std::bind(&DtTheme::midDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("textActive", std::bind(&DtTheme::textActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("textInactive", std::bind(&DtTheme::textInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("textDisable", std::bind(&DtTheme::textDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("brightTextActive", std::bind(&DtTheme::brightTextActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("brightTextInactive", std::bind(&DtTheme::brightTextInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("brightTextDisable", std::bind(&DtTheme::brightTextDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("buttonTextActive", std::bind(&DtTheme::buttonTextActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("buttonTextInactive", std::bind(&DtTheme::buttonTextInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("buttonTextDisable", std::bind(&DtTheme::buttonTextDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("baseActive", std::bind(&DtTheme::baseActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("baseInactive", std::bind(&DtTheme::baseInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("baseDisable", std::bind(&DtTheme::baseDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("windowActive", std::bind(&DtTheme::windowActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("windowInactive", std::bind(&DtTheme::windowInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("windowDisable", std::bind(&DtTheme::windowDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("shadowActive", std::bind(&DtTheme::shadowActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("shadowInactive", std::bind(&DtTheme::shadowInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("shadowDisable", std::bind(&DtTheme::shadowDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("highlightedTextActive", std::bind(&DtTheme::highlightedTextActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("highlightedTextInactive", std::bind(&DtTheme::highlightedTextInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("highlightedTextDisable", std::bind(&DtTheme::highlightedTextDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("linkActive", std::bind(&DtTheme::linkActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("linkInactive", std::bind(&DtTheme::linkInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("linkDisable", std::bind(&DtTheme::linkDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("linkVisitedActive", std::bind(&DtTheme::linkVisitedActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("linkVisitedInactive", std::bind(&DtTheme::linkVisitedInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("linkVisitedDisable", std::bind(&DtTheme::linkVisitedDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("alternateBaseActive", std::bind(&DtTheme::alternateBaseActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("alternateBaseInactive", std::bind(&DtTheme::alternateBaseInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("alternateBaseDisable", std::bind(&DtTheme::alternateBaseDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("noRoleActive", std::bind(&DtTheme::noRoleActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("noRoleInactive", std::bind(&DtTheme::noRoleInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("noRoleDisable", std::bind(&DtTheme::noRoleDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("toolTipBaseActive", std::bind(&DtTheme::toolTipBaseActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("toolTipBaseInactive", std::bind(&DtTheme::toolTipBaseInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("toolTipBaseDisable", std::bind(&DtTheme::toolTipBaseDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("toolTipTextActive", std::bind(&DtTheme::toolTipTextActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("toolTipTextInactive", std::bind(&DtTheme::toolTipTextInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("toolTipTextDisable", std::bind(&DtTheme::toolTipTextDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("placeholderTextActive", std::bind(&DtTheme::placeholderTextActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("placeholderTextInactive", std::bind(&DtTheme::placeholderTextInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("placeholderTextDisable", std::bind(&DtTheme::placeholderTextDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kWhite", std::bind(&DtTheme::kWhiteSet, this, std::placeholders::_1)); d->m_valueSet.insert("kBlack", std::bind(&DtTheme::kBlackSet, this, std::placeholders::_1)); d->m_valueSet.insert("kGray0", std::bind(&DtTheme::kGray0Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGray1", std::bind(&DtTheme::kGray1Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGray2", std::bind(&DtTheme::kGray2Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGray3", std::bind(&DtTheme::kGray3Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGray4", std::bind(&DtTheme::kGray4Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGray5", std::bind(&DtTheme::kGray5Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGray6", std::bind(&DtTheme::kGray6Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGray7", std::bind(&DtTheme::kGray7Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGray8", std::bind(&DtTheme::kGray8Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGray9", std::bind(&DtTheme::kGray9Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGray10", std::bind(&DtTheme::kGray10Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGray11", std::bind(&DtTheme::kGray11Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGray12", std::bind(&DtTheme::kGray12Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGray13", std::bind(&DtTheme::kGray13Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGray14", std::bind(&DtTheme::kGray14Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGray15", std::bind(&DtTheme::kGray15Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGray16", std::bind(&DtTheme::kGray16Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGray17", std::bind(&DtTheme::kGray17Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGrayAlpha0", std::bind(&DtTheme::kGrayAlpha0Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGrayAlpha1", std::bind(&DtTheme::kGrayAlpha1Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGrayAlpha2", std::bind(&DtTheme::kGrayAlpha2Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGrayAlpha3", std::bind(&DtTheme::kGrayAlpha3Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGrayAlpha4", std::bind(&DtTheme::kGrayAlpha4Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGrayAlpha5", std::bind(&DtTheme::kGrayAlpha5Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGrayAlpha6", std::bind(&DtTheme::kGrayAlpha6Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGrayAlpha7", std::bind(&DtTheme::kGrayAlpha7Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGrayAlpha8", std::bind(&DtTheme::kGrayAlpha8Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGrayAlpha9", std::bind(&DtTheme::kGrayAlpha9Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGrayAlpha10", std::bind(&DtTheme::kGrayAlpha10Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGrayAlpha11", std::bind(&DtTheme::kGrayAlpha11Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGrayAlpha12", std::bind(&DtTheme::kGrayAlpha12Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGrayAlpha13", std::bind(&DtTheme::kGrayAlpha13Set, this, std::placeholders::_1)); d->m_valueSet.insert("kFontStrong", std::bind(&DtTheme::kFontStrongSet, this, std::placeholders::_1)); d->m_valueSet.insert("kFontPrimary", std::bind(&DtTheme::kFontPrimarySet, this, std::placeholders::_1)); d->m_valueSet.insert("kFontPrimaryDisable", std::bind(&DtTheme::kFontPrimaryDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kFontSecondary", std::bind(&DtTheme::kFontSecondarySet, this, std::placeholders::_1)); d->m_valueSet.insert("kFontSecondaryDisable", std::bind(&DtTheme::kFontSecondaryDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kFontWhite", std::bind(&DtTheme::kFontWhiteSet, this, std::placeholders::_1)); d->m_valueSet.insert("kFontWhiteDisable", std::bind(&DtTheme::kFontWhiteDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kFontWhiteSecondary", std::bind(&DtTheme::kFontWhiteSecondarySet, this, std::placeholders::_1)); d->m_valueSet.insert("kFontWhiteSecondaryDisable", std::bind(&DtTheme::kFontWhiteSecondaryDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kBrandFocus", std::bind(&DtTheme::kBrandFocusSet, this, std::placeholders::_1)); d->m_valueSet.insert("kSuccessNormal", std::bind(&DtTheme::kSuccessNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kSuccessHover", std::bind(&DtTheme::kSuccessHoverSet, this, std::placeholders::_1)); d->m_valueSet.insert("kSuccessClick", std::bind(&DtTheme::kSuccessClickSet, this, std::placeholders::_1)); d->m_valueSet.insert("kWarningNormal", std::bind(&DtTheme::kWarningNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kWarningHover", std::bind(&DtTheme::kWarningHoverSet, this, std::placeholders::_1)); d->m_valueSet.insert("kWarningClick", std::bind(&DtTheme::kWarningClickSet, this, std::placeholders::_1)); d->m_valueSet.insert("kErrorNormal", std::bind(&DtTheme::kErrorNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kErrorHover", std::bind(&DtTheme::kErrorHoverSet, this, std::placeholders::_1)); d->m_valueSet.insert("kErrorClick", std::bind(&DtTheme::kErrorClickSet, this, std::placeholders::_1)); d->m_valueSet.insert("kBrand1", std::bind(&DtTheme::kBrand1Set, this, std::placeholders::_1)); d->m_valueSet.insert("kBrand2", std::bind(&DtTheme::kBrand2Set, this, std::placeholders::_1)); d->m_valueSet.insert("kBrand3", std::bind(&DtTheme::kBrand3Set, this, std::placeholders::_1)); d->m_valueSet.insert("kBrand4", std::bind(&DtTheme::kBrand4Set, this, std::placeholders::_1)); d->m_valueSet.insert("kBrand5", std::bind(&DtTheme::kBrand5Set, this, std::placeholders::_1)); d->m_valueSet.insert("kBrand6", std::bind(&DtTheme::kBrand6Set, this, std::placeholders::_1)); d->m_valueSet.insert("kBrand7", std::bind(&DtTheme::kBrand7Set, this, std::placeholders::_1)); d->m_valueSet.insert("kContainHover", std::bind(&DtTheme::kContainHoverSet, this, std::placeholders::_1)); d->m_valueSet.insert("kContainClick", std::bind(&DtTheme::kContainClickSet, this, std::placeholders::_1)); d->m_valueSet.insert("kContainGeneralNormal", std::bind(&DtTheme::kContainGeneralNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kContainSecondaryNormal", std::bind(&DtTheme::kContainSecondaryNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kContainSecondaryAlphaNormal", std::bind(&DtTheme::kContainSecondaryAlphaNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kComponentNormal", std::bind(&DtTheme::kComponentNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kComponentHover", std::bind(&DtTheme::kComponentHoverSet, this, std::placeholders::_1)); d->m_valueSet.insert("kComponentClick", std::bind(&DtTheme::kComponentClickSet, this, std::placeholders::_1)); d->m_valueSet.insert("kComponentDisable", std::bind(&DtTheme::kComponentDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kComponentAlphaNormal", std::bind(&DtTheme::kComponentAlphaNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kComponentAlphaHover", std::bind(&DtTheme::kComponentAlphaHoverSet, this, std::placeholders::_1)); d->m_valueSet.insert("kComponentAlphaClick", std::bind(&DtTheme::kComponentAlphaClickSet, this, std::placeholders::_1)); d->m_valueSet.insert("kComponentAlphaDisable", std::bind(&DtTheme::kComponentAlphaDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineWindowActive", std::bind(&DtTheme::kLineWindowActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineComponentNormal", std::bind(&DtTheme::kLineComponentNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineComponentHover", std::bind(&DtTheme::kLineComponentHoverSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineComponentClick", std::bind(&DtTheme::kLineComponentClickSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineComponentDisable", std::bind(&DtTheme::kLineComponentDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kModalMask", std::bind(&DtTheme::kModalMaskSet, this, std::placeholders::_1)); d->m_valueSet.insert("normalLine", std::bind(&DtTheme::normalLineSet, this, std::placeholders::_1)); d->m_valueSet.insert("focusLine", std::bind(&DtTheme::focusLineSet, this, std::placeholders::_1)); d->m_valueSet.insert("kRadiusMin", std::bind(&DtTheme::kRadiusMinSet, this, std::placeholders::_1)); d->m_valueSet.insert("kRadiusNormal", std::bind(&DtTheme::kRadiusNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kRadiusMax", std::bind(&DtTheme::kRadiusMaxSet, this, std::placeholders::_1)); d->m_valueSet.insert("kMarginMin", std::bind(&DtTheme::kMarginMinSet, this, std::placeholders::_1)); d->m_valueSet.insert("kMarginNormal", std::bind(&DtTheme::kMarginNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kMarginBig", std::bind(&DtTheme::kMarginBigSet, this, std::placeholders::_1)); d->m_valueSet.insert("kMarginWindow", std::bind(&DtTheme::kMarginWindowSet, this, std::placeholders::_1)); d->m_valueSet.insert("kMarginComponent", std::bind(&DtTheme::kMarginComponentSet, this, std::placeholders::_1)); d->m_valueSet.insert("kPaddingMinLeft", std::bind(&DtTheme::kPaddingMinLeftSet, this, std::placeholders::_1)); d->m_valueSet.insert("kPaddingMinTop", std::bind(&DtTheme::kPaddingMinTopSet, this, std::placeholders::_1)); d->m_valueSet.insert("kPaddingMinRight", std::bind(&DtTheme::kPaddingMinRightSet, this, std::placeholders::_1)); d->m_valueSet.insert("kPaddingMinBottom", std::bind(&DtTheme::kPaddingMinBottomSet, this, std::placeholders::_1)); d->m_valueSet.insert("kPaddingNormalLeft", std::bind(&DtTheme::kPaddingNormalLeftSet, this, std::placeholders::_1)); d->m_valueSet.insert("kPaddingNormalTop", std::bind(&DtTheme::kPaddingNormalTopSet, this, std::placeholders::_1)); d->m_valueSet.insert("kPaddingNormalRight", std::bind(&DtTheme::kPaddingNormalRightSet, this, std::placeholders::_1)); d->m_valueSet.insert("kPaddingNormalBottom", std::bind(&DtTheme::kPaddingNormalBottomSet, this, std::placeholders::_1)); d->m_valueSet.insert("kPaddingWindowLeft", std::bind(&DtTheme::kPaddingWindowLeftSet, this, std::placeholders::_1)); d->m_valueSet.insert("kPaddingWindowTop", std::bind(&DtTheme::kPaddingWindowTopSet, this, std::placeholders::_1)); d->m_valueSet.insert("kPaddingWindowRight", std::bind(&DtTheme::kPaddingWindowRightSet, this, std::placeholders::_1)); d->m_valueSet.insert("kPaddingWindowBottom", std::bind(&DtTheme::kPaddingWindowBottomSet, this, std::placeholders::_1)); d->m_valueSet.insert("kErrorDisable", std::bind(&DtTheme::kErrorDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kWarningDisable", std::bind(&DtTheme::kWarningDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineNormal", std::bind(&DtTheme::kLineNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineNormal", std::bind(&DtTheme::kLineNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kContainGeneralAlphaNormal", std::bind(&DtTheme::kContainGeneralAlphaNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kSuccessDisable", std::bind(&DtTheme::kSuccessDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineWindowInactive", std::bind(&DtTheme::kLineWindowInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("kContainAlphaClick", std::bind(&DtTheme::kContainAlphaClickSet, this, std::placeholders::_1)); d->m_valueSet.insert("kContainAlphaHover", std::bind(&DtTheme::kContainAlphaHoverSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineDisable", std::bind(&DtTheme::kLineDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kDivider", std::bind(&DtTheme::kDividerSet, this, std::placeholders::_1)); d->m_valueSet.insert("kBrand8", std::bind(&DtTheme::kBrand8Set, this, std::placeholders::_1)); d->m_valueSet.insert("kBrand9", std::bind(&DtTheme::kBrand9Set, this, std::placeholders::_1)); d->m_valueSet.insert("kLineSelectboxNormal", std::bind(&DtTheme::kLineSelectboxNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineSelectboxHover", std::bind(&DtTheme::kLineSelectboxHoverSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineSelectboxClick", std::bind(&DtTheme::kLineSelectboxClickSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineSelectboxSelected", std::bind(&DtTheme::kLineSelectboxSelectedSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineSelectboxDisable", std::bind(&DtTheme::kLineSelectboxDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kFontWhitePlaceholderTextDisable", std::bind(&DtTheme::kFontWhitePlaceholderTextDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kContainGeneralInactive", std::bind(&DtTheme::kContainGeneralInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("kFontPlaceholderTextDisable", std::bind(&DtTheme::kFontPlaceholderTextDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kFontWhitePlaceholderText", std::bind(&DtTheme::kFontWhitePlaceholderTextSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineInputDisable", std::bind(&DtTheme::kLineInputDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineDock", std::bind(&DtTheme::kLineDockSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineMenu", std::bind(&DtTheme::kLineMenuSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineTable", std::bind(&DtTheme::kLineTableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineInputNormal", std::bind(&DtTheme::kLineInputNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineInputHover", std::bind(&DtTheme::kLineInputHoverSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineInputClick", std::bind(&DtTheme::kLineInputClickSet, this, std::placeholders::_1)); d->m_valueSet.insert("kOrange1", std::bind(&DtTheme::kOrange1Set, this, std::placeholders::_1)); d->m_valueSet.insert("kGreen1", std::bind(&DtTheme::kGreen1Set, this, std::placeholders::_1)); d->m_valueSet.insert("kRed1", std::bind(&DtTheme::kRed1Set, this, std::placeholders::_1)); d->m_valueSet.insert("kDividerWhite", std::bind(&DtTheme::kDividerWhiteSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineSuccessNormal", std::bind(&DtTheme::kLineSuccessNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineSuccessHover", std::bind(&DtTheme::kLineSuccessHoverSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineSuccessClick", std::bind(&DtTheme::kLineSuccessClickSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineSuccessDisable", std::bind(&DtTheme::kLineSuccessDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineErrorNormal", std::bind(&DtTheme::kLineErrorNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineErrorHover", std::bind(&DtTheme::kLineErrorHoverSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineErrorClick", std::bind(&DtTheme::kLineErrorClickSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineWarningNormal", std::bind(&DtTheme::kLineWarningNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineWarningHover", std::bind(&DtTheme::kLineWarningHoverSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineWarningClick", std::bind(&DtTheme::kLineWarningClickSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineWarningDisable", std::bind(&DtTheme::kLineWarningDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kShadowPrimaryActive", std::bind(&DtTheme::kShadowPrimaryActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("kShadowPrimaryInactive", std::bind(&DtTheme::kShadowPrimaryInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("kShadowSecondaryActive", std::bind(&DtTheme::kShadowSecondaryActiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("kShadowSecondaryInactive", std::bind(&DtTheme::kShadowSecondaryInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("kShadowMin", std::bind(&DtTheme::kShadowMinSet, this, std::placeholders::_1)); d->m_valueSet.insert("kShadowMenu", std::bind(&DtTheme::kShadowMenuSet, this, std::placeholders::_1)); d->m_valueSet.insert("kShadowComponent", std::bind(&DtTheme::kShadowComponentSet, this, std::placeholders::_1)); d->m_valueSet.insert("kComponentSelectedNormal", std::bind(&DtTheme::kComponentSelectedNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kComponentSelectedHover", std::bind(&DtTheme::kComponentSelectedHoverSet, this, std::placeholders::_1)); d->m_valueSet.insert("kComponentSelectedClick", std::bind(&DtTheme::kComponentSelectedClickSet, this, std::placeholders::_1)); d->m_valueSet.insert("kComponentSelectedDisable", std::bind(&DtTheme::kComponentSelectedDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kComponentSelectedAlphaNormal", std::bind(&DtTheme::kComponentSelectedAlphaNormalSet, this, std::placeholders::_1)); d->m_valueSet.insert("kComponentSelectedAlphaHover", std::bind(&DtTheme::kComponentSelectedAlphaHoverSet, this, std::placeholders::_1)); d->m_valueSet.insert("kComponentSelectedAlphaClick", std::bind(&DtTheme::kComponentSelectedAlphaClickSet, this, std::placeholders::_1)); d->m_valueSet.insert("kComponentSelectedAlphaDisable", std::bind(&DtTheme::kComponentSelectedAlphaDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kComponentInput", std::bind(&DtTheme::kComponentInputSet, this, std::placeholders::_1)); d->m_valueSet.insert("kComponentInputAlpha", std::bind(&DtTheme::kComponentInputAlphaSet, this, std::placeholders::_1)); d->m_valueSet.insert("kSelectAlphaWhiteDisable", std::bind(&DtTheme::kSelectAlphaWhiteDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kSelectAlphaWhiteHover", std::bind(&DtTheme::kSelectAlphaWhiteHoverSet, this, std::placeholders::_1)); d->m_valueSet.insert("kSelectAlphaWhiteClick", std::bind(&DtTheme::kSelectAlphaWhiteClickSet, this, std::placeholders::_1)); d->m_valueSet.insert("kMenu", std::bind(&DtTheme::kMenuSet, this, std::placeholders::_1)); d->m_valueSet.insert("kLineErrorDisable", std::bind(&DtTheme::kLineErrorDisableSet, this, std::placeholders::_1)); d->m_valueSet.insert("kContainSecondaryInactive", std::bind(&DtTheme::kContainSecondaryInactiveSet, this, std::placeholders::_1)); d->m_valueSet.insert("kSelectAlphaWhite", std::bind(&DtTheme::kSelectAlphaWhiteSet, this, std::placeholders::_1)); d->m_valueSet.insert("kFontPlaceholderText", std::bind(&DtTheme::kFontPlaceholderTextSet, this, std::placeholders::_1)); } qreal DtTheme::transparency() const { return ThemeGlobalConfig::instance()->transparency(); } qreal DtTheme::fontSize() const { return ThemeGlobalConfig::instance()->fontSize(); } QString DtTheme::fontFamily() const { return ThemeGlobalConfig::instance()->fontFamily(); } Qt::LayoutDirection DtTheme::layoutDirection() const { return ThemeGlobalConfig::instance()->layoutDirection(); } //强调色 void DtTheme::onThemeColorChanged() { QString themeColor = ThemeGlobalConfig::instance()->themeColor(); //兼容现有强调色切换逻辑 if(themeColor == "daybreakBlue" || themeColor.contains("kBrand1")) { updateHighLightColor(d->m_kBrand1.pureColor()); }else if (themeColor == "jamPurple" || themeColor.contains("kBrand2")) { updateHighLightColor(d->m_kBrand2.pureColor()); } else if (themeColor == "magenta" || themeColor.contains("kBrand3")) { updateHighLightColor(d->m_kBrand3.pureColor()); } else if (themeColor == "dustGold" || themeColor.contains("kBrand4")) { updateHighLightColor(d->m_kBrand4.pureColor()); } else if (themeColor == "polarGreen" || themeColor.contains("kBrand5")) { updateHighLightColor(d->m_kBrand5.pureColor()); } else if (themeColor == "sunRed" || themeColor.contains("kBrand6")) { updateHighLightColor(d->m_kBrand6.pureColor()); } else if (themeColor == "sunsetOrange" || themeColor.contains("kBrand7")) { updateHighLightColor(d->m_kBrand7.pureColor()); } else if (themeColor.contains("kBrand8")) { updateHighLightColor(d->m_kBrand8.pureColor()); } else if (themeColor.contains("kBrand9")) { updateHighLightColor(d->m_kBrand9.pureColor()); } else if(themeColor.startsWith("#")) { //兼容gsettings键值为#FFFFFF格式的颜色 if(QColor::isValidColor(themeColor)) { updateHighLightColor(QColor(themeColor)); } } else { //兼容rgba格式颜色 QRegularExpression rgbaReg(R"((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d),\s*(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d),\s*(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d),\s*(0(\.\d+)?|1(\.0+)?|0|1))"); QRegularExpression rgbReg(R"((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d),\s*(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d),\s*(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))"); QStringList rgbaList = rgbaReg.match(themeColor).hasMatch() ? rgbaReg.match(themeColor).capturedTexts() : rgbReg.match(themeColor).capturedTexts(); if(rgbaList.size() >= 4) { QColor rgba; rgba.setRed(rgbaList.at(1).toInt()); rgba.setGreen(rgbaList.at(2).toInt()); rgba.setBlue(rgbaList.at(3).toInt()); if(rgbaList.size() >= 5) { auto alpha = rgbaList.at(4).toDouble(); rgba.setAlphaF(alpha > 1 ? alpha/255 : alpha); } updateHighLightColor(rgba); } } } void DtTheme::updateHighLightColor(QColor highlightColor) { d->m_highlightActive.setPureColor(highlightColor); d->m_highlightInactive.setPureColor(highlightColor); d->m_kBrandNormal.setPureColor(highlightColor); d->m_linkActive.setPureColor(highlightColor); d->m_linkInactive.setPureColor(highlightColor); d->m_linkVisitedActive.setPureColor(highlightColor); d->m_linkVisitedInactive.setPureColor(highlightColor); //强调色禁用为当前高亮色*0.44透明度 QColor color = highlightColor; color.setAlphaF(0.45); d->m_kBrandDisable.setPureColor(color); d->m_highlightDisable.setPureColor(color); d->m_linkDisable.setPureColor(color); d->m_linkVisitedDisable.setPureColor(color); //强调色悬浮需要在强调色基础上叠加一个0.1透明度黑色背景色(0,0,0,0.1) if(d->m_kBrandHover.colorType() == DtColorType::Gradient) { d->m_kBrandHover.setGradientBackground(highlightColor); } else { d->m_kBrandHover.setPureColor(d->m_kBrandHover.mixBackGroundColor(QColor(0,0,0,0.1*255),highlightColor, 1)); } //强调色点击需要在强调色基础上叠加一个0.2透明度黑色背景色(0,0,0,0.2) if(d->m_kBrandClick.colorType() == DtColorType::Gradient) { d->m_kBrandClick.setGradientBackground(highlightColor); } else { d->m_kBrandClick.setPureColor(d->m_kBrandClick.mixBackGroundColor(QColor(0,0,0,0.2*255),highlightColor, 1)); } //TODO 强调色描边需要根据主题不同,使用不同的公式计算悬停,点击,禁用 //强调色描边需要在强调色的基础上乘以0.55透明度 color.setAlphaF(0.55); d->m_kLineBrandNormal.setPureColor(color); //强调色描边悬浮在5.0主题下需要在强调色基础上叠加一个0.1透明度黑色背景色(0,0,0,0.1) if(d->m_kLineBrandHover.colorType() == DtColorType::Gradient) { d->m_kLineBrandHover.setGradientBackground(color); } else { d->m_kLineBrandHover.setPureColor(d->m_kLineBrandHover.mixBackGroundColor(QColor(0,0,0,0.1*255),color, 1)); } //强调色描边点击在5.0主题下需要在强调色基础上叠加一个0.2透明度黑色背景色(0,0,0,0.2) if(d->m_kLineBrandClick.colorType() == DtColorType::Gradient) { d->m_kLineBrandClick.setGradientBackground(color); } else { d->m_kLineBrandClick.setPureColor(d->m_kLineBrandClick.mixBackGroundColor(QColor(0,0,0,0.2*255),color, 1)); } //强调色描边不可用需要在强调色基础上再乘以0.45透明度 color.setAlphaF(0.55*0.45); d->m_kLineBrandDisable.setPureColor(color); } void DtTheme::onWindowRadiusChanged() { QString windowRadius = ThemeGlobalConfig::instance()->windowRadius(); if(windowRadius.contains("bigRadius")) { kRadiusWindowSet(12); kRadiusMenuSet(8); } else if(windowRadius.contains("mediumRadius")) { kRadiusWindowSet(6); kRadiusMenuSet(4); } else if(windowRadius.contains("noRadius")) { kRadiusWindowSet(0); kRadiusMenuSet(0); } else { QRegularExpression radiusReg(R"(([0-9][0-9]|[0-9]),\s*([0-9][0-9]|[0-9]))"); if(radiusReg.match(windowRadius).hasMatch()) { QStringList radiusList = radiusReg.match(windowRadius).capturedTexts(); kRadiusWindowSet(radiusList.at(1).toInt()); kRadiusMenuSet(radiusList.at(2).toInt()); } } } bool DtTheme::isDarkTheme() { return ThemeGlobalConfig::instance()->isDarkTheme(); } COLOR_TOKEN_GETTER(windowTextActive) COLOR_TOKEN_GETTER(windowTextInactive) COLOR_TOKEN_GETTER(windowTextDisable) COLOR_TOKEN_GETTER(buttonActive) COLOR_TOKEN_GETTER(buttonInactive) COLOR_TOKEN_GETTER(buttonDisable) COLOR_TOKEN_GETTER(lightActive) COLOR_TOKEN_GETTER(lightInactive) COLOR_TOKEN_GETTER(lightDisable) COLOR_TOKEN_GETTER(midLightActive) COLOR_TOKEN_GETTER(midLightInactive) COLOR_TOKEN_GETTER(midLightDisable) COLOR_TOKEN_GETTER(darkActive) COLOR_TOKEN_GETTER(darkInactive) COLOR_TOKEN_GETTER(darkDisable) COLOR_TOKEN_GETTER(midActive) COLOR_TOKEN_GETTER(midInactive) COLOR_TOKEN_GETTER(midDisable) COLOR_TOKEN_GETTER(textActive) COLOR_TOKEN_GETTER(textInactive) COLOR_TOKEN_GETTER(textDisable) COLOR_TOKEN_GETTER(brightTextActive) COLOR_TOKEN_GETTER(brightTextInactive) COLOR_TOKEN_GETTER(brightTextDisable) COLOR_TOKEN_GETTER(buttonTextActive) COLOR_TOKEN_GETTER(buttonTextInactive) COLOR_TOKEN_GETTER(buttonTextDisable) COLOR_TOKEN_GETTER(baseActive) COLOR_TOKEN_GETTER(baseInactive) COLOR_TOKEN_GETTER(baseDisable) COLOR_TOKEN_GETTER(windowActive) COLOR_TOKEN_GETTER(windowInactive) COLOR_TOKEN_GETTER(windowDisable) COLOR_TOKEN_GETTER(shadowActive) COLOR_TOKEN_GETTER(shadowInactive) COLOR_TOKEN_GETTER(shadowDisable) COLOR_TOKEN_GETTER(highlightActive) COLOR_TOKEN_GETTER(highlightInactive) COLOR_TOKEN_GETTER(highlightDisable) COLOR_TOKEN_GETTER(highlightedTextActive) COLOR_TOKEN_GETTER(highlightedTextInactive) COLOR_TOKEN_GETTER(highlightedTextDisable) COLOR_TOKEN_GETTER(linkActive) COLOR_TOKEN_GETTER(linkInactive) COLOR_TOKEN_GETTER(linkDisable) COLOR_TOKEN_GETTER(linkVisitedDisable) COLOR_TOKEN_GETTER(alternateBaseActive) COLOR_TOKEN_GETTER(alternateBaseInactive) COLOR_TOKEN_GETTER(alternateBaseDisable) COLOR_TOKEN_GETTER(noRoleActive) COLOR_TOKEN_GETTER(noRoleInactive) COLOR_TOKEN_GETTER(noRoleDisable) COLOR_TOKEN_GETTER(toolTipBaseActive) COLOR_TOKEN_GETTER(toolTipBaseInactive) COLOR_TOKEN_GETTER(toolTipBaseDisable) COLOR_TOKEN_GETTER(toolTipTextActive) COLOR_TOKEN_GETTER(toolTipTextInactive) COLOR_TOKEN_GETTER(toolTipTextDisable) COLOR_TOKEN_GETTER(placeholderTextActive) COLOR_TOKEN_GETTER(placeholderTextInactive) COLOR_TOKEN_GETTER(placeholderTextDisable) COLOR_TOKEN_GETTER(kWhite) COLOR_TOKEN_GETTER(kBlack) COLOR_TOKEN_GETTER(kGray0) COLOR_TOKEN_GETTER(kGray1) COLOR_TOKEN_GETTER(kGray2) COLOR_TOKEN_GETTER(kGray3) COLOR_TOKEN_GETTER(kGray4) COLOR_TOKEN_GETTER(kGray5) COLOR_TOKEN_GETTER(kGray6) COLOR_TOKEN_GETTER(kGray7) COLOR_TOKEN_GETTER(kGray8) COLOR_TOKEN_GETTER(kGray9) COLOR_TOKEN_GETTER(kGray10) COLOR_TOKEN_GETTER(kGray11) COLOR_TOKEN_GETTER(kGray12) COLOR_TOKEN_GETTER(kGray13) COLOR_TOKEN_GETTER(kGray14) COLOR_TOKEN_GETTER(kGray15) COLOR_TOKEN_GETTER(kGray16) COLOR_TOKEN_GETTER(kGray17) COLOR_TOKEN_GETTER(kGrayAlpha0) COLOR_TOKEN_GETTER(kGrayAlpha1) COLOR_TOKEN_GETTER(kGrayAlpha2) COLOR_TOKEN_GETTER(kGrayAlpha3) COLOR_TOKEN_GETTER(kGrayAlpha4) COLOR_TOKEN_GETTER(kGrayAlpha5) COLOR_TOKEN_GETTER(kGrayAlpha6) COLOR_TOKEN_GETTER(kGrayAlpha7) COLOR_TOKEN_GETTER(kGrayAlpha8) COLOR_TOKEN_GETTER(kGrayAlpha9) COLOR_TOKEN_GETTER(kGrayAlpha10) COLOR_TOKEN_GETTER(kGrayAlpha11) COLOR_TOKEN_GETTER(kGrayAlpha12) COLOR_TOKEN_GETTER(kGrayAlpha13) COLOR_TOKEN_GETTER(kFontStrong) COLOR_TOKEN_GETTER(kFontPrimary) COLOR_TOKEN_GETTER(kFontPrimaryDisable) COLOR_TOKEN_GETTER(kFontSecondary) COLOR_TOKEN_GETTER(kFontSecondaryDisable) COLOR_TOKEN_GETTER(kFontWhite) COLOR_TOKEN_GETTER(kFontWhiteDisable) COLOR_TOKEN_GETTER(kFontWhiteSecondary) COLOR_TOKEN_GETTER(kFontWhiteSecondaryDisable) COLOR_TOKEN_GETTER(kBrandNormal) COLOR_TOKEN_GETTER(kSuccessNormal) COLOR_TOKEN_GETTER(kWarningNormal) COLOR_TOKEN_GETTER(kErrorNormal) COLOR_TOKEN_GETTER(kBrand1) COLOR_TOKEN_GETTER(kBrand2) COLOR_TOKEN_GETTER(kBrand3) COLOR_TOKEN_GETTER(kBrand4) COLOR_TOKEN_GETTER(kBrand5) COLOR_TOKEN_GETTER(kBrand6) COLOR_TOKEN_GETTER(kBrand7) COLOR_TOKEN_GETTER(kContainGeneralNormal) COLOR_TOKEN_GETTER(kContainSecondaryNormal) COLOR_TOKEN_GETTER(kContainSecondaryAlphaNormal) COLOR_TOKEN_GETTER(kComponentNormal) COLOR_TOKEN_GETTER(kComponentDisable) COLOR_TOKEN_GETTER(kComponentAlphaNormal) COLOR_TOKEN_GETTER(kComponentAlphaDisable) COLOR_TOKEN_GETTER(kLineWindowActive) COLOR_TOKEN_GETTER(kLineComponentNormal) COLOR_TOKEN_GETTER(kLineComponentHover) COLOR_TOKEN_GETTER(kLineComponentClick) COLOR_TOKEN_GETTER(kLineComponentDisable) COLOR_TOKEN_GETTER(kLineBrandNormal) COLOR_TOKEN_GETTER(kLineBrandHover) COLOR_TOKEN_GETTER(kLineBrandClick) COLOR_TOKEN_GETTER(kLineBrandDisable) COLOR_TOKEN_GETTER(kModalMask) COLOR_TOKEN_GETTER(linkVisitedActive) COLOR_TOKEN_GETTER(linkVisitedInactive) COLOR_TOKEN_GETTER(kBrandHover) COLOR_TOKEN_GETTER(kBrandClick) COLOR_TOKEN_GETTER(kBrandFocus) COLOR_TOKEN_GETTER(kSuccessHover) COLOR_TOKEN_GETTER(kSuccessClick) COLOR_TOKEN_GETTER(kWarningHover) COLOR_TOKEN_GETTER(kWarningClick) COLOR_TOKEN_GETTER(kErrorHover) COLOR_TOKEN_GETTER(kErrorClick) COLOR_TOKEN_GETTER(kContainHover) COLOR_TOKEN_GETTER(kContainClick) COLOR_TOKEN_GETTER(kComponentHover) COLOR_TOKEN_GETTER(kComponentClick) COLOR_TOKEN_GETTER(kComponentAlphaHover) COLOR_TOKEN_GETTER(kComponentAlphaClick) COLOR_TOKEN_GETTER(kErrorDisable) COLOR_TOKEN_GETTER(kWarningDisable) COLOR_TOKEN_GETTER(kLineNormal) COLOR_TOKEN_GETTER(kContainGeneralAlphaNormal) COLOR_TOKEN_GETTER(kSuccessDisable) COLOR_TOKEN_GETTER(kLineWindowInactive) COLOR_TOKEN_GETTER(kContainAlphaClick) COLOR_TOKEN_GETTER(kContainAlphaHover) COLOR_TOKEN_GETTER(kBrandDisable) COLOR_TOKEN_GETTER(kLineDisable) COLOR_TOKEN_GETTER(kDivider) COLOR_TOKEN_GETTER(kBrand8) COLOR_TOKEN_GETTER(kBrand9) COLOR_TOKEN_GETTER(kLineSelectboxNormal) COLOR_TOKEN_GETTER(kLineSelectboxHover) COLOR_TOKEN_GETTER(kLineSelectboxClick) COLOR_TOKEN_GETTER(kLineSelectboxSelected) COLOR_TOKEN_GETTER(kLineSelectboxDisable) COLOR_TOKEN_GETTER(kFontWhitePlaceholderTextDisable) COLOR_TOKEN_GETTER(kContainGeneralInactive) COLOR_TOKEN_GETTER(kFontPlaceholderTextDisable) COLOR_TOKEN_GETTER(kFontWhitePlaceholderText) COLOR_TOKEN_GETTER(kLineInputDisable) COLOR_TOKEN_GETTER(kLineDock) COLOR_TOKEN_GETTER(kLineMenu) COLOR_TOKEN_GETTER(kLineTable) COLOR_TOKEN_GETTER(kLineInputNormal) COLOR_TOKEN_GETTER(kLineInputHover) COLOR_TOKEN_GETTER(kLineInputClick) COLOR_TOKEN_GETTER(kOrange1) COLOR_TOKEN_GETTER(kGreen1) COLOR_TOKEN_GETTER(kRed1) COLOR_TOKEN_GETTER(kDividerWhite) COLOR_TOKEN_GETTER(kLineSuccessNormal) COLOR_TOKEN_GETTER(kLineSuccessHover) COLOR_TOKEN_GETTER(kLineSuccessClick) COLOR_TOKEN_GETTER(kLineSuccessDisable) COLOR_TOKEN_GETTER(kLineErrorNormal) COLOR_TOKEN_GETTER(kLineErrorHover) COLOR_TOKEN_GETTER(kLineErrorClick) COLOR_TOKEN_GETTER(kLineWarningNormal) COLOR_TOKEN_GETTER(kLineWarningHover) COLOR_TOKEN_GETTER(kLineWarningClick) COLOR_TOKEN_GETTER(kLineWarningDisable) COLOR_TOKEN_GETTER(kComponentSelectedNormal) COLOR_TOKEN_GETTER(kComponentSelectedHover) COLOR_TOKEN_GETTER(kComponentSelectedClick) COLOR_TOKEN_GETTER(kComponentSelectedDisable) COLOR_TOKEN_GETTER(kComponentSelectedAlphaNormal) COLOR_TOKEN_GETTER(kComponentSelectedAlphaHover) COLOR_TOKEN_GETTER(kComponentSelectedAlphaClick) COLOR_TOKEN_GETTER(kComponentSelectedAlphaDisable) COLOR_TOKEN_GETTER(kComponentInput) COLOR_TOKEN_GETTER(kComponentInputAlpha) COLOR_TOKEN_GETTER(kSelectAlphaWhiteDisable) COLOR_TOKEN_GETTER(kSelectAlphaWhiteHover) COLOR_TOKEN_GETTER(kSelectAlphaWhiteClick) COLOR_TOKEN_GETTER(kMenu) COLOR_TOKEN_GETTER(kLineErrorDisable) COLOR_TOKEN_GETTER(kContainSecondaryInactive) COLOR_TOKEN_GETTER(kSelectAlphaWhite) COLOR_TOKEN_GETTER(kFontPlaceholderText) INT_TOKEN_GETTER(normalLine) INT_TOKEN_GETTER(focusLine) INT_TOKEN_GETTER(kRadiusMin) INT_TOKEN_GETTER(kRadiusNormal) INT_TOKEN_GETTER(kRadiusMax) INT_TOKEN_GETTER(kRadiusMenu) INT_TOKEN_GETTER(kRadiusWindow) INT_TOKEN_GETTER(kMarginMin) INT_TOKEN_GETTER(kMarginNormal) INT_TOKEN_GETTER(kMarginBig) INT_TOKEN_GETTER(kMarginWindow) INT_TOKEN_GETTER(kMarginComponent) INT_TOKEN_GETTER(kPaddingMinLeft) INT_TOKEN_GETTER(kPaddingMinTop) INT_TOKEN_GETTER(kPaddingMinRight) INT_TOKEN_GETTER(kPaddingMinBottom) INT_TOKEN_GETTER(kPaddingNormalLeft) INT_TOKEN_GETTER(kPaddingNormalTop) INT_TOKEN_GETTER(kPaddingNormalRight) INT_TOKEN_GETTER(kPaddingNormalBottom) INT_TOKEN_GETTER(kPadding8Left) INT_TOKEN_GETTER(kPadding8Top) INT_TOKEN_GETTER(kPadding8Right) INT_TOKEN_GETTER(kPadding8Bottom) INT_TOKEN_GETTER(kPaddingWindowLeft) INT_TOKEN_GETTER(kPaddingWindowTop) INT_TOKEN_GETTER(kPaddingWindowRight) INT_TOKEN_GETTER(kPaddingWindowBottom) SHADOW_TOKEN_GETTER(kShadowPrimaryActive) SHADOW_TOKEN_GETTER(kShadowPrimaryInactive) SHADOW_TOKEN_GETTER(kShadowSecondaryActive) SHADOW_TOKEN_GETTER(kShadowSecondaryInactive) SHADOW_TOKEN_GETTER(kShadowMin) SHADOW_TOKEN_GETTER(kShadowMenu) SHADOW_TOKEN_GETTER(kShadowComponent) COLOR_TOKEN_SETTER(windowTextActive) COLOR_TOKEN_SETTER(windowTextInactive) COLOR_TOKEN_SETTER(windowTextDisable) COLOR_TOKEN_SETTER(buttonActive) COLOR_TOKEN_SETTER(buttonInactive) COLOR_TOKEN_SETTER(buttonDisable) COLOR_TOKEN_SETTER(lightActive) COLOR_TOKEN_SETTER(lightInactive) COLOR_TOKEN_SETTER(lightDisable) COLOR_TOKEN_SETTER(midLightActive) COLOR_TOKEN_SETTER(midLightInactive) COLOR_TOKEN_SETTER(midLightDisable) COLOR_TOKEN_SETTER(darkActive) COLOR_TOKEN_SETTER(darkInactive) COLOR_TOKEN_SETTER(darkDisable) COLOR_TOKEN_SETTER(midActive) COLOR_TOKEN_SETTER(midInactive) COLOR_TOKEN_SETTER(midDisable) COLOR_TOKEN_SETTER(textActive) COLOR_TOKEN_SETTER(textInactive) COLOR_TOKEN_SETTER(textDisable) COLOR_TOKEN_SETTER(brightTextActive) COLOR_TOKEN_SETTER(brightTextInactive) COLOR_TOKEN_SETTER(brightTextDisable) COLOR_TOKEN_SETTER(buttonTextActive) COLOR_TOKEN_SETTER(buttonTextInactive) COLOR_TOKEN_SETTER(buttonTextDisable) COLOR_TOKEN_SETTER(baseActive) COLOR_TOKEN_SETTER(baseInactive) COLOR_TOKEN_SETTER(baseDisable) COLOR_TOKEN_SETTER(windowActive) COLOR_TOKEN_SETTER(windowInactive) COLOR_TOKEN_SETTER(windowDisable) COLOR_TOKEN_SETTER(shadowActive) COLOR_TOKEN_SETTER(shadowInactive) COLOR_TOKEN_SETTER(shadowDisable) COLOR_TOKEN_SETTER(highlightActive) COLOR_TOKEN_SETTER(highlightInactive) COLOR_TOKEN_SETTER(highlightDisable) COLOR_TOKEN_SETTER(highlightedTextActive) COLOR_TOKEN_SETTER(highlightedTextInactive) COLOR_TOKEN_SETTER(highlightedTextDisable) COLOR_TOKEN_SETTER(linkActive) COLOR_TOKEN_SETTER(linkInactive) COLOR_TOKEN_SETTER(linkDisable) COLOR_TOKEN_SETTER(linkVisitedDisable) COLOR_TOKEN_SETTER(alternateBaseActive) COLOR_TOKEN_SETTER(alternateBaseInactive) COLOR_TOKEN_SETTER(alternateBaseDisable) COLOR_TOKEN_SETTER(noRoleActive) COLOR_TOKEN_SETTER(noRoleInactive) COLOR_TOKEN_SETTER(noRoleDisable) COLOR_TOKEN_SETTER(toolTipBaseActive) COLOR_TOKEN_SETTER(toolTipBaseInactive) COLOR_TOKEN_SETTER(toolTipBaseDisable) COLOR_TOKEN_SETTER(toolTipTextActive) COLOR_TOKEN_SETTER(toolTipTextInactive) COLOR_TOKEN_SETTER(toolTipTextDisable) COLOR_TOKEN_SETTER(placeholderTextActive) COLOR_TOKEN_SETTER(placeholderTextInactive) COLOR_TOKEN_SETTER(placeholderTextDisable) COLOR_TOKEN_SETTER(kWhite) COLOR_TOKEN_SETTER(kBlack) COLOR_TOKEN_SETTER(kGray0) COLOR_TOKEN_SETTER(kGray1) COLOR_TOKEN_SETTER(kGray2) COLOR_TOKEN_SETTER(kGray3) COLOR_TOKEN_SETTER(kGray4) COLOR_TOKEN_SETTER(kGray5) COLOR_TOKEN_SETTER(kGray6) COLOR_TOKEN_SETTER(kGray7) COLOR_TOKEN_SETTER(kGray8) COLOR_TOKEN_SETTER(kGray9) COLOR_TOKEN_SETTER(kGray10) COLOR_TOKEN_SETTER(kGray11) COLOR_TOKEN_SETTER(kGray12) COLOR_TOKEN_SETTER(kGray13) COLOR_TOKEN_SETTER(kGray14) COLOR_TOKEN_SETTER(kGray15) COLOR_TOKEN_SETTER(kGray16) COLOR_TOKEN_SETTER(kGray17) COLOR_TOKEN_SETTER(kGrayAlpha0) COLOR_TOKEN_SETTER(kGrayAlpha1) COLOR_TOKEN_SETTER(kGrayAlpha2) COLOR_TOKEN_SETTER(kGrayAlpha3) COLOR_TOKEN_SETTER(kGrayAlpha4) COLOR_TOKEN_SETTER(kGrayAlpha5) COLOR_TOKEN_SETTER(kGrayAlpha6) COLOR_TOKEN_SETTER(kGrayAlpha7) COLOR_TOKEN_SETTER(kGrayAlpha8) COLOR_TOKEN_SETTER(kGrayAlpha9) COLOR_TOKEN_SETTER(kGrayAlpha10) COLOR_TOKEN_SETTER(kGrayAlpha11) COLOR_TOKEN_SETTER(kGrayAlpha12) COLOR_TOKEN_SETTER(kGrayAlpha13) COLOR_TOKEN_SETTER(kFontStrong) COLOR_TOKEN_SETTER(kFontPrimary) COLOR_TOKEN_SETTER(kFontPrimaryDisable) COLOR_TOKEN_SETTER(kFontSecondary) COLOR_TOKEN_SETTER(kFontSecondaryDisable) COLOR_TOKEN_SETTER(kFontWhite) COLOR_TOKEN_SETTER(kFontWhiteDisable) COLOR_TOKEN_SETTER(kFontWhiteSecondary) COLOR_TOKEN_SETTER(kFontWhiteSecondaryDisable) COLOR_TOKEN_SETTER(kBrandNormal) COLOR_TOKEN_SETTER(kSuccessNormal) COLOR_TOKEN_SETTER(kWarningNormal) COLOR_TOKEN_SETTER(kErrorNormal) COLOR_TOKEN_SETTER(kBrand1) COLOR_TOKEN_SETTER(kBrand2) COLOR_TOKEN_SETTER(kBrand3) COLOR_TOKEN_SETTER(kBrand4) COLOR_TOKEN_SETTER(kBrand5) COLOR_TOKEN_SETTER(kBrand6) COLOR_TOKEN_SETTER(kBrand7) COLOR_TOKEN_SETTER(kContainGeneralNormal) COLOR_TOKEN_SETTER(kContainSecondaryNormal) COLOR_TOKEN_SETTER(kContainSecondaryAlphaNormal) COLOR_TOKEN_SETTER(kComponentNormal) COLOR_TOKEN_SETTER(kComponentDisable) COLOR_TOKEN_SETTER(kComponentAlphaNormal) COLOR_TOKEN_SETTER(kComponentAlphaDisable) COLOR_TOKEN_SETTER(kLineWindowActive) COLOR_TOKEN_SETTER(kLineComponentNormal) COLOR_TOKEN_SETTER(kLineComponentHover) COLOR_TOKEN_SETTER(kLineComponentClick) COLOR_TOKEN_SETTER(kLineComponentDisable) COLOR_TOKEN_SETTER(kLineBrandNormal) COLOR_TOKEN_SETTER(kLineBrandHover) COLOR_TOKEN_SETTER(kLineBrandClick) COLOR_TOKEN_SETTER(kLineBrandDisable) COLOR_TOKEN_SETTER(kModalMask) COLOR_TOKEN_SETTER(kComponentAlphaClick) COLOR_TOKEN_SETTER(kComponentAlphaHover) COLOR_TOKEN_SETTER(kComponentHover) COLOR_TOKEN_SETTER(kComponentClick) COLOR_TOKEN_SETTER(kContainHover) COLOR_TOKEN_SETTER(kContainClick) COLOR_TOKEN_SETTER(kBrandHover) COLOR_TOKEN_SETTER(kBrandClick) COLOR_TOKEN_SETTER(kBrandFocus) COLOR_TOKEN_SETTER(kSuccessHover) COLOR_TOKEN_SETTER(kSuccessClick) COLOR_TOKEN_SETTER(kErrorHover) COLOR_TOKEN_SETTER(kErrorClick) COLOR_TOKEN_SETTER(kWarningHover) COLOR_TOKEN_SETTER(kWarningClick) COLOR_TOKEN_SETTER(linkVisitedActive) COLOR_TOKEN_SETTER(linkVisitedInactive) COLOR_TOKEN_SETTER(kErrorDisable) COLOR_TOKEN_SETTER(kWarningDisable) COLOR_TOKEN_SETTER(kLineNormal) COLOR_TOKEN_SETTER(kContainGeneralAlphaNormal) COLOR_TOKEN_SETTER(kSuccessDisable) COLOR_TOKEN_SETTER(kLineWindowInactive) COLOR_TOKEN_SETTER(kContainAlphaClick) COLOR_TOKEN_SETTER(kContainAlphaHover) COLOR_TOKEN_SETTER(kBrandDisable) COLOR_TOKEN_SETTER(kLineDisable) COLOR_TOKEN_SETTER(kDivider) COLOR_TOKEN_SETTER(kBrand8) COLOR_TOKEN_SETTER(kBrand9) COLOR_TOKEN_SETTER(kLineSelectboxNormal) COLOR_TOKEN_SETTER(kLineSelectboxHover) COLOR_TOKEN_SETTER(kLineSelectboxClick) COLOR_TOKEN_SETTER(kLineSelectboxSelected) COLOR_TOKEN_SETTER(kLineSelectboxDisable) COLOR_TOKEN_SETTER(kFontWhitePlaceholderTextDisable) COLOR_TOKEN_SETTER(kContainGeneralInactive) COLOR_TOKEN_SETTER(kFontPlaceholderTextDisable) COLOR_TOKEN_SETTER(kFontWhitePlaceholderText) COLOR_TOKEN_SETTER(kLineInputDisable) COLOR_TOKEN_SETTER(kLineDock) COLOR_TOKEN_SETTER(kLineMenu) COLOR_TOKEN_SETTER(kLineTable) COLOR_TOKEN_SETTER(kLineInputNormal) COLOR_TOKEN_SETTER(kLineInputHover) COLOR_TOKEN_SETTER(kLineInputClick) COLOR_TOKEN_SETTER(kOrange1) COLOR_TOKEN_SETTER(kGreen1) COLOR_TOKEN_SETTER(kRed1) COLOR_TOKEN_SETTER(kDividerWhite) COLOR_TOKEN_SETTER(kLineSuccessNormal) COLOR_TOKEN_SETTER(kLineSuccessHover) COLOR_TOKEN_SETTER(kLineSuccessClick) COLOR_TOKEN_SETTER(kLineSuccessDisable) COLOR_TOKEN_SETTER(kLineErrorNormal) COLOR_TOKEN_SETTER(kLineErrorHover) COLOR_TOKEN_SETTER(kLineErrorClick) COLOR_TOKEN_SETTER(kLineWarningNormal) COLOR_TOKEN_SETTER(kLineWarningHover) COLOR_TOKEN_SETTER(kLineWarningClick) COLOR_TOKEN_SETTER(kLineWarningDisable) COLOR_TOKEN_SETTER(kComponentSelectedNormal) COLOR_TOKEN_SETTER(kComponentSelectedHover) COLOR_TOKEN_SETTER(kComponentSelectedClick) COLOR_TOKEN_SETTER(kComponentSelectedDisable) COLOR_TOKEN_SETTER(kComponentSelectedAlphaNormal) COLOR_TOKEN_SETTER(kComponentSelectedAlphaHover) COLOR_TOKEN_SETTER(kComponentSelectedAlphaClick) COLOR_TOKEN_SETTER(kComponentSelectedAlphaDisable) COLOR_TOKEN_SETTER(kComponentInput) COLOR_TOKEN_SETTER(kComponentInputAlpha) COLOR_TOKEN_SETTER(kSelectAlphaWhiteDisable) COLOR_TOKEN_SETTER(kSelectAlphaWhiteHover) COLOR_TOKEN_SETTER(kSelectAlphaWhiteClick) COLOR_TOKEN_SETTER(kMenu) COLOR_TOKEN_SETTER(kLineErrorDisable) COLOR_TOKEN_SETTER(kContainSecondaryInactive) COLOR_TOKEN_SETTER(kSelectAlphaWhite) COLOR_TOKEN_SETTER(kFontPlaceholderText) INT_TOKEN_SETTER(normalLine) INT_TOKEN_SETTER(focusLine) INT_TOKEN_SETTER(kRadiusMin) INT_TOKEN_SETTER(kRadiusNormal) INT_TOKEN_SETTER(kRadiusMax) INT_TOKEN_SETTER(kRadiusMenu) INT_TOKEN_SETTER(kRadiusWindow) INT_TOKEN_SETTER(kMarginMin) INT_TOKEN_SETTER(kMarginNormal) INT_TOKEN_SETTER(kMarginBig) INT_TOKEN_SETTER(kMarginWindow) INT_TOKEN_SETTER(kMarginComponent) INT_TOKEN_SETTER(kPaddingMinLeft) INT_TOKEN_SETTER(kPaddingMinTop) INT_TOKEN_SETTER(kPaddingMinRight) INT_TOKEN_SETTER(kPaddingMinBottom) INT_TOKEN_SETTER(kPaddingNormalLeft) INT_TOKEN_SETTER(kPaddingNormalTop) INT_TOKEN_SETTER(kPaddingNormalRight) INT_TOKEN_SETTER(kPaddingNormalBottom) INT_TOKEN_SETTER(kPadding8Left) INT_TOKEN_SETTER(kPadding8Top) INT_TOKEN_SETTER(kPadding8Right) INT_TOKEN_SETTER(kPadding8Bottom) INT_TOKEN_SETTER(kPaddingWindowLeft) INT_TOKEN_SETTER(kPaddingWindowTop) INT_TOKEN_SETTER(kPaddingWindowRight) INT_TOKEN_SETTER(kPaddingWindowBottom) SHADOW_TOKEN_SETTER(kShadowPrimaryActive) SHADOW_TOKEN_SETTER(kShadowPrimaryInactive) SHADOW_TOKEN_SETTER(kShadowSecondaryActive) SHADOW_TOKEN_SETTER(kShadowSecondaryInactive) SHADOW_TOKEN_SETTER(kShadowMin) SHADOW_TOKEN_SETTER(kShadowMenu) SHADOW_TOKEN_SETTER(kShadowComponent) } // UkuiQuick ukui-quick/platform/ukui/app-launcher.h0000664000175000017500000000265215153755732017142 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #ifndef UKUI_QUICK_APP_LAUNCHER_H #define UKUI_QUICK_APP_LAUNCHER_H #include namespace UkuiQuick { class AppLauncherPrivate; class AppLauncher : public QObject { Q_OBJECT public: static AppLauncher *instance(); ~AppLauncher(); Q_INVOKABLE void launchApp(const QString &desktopFile); Q_INVOKABLE void launchAppWithArguments(const QString &desktopFile, const QStringList &args); Q_INVOKABLE void runCommand(const QString &cmd); Q_INVOKABLE void openUri(const QString &uri, const QString &parentWindow = ""); private: explicit AppLauncher(QObject *parent = nullptr); AppLauncherPrivate *d = nullptr; }; } // UkuiQuick #endif //UKUI_QUICK_APP_LAUNCHER_H ukui-quick/platform/ukui/dt-theme.h0000664000175000017500000015630715153755732016301 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #ifndef DT_THEME_H #define DT_THEME_H #include #include #include "dt-theme-definition.h" namespace UkuiQuick { class ThemeGlobalConfigPrivate; class ThemeGlobalConfig: public QObject { Q_OBJECT public: static ThemeGlobalConfig* instance(); qreal transparency() const; QString themeName() const; QString styleColor() const; qreal fontSize() const; QString fontFamily() const; QString themeColor() const; QString windowRadius() const; QString iconThemeName() const; Qt::LayoutDirection layoutDirection() const; bool isDarkTheme() const; Q_SIGNALS: void transparencyChanged(); void themeNameChanged(); void styleColorChanged(); void themeColorChanged(); void fontSizeChanged(); void fontFamilyChanged(); void windowRadiusChanged(); void iconThemeNameChanged(); void layoutDirectionChanged(); private: ThemeGlobalConfig(); ~ThemeGlobalConfig(); ThemeGlobalConfigPrivate *d = nullptr; }; class DtThemePrivate; //TODO 可通过AttachedProperty的方式将DtTheme注册,支持在qml中直接创建DtTheme类实现自定义token class DtTheme : public QObject { Q_OBJECT Q_PROPERTY(GradientColor* windowTextActive READ windowTextActive NOTIFY windowTextActiveChanged) Q_PROPERTY(GradientColor* windowTextInactive READ windowTextInactive NOTIFY windowTextInactiveChanged) Q_PROPERTY(GradientColor* windowTextDisable READ windowTextDisable NOTIFY windowTextDisableChanged) Q_PROPERTY(GradientColor* buttonActive READ buttonActive NOTIFY buttonActiveChanged) Q_PROPERTY(GradientColor* buttonInactive READ buttonInactive NOTIFY buttonInactiveChanged) Q_PROPERTY(GradientColor* buttonDisable READ buttonDisable NOTIFY buttonDisableChanged) Q_PROPERTY(GradientColor* lightActive READ lightActive NOTIFY lightActiveChanged) Q_PROPERTY(GradientColor* lightInactive READ lightInactive NOTIFY lightInactiveChanged) Q_PROPERTY(GradientColor* lightDisable READ lightDisable NOTIFY lightDisableChanged) Q_PROPERTY(GradientColor* darkActive READ darkActive NOTIFY darkActiveChanged) Q_PROPERTY(GradientColor* darkInactive READ darkInactive NOTIFY darkInactiveChanged) Q_PROPERTY(GradientColor* darkDisable READ darkDisable NOTIFY darkDisableChanged) Q_PROPERTY(GradientColor* baseActive READ baseActive NOTIFY baseActiveChanged) Q_PROPERTY(GradientColor* baseInactive READ baseInactive NOTIFY baseInactiveChanged) Q_PROPERTY(GradientColor* baseDisable READ baseDisable NOTIFY baseDisableChanged) Q_PROPERTY(GradientColor* midLightActive READ midLightActive NOTIFY midLightActiveChanged) Q_PROPERTY(GradientColor* midLightInactive READ midLightInactive NOTIFY midLightInactiveChanged) Q_PROPERTY(GradientColor* midLightDisable READ midLightDisable NOTIFY midLightDisableChanged) Q_PROPERTY(GradientColor* midActive READ midActive NOTIFY midActiveChanged) Q_PROPERTY(GradientColor* midInactive READ midInactive NOTIFY midInactiveChanged) Q_PROPERTY(GradientColor* midDisable READ midDisable NOTIFY midDisableChanged) Q_PROPERTY(GradientColor* textActive READ textActive NOTIFY textActiveChanged) Q_PROPERTY(GradientColor* textInactive READ textInactive NOTIFY textInactiveChanged) Q_PROPERTY(GradientColor* textDisable READ textDisable NOTIFY textDisableChanged) Q_PROPERTY(GradientColor* brightTextActive READ brightTextActive NOTIFY brightTextActiveChanged) Q_PROPERTY(GradientColor* brightTextInactive READ brightTextInactive NOTIFY brightTextInactiveChanged) Q_PROPERTY(GradientColor* brightTextDisable READ brightTextDisable NOTIFY brightTextDisableChanged) Q_PROPERTY(GradientColor* buttonTextActive READ buttonTextActive NOTIFY buttonTextActiveChanged) Q_PROPERTY(GradientColor* buttonTextInactive READ buttonTextInactive NOTIFY buttonTextInactiveChanged) Q_PROPERTY(GradientColor* buttonTextDisable READ buttonTextDisable NOTIFY buttonTextDisableChanged) Q_PROPERTY(GradientColor* windowActive READ windowActive NOTIFY windowActiveChanged) Q_PROPERTY(GradientColor* windowInactive READ windowInactive NOTIFY windowInactiveChanged) Q_PROPERTY(GradientColor* windowDisable READ windowDisable NOTIFY windowDisableChanged) Q_PROPERTY(GradientColor* shadowActive READ shadowActive NOTIFY shadowActiveChanged) Q_PROPERTY(GradientColor* shadowInactive READ shadowInactive NOTIFY shadowInactiveChanged) Q_PROPERTY(GradientColor* shadowDisable READ shadowDisable NOTIFY shadowDisableChanged) Q_PROPERTY(GradientColor* highlightActive READ highlightActive NOTIFY highlightActiveChanged) Q_PROPERTY(GradientColor* highlightInactive READ highlightInactive NOTIFY highlightInactiveChanged) Q_PROPERTY(GradientColor* highlightDisable READ highlightDisable NOTIFY highlightDisableChanged) Q_PROPERTY(GradientColor* linkActive READ linkActive NOTIFY linkActiveChanged) Q_PROPERTY(GradientColor* linkInactive READ linkInactive NOTIFY linkInactiveChanged) Q_PROPERTY(GradientColor* linkDisable READ linkDisable NOTIFY linkDisableChanged) Q_PROPERTY(GradientColor* linkVisitedDisable READ linkVisitedDisable NOTIFY linkVisitedDisableChanged) Q_PROPERTY(GradientColor* alternateBaseActive READ alternateBaseActive NOTIFY alternateBaseActiveChanged) Q_PROPERTY(GradientColor* alternateBaseInactive READ alternateBaseInactive NOTIFY alternateBaseInactiveChanged) Q_PROPERTY(GradientColor* alternateBaseDisable READ alternateBaseDisable NOTIFY alternateBaseDisableChanged) Q_PROPERTY(GradientColor* noRoleActive READ noRoleActive NOTIFY noRoleActiveChanged) Q_PROPERTY(GradientColor* noRoleInactive READ noRoleInactive NOTIFY noRoleInactiveChanged) Q_PROPERTY(GradientColor* noRoleDisable READ noRoleDisable NOTIFY noRoleDisableChanged) Q_PROPERTY(GradientColor* toolTipBaseActive READ toolTipBaseActive NOTIFY toolTipBaseActiveChanged) Q_PROPERTY(GradientColor* toolTipBaseInactive READ toolTipBaseInactive NOTIFY toolTipBaseInactiveChanged) Q_PROPERTY(GradientColor* toolTipBaseDisable READ toolTipBaseDisable NOTIFY toolTipBaseDisableChanged) Q_PROPERTY(GradientColor* toolTipTextActive READ toolTipTextActive NOTIFY toolTipTextActiveChanged) Q_PROPERTY(GradientColor* toolTipTextInactive READ toolTipTextInactive NOTIFY toolTipTextInactiveChanged) Q_PROPERTY(GradientColor* toolTipTextDisable READ toolTipTextDisable NOTIFY toolTipTextDisableChanged) Q_PROPERTY(GradientColor* placeholderTextActive READ placeholderTextActive NOTIFY placeholderTextActiveChanged) Q_PROPERTY(GradientColor* placeholderTextInactive READ placeholderTextInactive NOTIFY placeholderTextInactiveChanged) Q_PROPERTY(GradientColor* placeholderTextDisable READ placeholderTextDisable NOTIFY placeholderTextDisableChanged) Q_PROPERTY(GradientColor* kWhite READ kWhite NOTIFY kWhiteChanged) Q_PROPERTY(GradientColor* kBlack READ kBlack NOTIFY kBlackChanged) Q_PROPERTY(GradientColor* kGray0 READ kGray0 NOTIFY kGray0Changed) Q_PROPERTY(GradientColor* kGray1 READ kGray1 NOTIFY kGray1Changed) Q_PROPERTY(GradientColor* kGray2 READ kGray2 NOTIFY kGray2Changed) Q_PROPERTY(GradientColor* kGray3 READ kGray3 NOTIFY kGray3Changed) Q_PROPERTY(GradientColor* kGray4 READ kGray4 NOTIFY kGray4Changed) Q_PROPERTY(GradientColor* kGray5 READ kGray5 NOTIFY kGray5Changed) Q_PROPERTY(GradientColor* kGray6 READ kGray6 NOTIFY kGray6Changed) Q_PROPERTY(GradientColor* kGray7 READ kGray7 NOTIFY kGray7Changed) Q_PROPERTY(GradientColor* kGray8 READ kGray8 NOTIFY kGray8Changed) Q_PROPERTY(GradientColor* kGray9 READ kGray9 NOTIFY kGray9Changed) Q_PROPERTY(GradientColor* kGray10 READ kGray10 NOTIFY kGray10Changed) Q_PROPERTY(GradientColor* kGray11 READ kGray11 NOTIFY kGray11Changed) Q_PROPERTY(GradientColor* kGray12 READ kGray12 NOTIFY kGray12Changed) Q_PROPERTY(GradientColor* kGray13 READ kGray13 NOTIFY kGray13Changed) Q_PROPERTY(GradientColor* kGray14 READ kGray14 NOTIFY kGray14Changed) Q_PROPERTY(GradientColor* kGray15 READ kGray15 NOTIFY kGray15Changed) Q_PROPERTY(GradientColor* kGray16 READ kGray16 NOTIFY kGray16Changed) Q_PROPERTY(GradientColor* kGray17 READ kGray17 NOTIFY kGray17Changed) Q_PROPERTY(GradientColor* kGrayAlpha0 READ kGrayAlpha0 NOTIFY kGrayAlpha0Changed) Q_PROPERTY(GradientColor* kGrayAlpha1 READ kGrayAlpha1 NOTIFY kGrayAlpha1Changed) Q_PROPERTY(GradientColor* kGrayAlpha2 READ kGrayAlpha2 NOTIFY kGrayAlpha2Changed) Q_PROPERTY(GradientColor* kGrayAlpha3 READ kGrayAlpha3 NOTIFY kGrayAlpha3Changed) Q_PROPERTY(GradientColor* kGrayAlpha4 READ kGrayAlpha4 NOTIFY kGrayAlpha4Changed) Q_PROPERTY(GradientColor* kGrayAlpha5 READ kGrayAlpha5 NOTIFY kGrayAlpha5Changed) Q_PROPERTY(GradientColor* kGrayAlpha6 READ kGrayAlpha6 NOTIFY kGrayAlpha6Changed) Q_PROPERTY(GradientColor* kGrayAlpha7 READ kGrayAlpha7 NOTIFY kGrayAlpha7Changed) Q_PROPERTY(GradientColor* kGrayAlpha8 READ kGrayAlpha8 NOTIFY kGrayAlpha8Changed) Q_PROPERTY(GradientColor* kGrayAlpha9 READ kGrayAlpha9 NOTIFY kGrayAlpha9Changed) Q_PROPERTY(GradientColor* kGrayAlpha10 READ kGrayAlpha10 NOTIFY kGrayAlpha10Changed) Q_PROPERTY(GradientColor* kGrayAlpha11 READ kGrayAlpha11 NOTIFY kGrayAlpha11Changed) Q_PROPERTY(GradientColor* kGrayAlpha12 READ kGrayAlpha12 NOTIFY kGrayAlpha12Changed) Q_PROPERTY(GradientColor* kGrayAlpha13 READ kGrayAlpha13 NOTIFY kGrayAlpha13Changed) Q_PROPERTY(GradientColor* kFontStrong READ kFontStrong NOTIFY kFontStrongChanged) Q_PROPERTY(GradientColor* kFontPrimary READ kFontPrimary NOTIFY kFontPrimaryChanged) Q_PROPERTY(GradientColor* kFontPrimaryDisable READ kFontPrimaryDisable NOTIFY kFontPrimaryDisableChanged) Q_PROPERTY(GradientColor* kFontSecondary READ kFontSecondary NOTIFY kFontSecondaryChanged) Q_PROPERTY(GradientColor* kFontSecondaryDisable READ kFontSecondaryDisable NOTIFY kFontSecondaryDisableChanged) Q_PROPERTY(GradientColor* kFontWhite READ kFontWhite NOTIFY kFontWhiteChanged) Q_PROPERTY(GradientColor* kFontWhiteDisable READ kFontWhiteDisable NOTIFY kFontWhiteDisableChanged) Q_PROPERTY(GradientColor* kFontWhiteSecondary READ kFontWhiteSecondary NOTIFY kFontWhiteSecondaryChanged) Q_PROPERTY(GradientColor* kFontWhiteSecondaryDisable READ kFontWhiteSecondaryDisable NOTIFY kFontWhiteSecondaryDisableChanged) Q_PROPERTY(GradientColor* kBrandNormal READ kBrandNormal NOTIFY kBrandNormalChanged) Q_PROPERTY(GradientColor* kSuccessNormal READ kSuccessNormal NOTIFY kSuccessNormalChanged) Q_PROPERTY(GradientColor* kWarningNormal READ kWarningNormal NOTIFY kWarningNormalChanged) Q_PROPERTY(GradientColor* kErrorNormal READ kErrorNormal NOTIFY kErrorNormalChanged) Q_PROPERTY(GradientColor* kBrand1 READ kBrand1 NOTIFY kBrand1Changed) Q_PROPERTY(GradientColor* kBrand2 READ kBrand2 NOTIFY kBrand2Changed) Q_PROPERTY(GradientColor* kBrand3 READ kBrand3 NOTIFY kBrand3Changed) Q_PROPERTY(GradientColor* kBrand4 READ kBrand4 NOTIFY kBrand4Changed) Q_PROPERTY(GradientColor* kBrand5 READ kBrand5 NOTIFY kBrand5Changed) Q_PROPERTY(GradientColor* kBrand6 READ kBrand6 NOTIFY kBrand6Changed) Q_PROPERTY(GradientColor* kBrand7 READ kBrand7 NOTIFY kBrand7Changed) Q_PROPERTY(GradientColor* kContainGeneralNormal READ kContainGeneralNormal NOTIFY kContainGeneralNormalChanged) Q_PROPERTY(GradientColor* kContainSecondaryNormal READ kContainSecondaryNormal NOTIFY kContainSecondaryNormalChanged) Q_PROPERTY(GradientColor* kContainSecondaryAlphaNormal READ kContainSecondaryAlphaNormal NOTIFY kContainSecondaryAlphaNormalChanged) Q_PROPERTY(GradientColor* kComponentNormal READ kComponentNormal NOTIFY kComponentNormalChanged) Q_PROPERTY(GradientColor* kComponentDisable READ kComponentDisable NOTIFY kComponentDisableChanged) Q_PROPERTY(GradientColor* kComponentAlphaNormal READ kComponentAlphaNormal NOTIFY kComponentAlphaNormalChanged) Q_PROPERTY(GradientColor* kComponentAlphaDisable READ kComponentAlphaDisable NOTIFY kComponentAlphaDisableChanged) Q_PROPERTY(GradientColor* kLineWindowActive READ kLineWindowActive NOTIFY kLineWindowActiveChanged) Q_PROPERTY(GradientColor* kLineComponentNormal READ kLineComponentNormal NOTIFY kLineComponentNormalChanged) Q_PROPERTY(GradientColor* kLineComponentHover READ kLineComponentHover NOTIFY kLineComponentHoverChanged) Q_PROPERTY(GradientColor* kLineComponentClick READ kLineComponentClick NOTIFY kLineComponentClickChanged) Q_PROPERTY(GradientColor* kLineComponentDisable READ kLineComponentDisable NOTIFY kLineComponentDisableChanged) Q_PROPERTY(GradientColor* kLineBrandNormal READ kLineBrandNormal NOTIFY kLineBrandNormalChanged) Q_PROPERTY(GradientColor* kLineBrandHover READ kLineBrandHover NOTIFY kLineBrandHoverChanged) Q_PROPERTY(GradientColor* kLineBrandClick READ kLineBrandClick NOTIFY kLineBrandClickChanged) Q_PROPERTY(GradientColor* kLineBrandDisable READ kLineBrandDisable NOTIFY kLineBrandDisableChanged) Q_PROPERTY(GradientColor* kModalMask READ kModalMask NOTIFY kModalMaskChanged) Q_PROPERTY(int normalLine READ normalLine NOTIFY normalLineChanged) Q_PROPERTY(int focusLine READ focusLine NOTIFY focusLineChanged) Q_PROPERTY(int kRadiusMin READ kRadiusMin NOTIFY kRadiusMinChanged) Q_PROPERTY(int kRadiusNormal READ kRadiusNormal NOTIFY kRadiusNormalChanged) Q_PROPERTY(int kRadiusMax READ kRadiusMax NOTIFY kRadiusMaxChanged) Q_PROPERTY(int kRadiusMenu READ kRadiusMenu NOTIFY kRadiusMenuChanged) Q_PROPERTY(int kRadiusWindow READ kRadiusWindow NOTIFY kRadiusWindowChanged) Q_PROPERTY(int kMarginMin READ kMarginMin NOTIFY kMarginMinChanged) Q_PROPERTY(int kMarginNormal READ kMarginNormal NOTIFY kMarginNormalChanged) Q_PROPERTY(int kMarginBig READ kMarginBig NOTIFY kMarginBigChanged) Q_PROPERTY(int kMarginWindow READ kMarginWindow NOTIFY kMarginWindowChanged) Q_PROPERTY(int kMarginComponent READ kMarginComponent NOTIFY kMarginComponentChanged) Q_PROPERTY(int kPaddingMinLeft READ kPaddingMinLeft NOTIFY kPaddingMinLeftChanged) Q_PROPERTY(int kPaddingMinRight READ kPaddingMinRight NOTIFY kPaddingMinRightChanged) Q_PROPERTY(int kPaddingMinTop READ kPaddingMinTop NOTIFY kPaddingMinTopChanged) Q_PROPERTY(int kPaddingMinBottom READ kPaddingMinBottom NOTIFY kPaddingMinBottomChanged) Q_PROPERTY(int kPaddingNormalLeft READ kPaddingNormalLeft NOTIFY kPaddingNormalLeftChanged) Q_PROPERTY(int kPaddingNormalRight READ kPaddingNormalRight NOTIFY kPaddingNormalRightChanged) Q_PROPERTY(int kPaddingNormalTop READ kPaddingNormalTop NOTIFY kPaddingNormalTopChanged) Q_PROPERTY(int kPaddingNormalBottom READ kPaddingNormalBottom NOTIFY kPaddingNormalBottomChanged) Q_PROPERTY(int kPadding8Left READ kPadding8Left NOTIFY kPadding8LeftChanged) Q_PROPERTY(int kPadding8Right READ kPadding8Right NOTIFY kPadding8RightChanged) Q_PROPERTY(int kPadding8Top READ kPadding8Top NOTIFY kPadding8TopChanged) Q_PROPERTY(int kPadding8Bottom READ kPadding8Bottom NOTIFY kPadding8BottomChanged) Q_PROPERTY(int kPaddingWindowLeft READ kPaddingWindowLeft NOTIFY kPaddingWindowLeftChanged) Q_PROPERTY(int kPaddingWindowRight READ kPaddingWindowRight NOTIFY kPaddingWindowRightChanged) Q_PROPERTY(int kPaddingWindowTop READ kPaddingWindowTop NOTIFY kPaddingWindowTopChanged) Q_PROPERTY(int kPaddingWindowBottom READ kPaddingWindowBottom NOTIFY kPaddingWindowBottomChanged) Q_PROPERTY(GradientColor* linkVisitedActive READ linkVisitedActive NOTIFY linkVisitedActiveChanged) Q_PROPERTY(GradientColor* linkVisitedInactive READ linkVisitedInactive NOTIFY linkVisitedInactiveChanged) Q_PROPERTY(GradientColor* kBrandHover READ kBrandHover NOTIFY kBrandHoverChanged) Q_PROPERTY(GradientColor* kBrandClick READ kBrandClick NOTIFY kBrandClickChanged) Q_PROPERTY(GradientColor* kBrandFocus READ kBrandFocus NOTIFY kBrandFocusChanged) Q_PROPERTY(GradientColor* kSuccessHover READ kSuccessHover NOTIFY kSuccessHoverChanged) Q_PROPERTY(GradientColor* kSuccessClick READ kSuccessClick NOTIFY kSuccessClickChanged) Q_PROPERTY(GradientColor* kWarningHover READ kWarningHover NOTIFY kWarningHoverChanged) Q_PROPERTY(GradientColor* kWarningClick READ kWarningClick NOTIFY kWarningClickChanged) Q_PROPERTY(GradientColor* kErrorHover READ kErrorHover NOTIFY kErrorHoverChanged) Q_PROPERTY(GradientColor* kErrorClick READ kErrorClick NOTIFY kErrorClickChanged) Q_PROPERTY(GradientColor* kContainHover READ kContainHover NOTIFY kContainHoverChanged) Q_PROPERTY(GradientColor* kContainClick READ kContainClick NOTIFY kContainClickChanged) Q_PROPERTY(GradientColor* kComponentHover READ kComponentHover NOTIFY kComponentHoverChanged) Q_PROPERTY(GradientColor* kComponentClick READ kComponentClick NOTIFY kComponentClickChanged) Q_PROPERTY(GradientColor* kComponentAlphaHover READ kComponentAlphaHover NOTIFY kComponentAlphaHoverChanged) Q_PROPERTY(GradientColor* kComponentAlphaClick READ kComponentAlphaClick NOTIFY kComponentAlphaClickChanged) Q_PROPERTY(GradientColor* kErrorDisable READ kErrorDisable NOTIFY kErrorDisableChanged) Q_PROPERTY(GradientColor* kContainGeneralAlphaNormal READ kContainGeneralAlphaNormal NOTIFY kContainGeneralAlphaNormalChanged) Q_PROPERTY(GradientColor* kSuccessDisable READ kSuccessDisable NOTIFY kSuccessDisableChanged) Q_PROPERTY(GradientColor* kLineWindowInactive READ kLineWindowInactive NOTIFY kLineWindowInactiveChanged) Q_PROPERTY(GradientColor* kContainAlphaClick READ kContainAlphaClick NOTIFY kContainAlphaClickChanged) Q_PROPERTY(GradientColor* kWarningDisable READ kWarningDisable NOTIFY kWarningDisableChanged) Q_PROPERTY(GradientColor* kDivider READ kDivider NOTIFY kDividerChanged) Q_PROPERTY(GradientColor* kContainAlphaHover READ kContainAlphaHover NOTIFY kContainAlphaHoverChanged) Q_PROPERTY(GradientColor* highlightedTextActive READ highlightedTextActive NOTIFY highlightedTextActiveChanged) Q_PROPERTY(GradientColor* kBrandDisable READ kBrandDisable NOTIFY kBrandDisableChanged) Q_PROPERTY(GradientColor* kLineDisable READ kLineDisable NOTIFY kLineDisableChanged) Q_PROPERTY(GradientColor* kLineNormal READ kLineNormal NOTIFY kLineNormalChanged) Q_PROPERTY(qreal transparency READ transparency NOTIFY transparencyChanged) Q_PROPERTY(qreal fontSize READ fontSize NOTIFY fontSizeChanged) Q_PROPERTY(QString fontFamily READ fontFamily NOTIFY fontFamilyChanged) Q_PROPERTY(GradientColor* kBrand8 READ kBrand8 NOTIFY kBrand8Changed) Q_PROPERTY(GradientColor* kBrand9 READ kBrand9 NOTIFY kBrand9Changed) Q_PROPERTY(GradientColor* kLineSelectboxNormal READ kLineSelectboxNormal NOTIFY kLineSelectboxNormalChanged) Q_PROPERTY(GradientColor* kLineSelectboxHover READ kLineSelectboxHover NOTIFY kLineSelectboxHoverChanged) Q_PROPERTY(GradientColor* kLineSelectboxClick READ kLineSelectboxClick NOTIFY kLineSelectboxClickChanged) Q_PROPERTY(GradientColor* kLineSelectboxSelected READ kLineSelectboxSelected NOTIFY kLineSelectboxSelectedChanged) Q_PROPERTY(GradientColor* kLineSelectboxDisable READ kLineSelectboxDisable NOTIFY kLineSelectboxDisableChanged) Q_PROPERTY(GradientColor* kFontWhitePlaceholderTextDisable READ kFontWhitePlaceholderTextDisable NOTIFY kFontWhitePlaceholderTextDisableChanged) Q_PROPERTY(GradientColor* kContainGeneralInactive READ kContainGeneralInactive NOTIFY kContainGeneralInactiveChanged) Q_PROPERTY(GradientColor* kFontPlaceholderTextDisable READ kFontPlaceholderTextDisable NOTIFY kFontPlaceholderTextDisableChanged) Q_PROPERTY(GradientColor* kFontWhitePlaceholderText READ kFontWhitePlaceholderText NOTIFY kFontWhitePlaceholderTextChanged) Q_PROPERTY(GradientColor* kLineInputDisable READ kLineInputDisable NOTIFY kLineInputDisableChanged) Q_PROPERTY(GradientColor* kLineDock READ kLineDock NOTIFY kLineDockChanged) Q_PROPERTY(GradientColor* kLineMenu READ kLineMenu NOTIFY kLineMenuChanged) Q_PROPERTY(GradientColor* kLineTable READ kLineTable NOTIFY kLineTableChanged) Q_PROPERTY(GradientColor* kLineInputNormal READ kLineInputNormal NOTIFY kLineInputNormalChanged) Q_PROPERTY(GradientColor* kLineInputHover READ kLineInputHover NOTIFY kLineInputHoverChanged) Q_PROPERTY(GradientColor* kLineInputClick READ kLineInputClick NOTIFY kLineInputClickChanged) Q_PROPERTY(GradientColor* kOrange1 READ kOrange1 NOTIFY kOrange1Changed) Q_PROPERTY(GradientColor* kGreen1 READ kGreen1 NOTIFY kGreen1Changed) Q_PROPERTY(GradientColor* kRed1 READ kRed1 NOTIFY kRed1Changed) Q_PROPERTY(GradientColor* kDividerWhite READ kDividerWhite NOTIFY kDividerWhiteChanged) Q_PROPERTY(GradientColor* kLineSuccessNormal READ kLineSuccessNormal NOTIFY kLineSuccessNormalChanged) Q_PROPERTY(GradientColor* kLineSuccessHover READ kLineSuccessHover NOTIFY kLineSuccessHoverChanged) Q_PROPERTY(GradientColor* kLineSuccessClick READ kLineSuccessClick NOTIFY kLineSuccessClickChanged) Q_PROPERTY(GradientColor* kLineSuccessDisable READ kLineSuccessDisable NOTIFY kLineSuccessDisableChanged) Q_PROPERTY(GradientColor* kLineErrorNormal READ kLineErrorNormal NOTIFY kLineErrorNormalChanged) Q_PROPERTY(GradientColor* kLineErrorHover READ kLineErrorHover NOTIFY kLineErrorHoverChanged) Q_PROPERTY(GradientColor* kLineErrorClick READ kLineErrorClick NOTIFY kLineErrorClickChanged) Q_PROPERTY(GradientColor* kLineWarningNormal READ kLineWarningNormal NOTIFY kLineWarningNormalChanged) Q_PROPERTY(GradientColor* kLineWarningHover READ kLineWarningHover NOTIFY kLineWarningHoverChanged) Q_PROPERTY(GradientColor* kLineWarningClick READ kLineWarningClick NOTIFY kLineWarningClickChanged) Q_PROPERTY(GradientColor* kLineWarningDisable READ kLineWarningDisable NOTIFY kLineWarningDisableChanged) Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection NOTIFY layoutDirectionChanged) Q_PROPERTY(ShadowData* kShadowPrimaryActive READ kShadowPrimaryActive NOTIFY kShadowPrimaryActiveChanged) Q_PROPERTY(ShadowData* kShadowPrimaryInactive READ kShadowPrimaryInactive NOTIFY kShadowPrimaryInactiveChanged) Q_PROPERTY(ShadowData* kShadowSecondaryActive READ kShadowSecondaryActive NOTIFY kShadowSecondaryActiveChanged) Q_PROPERTY(ShadowData* kShadowSecondaryInactive READ kShadowSecondaryInactive NOTIFY kShadowSecondaryInactiveChanged) Q_PROPERTY(ShadowData* kShadowMin READ kShadowMin NOTIFY kShadowMinChanged) Q_PROPERTY(ShadowData* kShadowMenu READ kShadowMenu NOTIFY kShadowMenuChanged) Q_PROPERTY(ShadowData* kShadowComponent READ kShadowComponent NOTIFY kShadowComponentChanged) //2025.05.14添加 Q_PROPERTY(GradientColor* kComponentSelectedNormal READ kComponentSelectedNormal NOTIFY kComponentSelectedNormalChanged) Q_PROPERTY(GradientColor* kComponentSelectedHover READ kComponentSelectedHover NOTIFY kComponentSelectedHoverChanged) Q_PROPERTY(GradientColor* kComponentSelectedClick READ kComponentSelectedClick NOTIFY kComponentSelectedClickChanged) Q_PROPERTY(GradientColor* kComponentSelectedDisable READ kComponentSelectedDisable NOTIFY kComponentSelectedDisableChanged) Q_PROPERTY(GradientColor* kComponentSelectedAlphaNormal READ kComponentSelectedAlphaNormal NOTIFY kComponentSelectedAlphaNormalChanged) Q_PROPERTY(GradientColor* kComponentSelectedAlphaHover READ kComponentSelectedAlphaHover NOTIFY kComponentSelectedAlphaHoverChanged) Q_PROPERTY(GradientColor* kComponentSelectedAlphaClick READ kComponentSelectedAlphaClick NOTIFY kComponentSelectedAlphaClickChanged) Q_PROPERTY(GradientColor* kComponentSelectedAlphaDisable READ kComponentSelectedAlphaDisable NOTIFY kComponentSelectedAlphaDisableChanged) Q_PROPERTY(GradientColor* kComponentInput READ kComponentInput NOTIFY kComponentInputChanged) Q_PROPERTY(GradientColor* kComponentInputAlpha READ kComponentInputAlpha NOTIFY kComponentInputAlphaChanged) //2025.06.25添加 Q_PROPERTY(GradientColor* kSelectAlphaWhite READ kSelectAlphaWhite NOTIFY kSelectAlphaWhiteChanged) Q_PROPERTY(GradientColor* kSelectAlphaWhiteHover READ kSelectAlphaWhiteHover NOTIFY kSelectAlphaWhiteHoverChanged) Q_PROPERTY(GradientColor* kSelectAlphaWhiteClick READ kSelectAlphaWhiteClick NOTIFY kSelectAlphaWhiteClickChanged) Q_PROPERTY(GradientColor* kSelectAlphaWhiteDisable READ kSelectAlphaWhiteDisable NOTIFY kSelectAlphaWhiteDisableChanged) Q_PROPERTY(GradientColor* kMenu READ kMenu NOTIFY kMenuChanged) Q_PROPERTY(GradientColor* kLineErrorDisable READ kLineErrorDisable NOTIFY kLineErrorDisableChanged) Q_PROPERTY(GradientColor* kContainSecondaryInactive READ kContainSecondaryInactive NOTIFY kContainSecondaryInactiveChanged) Q_PROPERTY(GradientColor* kFontPlaceholderText READ kFontPlaceholderText NOTIFY kFontPlaceholderTextChanged) public: static DtTheme *self(QQmlEngine *engine); ~DtTheme(); GradientColor* windowTextActive() const; GradientColor* windowTextInactive() const; GradientColor* windowTextDisable() const; GradientColor* buttonActive() const; GradientColor* buttonInactive() const; GradientColor* buttonDisable() const; GradientColor* lightActive() const; GradientColor* lightInactive() const; GradientColor* lightDisable() const; GradientColor* midLightActive() const; GradientColor* midLightInactive() const; GradientColor* midLightDisable() const; GradientColor* darkActive() const; GradientColor* darkInactive() const; GradientColor* darkDisable() const; GradientColor* midActive() const; GradientColor* midInactive() const; GradientColor* midDisable() const; GradientColor* textActive() const; GradientColor* textInactive() const; GradientColor* textDisable() const; GradientColor* brightTextActive() const; GradientColor* brightTextInactive() const; GradientColor* brightTextDisable() const; GradientColor* buttonTextActive() const; GradientColor* buttonTextInactive() const; GradientColor* buttonTextDisable() const; GradientColor* baseActive() const; GradientColor* baseInactive() const; GradientColor* baseDisable() const; GradientColor* windowActive() const; GradientColor* windowInactive() const; GradientColor* windowDisable() const; GradientColor* shadowActive() const; GradientColor* shadowInactive() const; GradientColor* shadowDisable() const; GradientColor* highlightActive() const; GradientColor* highlightInactive() const; GradientColor* highlightDisable() const; GradientColor* highlightedTextActive() const; GradientColor* highlightedTextInactive() const; GradientColor* highlightedTextDisable() const; GradientColor* linkActive() const; GradientColor* linkInactive() const; GradientColor* linkDisable() const; GradientColor* linkVisitedDisable() const; GradientColor* alternateBaseActive() const; GradientColor* alternateBaseInactive() const; GradientColor* alternateBaseDisable() const; GradientColor* noRoleActive() const; GradientColor* noRoleInactive() const; GradientColor* noRoleDisable() const; GradientColor* toolTipBaseActive() const; GradientColor* toolTipBaseInactive() const; GradientColor* toolTipBaseDisable() const; GradientColor* toolTipTextActive() const; GradientColor* toolTipTextInactive() const; GradientColor* toolTipTextDisable() const; GradientColor* placeholderTextActive() const; GradientColor* placeholderTextInactive() const; GradientColor* placeholderTextDisable() const; GradientColor* kWhite() const; GradientColor* kBlack() const; GradientColor* kGray0() const; GradientColor* kGray1() const; GradientColor* kGray2() const; GradientColor* kGray3() const; GradientColor* kGray4() const; GradientColor* kGray5() const; GradientColor* kGray6() const; GradientColor* kGray7() const; GradientColor* kGray8() const; GradientColor* kGray9() const; GradientColor* kGray10() const; GradientColor* kGray11() const; GradientColor* kGray12() const; GradientColor* kGray13() const; GradientColor* kGray14() const; GradientColor* kGray15() const; GradientColor* kGray16() const; GradientColor* kGray17() const; GradientColor* kGrayAlpha0() const; GradientColor* kGrayAlpha1() const; GradientColor* kGrayAlpha2() const; GradientColor* kGrayAlpha3() const; GradientColor* kGrayAlpha4() const; GradientColor* kGrayAlpha5() const; GradientColor* kGrayAlpha6() const; GradientColor* kGrayAlpha7() const; GradientColor* kGrayAlpha8() const; GradientColor* kGrayAlpha9() const; GradientColor* kGrayAlpha10() const; GradientColor* kGrayAlpha11() const; GradientColor* kGrayAlpha12() const; GradientColor* kGrayAlpha13() const; GradientColor* kFontStrong() const; GradientColor* kFontPrimary() const; GradientColor* kFontPrimaryDisable() const; GradientColor* kFontSecondary() const; GradientColor* kFontSecondaryDisable() const; GradientColor* kFontWhite() const; GradientColor* kFontWhiteDisable() const; GradientColor* kFontWhiteSecondary() const; GradientColor* kFontWhiteSecondaryDisable() const; GradientColor* kBrandNormal() const; GradientColor* kSuccessNormal() const; GradientColor* kWarningNormal() const; GradientColor* kErrorNormal() const; GradientColor* kBrand1() const; GradientColor* kBrand2() const; GradientColor* kBrand3() const; GradientColor* kBrand4() const; GradientColor* kBrand5() const; GradientColor* kBrand6() const; GradientColor* kBrand7() const; GradientColor* kContainGeneralNormal() const; GradientColor* kContainSecondaryNormal() const; GradientColor* kContainSecondaryAlphaNormal() const; GradientColor* kComponentNormal() const; GradientColor* kComponentDisable() const; GradientColor* kComponentAlphaNormal() const; GradientColor* kComponentAlphaDisable() const; GradientColor* kLineWindowActive() const; GradientColor* kLineComponentNormal() const; GradientColor* kLineComponentHover() const; GradientColor* kLineComponentClick() const; GradientColor* kLineComponentDisable() const; GradientColor* kLineBrandNormal() const; GradientColor* kLineBrandHover() const; GradientColor* kLineBrandClick() const; GradientColor* kLineBrandDisable() const; GradientColor* kModalMask() const; int normalLine() const; int focusLine() const; int kRadiusMin() const; int kRadiusNormal() const; int kRadiusMax() const; int kRadiusMenu() const; int kRadiusWindow() const; int kMarginMin() const; int kMarginNormal() const; int kMarginBig() const; int kMarginWindow() const; int kMarginComponent() const; int kPaddingMinLeft() const; int kPaddingMinTop() const; int kPaddingMinRight() const; int kPaddingMinBottom() const; int kPaddingNormalLeft() const; int kPaddingNormalTop() const; int kPaddingNormalRight() const; int kPaddingNormalBottom() const; int kPadding8Left() const; int kPadding8Top() const; int kPadding8Right() const; int kPadding8Bottom() const; int kPaddingWindowLeft() const; int kPaddingWindowTop() const; int kPaddingWindowRight() const; int kPaddingWindowBottom() const; GradientColor* linkVisitedActive() const; GradientColor* linkVisitedInactive() const; GradientColor* kBrandHover() const; GradientColor* kBrandClick() const; GradientColor* kBrandFocus() const; GradientColor* kSuccessHover() const; GradientColor* kSuccessClick() const; GradientColor* kWarningHover() const; GradientColor* kWarningClick() const; GradientColor* kErrorHover() const; GradientColor* kErrorClick() const; GradientColor* kContainHover() const; GradientColor* kContainClick() const; GradientColor* kComponentHover() const; GradientColor* kComponentClick() const; GradientColor* kComponentAlphaHover() const; GradientColor* kComponentAlphaClick() const; GradientColor* kErrorDisable() const; GradientColor* kContainGeneralAlphaNormal() const; GradientColor* kSuccessDisable() const; GradientColor* kLineWindowInactive() const; GradientColor* kContainAlphaClick() const; GradientColor* kWarningDisable() const; GradientColor* kDivider() const; GradientColor* kContainAlphaHover() const; GradientColor* kBrandDisable() const; GradientColor* kLineDisable() const; GradientColor* kLineNormal() const; qreal transparency() const; qreal fontSize() const; QString fontFamily() const; Qt::LayoutDirection layoutDirection() const; GradientColor* kBrand8() const; GradientColor* kBrand9() const; GradientColor* kLineSelectboxNormal() const; GradientColor* kLineSelectboxHover() const; GradientColor* kLineSelectboxClick() const; GradientColor* kLineSelectboxSelected() const; GradientColor* kLineSelectboxDisable() const; GradientColor* kFontWhitePlaceholderTextDisable() const; GradientColor* kContainGeneralInactive() const; GradientColor* kFontPlaceholderTextDisable() const; GradientColor* kFontWhitePlaceholderText() const; GradientColor* kLineInputDisable() const; GradientColor* kLineDock() const; GradientColor* kLineMenu() const; GradientColor* kLineTable() const; GradientColor* kLineInputNormal() const; GradientColor* kLineInputHover() const; GradientColor* kLineInputClick() const; GradientColor* kOrange1() const; GradientColor* kGreen1() const; GradientColor* kRed1() const; GradientColor* kDividerWhite() const; GradientColor* kLineSuccessNormal() const; GradientColor* kLineSuccessHover() const; GradientColor* kLineSuccessClick() const; GradientColor* kLineSuccessDisable() const; GradientColor* kLineErrorNormal() const; GradientColor* kLineErrorHover() const; GradientColor* kLineErrorClick() const; GradientColor* kLineWarningNormal() const; GradientColor* kLineWarningHover() const; GradientColor* kLineWarningClick() const; GradientColor* kLineWarningDisable() const; ShadowData* kShadowPrimaryActive() const; ShadowData* kShadowPrimaryInactive() const; ShadowData* kShadowSecondaryActive() const; ShadowData* kShadowSecondaryInactive() const; ShadowData* kShadowMin() const; ShadowData* kShadowMenu() const; ShadowData* kShadowComponent() const; GradientColor* kComponentSelectedNormal() const; GradientColor* kComponentSelectedHover() const; GradientColor* kComponentSelectedClick() const; GradientColor* kComponentSelectedDisable() const; GradientColor* kComponentSelectedAlphaNormal() const; GradientColor* kComponentSelectedAlphaHover() const; GradientColor* kComponentSelectedAlphaClick() const; GradientColor* kComponentSelectedAlphaDisable() const; GradientColor* kComponentInput() const; GradientColor* kComponentInputAlpha() const; GradientColor* kSelectAlphaWhiteDisable() const; GradientColor* kSelectAlphaWhiteHover() const; GradientColor* kSelectAlphaWhiteClick() const; GradientColor* kContainSecondaryInactive() const; GradientColor* kSelectAlphaWhite() const; GradientColor* kFontPlaceholderText() const; GradientColor* kLineErrorDisable() const; GradientColor* kMenu() const; bool isDarkTheme(); private: DtTheme(QQmlEngine *engine, QObject *parent = nullptr); void proportySet(); void addFuncPointer(); void setTheme(); void onThemeColorChanged(); void onWindowRadiusChanged(); void updateHighLightColor(QColor highlightColor); void windowTextActiveSet(QVariant value); void windowTextInactiveSet(QVariant value); void windowTextDisableSet(QVariant value); void buttonActiveSet(QVariant value); void buttonInactiveSet(QVariant value); void buttonDisableSet(QVariant value); void lightActiveSet(QVariant value); void lightInactiveSet(QVariant value); void lightDisableSet(QVariant value); void midLightActiveSet(QVariant value); void midLightInactiveSet(QVariant value); void midLightDisableSet(QVariant value); void darkActiveSet(QVariant value); void darkInactiveSet(QVariant value); void darkDisableSet(QVariant value); void midActiveSet(QVariant value); void midInactiveSet(QVariant value); void midDisableSet(QVariant value); void textActiveSet(QVariant value); void textInactiveSet(QVariant value); void textDisableSet(QVariant value); void brightTextActiveSet(QVariant value); void brightTextInactiveSet(QVariant value); void brightTextDisableSet(QVariant value); void buttonTextActiveSet(QVariant value); void buttonTextInactiveSet(QVariant value); void buttonTextDisableSet(QVariant value); void baseActiveSet(QVariant value); void baseInactiveSet(QVariant value); void baseDisableSet(QVariant value); void windowActiveSet(QVariant value); void windowInactiveSet(QVariant value); void windowDisableSet(QVariant value); void shadowActiveSet(QVariant value); void shadowInactiveSet(QVariant value); void shadowDisableSet(QVariant value); void highlightActiveSet(QVariant value); void highlightInactiveSet(QVariant value); void highlightDisableSet(QVariant value); void highlightedTextActiveSet(QVariant value); void highlightedTextInactiveSet(QVariant value); void highlightedTextDisableSet(QVariant value); void linkActiveSet(QVariant value); void linkInactiveSet(QVariant value); void linkDisableSet(QVariant value); void linkVisitedActiveSet(QVariant value); void linkVisitedInactiveSet(QVariant value); void linkVisitedDisableSet(QVariant value); void alternateBaseActiveSet(QVariant value); void alternateBaseInactiveSet(QVariant value); void alternateBaseDisableSet(QVariant value); void noRoleActiveSet(QVariant value); void noRoleInactiveSet(QVariant value); void noRoleDisableSet(QVariant value); void toolTipBaseActiveSet(QVariant value); void toolTipBaseInactiveSet(QVariant value); void toolTipBaseDisableSet(QVariant value); void toolTipTextActiveSet(QVariant value); void toolTipTextInactiveSet(QVariant value); void toolTipTextDisableSet(QVariant value); void placeholderTextActiveSet(QVariant value); void placeholderTextInactiveSet(QVariant value); void placeholderTextDisableSet(QVariant value); void kWhiteSet(QVariant value); void kBlackSet(QVariant value); void kGray0Set(QVariant value); void kGray1Set(QVariant value); void kGray2Set(QVariant value); void kGray3Set(QVariant value); void kGray4Set(QVariant value); void kGray5Set(QVariant value); void kGray6Set(QVariant value); void kGray7Set(QVariant value); void kGray8Set(QVariant value); void kGray9Set(QVariant value); void kGray10Set(QVariant value); void kGray11Set(QVariant value); void kGray12Set(QVariant value); void kGray13Set(QVariant value); void kGray14Set(QVariant value); void kGray15Set(QVariant value); void kGray16Set(QVariant value); void kGray17Set(QVariant value); void kGrayAlpha0Set(QVariant value); void kGrayAlpha1Set(QVariant value); void kGrayAlpha2Set(QVariant value); void kGrayAlpha3Set(QVariant value); void kGrayAlpha4Set(QVariant value); void kGrayAlpha5Set(QVariant value); void kGrayAlpha6Set(QVariant value); void kGrayAlpha7Set(QVariant value); void kGrayAlpha8Set(QVariant value); void kGrayAlpha9Set(QVariant value); void kGrayAlpha10Set(QVariant value); void kGrayAlpha11Set(QVariant value); void kGrayAlpha12Set(QVariant value); void kGrayAlpha13Set(QVariant value); void kFontStrongSet(QVariant value); void kFontPrimarySet(QVariant value); void kFontPrimaryDisableSet(QVariant value); void kFontSecondarySet(QVariant value); void kFontSecondaryDisableSet(QVariant value); void kFontWhiteSet(QVariant value); void kFontWhiteDisableSet(QVariant value); void kFontWhiteSecondarySet(QVariant value); void kFontWhiteSecondaryDisableSet(QVariant value); void kBrandNormalSet(QVariant value); void kBrandHoverSet(QVariant value); void kBrandClickSet(QVariant value); void kBrandFocusSet(QVariant value); void kSuccessNormalSet(QVariant value); void kSuccessHoverSet(QVariant value); void kSuccessClickSet(QVariant value); void kWarningNormalSet(QVariant value); void kWarningHoverSet(QVariant value); void kWarningClickSet(QVariant value); void kErrorNormalSet(QVariant value); void kErrorHoverSet(QVariant value); void kErrorClickSet(QVariant value); void kBrand1Set(QVariant value); void kBrand2Set(QVariant value); void kBrand3Set(QVariant value); void kBrand4Set(QVariant value); void kBrand5Set(QVariant value); void kBrand6Set(QVariant value); void kBrand7Set(QVariant value); void kContainHoverSet(QVariant value); void kContainClickSet(QVariant value); void kContainGeneralNormalSet(QVariant value); void kContainSecondaryNormalSet(QVariant value); void kContainSecondaryAlphaNormalSet(QVariant value); void kComponentNormalSet(QVariant value); void kComponentHoverSet(QVariant value); void kComponentClickSet(QVariant value); void kComponentDisableSet(QVariant value); void kComponentAlphaNormalSet(QVariant value); void kComponentAlphaHoverSet(QVariant value); void kComponentAlphaClickSet(QVariant value); void kComponentAlphaDisableSet(QVariant value); void kLineWindowActiveSet(QVariant value); void kLineComponentNormalSet(QVariant value); void kLineComponentHoverSet(QVariant value); void kLineComponentClickSet(QVariant value); void kLineComponentDisableSet(QVariant value); void kLineBrandNormalSet(QVariant value); void kLineBrandHoverSet(QVariant value); void kLineBrandClickSet(QVariant value); void kLineBrandDisableSet(QVariant value); void kModalMaskSet(QVariant value); void normalLineSet(QVariant value); void focusLineSet(QVariant value); void kRadiusMinSet(QVariant value); void kRadiusNormalSet(QVariant value); void kRadiusMaxSet(QVariant value); void kRadiusMenuSet(QVariant value); void kRadiusWindowSet(QVariant value); void kMarginMinSet(QVariant value); void kMarginNormalSet(QVariant value); void kMarginBigSet(QVariant value); void kMarginWindowSet(QVariant value); void kMarginComponentSet(QVariant value); void kPaddingMinLeftSet(QVariant value); void kPaddingMinTopSet(QVariant value); void kPaddingMinRightSet(QVariant value); void kPaddingMinBottomSet(QVariant value); void kPaddingNormalLeftSet(QVariant value); void kPaddingNormalTopSet(QVariant value); void kPaddingNormalRightSet(QVariant value); void kPaddingNormalBottomSet(QVariant value); void kPadding8LeftSet(QVariant value); void kPadding8TopSet(QVariant value); void kPadding8RightSet(QVariant value); void kPadding8BottomSet(QVariant value); void kPaddingWindowLeftSet(QVariant value); void kPaddingWindowTopSet(QVariant value); void kPaddingWindowRightSet(QVariant value); void kPaddingWindowBottomSet(QVariant value); void kErrorDisableSet(QVariant value); void kContainGeneralAlphaNormalSet(QVariant value); void kSuccessDisableSet(QVariant value); void kLineWindowInactiveSet(QVariant value); void kContainAlphaClickSet(QVariant value); void kWarningDisableSet(QVariant value); void kDividerSet(QVariant value); void kContainAlphaHoverSet(QVariant value); void kBrandDisableSet(QVariant value); void kLineDisableSet(QVariant value); void kLineNormalSet(QVariant value); void kBrand8Set(QVariant value); void kBrand9Set(QVariant value); void kLineSelectboxNormalSet(QVariant value); void kLineSelectboxHoverSet(QVariant value); void kLineSelectboxClickSet(QVariant value); void kLineSelectboxSelectedSet(QVariant value); void kLineSelectboxDisableSet(QVariant value); void kFontWhitePlaceholderTextDisableSet(QVariant value); void kContainGeneralInactiveSet(QVariant value); void kFontPlaceholderTextDisableSet(QVariant value); void kFontWhitePlaceholderTextSet(QVariant value); void kLineInputDisableSet(QVariant value); void kLineDockSet(QVariant value); void kLineMenuSet(QVariant value); void kLineTableSet(QVariant value); void kLineInputNormalSet(QVariant value); void kLineInputHoverSet(QVariant value); void kLineInputClickSet(QVariant value); void kOrange1Set(QVariant value); void kGreen1Set(QVariant value); void kRed1Set(QVariant value); void kDividerWhiteSet(QVariant value); void kLineSuccessNormalSet(QVariant value); void kLineSuccessHoverSet(QVariant value); void kLineSuccessClickSet(QVariant value); void kLineSuccessDisableSet(QVariant value); void kLineErrorNormalSet(QVariant value); void kLineErrorHoverSet(QVariant value); void kLineErrorClickSet(QVariant value); void kLineWarningNormalSet(QVariant value); void kLineWarningHoverSet(QVariant value); void kLineWarningClickSet(QVariant value); void kLineWarningDisableSet(QVariant value); void kShadowPrimaryActiveSet(QVariant value); void kShadowPrimaryInactiveSet(QVariant value); void kShadowSecondaryActiveSet(QVariant value); void kShadowSecondaryInactiveSet(QVariant value); void kShadowMinSet(QVariant value); void kShadowMenuSet(QVariant value); void kShadowComponentSet(QVariant value); void kComponentSelectedNormalSet(QVariant value); void kComponentSelectedHoverSet(QVariant value); void kComponentSelectedClickSet(QVariant value); void kComponentSelectedDisableSet(QVariant value); void kComponentSelectedAlphaNormalSet(QVariant value); void kComponentSelectedAlphaHoverSet(QVariant value); void kComponentSelectedAlphaClickSet(QVariant value); void kComponentSelectedAlphaDisableSet(QVariant value); void kComponentInputSet(QVariant value); void kComponentInputAlphaSet(QVariant value); void kSelectAlphaWhiteDisableSet(QVariant value); void kSelectAlphaWhiteHoverSet(QVariant value); void kSelectAlphaWhiteClickSet(QVariant value); void kMenuSet(QVariant value); void kLineErrorDisableSet(QVariant value); void kContainSecondaryInactiveSet(QVariant value); void kSelectAlphaWhiteSet(QVariant value); void kFontPlaceholderTextSet(QVariant value); Q_SIGNALS: void windowTextActiveChanged(); void windowTextInactiveChanged(); void windowTextDisableChanged(); void buttonActiveChanged(); void buttonInactiveChanged(); void buttonDisableChanged(); void lightActiveChanged(); void lightInactiveChanged(); void lightDisableChanged(); void midLightActiveChanged(); void midLightInactiveChanged(); void midLightDisableChanged(); void darkActiveChanged(); void darkInactiveChanged(); void darkDisableChanged(); void midActiveChanged(); void midInactiveChanged(); void midDisableChanged(); void textActiveChanged(); void textInactiveChanged(); void textDisableChanged(); void brightTextActiveChanged(); void brightTextInactiveChanged(); void brightTextDisableChanged(); void buttonTextActiveChanged(); void buttonTextInactiveChanged(); void buttonTextDisableChanged(); void baseActiveChanged(); void baseInactiveChanged(); void baseDisableChanged(); void windowActiveChanged(); void windowInactiveChanged(); void windowDisableChanged(); void shadowActiveChanged(); void shadowInactiveChanged(); void shadowDisableChanged(); void highlightActiveChanged(); void highlightInactiveChanged(); void highlightDisableChanged(); void highlightedTextActiveChanged(); void highlightedTextInactiveChanged(); void highlightedTextDisableChanged(); void linkActiveChanged(); void linkInactiveChanged(); void linkDisableChanged(); void linkVisitedActiveChanged(); void linkVisitedInactiveChanged(); void linkVisitedDisableChanged(); void alternateBaseActiveChanged(); void alternateBaseInactiveChanged(); void alternateBaseDisableChanged(); void noRoleActiveChanged(); void noRoleInactiveChanged(); void noRoleDisableChanged(); void toolTipBaseActiveChanged(); void toolTipBaseInactiveChanged(); void toolTipBaseDisableChanged(); void toolTipTextActiveChanged(); void toolTipTextInactiveChanged(); void toolTipTextDisableChanged(); void placeholderTextActiveChanged(); void placeholderTextInactiveChanged(); void placeholderTextDisableChanged(); void kWhiteChanged(); void kBlackChanged(); void kGray0Changed(); void kGray1Changed(); void kGray2Changed(); void kGray3Changed(); void kGray4Changed(); void kGray5Changed(); void kGray6Changed(); void kGray7Changed(); void kGray8Changed(); void kGray9Changed(); void kGray10Changed(); void kGray11Changed(); void kGray12Changed(); void kGray13Changed(); void kGray14Changed(); void kGray15Changed(); void kGray16Changed(); void kGray17Changed(); void kGrayAlpha0Changed(); void kGrayAlpha1Changed(); void kGrayAlpha2Changed(); void kGrayAlpha3Changed(); void kGrayAlpha4Changed(); void kGrayAlpha5Changed(); void kGrayAlpha6Changed(); void kGrayAlpha7Changed(); void kGrayAlpha8Changed(); void kGrayAlpha9Changed(); void kGrayAlpha10Changed(); void kGrayAlpha11Changed(); void kGrayAlpha12Changed(); void kGrayAlpha13Changed(); void kFontStrongChanged(); void kFontPrimaryChanged(); void kFontPrimaryDisableChanged(); void kFontSecondaryChanged(); void kFontSecondaryDisableChanged(); void kFontWhiteChanged(); void kFontWhiteDisableChanged(); void kFontWhiteSecondaryChanged(); void kFontWhiteSecondaryDisableChanged(); void kBrandNormalChanged(); void kBrandHoverChanged(); void kBrandClickChanged(); void kBrandFocusChanged(); void kSuccessNormalChanged(); void kSuccessHoverChanged(); void kSuccessClickChanged(); void kWarningNormalChanged(); void kWarningHoverChanged(); void kWarningClickChanged(); void kErrorNormalChanged(); void kErrorHoverChanged(); void kErrorClickChanged(); void kBrand1Changed(); void kBrand2Changed(); void kBrand3Changed(); void kBrand4Changed(); void kBrand5Changed(); void kBrand6Changed(); void kBrand7Changed(); void kContainHoverChanged(); void kContainClickChanged(); void kContainGeneralNormalChanged(); void kContainSecondaryNormalChanged(); void kContainSecondaryAlphaNormalChanged(); void kComponentNormalChanged(); void kComponentHoverChanged(); void kComponentClickChanged(); void kComponentDisableChanged(); void kComponentAlphaNormalChanged(); void kComponentAlphaHoverChanged(); void kComponentAlphaClickChanged(); void kComponentAlphaDisableChanged(); void kLineWindowActiveChanged(); void kLineComponentNormalChanged(); void kLineComponentHoverChanged(); void kLineComponentClickChanged(); void kLineComponentDisableChanged(); void kLineBrandNormalChanged(); void kLineBrandHoverChanged(); void kLineBrandClickChanged(); void kLineBrandDisableChanged(); void kModalMaskChanged(); void normalLineChanged(); void focusLineChanged(); void kRadiusMinChanged(); void kRadiusNormalChanged(); void kRadiusMaxChanged(); void kRadiusMenuChanged(); void kRadiusWindowChanged(); void kMarginMinChanged(); void kMarginNormalChanged(); void kMarginBigChanged(); void kMarginWindowChanged(); void kMarginComponentChanged(); void kPaddingMinLeftChanged(); void kPaddingMinTopChanged(); void kPaddingMinRightChanged(); void kPaddingMinBottomChanged(); void kPaddingNormalLeftChanged(); void kPaddingNormalTopChanged(); void kPaddingNormalRightChanged(); void kPaddingNormalBottomChanged(); void kPadding8LeftChanged(); void kPadding8TopChanged(); void kPadding8RightChanged(); void kPadding8BottomChanged(); void kPaddingWindowLeftChanged(); void kPaddingWindowTopChanged(); void kPaddingWindowRightChanged(); void kPaddingWindowBottomChanged(); void transparencyChanged(); void kErrorDisableChanged(); void kContainGeneralAlphaNormalChanged(); void kSuccessDisableChanged(); void kLineWindowInactiveChanged(); void kContainAlphaClickChanged(); void kWarningDisableChanged(); void kDividerChanged(); void kContainAlphaHoverChanged(); void kBrandDisableChanged(); void kLineDisableChanged(); void kLineNormalChanged(); void fontSizeChanged(); void fontFamilyChanged(); void iconThemeNameChanged(); void layoutDirectionChanged(); void themeNameChanged(); void kBrand8Changed(); void kBrand9Changed(); void kLineSelectboxNormalChanged(); void kLineSelectboxHoverChanged(); void kLineSelectboxClickChanged(); void kLineSelectboxSelectedChanged(); void kLineSelectboxDisableChanged(); void kFontWhitePlaceholderTextDisableChanged(); void kContainGeneralInactiveChanged(); void kFontPlaceholderTextDisableChanged(); void kFontWhitePlaceholderTextChanged(); void kLineInputDisableChanged(); void kLineDockChanged(); void kLineMenuChanged(); void kLineTableChanged(); void kLineInputNormalChanged(); void kLineInputHoverChanged(); void kLineInputClickChanged(); void kOrange1Changed(); void kGreen1Changed(); void kRed1Changed(); void kDividerWhiteChanged(); void kLineSuccessNormalChanged(); void kLineSuccessHoverChanged(); void kLineSuccessClickChanged(); void kLineSuccessDisableChanged(); void kLineErrorNormalChanged(); void kLineErrorHoverChanged(); void kLineErrorClickChanged(); void kLineWarningNormalChanged(); void kLineWarningHoverChanged(); void kLineWarningClickChanged(); void kLineWarningDisableChanged(); void kShadowPrimaryActiveChanged(); void kShadowPrimaryInactiveChanged(); void kShadowSecondaryActiveChanged(); void kShadowSecondaryInactiveChanged(); void kShadowMinChanged(); void kShadowMenuChanged(); void kShadowComponentChanged(); void kComponentSelectedNormalChanged(); void kComponentSelectedHoverChanged(); void kComponentSelectedClickChanged(); void kComponentSelectedDisableChanged(); void kComponentSelectedAlphaNormalChanged(); void kComponentSelectedAlphaHoverChanged(); void kComponentSelectedAlphaClickChanged(); void kComponentSelectedAlphaDisableChanged(); void kComponentInputChanged(); void kComponentInputAlphaChanged(); void kSelectAlphaWhiteDisableChanged(); void kSelectAlphaWhiteHoverChanged(); void kSelectAlphaWhiteClickChanged(); void kMenuChanged(); void kLineErrorDisableChanged(); void kContainSecondaryInactiveChanged(); void kSelectAlphaWhiteChanged(); void kFontPlaceholderTextChanged(); private: DtThemePrivate *d = nullptr; }; } // UkuiQuick #endif //DT_THEME_H ukui-quick/platform/ukui/function-control.h0000664000175000017500000000527715153755732020074 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #ifndef FUNCTION_CONTROL_H #define FUNCTION_CONTROL_H #include namespace UkuiQuick { class FunctionControlPrivate; /** * FunctionControl是一个系统功能管控接口封装类,用于管理一些功能的开关 * */ class FunctionControl : public QObject { Q_OBJECT public: /** * @brief 获取FunctionControl实例 * @return FunctionControl* FunctionControl实例 * @note 此类线程不安全,不要在多线程环境中使用 */ static FunctionControl* instance(); ~FunctionControl() override; /** * @brief 添加管控模块@param name 配置监听,当模块配置发生变化时,会发送functionControlChanged信号 * 使用此方法添加的监听,会在FunctionControl内部增加一个监听计数,当监听计数为0时,会自动移除监听。 * 使用此方法添加的监听,可以调用removeWatch方法来移除监听。 * @param name 功能名称 */ void addWatch(const QString& name); /** * @brief 将针对@param name 的配置监听引用计数减一 * 当监听计数为0时,会自动移除监听。 * @param name 功能名称 */ void removeWatch(const QString& name); /** * @brief 直接移除所有针对功能@param name 的配置监听 * @param name 功能名称 */ void removeAllWatches(const QString& name); /** * @brief 获取功能@param name 的配置 * @param name 功能名称 * @return QJsonObject 功能配置 */ QJsonObject functionControlObject(const QString& name); Q_SIGNALS: /** * @brief 功能配置发生变化时,会发送此信号 * @param name 功能名称 */ void functionControlChanged(const QString& name); /** * @brief 当管控功能退出时,会发送此信号 * */ void functionControlExit(); private: explicit FunctionControl(QObject* parent = nullptr); FunctionControlPrivate *d = nullptr; }; } #endif //FUNCTION_CONTROL_H ukui-quick/platform/ukui/shadow-data.cpp0000664000175000017500000001320215153756415017302 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: amingamingaming * */ #include "shadow-data.h" namespace UkuiQuick { ShadowData::ShadowData(QObject* parent): QObject(parent) { } ShadowData::~ShadowData() { if(m_childShadow) { delete m_childShadow; m_childShadow = nullptr; } } ShadowData::ShadowData(int x, int y, int blurRadius, int spread, const QColor&color, bool isMulti, ShadowData* child, QObject* parent) : QObject(parent) , m_xOffset(x) , m_yOffset(y) , m_blurRadius(blurRadius) , m_spread(spread) , m_color(color) , m_isMultiShadow(isMulti) { setChildShadow(child); } ShadowData::ShadowData(const ShadowData &shadow) { m_xOffset = shadow.xOffset(); m_yOffset = shadow.yOffset(); m_blurRadius = shadow.blurRadius(); m_spread = shadow.spread(); m_color = shadow.shadowColor(); m_isMultiShadow = shadow.isMultiShadow(); setChildShadow(shadow.childShadow()); } ShadowData &ShadowData::operator=(const ShadowData &shadow) { m_xOffset = shadow.xOffset(); m_yOffset = shadow.yOffset(); m_blurRadius = shadow.blurRadius(); m_spread = shadow.spread(); m_color = shadow.shadowColor(); m_isMultiShadow = shadow.isMultiShadow(); setChildShadow(shadow.childShadow()); return *this; } bool ShadowData::operator!=(const ShadowData &shadow) { if(m_xOffset == shadow.xOffset() && m_yOffset == shadow.yOffset()&& m_blurRadius == shadow.blurRadius() && m_spread == shadow.spread() && m_color == shadow.shadowColor() && m_isMultiShadow == shadow.isMultiShadow()) { if (m_isMultiShadow && m_childShadow && shadow.childShadow()){ return *m_childShadow != *shadow.childShadow(); } return false; } return true; } void ShadowData::setXOffset(int x) { if(m_xOffset != x) { m_xOffset = x; Q_EMIT xOffsetChanged(); } } void ShadowData::setYOffset(int y) { if(m_yOffset != y) { m_yOffset = y; Q_EMIT yOffsetChanged(); } } void ShadowData::setBlurRadius(int radius) { if(m_blurRadius != radius) { m_blurRadius = radius; Q_EMIT blurRadiusChanged(); } } void ShadowData::setSpread(int spread) { if(m_spread != spread) { m_spread = spread; Q_EMIT spreadChanged(); } } void ShadowData::setShadowColor(const QColor &color) { if(m_color != color) { m_color = color; Q_EMIT shadowColorChanged(); } } void ShadowData::setIsMultiShadow(bool isMultiShadow) { if(m_isMultiShadow != isMultiShadow) { m_isMultiShadow = isMultiShadow; Q_EMIT isMultiShadowChanged(); } } void ShadowData::setChildShadow(ShadowData *child) { if(child && m_childShadow && child != m_childShadow) { m_childShadow->setXOffset(child->xOffset()); m_childShadow->setYOffset(child->yOffset()); m_childShadow->setBlurRadius(child->blurRadius()); m_childShadow->setSpread(child->spread()); m_childShadow->setShadowColor(child->shadowColor()); m_childShadow->setIsMultiShadow(child->isMultiShadow()); Q_EMIT childShadowChanged(); } else if (child && !m_childShadow) { m_childShadow = new ShadowData(child->xOffset(), child->yOffset(), child->blurRadius(), child->spread(), child->shadowColor()); setIsMultiShadow(true); Q_EMIT childShadowChanged(); } else if (!child && m_childShadow) { setIsMultiShadow(false); delete m_childShadow; m_childShadow = nullptr; Q_EMIT childShadowChanged(); } if (child) { connect(child, &ShadowData::xOffsetChanged, m_childShadow, [child, this]() { m_childShadow->setXOffset(child->xOffset()); }); connect(child, &ShadowData::yOffsetChanged, m_childShadow, [child, this]() { m_childShadow->setYOffset(child->yOffset()); }); connect(child, &ShadowData::blurRadiusChanged, m_childShadow, [child, this]() { m_childShadow->setBlurRadius(child->blurRadius()); }); connect(child, &ShadowData::spreadChanged, m_childShadow, [child, this]() { m_childShadow->setSpread(child->spread()); }); connect(child, &ShadowData::shadowColorChanged, m_childShadow, [child, this]() { m_childShadow->setShadowColor(child->shadowColor()); }); } } int ShadowData::xOffset() const { return m_xOffset; } int ShadowData::yOffset() const { return m_yOffset; } int ShadowData::blurRadius() const { return m_blurRadius; } int ShadowData::spread() const { return m_spread; } QColor ShadowData::shadowColor() const { return m_color; } bool ShadowData::isMultiShadow() const { return m_isMultiShadow; } ShadowData *ShadowData::childShadow() const { return m_childShadow; } } ukui-quick/platform/ukui/gradient-color.cpp0000664000175000017500000001337115167355777020041 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: amingamingaming * */ #include "gradient-color.h" #include #include namespace UkuiQuick { GradientColor::GradientColor() { m_gradientDeg = 0; m_gradientStart = QColor(0,0,0,0); m_gradientEnd = QColor(0,0,0,0); m_gradientBacrground = QColor(0,0,0,0); m_pureColor = QColor(0,0,0,0); m_colorType = DtColorType::Type::Pure; } GradientColor::GradientColor(QColor pure) { m_gradientDeg = 0; m_gradientStart = QColor(0,0,0,0); m_gradientEnd = QColor(0,0,0,0); m_gradientBacrground = QColor(0,0,0,0); m_pureColor = pure; m_colorType = DtColorType::Type::Pure; } GradientColor::GradientColor(int deg, QColor start, QColor end, QColor background) { m_gradientDeg = deg; m_gradientStart = start; m_gradientEnd = end; m_gradientBacrground = background; m_colorType = DtColorType::Type::Gradient; } GradientColor::GradientColor(const GradientColor &color) { m_gradientDeg = color.gradientDeg(); m_gradientStart = color.gradientStart(); m_gradientEnd = color.gradientEnd(); m_gradientBacrground = color.gradientBackground(); m_pureColor = color.pureColor(); m_colorType = color.colorType(); } GradientColor &GradientColor::operator= (const GradientColor &color) { m_gradientDeg = color.gradientDeg(); m_gradientStart = color.gradientStart(); m_gradientEnd = color.gradientEnd(); m_gradientBacrground = color.gradientBackground(); m_pureColor = color.pureColor(); m_colorType = color.colorType(); return *this; } bool GradientColor::operator!= (GradientColor *color) { if(color == nullptr) { return true; } if(m_colorType != color->colorType()) { return true; } if(m_colorType == DtColorType::Pure && m_pureColor == color->pureColor()) { return false; } if(m_colorType == DtColorType::Gradient && m_gradientDeg == color->gradientDeg() && m_gradientStart == color->gradientStart() && m_gradientEnd == color->gradientEnd() && m_gradientBacrground == color->gradientBackground()) { return false; } return true; } bool GradientColor::operator!= (QColor color) { if(m_colorType == DtColorType::Gradient) { return true; } if(m_pureColor == color) { return false; } return true; } QColor GradientColor::gradientStart() const { return m_gradientStart; }; QColor GradientColor::gradientEnd() const { return m_gradientEnd; }; QColor GradientColor::gradientBackground() const { return m_gradientBacrground; }; int GradientColor::gradientDeg() const { return m_gradientDeg; }; QColor GradientColor::pureColor() const { return m_pureColor; } DtColorType::Type GradientColor::colorType() const { return m_colorType; } void GradientColor::setGradientStart(QColor start) { if(m_gradientStart != start) { m_gradientStart = start; Q_EMIT gradientStartChanged(); } } void GradientColor::setGradientEnd(QColor end) { if(m_gradientEnd != end) { m_gradientEnd = end; Q_EMIT gradientEndChanged(); } } void GradientColor::setGradientBackground(QColor background) { if(m_gradientBacrground != background) { m_gradientBacrground = background; Q_EMIT gradientBackgroundChanged(); } } void GradientColor::setGradientDeg(int deg) { if(m_gradientDeg != deg) { m_gradientDeg = deg; Q_EMIT gradientDegChanged(); } } void GradientColor::setPureColor(QColor color) { if(m_pureColor != color) { m_pureColor = color; Q_EMIT pureColorChanged(); } } void GradientColor::setColorType(UkuiQuick::DtColorType::Type type) { if(m_colorType != type) { m_colorType = type; Q_EMIT colorTypeChanged(); } } QColor GradientColor::setAlphaF(QColor color, qreal alpha) { color.setAlphaF(color.alphaF() * alpha > 1? 1 : color.alphaF() * alpha); return color; } QColor GradientColor::mixBackGroundColor(const QColor &color, const QColor &back, const qreal &alpha) { if(color.alpha() == 0) { return back; } if(back.alpha() == 0) { return color; } qreal a = color.alphaF() + (1 - color.alphaF()) * back.alphaF(); if(a == 0) { return QColor(0, 0, 0, 0); } qreal r = (color.red() * color.alphaF() * (1 - back.alphaF()) + back.red() * back.alphaF()) / a; qreal g = (color.green() * color.alphaF() * (1 - back.alphaF()) + back.green() * back.alphaF()) / a; qreal b = (color.blue() * color.alphaF() * (1 - back.alphaF()) + back.blue() * back.alphaF()) / a; a = a * alpha; return QColor(qRound(r), qRound(g), qRound(b), qRound(a > 1 ? 255 : a * 255)); } QPoint GradientColor::getGradientStartPoint(int deg, int width, int height) { int x = (0.5 - 0.5 * qCos(deg * M_PI / 180)) * width; int y = (0.5 - 0.5 * qSin(deg * M_PI / 180)) * height; return QPoint(x, y); } QPoint GradientColor::getGradientEndPoint(int deg, int width, int height) { int x = (0.5 + 0.5 * qCos(deg * M_PI / 180)) * width; int y = (0.5 + 0.5 * qSin(deg * M_PI / 180)) * height; return QPoint(x, y); } } ukui-quick/platform/ukui/application-icon-proxy.h0000664000175000017500000000306115153755732021166 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #ifndef APPLICATION_ICON_PROXY_H #define APPLICATION_ICON_PROXY_H #include namespace UkuiQuick { class ApplicationIconProxyPrivate; class ApplicationIconProxy : public QObject { Q_OBJECT Q_PROPERTY(QString appId READ appId WRITE setAppId NOTIFY appIdChanged FINAL) Q_PROPERTY(QString icon READ icon NOTIFY iconChanged FINAL) public: explicit ApplicationIconProxy(QObject *parent = nullptr); explicit ApplicationIconProxy(const QString &appId, QObject *parent = nullptr); ~ApplicationIconProxy() override; QString appId() const; void setAppId(const QString &appId); QString icon() const; Q_SIGNALS: void appIdChanged(); void iconChanged(); private: ApplicationIconProxyPrivate *d = nullptr; Q_DISABLE_COPY(ApplicationIconProxy) }; } #endif //APPLICATION_ICON_PROXY_H ukui-quick/platform/ukui/application-icon-proxy.cpp0000664000175000017500000000712415153755732021525 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #include "application-icon-proxy.h" #include #include #include #include #include namespace UkuiQuick { class ApplicationIconConfig : public QObject { Q_OBJECT public: static ApplicationIconConfig* instance(); QString getIcon(const QString &appId) const; Q_SIGNALS: void changed(); private: explicit ApplicationIconConfig(QObject *parent = nullptr); QJsonObject m_map; QString m_prefix; }; static ApplicationIconConfig *g_instance = nullptr; static std::once_flag onceFlag; ApplicationIconConfig* ApplicationIconConfig::instance() { std::call_once(onceFlag, [ & ] { g_instance = new ApplicationIconConfig(); }); return g_instance; } QString ApplicationIconConfig::getIcon(const QString &appId) const { if (appId.isEmpty()) { return {}; } if (m_map.contains(appId)) { return QDir::cleanPath(m_prefix + "/" + m_map.value(appId).toString()); } return {}; } ApplicationIconConfig::ApplicationIconConfig(QObject* parent) : QObject(parent) { QString path = QStringLiteral("/usr/share/config/themeconfig/iconsoverride/iconsoverride.json"); QFile file(path); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "ApplicationIconConfig: Failed to open config file:" << path; return; } QByteArray data = file.readAll(); file.close(); QJsonParseError parseError{}; QJsonDocument doc = QJsonDocument::fromJson(data, &parseError); if (doc.isNull()) { qWarning() << "ApplicationIconConfig: Failed to parse JSON:" << parseError.errorString() << path; } if (!doc.isObject()) { qWarning() << "ApplicationIconConfig: Config file format error." << path; return; } QJsonObject obj = doc.object(); m_map = obj.value("rules").toObject(); m_prefix = obj.value("prefix").toString(); //TODO 监听配置文件变化 } class ApplicationIconProxyPrivate { public: QString appId; QString icon; }; ApplicationIconProxy::ApplicationIconProxy(QObject* parent) : QObject(parent), d(new ApplicationIconProxyPrivate) { } ApplicationIconProxy::ApplicationIconProxy(const QString &appId, QObject* parent) : QObject(parent), d(new ApplicationIconProxyPrivate) { d->appId = appId; d->icon = ApplicationIconConfig::instance()->getIcon(appId); } ApplicationIconProxy::~ApplicationIconProxy() { if (d) { delete d; d = nullptr; } } QString ApplicationIconProxy::appId() const { return d->appId; } void ApplicationIconProxy::setAppId(const QString &appId) { if (d->appId != appId) { d->appId = appId; Q_EMIT appIdChanged(); d->icon = ApplicationIconConfig::instance()->getIcon(appId); Q_EMIT iconChanged(); } } QString ApplicationIconProxy::icon() const { return d->icon; } } #include "application-icon-proxy.moc"ukui-quick/platform/ukui/dt-theme-definition.h0000775000175000017500000007007315153755732020425 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #ifndef DTTHEMEDEFINITION_H #define DTTHEMEDEFINITION_H #include "gradient-color.h" #include "shadow-data.h" #define DESIGN_TOKENS \ GradientColor m_windowTextActive = GradientColor(QColor(0, 0, 0, 0.88 * 255)); \ GradientColor m_windowTextInactive = GradientColor(QColor(0, 0, 0, 0.28 * 255)); \ GradientColor m_windowTextDisable = GradientColor(QColor(0, 0, 0, 0.28 * 255)); \ GradientColor m_buttonActive = GradientColor(QColor(240, 240, 240, 1 * 255)); \ GradientColor m_buttonInactive = GradientColor(QColor(240, 240, 240, 1 * 255)); \ GradientColor m_buttonDisable = GradientColor(QColor(240, 240, 240, 045 * 255)); \ GradientColor m_lightActive = GradientColor(QColor(255, 255, 255, 1 * 255)); \ GradientColor m_lightInactive = GradientColor(QColor(255, 255, 255, 1 * 255)); \ GradientColor m_lightDisable = GradientColor(QColor(255, 255, 255, 0.45 * 255)); \ GradientColor m_midLightActive = GradientColor(QColor(214, 214, 214, 1 * 255)); \ GradientColor m_midLightInactive = GradientColor(QColor(214, 214, 214, 1 * 255)); \ GradientColor m_midLightDisable = GradientColor(QColor(204, 204, 204, 0.42 * 255)); \ GradientColor m_darkActive = GradientColor(QColor(0, 0, 0, 1 * 255)); \ GradientColor m_darkInactive = GradientColor(QColor(0, 0, 0, 1 * 255)); \ GradientColor m_darkDisable = GradientColor(QColor(0, 0, 0, 1 * 255)); \ GradientColor m_midActive = GradientColor(QColor(102, 102, 102, 1 * 255)); \ GradientColor m_midInactive = GradientColor(QColor(102, 102, 102, 1 * 255)); \ GradientColor m_midDisable = GradientColor(QColor(102, 102, 102, 0.4 * 255)); \ GradientColor m_textActive = GradientColor(QColor(0, 0, 0, 0.88 * 255)); \ GradientColor m_textInactive = GradientColor(QColor(0, 0, 0, 0.88 * 255)); \ GradientColor m_textDisable = GradientColor(QColor(0, 0, 0, 0.28 * 255)); \ GradientColor m_brightTextActive = GradientColor(QColor(0, 0, 0, 1 * 255)); \ GradientColor m_brightTextInactive = GradientColor(QColor(0, 0, 0, 1 * 255)); \ GradientColor m_brightTextDisable = GradientColor(QColor(0, 0, 0, 0.45 * 255)); \ GradientColor m_buttonTextActive = GradientColor(QColor(0, 0, 0, 0.88 * 255)); \ GradientColor m_buttonTextInactive = GradientColor(QColor(0, 0, 0, 0.88 * 255)); \ GradientColor m_buttonTextDisable = GradientColor(QColor(0, 0, 0, 0.28 * 255)); \ GradientColor m_baseActive = GradientColor(QColor(255, 255, 255, 1 * 255)); \ GradientColor m_baseInactive = GradientColor(QColor(250, 250, 250, 1 * 255)); \ GradientColor m_baseDisable = GradientColor(QColor(250, 250, 250, 1 * 255)); \ GradientColor m_windowActive = GradientColor(QColor(245, 245, 245, 1 * 255)); \ GradientColor m_windowInactive = GradientColor(QColor(240, 240, 240, 1 * 255)); \ GradientColor m_windowDisable = GradientColor(QColor(240, 240, 240, 1 * 255)); \ GradientColor m_shadowActive = GradientColor(QColor(0, 0, 0, 0.25 * 255)); \ GradientColor m_shadowInactive = GradientColor(QColor(0, 0, 0, 0.18 * 255)); \ GradientColor m_shadowDisable = GradientColor(QColor(0, 0, 0, 0.11 * 255)); \ GradientColor m_highlightActive = GradientColor(QColor(55, 118, 245, 1 * 255)); \ GradientColor m_highlightInactive = GradientColor(QColor(55, 118, 245, 1 * 255)); \ GradientColor m_highlightDisable = GradientColor(QColor(55, 118, 245, 0.4 * 255)); \ GradientColor m_highlightedTextActive = GradientColor(QColor(255, 255, 255, 0.98 * 255)); \ GradientColor m_highlightedTextInactive = GradientColor(QColor(255, 255, 255, 0.98 * 255)); \ GradientColor m_highlightedTextDisable = GradientColor(QColor(255, 255, 255, 0.68 * 255)); \ GradientColor m_linkActive = GradientColor(QColor(46, 95, 209, 1 * 255)); \ GradientColor m_linkInactive = GradientColor(QColor(46, 95, 209, 1 * 255)); \ GradientColor m_linkDisable = GradientColor(QColor(46, 95, 209, 0.4 * 255)); \ GradientColor m_linkVisitedDisable = GradientColor(QColor(120, 48, 191, 0.4 * 255)); \ GradientColor m_alternateBaseActive = GradientColor(QColor(0, 0, 0, 0.03 * 255)); \ GradientColor m_alternateBaseInactive = GradientColor(QColor(0, 0, 0, 0.01 * 255)); \ GradientColor m_alternateBaseDisable = GradientColor(QColor(0, 0, 0, 0.01 * 255)); \ GradientColor m_noRoleActive = GradientColor(QColor(250, 250, 250, 1 * 255)); \ GradientColor m_noRoleInactive = GradientColor(QColor(250, 250, 250, 1 * 255)); \ GradientColor m_noRoleDisable = GradientColor(QColor(250, 250, 250, 0.42 * 255)); \ GradientColor m_toolTipBaseActive = GradientColor(QColor(255, 255, 255, 1 * 255)); \ GradientColor m_toolTipBaseInactive = GradientColor(QColor(255, 255, 255, 1 * 255)); \ GradientColor m_toolTipBaseDisable = GradientColor(QColor(255, 255, 255, 0.4 * 255)); \ GradientColor m_toolTipTextActive = GradientColor(QColor(0, 0, 0, 0.88 * 255)); \ GradientColor m_toolTipTextInactive = GradientColor(QColor(0, 0, 0, 0.88 * 255)); \ GradientColor m_toolTipTextDisable = GradientColor(QColor(0, 0, 0, 0.28 * 255)); \ GradientColor m_placeholderTextActive = GradientColor(QColor(0, 0, 0, 0.28 * 255)); \ GradientColor m_placeholderTextInactive = GradientColor(QColor(0, 0, 0, 0.28 * 255)); \ GradientColor m_placeholderTextDisable = GradientColor(QColor(0, 0, 0, 0.12 * 255)); \ \ GradientColor m_kWhite = GradientColor(QColor(255, 255, 255, 1 * 255)); \ GradientColor m_kBlack = GradientColor(QColor(0, 0, 0, 1 * 255)); \ GradientColor m_kGray0 = GradientColor(QColor(255, 255, 255, 1 * 255)); \ GradientColor m_kGray1 = GradientColor(QColor(250, 250, 250, 1 * 255)); \ GradientColor m_kGray2 = GradientColor(QColor(245, 245, 245, 1 * 255)); \ GradientColor m_kGray3 = GradientColor(QColor(240, 240, 240, 1 * 255)); \ GradientColor m_kGray4 = GradientColor(QColor(235, 235, 235, 1 * 255)); \ GradientColor m_kGray5 = GradientColor(QColor(229, 229, 229, 1 * 255)); \ GradientColor m_kGray6 = GradientColor(QColor(224, 224, 224, 1 * 255)); \ GradientColor m_kGray7 = GradientColor(QColor(214, 214, 214, 1 * 255)); \ GradientColor m_kGray8 = GradientColor(QColor(199, 199, 199, 1 * 255)); \ GradientColor m_kGray9 = GradientColor(QColor(184, 184, 184, 1 * 255)); \ GradientColor m_kGray10 = GradientColor(QColor(163, 163, 163, 1 * 255)); \ GradientColor m_kGray11 = GradientColor(QColor(143, 143, 143, 1 * 255)); \ GradientColor m_kGray12 = GradientColor(QColor(122, 122, 122, 1 * 255)); \ GradientColor m_kGray13 = GradientColor(QColor(102, 102, 102, 1 * 255)); \ GradientColor m_kGray14 = GradientColor(QColor(82, 82, 82, 1 * 255)); \ GradientColor m_kGray15 = GradientColor(QColor(61, 61, 61, 1 * 255)); \ GradientColor m_kGray16 = GradientColor(QColor(41, 41, 41, 1 * 255)); \ GradientColor m_kGray17 = GradientColor(QColor(26, 26, 26, 1 * 255)); \ GradientColor m_kGrayAlpha0 = GradientColor(QColor(0, 0, 0, 0)); \ GradientColor m_kGrayAlpha1 = GradientColor(QColor(0, 0, 0, 0.04 * 255)); \ GradientColor m_kGrayAlpha2 = GradientColor(QColor(0, 0, 0, 0.06 * 255)); \ GradientColor m_kGrayAlpha3 = GradientColor(QColor(0, 0, 0, 0.08 * 255)); \ GradientColor m_kGrayAlpha4 = GradientColor(QColor(0, 0, 0, 0.12 * 255)); \ GradientColor m_kGrayAlpha5 = GradientColor(QColor(0, 0, 0, 0.16 * 255)); \ GradientColor m_kGrayAlpha6 = GradientColor(QColor(0, 0, 0, 0.24 * 255)); \ GradientColor m_kGrayAlpha7 = GradientColor(QColor(0, 0, 0, 0.30 * 255)); \ GradientColor m_kGrayAlpha8 = GradientColor(QColor(0, 0, 0, 0.38 * 255)); \ GradientColor m_kGrayAlpha9 = GradientColor(QColor(0, 0, 0, 0.42 * 255)); \ GradientColor m_kGrayAlpha10 = GradientColor(QColor(0, 0, 0, 0.55 * 255)); \ GradientColor m_kGrayAlpha11 = GradientColor(QColor(0, 0, 0, 0.9 * 255)); \ GradientColor m_kGrayAlpha12 = GradientColor(QColor(0, 0, 0, 0.02 * 255)); \ GradientColor m_kGrayAlpha13 = GradientColor(QColor(0, 0, 0, 0.75 * 255)); \ \ GradientColor m_kFontStrong = GradientColor(QColor(0, 0, 0, 1 * 255)); \ GradientColor m_kFontPrimary = GradientColor(QColor(0, 0, 0, 0.88 * 255)); \ GradientColor m_kFontPrimaryDisable = GradientColor(QColor(0, 0, 0, 0.28 * 255)); \ GradientColor m_kFontSecondary = GradientColor(QColor(0, 0, 0, 0.4 * 255)); \ GradientColor m_kFontSecondaryDisable = GradientColor(QColor(0, 0, 0, 0.15 * 255)); \ GradientColor m_kFontWhite = GradientColor(QColor(255, 255, 255, 0.98 * 255)); \ GradientColor m_kFontWhiteDisable = GradientColor(QColor(255, 255, 255, 0.68 * 255)); \ GradientColor m_kFontWhiteSecondary = GradientColor(QColor(255, 255, 255, 0.65 * 255)); \ GradientColor m_kFontWhiteSecondaryDisable = GradientColor(QColor(255, 255, 255, 0.24 * 255)); \ \ GradientColor m_kBrandNormal = GradientColor(QColor(54, 118, 245, 1 * 255)); \ GradientColor m_kSuccessNormal = GradientColor(QColor(24, 168, 13, 1 * 255)); \ GradientColor m_kWarningNormal = GradientColor(QColor(255, 128, 31, 1 * 255)); \ GradientColor m_kErrorNormal = GradientColor(QColor(245, 73, 73, 1 * 255)); \ GradientColor m_kBrand1 = GradientColor(QColor(55, 144, 250, 1 * 255)); \ GradientColor m_kBrand2 = GradientColor(QColor(137, 38, 235, 1 * 255)); \ GradientColor m_kBrand3 = GradientColor(QColor(228, 74, 232, 1 * 255)); \ GradientColor m_kBrand4 = GradientColor(QColor(242, 181, 39, 1 * 255)); \ GradientColor m_kBrand5 = GradientColor(QColor(20, 166, 33, 1 * 255)); \ GradientColor m_kBrand6 = GradientColor(QColor(240, 73, 67, 1 * 255)); \ GradientColor m_kBrand7 = GradientColor(QColor(250, 125, 15, 1 * 255)); \ \ GradientColor m_kContainGeneralNormal = GradientColor(QColor(255, 255, 255, 1 * 255)); \ GradientColor m_kContainSecondaryNormal = GradientColor(QColor(250, 250, 250, 1 * 255)); \ GradientColor m_kContainSecondaryAlphaNormal = GradientColor(QColor(0, 0, 0, 0.02 * 255)); \ GradientColor m_kComponentNormal = GradientColor(QColor(240, 240, 240, 1 * 255)); \ GradientColor m_kComponentDisable = GradientColor(QColor(245, 245, 245, 0.45 * 255)); \ GradientColor m_kComponentAlphaNormal = GradientColor(QColor(0, 0, 0, 0.04 * 255)); \ GradientColor m_kComponentAlphaDisable = GradientColor(QColor(0, 0, 0, 0.02 * 255)); \ GradientColor m_kLineWindowActive = GradientColor(QColor(255, 255, 255, 0.7 * 255)); \ GradientColor m_kLineNormal = GradientColor(QColor(0, 0, 0, 0.06 * 255)); \ GradientColor m_kLineComponentNormal = GradientColor(QColor(0, 0, 0, 0.06 * 255)); \ GradientColor m_kLineComponentHover = GradientColor(QColor(0, 0, 0, 0.08 * 255)); \ GradientColor m_kLineComponentClick = GradientColor(QColor(0, 0, 0, 0.12 * 255)); \ GradientColor m_kLineComponentDisable = GradientColor(QColor(0, 0, 0, 0.03 * 255)); \ GradientColor m_kLineBrandNormal = GradientColor(QColor(54, 118, 245, 0.55 * 255)); \ GradientColor m_kLineBrandHover = GradientColor(0, QColor(0,0,0,0.1*255), QColor(0,0,0,0.1*255), QColor(54,118,245,0.55*255));\ GradientColor m_kLineBrandClick = GradientColor(0, QColor(0,0,0,0.2*255), QColor(0,0,0,0.2*255), QColor(54,118,245,0.55*255));\ GradientColor m_kLineBrandDisable = GradientColor(QColor(54, 118, 245, 0.23 * 255)); \ GradientColor m_kModalMask = GradientColor(QColor(0, 0, 0, 0.24 * 255)); \ \ int m_normalLine = 1; \ int m_focusLine = 2; \ \ int m_kRadiusMin = 4; \ int m_kRadiusNormal = 6; \ int m_kRadiusMax = 8; \ int m_kRadiusMenu = 8; \ int m_kRadiusWindow = 12; \ \ int m_kMarginMin = 4; \ int m_kMarginNormal = 8; \ int m_kMarginBig = 16; \ int m_kMarginWindow = 24; \ int m_kMarginComponent = 40; \ \ int m_kPaddingMinLeft = 8; \ int m_kPaddingMinTop = 2; \ int m_kPaddingMinRight = 8; \ int m_kPaddingMinBottom = 2; \ int m_kPaddingNormalLeft = 16; \ int m_kPaddingNormalTop = 2; \ int m_kPaddingNormalRight = 16; \ int m_kPaddingNormalBottom = 2; \ int m_kPadding8Left = 8; \ int m_kPadding8Top = 8; \ int m_kPadding8Right = 8; \ int m_kPadding8Bottom = 8; \ int m_kPaddingWindowLeft = 24; \ int m_kPaddingWindowTop = 16; \ int m_kPaddingWindowRight = 24; \ int m_kPaddingWindowBottom = 24; \ \ GradientColor m_kErrorHover = GradientColor(0, QColor(0,0,0,0.1*255), QColor(0,0,0,0.1*255), QColor(245,73,73,1*255));\ GradientColor m_kErrorClick = GradientColor(0, QColor(0,0,0,0.2*255), QColor(0,0,0,0.2*255), QColor(245,73,73,1*255));\ GradientColor m_kBrandClick = GradientColor(0, QColor(0,0,0,0.2*255), QColor(0,0,0,0.2*255), QColor(54,118,245,1*255));\ GradientColor m_kBrandFocus = GradientColor(QColor(0, 0, 0, 0.75 * 255)); \ GradientColor m_kBrandHover = GradientColor(0, QColor(0,0,0,0.1*255), QColor(0,0,0,0.1*255), QColor(54,118,245,1*255));\ GradientColor m_kContainHover = GradientColor(QColor(245, 245, 245, 1 * 255)); \ GradientColor m_kContainClick = GradientColor(QColor(235, 235, 235, 1 * 255)); \ GradientColor m_kComponentHover = GradientColor(QColor(245, 245, 245, 1 * 255)); \ GradientColor m_kComponentClick = GradientColor(QColor(224, 224, 224, 1 * 255)); \ GradientColor m_kComponentAlphaHover = GradientColor(QColor(0,0,0,0.08*255)); \ GradientColor m_kComponentAlphaClick = GradientColor(QColor(0,0,0,0.12*255)); \ GradientColor m_kSuccessHover = GradientColor(0, QColor(0,0,0,0.1*255), QColor(0,0,0,0.1*255), QColor(24,168,13,1*255));\ GradientColor m_kSuccessClick = GradientColor(0, QColor(0,0,0,0.2*255), QColor(0,0,0,0.2*255), QColor(0,180,250,1*255));\ GradientColor m_kWarningHover = GradientColor(0, QColor(0,0,0,0.1*255), QColor(0,0,0,0.1*255), QColor(255,128,31,1*255));\ GradientColor m_kWarningClick = GradientColor(0, QColor(0,0,0,0.2*255), QColor(0,0,0,0.2*255), QColor(255,128,31,1*255));\ GradientColor m_linkVisitedActive = GradientColor(QColor(120, 48, 191, 1 * 255)); \ GradientColor m_linkVisitedInactive = GradientColor(QColor(120, 48, 191, 1 * 255)); \ GradientColor m_kSuccessDisable = GradientColor(QColor(24, 168, 13, 0.45 * 255)); \ GradientColor m_kWarningDisable = GradientColor(QColor(255, 128, 31, 0.45 * 255)); \ GradientColor m_kErrorDisable = GradientColor(QColor(245, 73, 73, 0.45 * 255)); \ GradientColor m_kLineWindowInactive = GradientColor(QColor(255, 255, 255, 0.4 * 255)); \ GradientColor m_kContainAlphaClick = GradientColor(QColor(0, 0, 0, 0.08 * 255)); \ GradientColor m_kContainAlphaHover = GradientColor(QColor(0, 0, 0, 0.04 * 255)); \ GradientColor m_kBrandDisable = GradientColor(QColor(54, 118, 245, 0.4 * 255)); \ GradientColor m_kLineDisable = GradientColor(QColor(0, 0, 0, 0.03 * 255)); \ GradientColor m_kDivider = GradientColor(QColor(0, 0, 0, 0.08 * 255)); \ GradientColor m_kContainGeneralAlphaNormal = GradientColor(QColor(0, 0, 0, 0)); \ \ GradientColor m_kBrand8 = GradientColor(QColor(54, 118, 245, 1 * 255)); \ GradientColor m_kBrand9 = GradientColor(QColor(6, 192, 199, 1 * 255)); \ GradientColor m_kLineSelectboxNormal = GradientColor(QColor(0, 0, 0, 0.2 * 255)); \ GradientColor m_kLineSelectboxHover = GradientColor(QColor(0, 0, 0, 0.24 * 255)); \ GradientColor m_kLineSelectboxClick = GradientColor(QColor(0, 0, 0, 0.28 * 255)); \ GradientColor m_kLineSelectboxSelected = GradientColor(QColor(0, 0, 0, 0 * 255)); \ GradientColor m_kLineSelectboxDisable = GradientColor(QColor(0, 0, 0, 0.08 * 255)); \ GradientColor m_kFontWhitePlaceholderTextDisable = GradientColor(QColor(255, 255, 255, 0.13 * 255));\ GradientColor m_kContainGeneralInactive = GradientColor(QColor(250, 250, 250, 1 * 255)); \ GradientColor m_kFontPlaceholderTextDisable = GradientColor(QColor(0, 0, 0, 0.12 * 255)); \ GradientColor m_kFontWhitePlaceholderText = GradientColor(QColor(255, 255, 255, 0.35 * 255)); \ GradientColor m_kLineInputDisable = GradientColor(QColor(0, 0, 0, 0.04 * 255)); \ GradientColor m_kLineDock = GradientColor(QColor(255, 255, 255, 0.55 * 255)); \ GradientColor m_kLineMenu = GradientColor(QColor(255, 255, 255, 0.7 * 255)); \ GradientColor m_kLineTable = GradientColor(QColor(0, 0, 0, 0.12 * 255)); \ GradientColor m_kLineInputNormal = GradientColor(QColor(0, 0, 0, 0.08 * 255)); \ GradientColor m_kLineInputHover = GradientColor(QColor(0, 0, 0, 0.12 * 255)); \ GradientColor m_kLineInputClick = GradientColor(QColor(54, 118, 245, 1 * 255)); \ GradientColor m_kOrange1 = GradientColor(QColor(255, 128, 31, 1 * 255)); \ GradientColor m_kGreen1 = GradientColor(QColor(24, 168, 13, 1 * 255)); \ GradientColor m_kRed1 = GradientColor(QColor(235, 70, 70, 1 * 255)); \ GradientColor m_kDividerWhite = GradientColor(QColor(255, 255, 255, 0.12 * 255)); \ \ GradientColor m_kLineSuccessNormal = GradientColor(QColor(24, 168, 13, 0.55 * 255)); \ GradientColor m_kLineSuccessHover = GradientColor(0, QColor(0,0,0,0.1*255), QColor(0,0,0,0.1*255), QColor(24,168,13,0.55*255));\ GradientColor m_kLineSuccessClick = GradientColor(0, QColor(0,0,0,0.2*255), QColor(0,0,0,0.2*255), QColor(24,168,13,0.55*255));\ GradientColor m_kLineSuccessDisable = GradientColor(QColor(24, 168, 13, 0.23 * 255)); \ GradientColor m_kLineErrorNormal = GradientColor(QColor(245, 73, 73, 0.55 * 255)); \ GradientColor m_kLineErrorHover = GradientColor(0, QColor(0,0,0,0.1*255), QColor(0,0,0,0.1*255), QColor(245,73,73,0.55*255));\ GradientColor m_kLineErrorClick = GradientColor(0, QColor(0,0,0,0.2*255), QColor(0,0,0,0.2*255), QColor(245,73,73,0.55*255));\ GradientColor m_kLineWarningNormal = GradientColor(QColor(255, 128, 31, 0.55 * 255)); \ GradientColor m_kLineWarningHover = GradientColor(0, QColor(0,0,0,0.1*255), QColor(0,0,0,0.1*255), QColor(255,128,31, 0.55*255));\ GradientColor m_kLineWarningClick = GradientColor(0, QColor(0,0,0,0.2*255), QColor(0,0,0,0.2*255), QColor(255,128,31, 0.55*255));\ GradientColor m_kLineWarningDisable = GradientColor(QColor(255, 128, 31, 0.23 * 255)); \ ShadowData m_kShadowPrimaryActive = ShadowData(0, 18, 38, 0, QColor(0, 0, 0, 0.13 * 255)); \ ShadowData m_kShadowPrimaryInactive = ShadowData(0, 8, 16, 0, QColor(0, 0, 0, 0.18 * 255)); \ ShadowData m_kShadowSecondaryActive = ShadowData(0, 12, 26, 0, QColor(0, 0, 0, 0.25 * 255)); \ ShadowData m_kShadowSecondaryInactive = ShadowData(0, 6, 13, 0, QColor(0, 0, 0, 0.18 * 255)); \ ShadowData m_kShadowMenu = ShadowData(0, 6, 14, 0, QColor(0, 0, 0, 0.2 * 255)); \ ShadowData m_kShadowMin = ShadowData(0, 2, 6, 0, QColor(0, 0, 0, 0.15 * 255)); \ ShadowData m_kShadowComponent = ShadowData(0, 1, 2, 0, QColor(0, 0, 0, 0.1 * 255)); \ GradientColor m_kComponentSelectedNormal = GradientColor(QColor(240, 240, 240, 1 * 255)); \ GradientColor m_kComponentSelectedHover = GradientColor(QColor(235, 235, 235, 1 * 255)); \ GradientColor m_kComponentSelectedClick = GradientColor(QColor(224, 224, 224, 1 * 255)); \ GradientColor m_kComponentSelectedDisable = GradientColor(QColor(224, 224, 224, 0.45 * 255)); \ GradientColor m_kComponentSelectedAlphaNormal = GradientColor(QColor(0, 0, 0, 0.08 * 255)); \ GradientColor m_kComponentSelectedAlphaHover = GradientColor(QColor(0, 0, 0, 0.12 * 255)); \ GradientColor m_kComponentSelectedAlphaClick = GradientColor(QColor(0, 0, 0, 0.16 * 255)); \ GradientColor m_kComponentSelectedAlphaDisable = GradientColor(QColor(0, 0, 0, 0.04 * 255)); \ GradientColor m_kComponentInput = GradientColor(QColor(255, 255, 255, 1 * 255)); \ GradientColor m_kComponentInputAlpha = GradientColor(QColor(255, 255, 255, 0.5 * 255)); \ GradientColor m_kSelectAlphaWhiteDisable = GradientColor(QColor(255, 255, 255, 0.45 * 255)); \ GradientColor m_kSelectAlphaWhiteHover = GradientColor(0, QColor(0,0,0,0.1*255), QColor(0,0,0,0.1*255), QColor(255,255,255,1*255));\ GradientColor m_kSelectAlphaWhiteClick = GradientColor(0, QColor(0,0,0,0.2*255), QColor(0,0,0,0.2*255), QColor(255,255,255,1*255));\ GradientColor m_kMenu = GradientColor(QColor(250, 250, 250, 1 * 255)); \ GradientColor m_kLineErrorDisable = GradientColor(QColor(245, 73, 73, 0.23 * 255)); \ GradientColor m_kContainSecondaryInactive = GradientColor(QColor(245, 245, 245, 1 * 255)); \ GradientColor m_kSelectAlphaWhite = GradientColor(QColor(255, 255, 255, 1 * 255)); \ GradientColor m_kFontPlaceholderText = GradientColor(QColor(0, 0, 0, 0.28 * 255)); \ namespace UkuiQuick { class DtThemeDefinition : public QObject { Q_OBJECT public: DtThemeDefinition(QObject *parent = nullptr); }; } #endif //DTTHEMEDEFINITION_H ukui-quick/platform/ukui/shadow-data.h0000664000175000017500000000577315153755732016766 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: amingamingaming * */ #ifndef SHADOWDATA_H #define SHADOWDATA_H #include #include namespace UkuiQuick { class ShadowData : public QObject { Q_OBJECT Q_PROPERTY(int xOffset READ xOffset WRITE setXOffset NOTIFY xOffsetChanged) Q_PROPERTY(int yOffset READ yOffset WRITE setYOffset NOTIFY yOffsetChanged) Q_PROPERTY(int blurRadius READ blurRadius WRITE setBlurRadius NOTIFY blurRadiusChanged) Q_PROPERTY(int spread READ spread WRITE setSpread NOTIFY spreadChanged) Q_PROPERTY(QColor shadowColor READ shadowColor WRITE setShadowColor NOTIFY shadowColorChanged) Q_PROPERTY(bool isMultiShadow READ isMultiShadow WRITE setIsMultiShadow NOTIFY isMultiShadowChanged) Q_PROPERTY(ShadowData* childShadow READ childShadow WRITE setChildShadow NOTIFY childShadowChanged) public: explicit ShadowData(QObject *parent = nullptr); ~ShadowData() override; ShadowData(int x, int y, int blurRadius, int spread, const QColor &color, bool isMulti = false, ShadowData* child = nullptr, QObject *parent = nullptr); ShadowData(const ShadowData &shadow); ShadowData &operator= (const ShadowData &shadow); bool operator!=(const ShadowData &shadow); void setXOffset(int x); void setYOffset(int y); void setBlurRadius(int radius); void setSpread(int spread); void setShadowColor(const QColor &color); void setIsMultiShadow(bool isMultiShadow); void setChildShadow(ShadowData* child); int xOffset() const; int yOffset() const; int blurRadius() const; int spread() const; QColor shadowColor() const; bool isMultiShadow() const; ShadowData* childShadow() const; Q_SIGNALS: void xOffsetChanged(); void yOffsetChanged(); void blurRadiusChanged(); void spreadChanged(); void shadowColorChanged(); void isMultiShadowChanged(); void childShadowChanged(); private: //水平偏移量 int m_xOffset = 0; //垂直偏移量 int m_yOffset = 0; //阴影模糊半径 int m_blurRadius = 0; //阴影扩展半径 int m_spread = 0; //阴影颜色 QColor m_color = QColor(0,0,0,0); //是否是多重阴影 bool m_isMultiShadow = false; //另一层阴影 ShadowData* m_childShadow = nullptr; }; } Q_DECLARE_METATYPE(UkuiQuick::ShadowData) #endif // SHADOWDATA_H ukui-quick/platform/ukui/gradient-color.h0000664000175000017500000000622715153755732017476 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: amingamingaming * */ #ifndef GRADIENTCOLOR_H #define GRADIENTCOLOR_H #include #include namespace UkuiQuick { class DtColorType { Q_GADGET public: enum Type { Pure = 0, Gradient }; Q_ENUM(Type) }; class GradientColor : public QObject { Q_OBJECT Q_PROPERTY(QColor gradientStart READ gradientStart WRITE setGradientStart NOTIFY gradientStartChanged) Q_PROPERTY(QColor gradientEnd READ gradientEnd WRITE setGradientEnd NOTIFY gradientEndChanged) Q_PROPERTY(QColor gradientBackground READ gradientBackground WRITE setGradientBackground NOTIFY gradientBackgroundChanged) Q_PROPERTY(int gradientDeg READ gradientDeg WRITE setGradientDeg NOTIFY gradientDegChanged) Q_PROPERTY(QColor pureColor READ pureColor WRITE setPureColor NOTIFY pureColorChanged) Q_PROPERTY(UkuiQuick::DtColorType::Type colorType READ colorType NOTIFY colorTypeChanged) public: GradientColor(); GradientColor(QColor pure); GradientColor(int deg, QColor start, QColor end, QColor background) ; GradientColor(const GradientColor &color); QColor gradientStart() const; QColor gradientEnd() const; QColor gradientBackground() const; int gradientDeg() const; QColor pureColor() const; UkuiQuick::DtColorType::Type colorType() const; GradientColor &operator= (const GradientColor &color); bool operator!= (GradientColor *color); bool operator!= (QColor color); void setGradientStart(QColor start); void setGradientEnd(QColor end); void setGradientBackground(QColor background); void setGradientDeg(int deg); void setPureColor(QColor color); void setColorType(UkuiQuick::DtColorType::Type type); Q_INVOKABLE QColor setAlphaF(QColor color, qreal alpha); Q_INVOKABLE QColor mixBackGroundColor(const QColor &color, const QColor &back, const qreal &alpha); Q_INVOKABLE QPoint getGradientStartPoint(int deg, int width, int height); Q_INVOKABLE QPoint getGradientEndPoint(int deg, int width, int height); Q_SIGNALS: void gradientStartChanged(); void gradientEndChanged(); void gradientBackgroundChanged(); void gradientDegChanged(); void pureColorChanged(); void colorTypeChanged(); private: int m_gradientDeg; QColor m_gradientStart; QColor m_gradientEnd; QColor m_gradientBacrground; QColor m_pureColor; DtColorType::Type m_colorType; }; } Q_DECLARE_METATYPE(UkuiQuick::GradientColor) #endif // GRADIENTCOLOR_H ukui-quick/platform/ukui/ukui-theme-proxy.h0000664000175000017500000001337515153755732020023 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_ITEMS_UKUI_THEME_PROXY_H #define UKUI_QUICK_ITEMS_UKUI_THEME_PROXY_H #include #include #include #include namespace UkuiQuick { class ThemePrivate; class Theme : public QObject { Q_OBJECT Q_PROPERTY(QFont font READ font NOTIFY fontChanged) Q_PROPERTY(QPalette palette READ palette NOTIFY paletteChanged) Q_PROPERTY(qreal fontSize READ fontSize NOTIFY fontChanged) Q_PROPERTY(QString fontFamily READ fontFamily NOTIFY fontChanged) Q_PROPERTY(QString themeName READ themeName NOTIFY themeNameChanged) Q_PROPERTY(bool isDarkTheme READ isDarkTheme NOTIFY themeNameChanged) Q_PROPERTY(QString themeColor READ themeColor NOTIFY themeColorChanged) Q_PROPERTY(qreal themeTransparency READ themeTransparency NOTIFY themeTransparencyChanged) Q_PROPERTY(int maxRadius READ maxRadius NOTIFY themeRadiusChanged) Q_PROPERTY(int normalRadius READ normalRadius NOTIFY themeRadiusChanged) Q_PROPERTY(int minRadius READ minRadius NOTIFY themeRadiusChanged) Q_PROPERTY(int windowRadius READ windowRadius NOTIFY windowRadiusChanged) Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection NOTIFY layoutDirectionChanged) public: enum ColorGroup { Active, Disabled, Inactive }; Q_ENUM(ColorGroup) enum ColorRole { Window, WindowText, Base, Text, AlternateBase, Button, ButtonText, Light, MidLight, Dark, Mid, Shadow, Highlight, HighlightedText, BrightText, Link, LinkVisited, ToolTipBase, ToolTipText, PlaceholderText }; Q_ENUM(ColorRole) ~Theme() override; static Theme *instance(); // Attached Prop static Theme *qmlAttachedProperties(QObject *object); // func QFont font() const; QPalette palette() const; Q_INVOKABLE QColor color(Theme::ColorRole role, Theme::ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor color(Theme::ColorRole role, Theme::ColorGroup group, qreal alpha) const; // 获取带有主题透明度的颜色 Q_INVOKABLE QColor colorWithThemeTransparency(Theme::ColorRole role, Theme::ColorGroup group = Theme::Active) const; // 获取自定义透明度的颜色 Q_INVOKABLE QColor colorWithCustomTransparency(Theme::ColorRole role, Theme::ColorGroup group, qreal alphaF) const; // color Q_INVOKABLE QColor window(Theme::ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor windowText(Theme::ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor base(Theme::ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor text(Theme::ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor alternateBase(Theme::ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor button(Theme::ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor buttonText(Theme::ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor light(Theme::ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor midLight(Theme::ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor dark(Theme::ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor mid(Theme::ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor shadow(Theme::ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor highlight(Theme::ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor highlightedText(Theme::ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor separator(Theme::ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor brightText(ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor link(ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor linkVisited(ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor toolTipBase(ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor toolTipText(ColorGroup group = Theme::Active) const; Q_INVOKABLE QColor placeholderText(ColorGroup group = Theme::Active) const; // font qreal fontSize() const; QString fontFamily() const; // theme QString themeName() const; QString themeColor() const; // only ukui bool isDarkTheme() const; qreal themeTransparency() const; // theme radius int maxRadius() const; int normalRadius() const; int minRadius() const; //window radius int windowRadius() const; //system layoutDirection Qt::LayoutDirection layoutDirection() const; Q_SIGNALS: void fontChanged(); void paletteChanged(); void themeNameChanged(); void themeColorChanged(); void themeTransparencyChanged(); void themeRadiusChanged(); void iconThemeChanged(); void windowRadiusChanged(); void layoutDirectionChanged(); private: explicit Theme(QObject *parent = nullptr); inline QPalette::ColorGroup switchColorGroup(ColorGroup group) const; private: ThemePrivate *d {nullptr}; }; } // UkuiQuick Q_DECLARE_METATYPE(UkuiQuick::Theme::ColorRole) Q_DECLARE_METATYPE(UkuiQuick::Theme::ColorGroup) QML_DECLARE_TYPEINFO(UkuiQuick::Theme, QML_HAS_ATTACHED_PROPERTIES) #endif //UKUI_QUICK_ITEMS_UKUI_THEME_PROXY_H ukui-quick/platform/ukui/app-launcher.cpp0000664000175000017500000001136215153756415017472 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #include "app-launcher.h" #include #include #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #else #include #endif static UkuiQuick::AppLauncher *g_appLauncher = nullptr; static std::once_flag onceFlag; static const QString KYLIN_APP_MANAGER_NAME("com.kylin.ProcessManager"); static const QString KYLIN_APP_MANAGER_PATH("/com/kylin/ProcessManager/AppLauncher"); static const QString KYLIN_APP_MANAGER_INTERFACE("com.kylin.ProcessManager.AppLauncher"); namespace UkuiQuick { class AppLauncherPrivate { }; AppLauncher *AppLauncher::instance() { std::call_once(onceFlag, [ & ] { g_appLauncher = new AppLauncher(); }); return g_appLauncher; } AppLauncher::AppLauncher(QObject *parent) : QObject(parent), d(new AppLauncherPrivate) { } AppLauncher::~AppLauncher() { if(d) { delete d; d = nullptr; } g_appLauncher = nullptr; } void AppLauncher::launchApp(const QString &desktopFile) { if (desktopFile.isEmpty()) { return; } QDBusMessage message = QDBusMessage::createMethodCall(KYLIN_APP_MANAGER_NAME, KYLIN_APP_MANAGER_PATH, KYLIN_APP_MANAGER_INTERFACE, "LaunchApp"); message << desktopFile; auto watcher = new QDBusPendingCallWatcher(QDBusPendingCall(QDBusConnection::sessionBus().asyncCall(message)), this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [desktopFile] (QDBusPendingCallWatcher *self) { if (self->isError()) { qWarning() << "Fail to call " << KYLIN_APP_MANAGER_INTERFACE << self->error(); XdgDesktopFile xdf; xdf.load(desktopFile); if(!xdf.startDetached()) { qWarning() << "Fail to Launch" << desktopFile; } } self->deleteLater(); }); } void AppLauncher::launchAppWithArguments(const QString &desktopFile, const QStringList &args) { if (desktopFile.isEmpty()) { return; } QDBusMessage message = QDBusMessage::createMethodCall(KYLIN_APP_MANAGER_NAME, KYLIN_APP_MANAGER_PATH, KYLIN_APP_MANAGER_INTERFACE, "LaunchAppWithArguments"); message << desktopFile << args; auto watcher = new QDBusPendingCallWatcher(QDBusPendingCall(QDBusConnection::sessionBus().asyncCall(message)), this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [desktopFile, args] (QDBusPendingCallWatcher *self) { if (self->isError()) { qWarning() << "Fail to call " << KYLIN_APP_MANAGER_INTERFACE << self->error(); XdgDesktopFile xdf; xdf.load(desktopFile); if(!xdf.startDetached(args)) { qWarning() << "Fail to Launch" << desktopFile << "with args " << args; } } self->deleteLater(); }); } void AppLauncher::openUri(const QString &uri, const QString &parentWindow) { if (uri.isEmpty()) { return; } //这个接口调用了org.freedesktop.portal.OpenURI,当uri为本地文件时会调用后端org.freedesktop.impl.portal.AppChooser, // 会显示peony的应用选择窗 if(!QDesktopServices::openUrl(QUrl(uri))) { qWarning() << "Fail to open" << uri; } } void AppLauncher::runCommand(const QString &cmd) { if (cmd.isEmpty()) { return; } QDBusMessage message = QDBusMessage::createMethodCall(KYLIN_APP_MANAGER_NAME, KYLIN_APP_MANAGER_PATH, KYLIN_APP_MANAGER_INTERFACE, "RunCommand"); message << cmd; auto watcher = new QDBusPendingCallWatcher(QDBusPendingCall(QDBusConnection::sessionBus().asyncCall(message)), this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [cmd] (QDBusPendingCallWatcher *self) { if (self->isError()) { qWarning() << "Fail to call " << KYLIN_APP_MANAGER_INTERFACE << self->error(); QProcess::startDetached(cmd, {}); } self->deleteLater(); }); } } // UkuiQuick ukui-quick/platform/ukui/function-control.cpp0000664000175000017500000001325315153755732020420 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #include "function-control.h" #include #include #include #include #include #include #include "dbus-connector.h" static UkuiQuick::FunctionControl *g_instance = nullptr; namespace UkuiQuick { class FunctionControlPrivate : public QObject { Q_OBJECT public: FunctionControlPrivate(FunctionControl *q, QObject *parent = nullptr); void loadConfig(const QString &path, const QString &name); void refreshConfig(const QString &name); public Q_SLOTS: void onConfigChanged(const QString &userName, const QStringList &appConfigList); void onSecurityExit(); public: QHash m_configRefs; QHash m_configs; QDBusInterface *m_interface = nullptr; FunctionControl *q = nullptr; }; FunctionControlPrivate::FunctionControlPrivate(FunctionControl *q, QObject* parent) : QObject(parent), q(q) { } void FunctionControlPrivate::onConfigChanged(const QString &userName, const QStringList &appConfigList) { qDebug() << "FunctionControlPrivate::onConfigChanged" << userName << appConfigList << m_configRefs; if (QString(qgetenv("USER")) != userName && !userName.isEmpty()) { return; } for (auto &config : appConfigList) { if (m_configRefs.contains(config)) { refreshConfig(config); Q_EMIT g_instance->functionControlChanged(config); } } } void FunctionControlPrivate::onSecurityExit() { m_configs.clear(); Q_EMIT q->functionControlExit(); } void FunctionControlPrivate::loadConfig(const QString &path, const QString &name) { if (!QFile::exists(path)) { qWarning() << "Function control config file does not exist:" << path << ":" << name; m_configs.remove(name); return; } QFile file(path); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "Failed to open function control config file:" << path << ":" << name; return; } QByteArray data = file.readAll(); file.close(); QJsonParseError parseError{}; QJsonDocument doc = QJsonDocument::fromJson(data, &parseError); if (doc.isNull()) { qWarning() << "FunctionControl: Failed to parse JSON:" << parseError.errorString() << path; } if (!doc.isObject()) { qWarning() << "FunctionControl: Configuration file format error." << path; return; } QJsonObject obj = doc.object(); m_configs.insert(name, obj); } void FunctionControlPrivate::refreshConfig(const QString &name) { if (m_interface) { QDBusReply reply = m_interface->call("GetConfigPath", QString(qgetenv("USER")), name); if (!reply.isValid()) { qWarning() << "FunctionControl: Call GetConfigPath error" << reply.error(); } else { loadConfig(reply.value(), name); Q_EMIT q->functionControlChanged(name); } } } FunctionControl* FunctionControl::instance() { if (!g_instance) { g_instance = new FunctionControl(); } return g_instance; } FunctionControl::~FunctionControl() { g_instance = nullptr; disconnect(this); if (d) { d->disconnect(d); delete d; d = nullptr; } } void FunctionControl::addWatch(const QString &name) { if (d->m_configRefs.contains(name)) { d->m_configRefs.insert(name, d->m_configRefs.value(name) + 1); } else { d->refreshConfig(name); d->m_configRefs.insert(name, 1); } } void FunctionControl::removeWatch(const QString &name) { if (d->m_configRefs.contains(name)) { int value = d->m_configRefs.value(name) - 1; if (value < 1) { d->m_configRefs.remove(name); d->m_configs.remove(name); } else { d->m_configRefs.insert(name, value); } } } void FunctionControl::removeAllWatches(const QString &name) { d->m_configRefs.remove(name); d->m_configs.remove(name); } QJsonObject FunctionControl::functionControlObject(const QString &name) { return d->m_configs.value(name); } FunctionControl::FunctionControl(QObject* parent): QObject(parent), d(new FunctionControlPrivate(this)) { d->m_interface = new QDBusInterface(QStringLiteral("com.kylin.ukui.SettingsDaemon"), QStringLiteral("/securityConfig"), QStringLiteral("com.kylin.ukui.SettingsDaemon.interface"), QDBusConnection::systemBus(), d); if (!d->m_interface->isValid()) { qWarning() << "FunctionControl::Failed to connect to com.kylin.ukui.SettingsDaemon"; return; } connect(d->m_interface, SIGNAL(ConfigChanged(QString, QStringList)), d, SLOT(onConfigChanged(QString, QStringList))); connect(d->m_interface, SIGNAL(SecurityExit()), d, SLOT(onSecurityExit())); for (auto config: d->m_configRefs.keys()) { d->refreshConfig(config); } } } #include "function-control.moc" ukui-quick/platform/ukui/dbus-connector.h0000664000175000017500000000414515153755732017507 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #ifndef DBUS_CONNECTOR_H #define DBUS_CONNECTOR_H #include #include #include namespace UkuiQuick { class DbusConnectorPrivate; /** * @brief DbusConnector是一个用于连接DBus服务的类 * @details 该类继承自QThread,用于在后台线程中连接DBus服务 * 连接成功后,会发送ready信号,参数为QDBusInterface指针 * @note 该类会自动尝试连接DBus服务,当连接超时后,后台线程会保持监听目标服务的注册状态,目标服务注册成功后,会自动尝试连接 * @note 该类不会管理QDBusInterface指针,需要使用者自行管理生命周期 */ class DbusConnector : public QThread { Q_OBJECT public: /** * @brief 构造函数 * @param service 服务名 * @param path 路径 * @param interface 接口 * @param conn 连接,默认为QDBusConnection::sessionBus() * @param parent 父对象 */ DbusConnector(const QString &service, const QString &path, const QString &interface, QDBusConnection conn = QDBusConnection::sessionBus(), QObject *parent = nullptr); ~DbusConnector() override; Q_SIGNALS: /** * @brief 连接成功信号 * @param interface QDBusInterface指针 */ void ready(QDBusInterface*); protected: void run() override; private: DbusConnectorPrivate *d = nullptr; }; } #endif //DBUS_CONNECTOR_H ukui-quick/platform/ukui/settings.h0000664000175000017500000000352115153755732016417 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #ifndef UKUI_QUICK_ITEMS_SETTINGS_H #define UKUI_QUICK_ITEMS_SETTINGS_H #include namespace UkuiQuick { class SettingsPrivate; class Settings : public QObject { Q_OBJECT Q_PROPERTY(QString liteAnimation READ liteAnimation NOTIFY liteAnimationChanged) Q_PROPERTY(QString liteFunction READ liteFunction NOTIFY liteFunctionChanged) Q_PROPERTY(bool tabletMode READ tabletMode NOTIFY tabletModeChanged) Q_PROPERTY(QString platformName READ platformName) Q_PROPERTY(bool isHardWareRendering READ isHardWareRendering) Q_PROPERTY(bool verticalSync READ verticalSync) public: static Settings *instance(); QString liteAnimation(); QString liteFunction(); bool tabletMode(); void refresh(); QString platformName(); static bool verticalSync(); static bool isHardWareRendering(); private: static bool getRendererInfo(); Q_SIGNALS: void liteAnimationChanged(); void liteFunctionChanged(); void tabletModeChanged(); private: explicit Settings(QObject *parent = nullptr); SettingsPrivate *d = nullptr; }; } #endif //UKUI_QUICK_ITEMS_SETTINGS_H ukui-quick/platform/ukui/dt-theme-definition.cpp0000664000175000017500000000156015153755732020750 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #include "dt-theme-definition.h" namespace UkuiQuick { DtThemeDefinition::DtThemeDefinition(QObject* parent) : QObject(parent) { } }ukui-quick/platform/ukui/screen-area-utils.cpp0000664000175000017500000000733315153755732020442 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: youdiansaodongxi * */ #include "screen-area-utils.h" #include #include #include #define UKUI_PANEL_SETTING "org.ukui.panel.settings" #define UKUI_PANEL_POSITION_KEY "panelposition" #define UKUI_PANEL_SIZE_KEY "panelsize" #define UKUI_PANEL_AUtOHIDE_KEY "hidepanel" namespace UkuiQuick { class ScreenAreaUtilsPrivate { public: QGSettings *m_setting {nullptr}; int m_panelPos {4}; int m_panelSize {48}; bool m_panelAutoHide {false}; }; static ScreenAreaUtils *g_screenAreaUtils = nullptr; static std::once_flag onceFlag; ScreenAreaUtils *ScreenAreaUtils::instance() { std::call_once(onceFlag, [ & ] { g_screenAreaUtils = new ScreenAreaUtils(); }); return g_screenAreaUtils; } ScreenAreaUtils::~ScreenAreaUtils() { if(d) { delete d; d = nullptr; } g_screenAreaUtils = nullptr; } QRect ScreenAreaUtils::getAvailableGeometry(QScreen *screen) { QRect availableRect = screen->geometry(); int adjustment = d->m_panelAutoHide ? 0 : d->m_panelSize; //上: 1, 下: 0, 左: 2, 右: 3 switch (d->m_panelPos) { default: case 0: { availableRect.adjust(0, 0, 0, - adjustment); break; } case 1: { availableRect.adjust(0, adjustment, 0, 0); break; } case 2: { availableRect.adjust(adjustment, 0, 0, 0); break; } case 3: { availableRect.adjust(0, 0, - adjustment, 0); break; } } return availableRect; } ScreenAreaUtils::ScreenAreaUtils(QObject *parent) : QObject(parent), d(new ScreenAreaUtilsPrivate) { initPanelSetting(); } void ScreenAreaUtils::initPanelSetting() { const QByteArray id(UKUI_PANEL_SETTING); if (QGSettings::isSchemaInstalled(id)) { d->m_setting = new QGSettings(id, QByteArray(), this); QStringList keys = d->m_setting->keys(); if (keys.contains(UKUI_PANEL_POSITION_KEY)) { d->m_panelPos = d->m_setting->get(UKUI_PANEL_POSITION_KEY).toInt(); } if (keys.contains(UKUI_PANEL_SIZE_KEY)) { d->m_panelSize = d->m_setting->get(UKUI_PANEL_SIZE_KEY).toInt(); } if (keys.contains(UKUI_PANEL_AUtOHIDE_KEY)) { d->m_panelAutoHide = d->m_setting->get(UKUI_PANEL_AUtOHIDE_KEY).toBool(); } connect(d->m_setting, &QGSettings::changed, this, [this] (const QString& key) { if (key == UKUI_PANEL_POSITION_KEY) { d->m_panelPos = d->m_setting->get(UKUI_PANEL_POSITION_KEY).toInt(); Q_EMIT availableGeometryChanged(); } else if (key == UKUI_PANEL_SIZE_KEY) { d->m_panelSize = d->m_setting->get(key).toInt(); Q_EMIT availableGeometryChanged(); } else if (key == UKUI_PANEL_AUtOHIDE_KEY) { d->m_panelAutoHide = d->m_setting->get(key).toInt(); Q_EMIT availableGeometryChanged(); } }); } } } ukui-quick/platform/glinfo-query.h0000664000175000017500000000220215153755732016216 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: sundagao * */ #ifndef GLINFOQUERY_H #define GLINFOQUERY_H #include #include namespace UkuiQuick { class GLXInfoQuery; class EGLInfoQuery; class GLInfoQuery { public: GLInfoQuery(); ~GLInfoQuery(); QString glRenderDevice(); bool isHardwareRendering(); private: std::unique_ptr m_eglQuery; std::unique_ptr m_glxQuery; }; } #endif // GLINFOQUERY_H ukui-quick/platform/wayland-window-manager.cpp0000664000175000017500000003021115153756415020504 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #include "wayland-window-manager.h" #include #include #include namespace UkuiQuick { WaylandWindowManager::WaylandWindowManager(QObject *parent) : AbstractWindowManager(parent) { m_connection = KWayland::Client::ConnectionThread::fromApplication(this); if (!m_connection) { return; } m_registry = new UkuiQuick::WaylandClient::Registry(this); m_registry->create(m_connection); QObject::connect(m_registry, &UkuiQuick::WaylandClient::Registry::ukuiWindowManagementAnnounced, [&](quint32 name, quint32 version){ m_windowManagement = m_registry->createUkuiWindowManagement(name, version, this); connect(m_windowManagement, &UkuiWindowManagement::windowCreated, this, &WaylandWindowManager::addWindow); connect(m_windowManagement, &UkuiWindowManagement::activeWindowChanged, [&](){ auto w = m_windowManagement->activeWindow(); if(w) { if(m_windows.contains(w)) { Q_EMIT activeWindowChanged(w->uuid()); } } else { Q_EMIT activeWindowChanged(""); } }); }); connect(m_registry, &UkuiQuick::WaylandClient::Registry::plasmaVirtualDesktopManagementAnnounced, [&](quint32 name, quint32 version){ m_virtualDesktopManagement = m_registry->createPlasmaVirtualDesktopManagement(name, version, this); if(m_virtualDesktopManagement) { connect(m_virtualDesktopManagement, &KWayland::Client::PlasmaVirtualDesktopManagement::desktopCreated, this, &WaylandWindowManager::desktopCreated); connect(m_virtualDesktopManagement, &KWayland::Client::PlasmaVirtualDesktopManagement::desktopRemoved, this, &WaylandWindowManager::desktopRemoved); } }); m_registry->setup(); m_connection->roundtrip(); } WaylandWindowManager::~WaylandWindowManager() = default; void WaylandWindowManager::addWindow(UkuiWindow *window) { if (m_windows.indexOf(window) != -1) { return; } m_windows.append(window); m_uuidToWindow.insert(window->uuid(), window); auto removeWindow = [&, window] { if (m_windows.removeAll(window)) { m_uuidToWindow.remove(window->uuid()); Q_EMIT windowRemoved(window->uuid()); } }; connect(window, &UkuiWindow::unmapped, this, removeWindow); connect(window, &UkuiWindow::destroyed, this, removeWindow); connect(window, &UkuiWindow::titleChanged, this, [&, window](){Q_EMIT titleChanged(window->uuid());}); connect(window, &UkuiWindow::iconChanged, this, [&, window](){Q_EMIT iconChanged(window->uuid());}); connect(window, &UkuiWindow::skipTaskbarChanged, this, [&, window](){Q_EMIT skipTaskbarChanged(window->uuid());}); connect(window, &UkuiWindow::onAllDesktopsChanged, this, [&, window](){Q_EMIT onAllDesktopsChanged(window->uuid());}); connect(window, &UkuiWindow::ukuiVirtualDesktopEntered, this, [&, window](){Q_EMIT windowDesktopChanged(window->uuid());}); connect(window, &UkuiWindow::ukuiVirtualDesktopLeft, this, [&, window](){Q_EMIT windowDesktopChanged(window->uuid());}); connect(window, &UkuiWindow::demandsAttentionChanged, this, [&, window](){Q_EMIT demandsAttentionChanged(window->uuid());}); connect(window, &UkuiWindow::geometryChanged, this, [&, window](){Q_EMIT geometryChanged(window->uuid());}); connect(window, &UkuiWindow::maximizedChanged, this, [&, window](){Q_EMIT maximizedChanged(window->uuid());}); connect(window, &UkuiWindow::fullscreenChanged, this, [&, window](){Q_EMIT fullscreenChanged(window->uuid());}); Q_EMIT windowAdded(window->uuid()); if(m_windowManagement->activeWindow() == window) { Q_EMIT activeWindowChanged(window->uuid()); } } QStringList WaylandWindowManager::windows() { return m_uuidToWindow.keys(); } QIcon WaylandWindowManager::windowIcon(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { return m_uuidToWindow.value(wid.toUtf8())->icon(); } return {}; } QString WaylandWindowManager::windowTitle(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { return m_uuidToWindow.value(wid.toUtf8())->title(); } return {}; } bool WaylandWindowManager::skipTaskBar(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { return m_uuidToWindow.value(wid.toUtf8())->skipTaskbar(); } return {}; } QString WaylandWindowManager::windowGroup(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { return m_uuidToWindow.value(wid.toUtf8())->appId(); } return {}; } bool WaylandWindowManager::isMaximized(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { return m_uuidToWindow.value(wid.toUtf8())->isMaximized(); } return false; } void WaylandWindowManager::maximizeWindow(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { m_uuidToWindow.value(wid.toUtf8())->requestToggleMaximized(); } } bool WaylandWindowManager::isMinimized(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { return m_uuidToWindow.value(wid.toUtf8())->isMinimized(); } return false; } void WaylandWindowManager::minimizeWindow(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { m_uuidToWindow.value(wid.toUtf8())->requestToggleMinimized(); } } bool WaylandWindowManager::isKeepAbove(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { return m_uuidToWindow.value(wid.toUtf8())->isKeepAbove(); } return false; } void WaylandWindowManager::keepAboveWindow(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { m_uuidToWindow.value(wid.toUtf8())->requestToggleKeepAbove(); } } bool WaylandWindowManager::isOnAllDesktops(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { return m_uuidToWindow.value(wid.toUtf8())->isOnAllDesktops(); } return false; } bool WaylandWindowManager::isOnCurrentDesktop(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { return m_uuidToWindow.value(wid.toUtf8())->ukuiVirtualDesktops().contains(m_currentDesktop); } return false; } void WaylandWindowManager::activateWindow(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { m_uuidToWindow.value(wid.toUtf8())->requestActivate(); } } QString WaylandWindowManager::currentActiveWindow() { if(!m_windowManagement) { return {}; } m_connection->roundtrip(); auto window = m_windowManagement->activeWindow(); if(window) { return window->uuid(); } return {}; } void WaylandWindowManager::closeWindow(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { m_uuidToWindow.value(wid.toUtf8())->requestClose(); } } void WaylandWindowManager::restoreWindow(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { auto window = m_uuidToWindow.value(wid.toUtf8()); window->requestActivate(); if (window->isMaximized()) { window->requestToggleMaximized(); } } } bool WaylandWindowManager::isDemandsAttention(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { return m_uuidToWindow.value(wid.toUtf8())->isDemandingAttention(); } return false; } void WaylandWindowManager::desktopCreated(const QString &id, quint32 position) { if(m_desktops.contains(id)) { return; } m_desktops.append(id); const auto *desktop = m_virtualDesktopManagement->getVirtualDesktop(id); if(desktop->isActive()) { setCurrentDesktop(desktop->id()); } connect(desktop, &KWayland::Client::PlasmaVirtualDesktop::activated, this, [&, desktop](){ setCurrentDesktop(desktop->id()); }); } void WaylandWindowManager::setCurrentDesktop(const QString &id) { if(id != m_currentDesktop) { m_currentDesktop = id; Q_EMIT currentDesktopChanged(); } } void WaylandWindowManager::desktopRemoved(const QString &id) { m_desktops.removeAll(id); if (m_currentDesktop == id) { setCurrentDesktop(QString()); } } quint32 WaylandWindowManager::pid(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { return m_uuidToWindow.value(wid.toUtf8())->pid(); } return 0; } QString WaylandWindowManager::appId(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { return m_uuidToWindow.value(wid.toUtf8())->appId(); } return {}; } QRect WaylandWindowManager::geometry(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { return m_uuidToWindow.value(wid.toUtf8())->geometry(); } return {}; } void WaylandWindowManager::setStartupGeometry(const QString &wid, QQuickItem *item) { if(!item || !item->window() || !m_uuidToWindow.contains(wid.toUtf8())) { return; } KWayland::Client::Surface *s = KWayland::Client::Surface::fromWindow(item->window()); m_uuidToWindow.value(wid.toUtf8())->setStartupGeometry(s, {item->mapToScene({0, 0}).toPoint(), item->size().toSize()}); } void WaylandWindowManager::setMinimizedGeometry(const QString &wid, QQuickItem *item) { if(!item || !item->window() || !m_uuidToWindow.contains(wid.toUtf8())) { return; } KWayland::Client::Surface *s = KWayland::Client::Surface::fromWindow(item->window()); m_uuidToWindow.value(wid.toUtf8())->setMinimizedGeometry(s, {item->mapToScene({0, 0}).toPoint(), item->size().toSize()}); } void WaylandWindowManager::unsetMinimizedGeometry(const QString &wid, QQuickItem *item) { if(!item || !item->window() || !m_uuidToWindow.contains(wid.toUtf8())) { return; } KWayland::Client::Surface *s = KWayland::Client::Surface::fromWindow(item->window()); m_uuidToWindow.value(wid.toUtf8())->unsetMinimizedGeometry(s); } void WaylandWindowManager::activateWindowView(const QStringList &wids) { if(qgetenv("XDG_SESSION_DESKTOP") == "kylin-wlcom" || qgetenv("DESKTOP_SESSION") == "kylin-wlcom") { if(wids.isEmpty()) { for(auto window : m_windows) { if(window->isHighlight()) { window->unsetHightlight(); } } } else { //wlcom只能高亮1个窗口 if(m_uuidToWindow.contains(wids.first().toUtf8())) { return m_uuidToWindow.value(wids.first().toUtf8())->setHighlight(); } } } AbstractWindowManager::activateWindowView(wids); } bool WaylandWindowManager::showingDesktop() { if(!m_windowManagement) { return false; } m_connection->roundtrip(); return m_windowManagement->isShowingDesktop(); } void WaylandWindowManager::setShowingDesktop(bool showing) { if(m_windowManagement) { m_windowManagement->setShowingDesktop(showing); } } bool WaylandWindowManager::isFullscreen(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { return m_uuidToWindow.value(wid.toUtf8())->isFullscreen(); } return false; } bool WaylandWindowManager::isMaximizable(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { return m_uuidToWindow.value(wid.toUtf8())->isMaximizeable(); } return false; } bool WaylandWindowManager::isMinimizable(const QString &wid) { if(m_uuidToWindow.contains(wid.toUtf8())) { return m_uuidToWindow.value(wid.toUtf8())->isMinimizeable(); } return false; } } ukui-quick/platform/wm-impl-wayland.cpp0000664000175000017500000001736315153755732017165 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "wm-impl-wayland.h" #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #else #include #endif #include #include #include "window-helper.h" namespace UkuiQuick { WMImplWayland::WMImplWayland(QWindow *window) : WMInterface(window) { m_pos = window->position(); m_useUkuiShellIntegration = WindowProxy::useUkuiShellIntegration(window); if(!m_useUkuiShellIntegration) { window->installEventFilter(this); initSurface(); } } QPoint WMImplWayland::position() const { return m_pos; } void WMImplWayland::setPosition(const QPoint &point) { m_pos = point; window()->setPosition(m_pos); if (m_shellSurface && !m_useUkuiShellIntegration) { m_shellSurface->setPosition(m_pos); } } void WMImplWayland::setWindowType(WindowType::Type type) { if (m_useUkuiShellIntegration) { window()->setProperty("ukui_surface_role_v1", ukui_surface_roleMap.value(type)); return; } if (!m_shellSurface) { return; } using namespace WaylandClient; switch (type) { default: case WindowType::Normal: m_shellSurface->setRole(UkuiShellSurface::Role::Normal); break; case WindowType::Desktop: m_shellSurface->setRole(UkuiShellSurface::Role::Desktop); break; case WindowType::Dock: case WindowType::Panel: m_shellSurface->setRole(UkuiShellSurface::Role::Panel); break; case WindowType::SystemWindow: m_shellSurface->setRole(UkuiShellSurface::Role::SystemWindow); break; case WindowType::Notification: m_shellSurface->setRole(UkuiShellSurface::Role::Notification); break; case WindowType::CriticalNotification: m_shellSurface->setRole(UkuiShellSurface::Role::CriticalNotification); break; case WindowType::ScreenLockNotification: m_shellSurface->setRole(UkuiShellSurface::Role::ScreenLockNotification); break; case WindowType::OnScreenDisplay: m_shellSurface->setRole(UkuiShellSurface::Role::OnScreenDisplay); break; case WindowType::Dialog: window()->setFlags(window()->flags() | Qt::Dialog); case WindowType::Menu: case WindowType::ToolTip: m_shellSurface->setRole(UkuiShellSurface::Role::ToolTip); break; case WindowType::PopupMenu: window()->setFlags(window()->flags() | Qt::Popup); m_shellSurface->setRole(UkuiShellSurface::Role::ToolTip); break; case WindowType::AppletPopup: m_shellSurface->setRole(UkuiShellSurface::Role::AppletPop); break; } // thank kde. if (type == WindowType::OnScreenDisplay) { window()->setFlags((window()->flags() & ~Qt::Dialog) | Qt::Window); } bool onAllDesktop = (type == WindowType::Desktop || type == WindowType::Dock || type == WindowType::Panel || type == WindowType::SystemWindow || type == WindowType::Notification || type == WindowType::CriticalNotification || type == WindowType::OnScreenDisplay); // TODO: wlcom ? #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) KX11Extras::setOnAllDesktops(window()->winId(), onAllDesktop); #else KWindowSystem::setOnAllDesktops(window()->winId(), onAllDesktop); #endif } void WMImplWayland::setSkipTaskBar(bool skip) { if(m_useUkuiShellIntegration) { window()->setProperty("ukui_surface_skip_taskbar",skip); } else if (m_shellSurface) { m_shellSurface->setSkipTaskbar(skip); } } void WMImplWayland::setSkipSwitcher(bool skip) { if(m_useUkuiShellIntegration) { window()->setProperty("ukui_surface_skip_switcher",skip); } else if (m_shellSurface) { m_shellSurface->setSkipSwitcher(skip); } } void WMImplWayland::setRemoveTitleBar(bool remove) { if (window()->flags().testFlag(Qt::FramelessWindowHint)) { return; } if(m_useUkuiShellIntegration) { window()->setProperty("ukui_surface_no_titlebar", remove); } else if (m_shellSurface) { m_shellSurface->setRemoveTitleBar(remove); } } void WMImplWayland::setPanelAutoHide(bool autoHide) { if(m_useUkuiShellIntegration) { window()->setProperty("ukui_surface_panel_auto_hide", autoHide); } else if (m_shellSurface) { m_shellSurface->setPanelAutoHide(autoHide); } } void WMImplWayland::setPanelTakesFocus(bool takesFocus) { if(m_useUkuiShellIntegration) { window()->setProperty("ukui_surface_panel_takes_focus", takesFocus); } else if (m_shellSurface) { m_shellSurface->setPanelTakesFocus(takesFocus); } } QScreen* WMImplWayland::currentScreen() { auto shell = WaylandIntegration::self()->waylandUkuiShell(); shell->updateCurrentOutput(); WaylandIntegration::self()->sync(); if (shell->isCurrentOutputReady()) { QString name = shell->outputName(); for (auto screen: qGuiApp->screens()) { if (screen->name() == name) { return screen; } } } return qGuiApp->primaryScreen(); } void WMImplWayland::setDecorationComponents(Decoration::Components components) { if (m_useUkuiShellIntegration) { window()->setProperty("ukui_surface_decoration_components", static_cast(components)); } else if (m_shellSurface) { m_shellSurface->setDecorationComponents(components); } } bool WMImplWayland::eventFilter(QObject *watched, QEvent *event) { if (watched == window()) { switch (event->type()) { case QEvent::PlatformSurface: { const auto surfaceEvent = static_cast(event); if (surfaceEvent->surfaceEventType() != QPlatformSurfaceEvent::SurfaceCreated) { destroySurface(); } break; } case QEvent::Expose: if (!m_shellSurface) { initSurface(); // 初始化位置 if (m_shellSurface) { m_shellSurface->setPosition(m_pos); } } break; case QEvent::Hide: destroySurface(); break; default: break; } } return QObject::eventFilter(watched, event); } void WMImplWayland::initSurface() { auto shell = WaylandIntegration::self()->waylandUkuiShell(); if (!shell) { return; } KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(window()); if (!surface) { return; } m_shellSurface.reset(shell->createSurface(surface)); } inline void WMImplWayland::destroySurface() { m_shellSurface.reset(); } } // UkuiQuick ukui-quick/platform/startup-management.cpp0000664000175000017500000000454415153756415017756 0ustar fengfeng/* * * Copyright (C) 2025, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #include "startup-management.h" #include #include #include "registry.h" #include "ukui-startup-manager.h" namespace UkuiQuick { static StartupManagement* instance = nullptr; class StartupManagementPrivate { public: WaylandClient::UkuiStartupManagement *management = nullptr; }; StartupManagement* StartupManagement::self() { if (!instance) { instance = new StartupManagement(); } return instance; } StartupManagement::~StartupManagement() { if (d) { delete d; d = nullptr; } instance = nullptr; } void StartupManagement::setStartupGeometry(uint32_t pid, int32_t x, int32_t y, uint32_t width, uint32_t height) { if (d->management && d->management->isValid()) { d->management->setStartupGeometry(pid, x, y, width, height); } } StartupManagement::StartupManagement(QObject* parent) : d(new StartupManagementPrivate) { auto connection = KWayland::Client::ConnectionThread::fromApplication(this); if (!connection) { return; } auto registry = new UkuiQuick::WaylandClient::Registry(qGuiApp); registry->create(connection); registry->setup(); connection->roundtrip(); const WaylandClient::Registry::AnnouncedInterface interface = registry->interface(UkuiQuick::WaylandClient::Registry::Interface::UkuiStartupManagement); if (interface.name == 0) { qWarning() << "The compositor does not support the UkuiStartupManagement protocol"; return; } d->management = registry->createUkuiStartupManagement(interface.name, interface.version, this); } } ukui-quick/platform/ukui-quick-platform-plugin.h0000664000175000017500000000214415153755732021007 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_ITEMS_UKUI_QUICK_VIEWS_PLUGIN_H #define UKUI_QUICK_ITEMS_UKUI_QUICK_CORE_VIEWS_PLUGIN_H #include class UkuiQuickPlatformPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) public: void registerTypes(const char *uri) override; }; #endif //UKUI_QUICK_ITEMS_UKUI_QUICK_VIEWS_PLUGIN_H ukui-quick/platform/startup-management.h0000664000175000017500000000244215153755732017417 0ustar fengfeng/* * * Copyright (C) 2025, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef STARTUP_MANAGEMENT_H #define STARTUP_MANAGEMENT_H #include namespace UkuiQuick { class StartupManagementPrivate; class StartupManagement : public QObject { Q_OBJECT public: static StartupManagement* self(); ~StartupManagement() override; void setStartupGeometry(uint32_t pid, int32_t x, int32_t y, uint32_t width, uint32_t height); private: explicit StartupManagement(QObject *parent = nullptr); StartupManagementPrivate* d = nullptr; }; } #endif //STARTUP_MANAGEMENT_H ukui-quick/platform/ukui-quick-platform-config.cmake.in0000664000175000017500000000121715153756415022213 0ustar fengfeng@PACKAGE_INIT@ include(CMakeFindDependencyMacro) find_dependency(Qt@QT_VERSION_MAJOR@Core "@REQUIRED_QT_VERSION@") find_dependency(Qt@QT_VERSION_MAJOR@Widgets @REQUIRED_QT_VERSION@) find_dependency(Qt@QT_VERSION_MAJOR@Qml @REQUIRED_QT_VERSION@) find_dependency(Qt@QT_VERSION_MAJOR@Quick @REQUIRED_QT_VERSION@) if (QT_VERSION_MAJOR EQUAL "5") find_dependency(Qt@QT_VERSION_MAJOR@X11Extras @REQUIRED_QT_VERSION@) find_dependency(KF5Wayland) elseif (QT_VERSION_MAJOR EQUAL "6") find_package(KWayland) endif() find_dependency(Qt@QT_VERSION_MAJOR@DBus @REQUIRED_QT_VERSION@) include("${CMAKE_CURRENT_LIST_DIR}/ukui-quick-platform-targets.cmake") ukui-quick/platform/wm-interface.cpp0000664000175000017500000000420515153755732016516 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include #include "wm-interface.h" #include "wm-impl-x11.h" #include "wm-impl-wayland.h" namespace UkuiQuick { class WMInterface::Private { public: QWindow *window {nullptr}; }; WMInterface::WMInterface(QWindow *window) : QObject(window), d(new Private) { d->window = window; } WMInterface::~WMInterface() { if (d) { delete d; d = nullptr; } } QWindow *WMInterface::window() const { return d->window; } bool WMInterface::isWayland() { static bool isWl = QGuiApplication::platformName().startsWith(QStringLiteral("wayland")); return isWl; } QPoint WMInterface::position() const { return d->window->position(); } void WMInterface::setPosition(const QPoint &pos) { d->window->setPosition(pos); } void WMInterface::setDecorationComponents(Decoration::Components components) { Q_UNUSED(components); } WMInterface *WManager::getWM(QWindow *window) { // cache static QHash globalCache = QHash(); auto wm = globalCache.value(window); if (!wm) { if (WMInterface::isWayland()) { wm = new WMImplWayland(window); } else { wm = new WMImplX11(window); } globalCache.insert(window, wm); QObject::connect(wm, &QObject::destroyed, wm, [window] { globalCache.remove(window); }); } return wm; } } // UkuiQuick ukui-quick/platform/cmake/0000775000175000017500000000000015153755732014510 5ustar fengfengukui-quick/platform/cmake/FindWaylandScanner.cmake0000664000175000017500000000522215153755732021225 0ustar fengfeng# SPDX-FileCopyrightText: 2012-2014 Pier Luigi Fiorini # SPDX-FileCopyrightText: 2024 KylinSoft Co., Ltd. # SPDX-License-Identifier: BSD-3-Clause include(${ECM_MODULE_DIR}ECMFindModuleHelpers.cmake) ecm_find_package_version_check(WaylandScanner) # Find wayland-scanner find_program(WaylandScanner_EXECUTABLE NAMES wayland-scanner) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(WaylandScanner FOUND_VAR WaylandScanner_FOUND REQUIRED_VARS WaylandScanner_EXECUTABLE ) mark_as_advanced(WaylandScanner_EXECUTABLE) if(NOT TARGET Wayland::Scanner AND WaylandScanner_FOUND) add_executable(Wayland::Scanner IMPORTED) set_target_properties(Wayland::Scanner PROPERTIES IMPORTED_LOCATION "${WaylandScanner_EXECUTABLE}" ) endif() include(FeatureSummary) set_package_properties(WaylandScanner PROPERTIES URL "https://wayland.freedesktop.org/" DESCRIPTION "Executable that converts XML protocol files to C code" ) function(ukui_add_wayland_client_protocol target_or_sources_var) # Parse arguments set(options PRIVATE_CODE) set(oneValueArgs PROTOCOL BASENAME) cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "" ${ARGN}) if(ARGS_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Unknown keywords given to ecm_add_wayland_client_protocol(): \"${ARGS_UNPARSED_ARGUMENTS}\"") endif() get_filename_component(_infile ${ARGS_PROTOCOL} ABSOLUTE) set(_client_header "${CMAKE_CURRENT_BINARY_DIR}/wayland-${ARGS_BASENAME}-client-protocol.h") set(_code "${CMAKE_CURRENT_BINARY_DIR}/wayland-${ARGS_BASENAME}-protocol.c") if(ARGS_PRIVATE_CODE) set(_code_type private-code) else() set(_code_type public-code) endif() set_source_files_properties(${_client_header} GENERATED) set_source_files_properties(${_code} GENERATED) set_property(SOURCE ${_client_header} ${_code} PROPERTY SKIP_AUTOMOC ON) add_custom_command(OUTPUT "${_client_header}" COMMAND ${WaylandScanner_EXECUTABLE} client-header ${_infile} ${_client_header} DEPENDS ${_infile} VERBATIM) add_custom_command(OUTPUT "${_code}" COMMAND ${WaylandScanner_EXECUTABLE} ${_code_type} ${_infile} ${_code} DEPENDS ${_infile} ${_client_header} VERBATIM) if (TARGET ${target_or_sources_var}) target_sources(${target_or_sources_var} PRIVATE "${_client_header}" "${_code}") else() list(APPEND ${target_or_sources_var} "${_client_header}" "${_code}") set(${target_or_sources_var} ${${target_or_sources_var}} PARENT_SCOPE) endif() endfunction()ukui-quick/platform/wm-interface.h0000664000175000017500000000607715153755732016174 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_WM_INTERFACE_H #define UKUI_QUICK_WM_INTERFACE_H #include #include #include #include #include "window-type.h" namespace UkuiQuick { /** * @class WMInterface * * 抽出x和wayland环境或窗口管理器的常见接口 * */ class WMInterface : public QObject { Q_OBJECT public: static bool isWayland(); explicit WMInterface(QWindow *window); ~WMInterface() override; QWindow *window() const; // protocols /** * 设置窗口位置 */ virtual QPoint position() const; virtual void setPosition(const QPoint &pos); /** * 设置窗口类型 * 此处支持常用的几个窗口类型 * 每个类型在窗口管理器中的行为都不一样,使用前请先了解相关文档 * * @see WindowType::Type */ virtual void setWindowType(WindowType::Type type) = 0; /** * 不在taskBar(任务栏)上显示 */ virtual void setSkipTaskBar(bool skip = true) = 0; /** * 不在多任务选择器中显示 */ virtual void setSkipSwitcher(bool skip = true) = 0; /** * 移除窗口管理器添加的标题栏 */ virtual void setRemoveTitleBar(bool remove = true) = 0; /** * 设置窗口是否获取焦点 * @param takesFocus */ virtual void setPanelTakesFocus(bool takesFocus = true) = 0; /** * 设置窗口为'自动隐藏'状态 * 设置为true时, 窗口管理器会将窗口所在的位置归还给可用区域,但是目前不会将该窗口隐藏,需要窗口自己实现 * 设置为false时,窗口管理器会将窗口所在的区域从可用区域中移除,普通应用将无法覆盖该区域. * * @warning 该接口仅对Panel和Dock层级有效(wayland) */ virtual void setPanelAutoHide(bool autoHide = false) = 0; /** * 获取当前鼠标所在屏幕 * @return */ virtual QScreen* currentScreen() = 0; // TODO: add wm interfaces virtual void setDecorationComponents(Decoration::Components components); private: class Private; Private *d {nullptr}; }; /** * @class WManager * * WMInterface的工厂类,通过getWM方法获取WMInterface实例 * */ class WManager { public: static WMInterface *getWM(QWindow *window); }; } // UkuiQuick #endif //UKUI_QUICK_WM_INTERFACE_H ukui-quick/platform/test/0000775000175000017500000000000015153756415014406 5ustar fengfengukui-quick/platform/test/PaletteColor.qml0000664000175000017500000000120715153755732017517 0ustar fengfengimport QtQuick 2.12 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 Column { property alias paletteRole: backGround.paletteRole property alias paletteGroup: backGround.paletteGroup property alias colorText: colorText.text width: childrenRect.width StyleBackground { id: backGround width: 120 height: 80 radius: 0 paletteRole: Theme.BrightText useStyleTransparency: false alpha: 1 } StyleText { id: colorText wrapMode: Text.Wrap verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } } ukui-quick/platform/test/ToolTipTest.qml0000664000175000017500000000257015153755732017360 0ustar fengfengimport QtQuick 2.15 import org.ukui.quick.platform 1.0 import org.ukui.quick.items 1.0 Row { width: 900 height: 900 spacing: 4 StyleBackground { width: 80 height: 80 radius: 12 paletteRole: Theme.BrightText useStyleTransparency: false alpha: 0.5 Tooltip { width: parent.width height: parent.height mainText: "tooltip test 1" posFollowCursor: false location: Dialog.TopEdge margin: 9 } } StyleBackground { width: 80 height: 80 radius: 12 paletteRole: Theme.BrightText useStyleTransparency: false alpha: 0.5 Tooltip { width: parent.width height: parent.height mainText: "tooltip test 2 \n" posFollowCursor: false location: Dialog.TopEdge margin: 9 } } StyleBackground { width: 80 height: 80 radius: 12 paletteRole: Theme.BrightText useStyleTransparency: false alpha: 0.5 Tooltip { width: parent.width height: parent.height mainText: "tooltip test 3 tooltip test 3 tooltip test 3 tooltip test 3" posFollowCursor: false location: Dialog.TopEdge margin: 9 } } }ukui-quick/platform/test/DtItemPage.qml0000664000175000017500000000422515153755732017110 0ustar fengfengimport QtQuick 2.0 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 import QtQuick.Controls 2.12 DtThemeBackground { id: iconPage width: parent.width height: parent.height useStyleTransparency: false backgroundColor: GlobalTheme.kContainGeneralAlphaNormal Grid { columns: 4 spacing: 20 Icon { id: iconTest width: 100 height: 100 clip: true source: "battery-level-100-charging-symbolic" mode: Icon.AutoHighlight dtThemeHighlightColor: GlobalTheme.kWarningClick } Icon { id: iconTest2 width: 100 height: 100 clip: true source: "system-log-out-symbolic" mode: Icon.Highlight dtThemeHighlightColor: GlobalTheme.kWarningClick } TouchSlider { width: 200 height: 16 orientation: Qt.Horizontal } DtThemeBackground { width:50 height: 50 backgroundColor: GlobalTheme.kContainGeneralAlphaNormal HoverButton { width: 48 height: 48 icon.width: 44 icon.height: 44 icon.source: "list-add-symbolic" } } TouchSlider { width: 16 height: 200 orientation: Qt.Vertical } SearchLineEdit { width:300 height:32 placeholderText:"placeholdertext" } SearchLineEdit { width:300 height:60 borderRadius: 15 showAIButton: true enableSpecificBorder: false placeholderText:"搜索" enabled: false textInput.enabled: true } SearchLineEdit { width:500 height:130 showAIButton: true enableSpecificBorder: false enableTransparentMode : true borderRadius: 10 borderWidth: 5 enabled: true enableContextMenu:true } } } ukui-quick/platform/test/TokenColorsPage.qml0000664000175000017500000004130415153755732020163 0ustar fengfengimport QtQuick 2.0 import org.ukui.quick.platform 1.0 import QtQuick.Layouts 1.15 GridView { id: activeView width: parent.width height: parent.height interactive: true cellWidth: 140 model: tokenModel delegate: TokenColor { color: dtToken colorText: model.name } clip: true ListModel { id: tokenModel dynamicRoles: true Component.onCompleted: { append({"dtToken": GlobalTheme.windowTextActive, "name": "windowTextActive"}) append({"dtToken": GlobalTheme.windowTextInactive, "name": "windowTextInactive"}) append({"dtToken": GlobalTheme.windowTextDisable, "name": "windowTextDisable"}) append({"dtToken": GlobalTheme.buttonActive, "name": "buttonActive"}) append({"dtToken": GlobalTheme.buttonInactive, "name": "buttonInactive"}) append({"dtToken": GlobalTheme.buttonDisable, "name": "buttonDisable"}) append({"dtToken": GlobalTheme.lightActive, "name": "lightActive"}) append({"dtToken": GlobalTheme.lightInactive, "name": "lightInactive"}) append({"dtToken": GlobalTheme.lightDisable, "name": "lightDisable"}) append({"dtToken": GlobalTheme.darkActive, "name": "darkActive"}) append({"dtToken": GlobalTheme.darkInactive, "name": "darkInactive"}) append({"dtToken": GlobalTheme.darkDisable, "name": "darkDisable"}) append({"dtToken": GlobalTheme.baseActive, "name": "baseActive"}) append({"dtToken": GlobalTheme.baseInactive, "name": "baseInactive"}) append({"dtToken": GlobalTheme.baseDisable, "name": "baseDisable"}) append({"dtToken": GlobalTheme.midLightActive, "name": "midLightActive"}) append({"dtToken": GlobalTheme.midLightInactive, "name": "midLightInactive"}) append({"dtToken": GlobalTheme.midLightDisable, "name": "midLightDisable"}) append({"dtToken": GlobalTheme.midActive, "name": "midActive"}) append({"dtToken": GlobalTheme.midInactive, "name": "midInactive"}) append({"dtToken": GlobalTheme.midDisable, "name": "midDisable"}) append({"dtToken": GlobalTheme.textActive, "name": "textActive"}) append({"dtToken": GlobalTheme.textInactive, "name": "textInactive"}) append({"dtToken": GlobalTheme.textDisable, "name": "textDisable"}) append({"dtToken": GlobalTheme.brightTextActive, "name": "brightTextActive"}) append({"dtToken": GlobalTheme.brightTextInactive, "name": "brightTextInactive"}) append({"dtToken": GlobalTheme.brightTextDisable, "name": "brightTextDisable"}) append({"dtToken": GlobalTheme.buttonTextActive, "name": "buttonTextActive"}) append({"dtToken": GlobalTheme.buttonTextInactive, "name": "buttonTextInactive"}) append({"dtToken": GlobalTheme.buttonTextDisable, "name": "buttonTextDisable"}) append({"dtToken": GlobalTheme.baseActive, "name": "baseActive"}) append({"dtToken": GlobalTheme.baseInactive, "name": "baseInactive"}) append({"dtToken": GlobalTheme.baseDisable, "name": "baseDisable"}) append({"dtToken": GlobalTheme.windowActive, "name": "windowActive"}) append({"dtToken": GlobalTheme.windowInactive, "name": "windowInactive"}) append({"dtToken": GlobalTheme.windowDisable, "name": "windowDisable"}) append({"dtToken": GlobalTheme.shadowActive, "name": "shadowActive"}) append({"dtToken": GlobalTheme.shadowInactive, "name": "shadowInactive"}) append({"dtToken": GlobalTheme.shadowDisable, "name": "shadowDisable"}) append({"dtToken": GlobalTheme.highlightActive, "name": "highlightActive"}) append({"dtToken": GlobalTheme.highlightInactive, "name": "highlightInactive"}) append({"dtToken": GlobalTheme.highlightDisable, "name": "highlightDisable"}) append({"dtToken": GlobalTheme.linkActive, "name": "linkActive"}) append({"dtToken": GlobalTheme.linkInactive, "name": "linkInactive"}) append({"dtToken": GlobalTheme.linkDisable, "name": "linkDisable"}) append({"dtToken": GlobalTheme.linkVisitedActive, "name": "linkVisitedActive"}) append({"dtToken": GlobalTheme.linkVisitedInactive, "name": "linkVisitedInactive"}) append({"dtToken": GlobalTheme.linkVisitedDisable, "name": "linkVisitedDisable"}) append({"dtToken": GlobalTheme.alternateBaseActive, "name": "alternateBaseActive"}) append({"dtToken": GlobalTheme.alternateBaseInactive, "name": "alternateBaseInactive"}) append({"dtToken": GlobalTheme.alternateBaseDisable, "name": "alternateBaseDisable"}) append({"dtToken": GlobalTheme.noRoleActive, "name": "noRoleActive"}) append({"dtToken": GlobalTheme.noRoleInactive, "name": "noRoleInactive"}) append({"dtToken": GlobalTheme.noRoleDisable, "name": "noRoleDisable"}) append({"dtToken": GlobalTheme.toolTipBaseActive, "name": "toolTipBaseActive"}) append({"dtToken": GlobalTheme.toolTipBaseInactive, "name": "toolTipBaseInactive"}) append({"dtToken": GlobalTheme.toolTipBaseDisable, "name": "toolTipBaseDisable"}) append({"dtToken": GlobalTheme.toolTipTextActive, "name": "toolTipTextActive"}) append({"dtToken": GlobalTheme.toolTipTextInactive, "name": "toolTipTextInactive"}) append({"dtToken": GlobalTheme.toolTipTextDisable, "name": "toolTipTextDisable"}) append({"dtToken": GlobalTheme.placeholderTextActive, "name": "placeholderTextActive"}) append({"dtToken": GlobalTheme.placeholderTextInactive, "name": "placeholderTextInactive"}) append({"dtToken": GlobalTheme.placeholderTextDisable, "name": "placeholderTextDisable"}) append({"dtToken": GlobalTheme.kWhite, "name": "kWhite"}) append({"dtToken": GlobalTheme.kBlack, "name": "kBlack"}) append({"dtToken": GlobalTheme.kGray0, "name": "kGray0"}) append({"dtToken": GlobalTheme.kGray1, "name": "kGray1"}) append({"dtToken": GlobalTheme.kGray2, "name": "kGray2"}) append({"dtToken": GlobalTheme.kGray3, "name": "kGray3"}) append({"dtToken": GlobalTheme.kGray4, "name": "kGray4"}) append({"dtToken": GlobalTheme.kGray5, "name": "kGray5"}) append({"dtToken": GlobalTheme.kGray6, "name": "kGray6"}) append({"dtToken": GlobalTheme.kGray7, "name": "kGray7"}) append({"dtToken": GlobalTheme.kGray8, "name": "kGray8"}) append({"dtToken": GlobalTheme.kGray9, "name": "kGray9"}) append({"dtToken": GlobalTheme.kGray10, "name": "kGray10"}) append({"dtToken": GlobalTheme.kGray11, "name": "kGray11"}) append({"dtToken": GlobalTheme.kGray12, "name": "kGray12"}) append({"dtToken": GlobalTheme.kGray13, "name": "kGray13"}) append({"dtToken": GlobalTheme.kGray14, "name": "kGray14"}) append({"dtToken": GlobalTheme.kGray15, "name": "kGray15"}) append({"dtToken": GlobalTheme.kGray16, "name": "kGray16"}) append({"dtToken": GlobalTheme.kGray17, "name": "kGray17"}) append({"dtToken": GlobalTheme.kGrayAlpha0, "name": "kGrayAlpha0"}) append({"dtToken": GlobalTheme.kGrayAlpha1, "name": "kGrayAlpha1"}) append({"dtToken": GlobalTheme.kGrayAlpha2, "name": "kGrayAlpha2"}) append({"dtToken": GlobalTheme.kGrayAlpha3, "name": "kGrayAlpha3"}) append({"dtToken": GlobalTheme.kGrayAlpha4, "name": "kGrayAlpha4"}) append({"dtToken": GlobalTheme.kGrayAlpha5, "name": "kGrayAlpha5"}) append({"dtToken": GlobalTheme.kGrayAlpha6, "name": "kGrayAlpha6"}) append({"dtToken": GlobalTheme.kGrayAlpha7, "name": "kGrayAlpha7"}) append({"dtToken": GlobalTheme.kGrayAlpha8, "name": "kGrayAlpha8"}) append({"dtToken": GlobalTheme.kGrayAlpha9, "name": "kGrayAlpha9"}) append({"dtToken": GlobalTheme.kGrayAlpha10, "name": "kGrayAlpha10"}) append({"dtToken": GlobalTheme.kGrayAlpha11, "name": "kGrayAlpha11"}) append({"dtToken": GlobalTheme.kGrayAlpha12, "name": "kGrayAlpha12"}) append({"dtToken": GlobalTheme.kGrayAlpha13, "name": "kGrayAlpha13"}) append({"dtToken": GlobalTheme.kFontStrong, "name": "kFontStrong"}) append({"dtToken": GlobalTheme.kFontPrimary, "name": "kFontPrimary"}) append({"dtToken": GlobalTheme.kFontPrimaryDisable, "name": "kFontPrimaryDisable"}) append({"dtToken": GlobalTheme.kFontSecondary, "name": "kFontSecondary"}) append({"dtToken": GlobalTheme.kFontSecondaryDisable, "name": "kFontSecondaryDisable"}) append({"dtToken": GlobalTheme.kFontWhite, "name": "kFontWhite"}) append({"dtToken": GlobalTheme.kFontWhiteDisable, "name": "kFontWhiteDisable"}) append({"dtToken": GlobalTheme.kFontWhiteSecondary, "name": "kFontWhiteSecondary"}) append({"dtToken": GlobalTheme.kFontWhiteSecondaryDisable, "name": "kFontWhiteSecondaryDisable"}) append({"dtToken": GlobalTheme.kBrandNormal, "name": "kBrandNormal"}) append({"dtToken": GlobalTheme.kBrandHover, "name": "kBrandHover"}) append({"dtToken": GlobalTheme.kBrandClick, "name": "kBrandClick"}) append({"dtToken": GlobalTheme.kBrandFocus, "name": "kBrandFocus"}) append({"dtToken": GlobalTheme.kSuccessNormal, "name": "kSuccessNormal"}) append({"dtToken": GlobalTheme.kSuccessHover, "name": "kSuccessHover"}) append({"dtToken": GlobalTheme.kSuccessClick, "name": "kSuccessClick"}) append({"dtToken": GlobalTheme.kWarningNormal, "name": "kWarningNormal"}) append({"dtToken": GlobalTheme.kWarningHover, "name": "kWarningHover"}) append({"dtToken": GlobalTheme.kWarningClick, "name": "kWarningClick"}) append({"dtToken": GlobalTheme.kErrorNormal, "name": "kErrorNormal"}) append({"dtToken": GlobalTheme.kErrorHover, "name": "kErrorHover"}) append({"dtToken": GlobalTheme.kErrorClick, "name": "kErrorClick"}) append({"dtToken": GlobalTheme.kBrand1, "name": "kBrand1"}) append({"dtToken": GlobalTheme.kBrand2, "name": "kBrand2"}) append({"dtToken": GlobalTheme.kBrand3, "name": "kBrand3"}) append({"dtToken": GlobalTheme.kBrand4, "name": "kBrand4"}) append({"dtToken": GlobalTheme.kBrand5, "name": "kBrand5"}) append({"dtToken": GlobalTheme.kBrand6, "name": "kBrand6"}) append({"dtToken": GlobalTheme.kBrand7, "name": "kBrand7"}) append({"dtToken": GlobalTheme.kContainHover, "name": "kContainHover"}) append({"dtToken": GlobalTheme.kContainClick, "name": "kContainClick"}) append({"dtToken": GlobalTheme.kContainGeneralNormal, "name": "kContainGeneralNormal"}) append({"dtToken": GlobalTheme.kContainSecondaryNormal, "name": "kContainSecondaryNormal"}) append({"dtToken": GlobalTheme.kContainSecondaryAlphaNormal, "name": "kContainSecondaryAlphaNormal"}) append({"dtToken": GlobalTheme.kContainSecondaryAlphaHover, "name": "kContainSecondaryAlphaHover"}) append({"dtToken": GlobalTheme.kContainSecondaryAlphaClick, "name": "kContainSecondaryAlphaClick"}) append({"dtToken": GlobalTheme.kComponentNormal, "name": "kComponentNormal"}) append({"dtToken": GlobalTheme.kComponentHover, "name": "kComponentHover"}) append({"dtToken": GlobalTheme.kComponentClick, "name": "kComponentClick"}) append({"dtToken": GlobalTheme.kComponentDisable, "name": "kComponentDisable"}) append({"dtToken": GlobalTheme.kComponentAlphaNormal, "name": "kComponentAlphaNormal"}) append({"dtToken": GlobalTheme.kComponentAlphaHover, "name": "kComponentAlphaHover"}) append({"dtToken": GlobalTheme.kComponentAlphaClick, "name": "kComponentAlphaClick"}) append({"dtToken": GlobalTheme.kComponentAlphaDisable, "name": "kComponentAlphaDisable"}) append({"dtToken": GlobalTheme.kLineWindow, "name": "kLineWindow"}) append({"dtToken": GlobalTheme.kLineWindowActive, "name": "kLineWindowActive"}) append({"dtToken": GlobalTheme.kLineNormalAlpha, "name": "kLineNormalAlpha"}) append({"dtToken": GlobalTheme.kLineDisableAlpha, "name": "kLineDisableAlpha"}) append({"dtToken": GlobalTheme.kLineComponentNormal, "name": "kLineComponentNormal"}) append({"dtToken": GlobalTheme.kLineComponentHover, "name": "kLineComponentHover"}) append({"dtToken": GlobalTheme.kLineComponentClick, "name": "kLineComponentClick"}) append({"dtToken": GlobalTheme.kLineComponentDisable, "name": "kLineComponentDisable"}) append({"dtToken": GlobalTheme.kLineBrandNormal, "name": "kLineBrandNormal"}) append({"dtToken": GlobalTheme.kLineBrandHover, "name": "kLineBrandHover"}) append({"dtToken": GlobalTheme.kLineBrandClick, "name": "kLineBrandClick"}) append({"dtToken": GlobalTheme.kLineBrandDisable, "name": "kLineBrandDisable"}) append({"dtToken": GlobalTheme.kModalMask, "name": "kModalMask"}) append({"dtToken": GlobalTheme.kErrorDisable, "name": "kErrorDisable"}) append({"dtToken": GlobalTheme.kWarningDisable, "name": "kWarningDisable"}) append({"dtToken": GlobalTheme.kLineNormal, "name": "kLineNormal"}) append({"dtToken": GlobalTheme.tokenColor, "name": "tokenColor"}) append({"dtToken": GlobalTheme.kContainGeneralAlphaNormal, "name": "kContainGeneralAlphaNormal"}) append({"dtToken": GlobalTheme.kSuccessDisable, "name": "kSuccessDisable"}) append({"dtToken": GlobalTheme.kLineWindowInactive, "name": "kLineWindowInactive"}) append({"dtToken": GlobalTheme.tokenColor1, "name": "tokenColor1"}) append({"dtToken": GlobalTheme.kContainAlphaClick, "name": "kContainAlphaClick"}) append({"dtToken": GlobalTheme.kContainAlphaHover, "name": "kContainAlphaHover"}) append({"dtToken": GlobalTheme.kBrandDisable, "name": "kBrandDisable"}) append({"dtToken": GlobalTheme.kLineDisable, "name": "kLineDisable"}) append({"dtToken": GlobalTheme.kDivider, "name": "kDivider"}) // append({"dtToken": GlobalTheme.normalLine, "name": "normalLine"}) // append({"dtToken": GlobalTheme.focusLine, "name": "focusLine"}) // append({"dtToken": GlobalTheme.kRadiusMin, "name": "kRadiusMin"}) // append({"dtToken": GlobalTheme.kRadiusNormal, "name": "kRadiusNormal"}) // append({"dtToken": GlobalTheme.kRadiusMenu, "name": "kRadiusMenu"}) // append({"dtToken": GlobalTheme.kRadiusWindow, "name": "kRadiusWindow"}) // append({"dtToken": GlobalTheme.kMarginMin, "name": "kMarginMin"}) // append({"dtToken": GlobalTheme.kMarginNormal, "name": "kMarginNormal"}) // append({"dtToken": GlobalTheme.kMarginBig, "name": "kMarginBig"}) // append({"dtToken": GlobalTheme.kMarginWindow, "name": "kMarginWindow"}) // append({"dtToken": GlobalTheme.kMarginComponent, "name": "kMarginComponent"}) // append({"dtToken": GlobalTheme.kPaddingMinLeft, "name": "kPaddingMinLeft"}) // append({"dtToken": GlobalTheme.kPaddingMinRight, "name": "kPaddingMinRight"}) // append({"dtToken": GlobalTheme.kPaddingMinTop, "name": "kPaddingMinTop"}) // append({"dtToken": GlobalTheme.kPaddingMinBottom, "name": "kPaddingMinBottom"}) // append({"dtToken": GlobalTheme.kPaddingNormalLeft, "name": "kPaddingNormalLeft"}) // append({"dtToken": GlobalTheme.kPaddingNormalRight, "name": "kPaddingNormalRight"}) // append({"dtToken": GlobalTheme.kPaddingNormalTop, "name": "kPaddingNormalTop"}) // append({"dtToken": GlobalTheme.kPaddingNormalBottom, "name": "kPaddingNormalBottom"}) // append({"dtToken": GlobalTheme.kPadding8Left, "name": "kPadding8Left"}) // append({"dtToken": GlobalTheme.kPadding8Right, "name": "kPadding8Right"}) // append({"dtToken": GlobalTheme.kPadding8Top, "name": "kPadding8Top"}) // append({"dtToken": GlobalTheme.kPadding8Bottom, "name": "kPadding8Bottom"}) // append({"dtToken": GlobalTheme.kPaddingWindowLeft, "name": "kPaddingWindowLeft"}) // append({"dtToken": GlobalTheme.kPaddingWindowRight, "name": "kPaddingWindowRight"}) // append({"dtToken": GlobalTheme.kPaddingWindowTop, "name": "kPaddingWindowTop"}) // append({"dtToken": GlobalTheme.kPaddingWindowBottom, "name": "kPaddingWindowBottom"}) } } } ukui-quick/platform/test/TokenIntsPage.qml0000664000175000017500000000533615153755732017644 0ustar fengfengimport QtQuick 2.0 import org.ukui.quick.platform 1.0 import QtQuick.Layouts 1.15 ListView { id: activeView width: parent.width height: parent.height interactive: true model: tokenModel delegate: SettingsValue { name: model.name value: model.dtToken } clip: true ListModel { id: tokenModel // dynamicRoles: true Component.onCompleted: { append({"dtToken": GlobalTheme.normalLine, "name": "normalLine"}) append({"dtToken": GlobalTheme.focusLine, "name": "focusLine"}) append({"dtToken": GlobalTheme.kRadiusMin, "name": "kRadiusMin"}) append({"dtToken": GlobalTheme.kRadiusNormal, "name": "kRadiusNormal"}) append({"dtToken": GlobalTheme.kRadiusMenu, "name": "kRadiusMenu"}) append({"dtToken": GlobalTheme.kRadiusWindow, "name": "kRadiusWindow"}) append({"dtToken": GlobalTheme.kMarginMin, "name": "kMarginMin"}) append({"dtToken": GlobalTheme.kMarginNormal, "name": "kMarginNormal"}) append({"dtToken": GlobalTheme.kMarginBig, "name": "kMarginBig"}) append({"dtToken": GlobalTheme.kMarginWindow, "name": "kMarginWindow"}) append({"dtToken": GlobalTheme.kMarginComponent, "name": "kMarginComponent"}) append({"dtToken": GlobalTheme.kPaddingMinLeft, "name": "kPaddingMinLeft"}) append({"dtToken": GlobalTheme.kPaddingMinRight, "name": "kPaddingMinRight"}) append({"dtToken": GlobalTheme.kPaddingMinTop, "name": "kPaddingMinTop"}) append({"dtToken": GlobalTheme.kPaddingMinBottom, "name": "kPaddingMinBottom"}) append({"dtToken": GlobalTheme.kPaddingNormalLeft, "name": "kPaddingNormalLeft"}) append({"dtToken": GlobalTheme.kPaddingNormalRight, "name": "kPaddingNormalRight"}) append({"dtToken": GlobalTheme.kPaddingNormalTop, "name": "kPaddingNormalTop"}) append({"dtToken": GlobalTheme.kPaddingNormalBottom, "name": "kPaddingNormalBottom"}) append({"dtToken": GlobalTheme.kPadding8Left, "name": "kPadding8Left"}) append({"dtToken": GlobalTheme.kPadding8Right, "name": "kPadding8Right"}) append({"dtToken": GlobalTheme.kPadding8Top, "name": "kPadding8Top"}) append({"dtToken": GlobalTheme.kPadding8Bottom, "name": "kPadding8Bottom"}) append({"dtToken": GlobalTheme.kPaddingWindowLeft, "name": "kPaddingWindowLeft"}) append({"dtToken": GlobalTheme.kPaddingWindowRight, "name": "kPaddingWindowRight"}) append({"dtToken": GlobalTheme.kPaddingWindowTop, "name": "kPaddingWindowTop"}) append({"dtToken": GlobalTheme.kPaddingWindowBottom, "name": "kPaddingWindowBottom"}) } } }ukui-quick/platform/test/CMakeLists.txt0000664000175000017500000000116415153756415017150 0ustar fengfengcmake_minimum_required(VERSION 3.14) project(platform-test) find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Quick Qml Widgets QuickControls2 REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Quick Qml Widgets QuickControls2 REQUIRED) set(QRC_FILES qml.qrc) set(PROJECT_SOURCES main.cpp ${QRC_FILES} ) add_executable(${PROJECT_NAME} ${PROJECT_SOURCES}) target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Qml Qt${QT_VERSION_MAJOR}::QuickControls2 ) ukui-quick/platform/test/main.cpp0000664000175000017500000000407415153755732016044 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #include #include int main(int argc, char *argv[]) { QString sessionType(qgetenv("XDG_SESSION_TYPE")); if(sessionType == "wayland") { qputenv("QT_WAYLAND_DISABLE_FIXED_POSITIONS", "true"); } QApplication app(argc, argv); auto view = new QQuickView; view->setResizeMode(QQuickView::SizeRootObjectToView); view->resize(1500, 950); view->setSource(QUrl("qrc:///main.qml")); view->show(); //计算混色 /* QColor back(245, 63, 63, 255); QColor fore(0, 0, 0, 0.2 * 255); qDebug() << fore.alphaF(); qreal tiny = 1 - fore.alphaF(); qreal alpha = fore.alphaF() + back.alphaF() * tiny; qreal r = (fore.redF() * fore.alphaF() + back.redF() * back.alphaF() * tiny) / alpha; qreal g = (fore.greenF() * fore.alphaF() + back.greenF() * back.alphaF() * tiny) / alpha; qreal b = (fore.blueF() * fore.alphaF() + back.blueF() * back.alphaF() * tiny) / alpha; qDebug() << QColor::fromRgbF(r, g, b, alpha); qDebug() << QColor::fromRgbF(r, g, b, alpha).red(); qDebug() << QColor::fromRgbF(r, g, b, alpha).green(); qDebug() << QColor::fromRgbF(r, g, b, alpha).blue(); qDebug() << QColor::fromRgbF(r, g, b, alpha).alpha();*/ return QApplication::exec(); }ukui-quick/platform/test/SettingsValue.qml0000664000175000017500000000107515153755732017722 0ustar fengfengimport QtQuick 2.15 import QtQuick.Controls 2.15 import org.ukui.quick.items 1.0 Row { property alias name: nameText.text property alias value: valueText.text width: childrenRect.width height: childrenRect.height spacing: 10 StyleText { id: nameText wrapMode: Text.Wrap verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } StyleText { id: valueText wrapMode: Text.Wrap verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } }ukui-quick/platform/test/AppLauncherButton.qml0000664000175000017500000000214615153755732020523 0ustar fengfengimport QtQuick 2.12 import QtQuick.Controls 2.15 as Controls import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 MouseArea { readonly property alias background: backgroundBase property alias text : nameText.text hoverEnabled: true activeFocusOnTab: true StyleBackground { id: backgroundBase anchors.fill: parent border.width: parent.activeFocus ? 2 : 0 paletteRole: Theme.BrightText borderColor: Theme.Highlight useStyleTransparency: false alpha: parent.containsPress ? 0.30 : parent.containsMouse ? 0.15 : 0.0 StyleText { id: nameText width: parent.width height: parent.height wrapMode: Text.Wrap verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } Tooltip { width: parent.width height: parent.height mainText: (nameText.text) posFollowCursor: false location: Dialog.TopEdge margin: 9 interactive: true } } } ukui-quick/platform/test/SettingsValuePage.qml0000664000175000017500000000262115153755732020515 0ustar fengfengimport QtQuick 2.0 import org.ukui.quick.platform 1.0 import org.ukui.quick.items 1.0 Column { spacing: 10 SettingsValue { name: "liteAnimation" value: Settings.liteAnimation } SettingsValue { name: "liteFunction" value: Settings.liteFunction } SettingsValue { name: "tabletMode" value: Settings.tabletMode } ShadowedRectangle { width: 800 height: 500 pureColor: false startColor: "red" endColor: "yellow" color: "blue" angle: 2 // color: "transparent" corners { topLeftRadius: 10 topRightRadius: 50 bottomLeftRadius: 80 bottomRightRadius: 0 } radius: 5 shadow { size: 10 color: Qt.rgba(0, 0, 0, 0.5) yOffset: 2 } border { width: 1 color: "black" } } Button { id: roundButton width: 100 height: 100 background.radius: 50 ShadowedTexture { source: roundButton anchors.fill: parent radius: 50 shadow { size: 20 color: Qt.rgba(0, 0, 0, 1) yOffset: 2 } border { width: 1 color: "blue" } } } }ukui-quick/platform/test/PaletteColorPage.qml0000664000175000017500000000507615153755732020324 0ustar fengfengimport QtQuick 2.0 import org.ukui.quick.platform 1.0 import org.ukui.quick.items 1.0 import QtQuick.Layouts 1.15 ColumnLayout { id: views spacing: 10 width: parent.width height: parent.height GridView { id: activeView Layout.fillWidth: true Layout.preferredHeight: contentHeight cellWidth: 140 model: paletteModel delegate: PaletteColor{ paletteRole: model.role paletteGroup: Theme.Active colorText: model.name } clip: true } GridView { id: disabledView Layout.fillWidth: true Layout.preferredHeight: contentHeight cellWidth: 140 model: paletteModel delegate: PaletteColor{ paletteRole: model.role paletteGroup: Theme.Disabled colorText:model.name } clip: true } GridView { id: inactiveView model: paletteModel Layout.fillWidth: true Layout.preferredHeight: contentHeight cellWidth: 140 delegate: PaletteColor{ paletteRole: model.role paletteGroup: Theme.Inactive colorText:model.name } clip: true } ListModel { id: paletteModel Component.onCompleted: { append({role: Theme.Window, name: "Window"}) append({role: Theme.WindowText, name: "WindowText"}) append({role: Theme.Base, name: "Base"}) append({role: Theme.Text, name: "Text"}) append({role: Theme.AlternateBase, name: "AlternateBase"}) append({role: Theme.Button, name: "Button"}) append({role: Theme.ButtonText, name: "ButtonText"}) append({role: Theme.Light, name: "Light"}) append({role: Theme.MidLight, name: "MidLight"}) append({role: Theme.Dark, name: "Dark"}) append({role: Theme.Mid, name: "Mid"}) append({role: Theme.Shadow, name: "Shadow"}) append({role: Theme.Shadow, name: "Shadow"}) append({role: Theme.Highlight, name: "Highlight"}) append({role: Theme.HighlightedText, name: "HighlightedText"}) append({role: Theme.BrightText, name: "BrightText"}) append({role: Theme.Link, name: "Link"}) append({role: Theme.LinkVisited, name: "LinkVisited"}) append({role: Theme.ToolTipBase, name: "ToolTipBase"}) append({role: Theme.ToolTipText, name: "ToolTipText"}) append({role: Theme.PlaceholderText, name: "PlaceholderText"}) } } } ukui-quick/platform/test/AppLauncherPage.qml0000664000175000017500000000262515153755732020126 0ustar fengfengimport QtQuick 2.15 import org.ukui.quick.platform 1.0 as PlatForm import org.ukui.quick.items 1.0 Column { id: root Row { width: 900 height: 50 spacing: 4 TextInputWidget { id: desktopFileText width: 400 height: parent.height focus: true } TextInputWidget { id: launchAppArg width: 100 height: parent.height } AppLauncherButton { text: "launchApp" width: 200; height: parent.height onClicked: { PlatForm.AppLauncher.launchApp(desktopFileText.text); } } AppLauncherButton { text: "launchAppWithArgs" width: 200; height: parent.height onClicked: { PlatForm.AppLauncher.launchAppWithArguments(desktopFileText.text, launchAppArg.text.split(' ')); } } } Row { width: 900 height: 50 spacing: 4 TextInputWidget { id: uriText width: 400 height: parent.height focus: true } AppLauncherButton { text: "openUri" width: 200; height: parent.height onClicked: { PlatForm.AppLauncher.openUri(uriText.text, ""); } } } }ukui-quick/platform/test/main.qml0000664000175000017500000000163515153755732016053 0ustar fengfengimport QtQuick 2.15 import QtQuick.Controls 1.4 TabView { Tab { width: parent.width height: parent.height title: "Palette" PaletteColorPage {} } Tab { width: parent.width title: "Settings" SettingsValuePage {} } Tab { width: parent.width height: parent.height title: "AppLauncher" AppLauncherPage {} } Tab { width: parent.width height: parent.height title: "ToolTip" ToolTipTest {} } Tab { width: parent.width height: parent.height title: "Design Token" TokenColorsPage {} } Tab { width: parent.width height: parent.height title: "Design Token" TokenIntsPage {} } Tab { width: parent.width height: parent.height title: "DtTheme Item" DtItemPage {} } } ukui-quick/platform/test/qml.qrc0000664000175000017500000000106315153756415015706 0ustar fengfeng main.qml PaletteColor.qml PaletteColorPage.qml SettingsValue.qml SettingsValuePage.qml TextInputWidget.qml AppLauncherButton.qml AppLauncherPage.qml ToolTipTest.qml TokenColor.qml TokenColorsPage.qml TokenIntsPage.qml DtItemPage.qml ukui-quick/platform/test/TextInputWidget.qml0000664000175000017500000000220615153755732020232 0ustar fengfengimport QtQuick 2.15 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 Item { property alias text: textInput.text; StyleBackground { anchors.fill: parent alpha: 0.04 useStyleTransparency: false paletteRole: Platform.Theme.Text border.width: 1 borderAlpha: textInput.activeFocus ? 1 : 0.1 borderColor: textInput.activeFocus ? Platform.Theme.Highlight : Platform.Theme.BrightText z: 0 } TextInput { id: textInput clip: true anchors.fill: parent selectByMouse: true verticalAlignment: TextInput.AlignVCenter font.pointSize: Platform.Theme.fontSize activeFocusOnPress: true; leftPadding: 2 rightPadding: 2 activeFocusOnTab: true; z: 1 function updateTextInputColor() { color = Platform.Theme.text(); selectionColor = Platform.Theme.highlight(); } Platform.Theme.onPaletteChanged: { updateTextInputColor(); } Component.onCompleted: { updateTextInputColor(); } } }ukui-quick/platform/test/TokenColor.qml0000664000175000017500000000101615153755732017177 0ustar fengfengimport QtQuick 2.12 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 Column { property alias color: backGround.backgroundColor property alias colorText: colorText.text width: childrenRect.width DtThemeBackground { id: backGround width: 120 height: 80 radius: 0 } StyleText { id: colorText width: 140 wrapMode: Text.WrapAnywhere verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } } ukui-quick/.gitignore0000664000175000017500000000046115153755732013575 0ustar fengfeng# Ignore the following files .vscode *~ *.[oa] *.diff *.kate-swp *.kdev4 .kdev_include_paths *.kdevelop.pcs *.moc *.moc.cpp *.orig *.user .*.swp .swp.* Doxyfile Makefile avail random_seed /build*/ CMakeLists.txt.user* *.unc-backup* .clang-format /compile_commands.json .clangd .idea /cmake-build* .cache ukui-quick/items/0000775000175000017500000000000015153756415012724 5ustar fengfengukui-quick/items/action-extension.cpp0000664000175000017500000001413515153755732016724 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "action-extension.h" #include namespace UkuiQuick { class ActionExtensionPrivate : public QObject { Q_OBJECT public: explicit ActionExtensionPrivate(QObject *parent, QAction *action); ~ActionExtensionPrivate() override; static void appendAction(QQmlListProperty *list, QAction *action); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) static qsizetype actionCount(QQmlListProperty *list); static QAction *actionAt(QQmlListProperty *list, qsizetype qsizetype); static void replaceAction(QQmlListProperty *list, qsizetype index, QAction *action); #else static int actionCount(QQmlListProperty *list); static QAction *actionAt(QQmlListProperty *list, int index); static void replaceAction(QQmlListProperty *list, int index, QAction *action); #endif static void clearAction(QQmlListProperty *list); static void removeLastAction(QQmlListProperty *list); static void resetMenu(ActionExtensionPrivate *aep); QString iconName; QList subActions; QAction *rawAction {nullptr}; std::unique_ptr subMenu; }; ActionExtensionPrivate::ActionExtensionPrivate(QObject *parent, QAction *action) : QObject(parent), rawAction(action) { } ActionExtensionPrivate::~ActionExtensionPrivate() { subMenu.reset(); } void ActionExtensionPrivate::appendAction(QQmlListProperty *list, QAction *action) { if (!action) { return; } auto aep = qobject_cast(list->object); if (!aep->subActions.contains(action)) { aep->subActions.append(action); if (!aep->subMenu) { aep->subMenu.reset(new QMenu); aep->subMenu->setAttribute(Qt::WA_DeleteOnClose, false); aep->rawAction->setMenu(aep->subMenu.get()); } aep->subMenu->addAction(action); } } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) qsizetype ActionExtensionPrivate::actionCount(QQmlListProperty *list) #else int ActionExtensionPrivate::actionCount(QQmlListProperty *list) #endif { auto aep = qobject_cast(list->object); return aep->subActions.size(); } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QAction *ActionExtensionPrivate::actionAt(QQmlListProperty *list, qsizetype index) #else QAction *ActionExtensionPrivate::actionAt(QQmlListProperty *list, int index) #endif { auto aep = qobject_cast(list->object); if (index < 0 || index >= aep->subActions.size()) { return nullptr; } return aep->subActions.at(index); } void ActionExtensionPrivate::clearAction(QQmlListProperty *list) { auto aep = qobject_cast(list->object); aep->subActions.clear(); resetMenu(aep); } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) void ActionExtensionPrivate::replaceAction(QQmlListProperty *list, qsizetype index, QAction *action) #else void ActionExtensionPrivate::replaceAction(QQmlListProperty *list, int index, QAction *action) #endif { if (!action) { return; } auto aep = qobject_cast(list->object); if (index < 0 || index >= aep->subActions.size()) { return; } aep->subActions.replace(index, action); aep->subMenu->clear(); aep->subMenu->addActions(aep->subActions); } void ActionExtensionPrivate::removeLastAction(QQmlListProperty *list) { auto aep = qobject_cast(list->object); if (aep->subActions.isEmpty()) { return; } aep->subMenu->removeAction(aep->subActions.takeLast()); if (aep->subActions.isEmpty()) { resetMenu(aep); } } void ActionExtensionPrivate::resetMenu(ActionExtensionPrivate *aep) { aep->rawAction->setMenu(nullptr); aep->subMenu.reset(); } // ====== ActionExtension ====== // ActionExtension::ActionExtension(QObject *parent) : QObject(parent), d(new ActionExtensionPrivate(this, qobject_cast(parent))) { } ActionExtension::~ActionExtension() { } QString ActionExtension::iconName() const { return d->iconName; } void ActionExtension::setIconName(const QString &name) { if (name == d->iconName) { return; } d->iconName = name; d->rawAction->setIcon(QIcon::fromTheme(name)); Q_EMIT iconNameChanged(); } bool ActionExtension::isSeparator() const { return d->rawAction->isSeparator(); } void ActionExtension::setSeparator(bool isSeparator) { if (isSeparator == d->rawAction->isSeparator()) { return; } d->rawAction->setSeparator(isSeparator); Q_EMIT isSeparatorChanged(); } QMenu *ActionExtension::menu() const { return d->rawAction->menu(); } void ActionExtension::setMenu(QMenu *menu) { if (menu == d->rawAction->menu()) { return; } d->rawAction->setMenu(menu); d->subMenu.reset(menu); Q_EMIT menuChanged(); } QQmlListProperty ActionExtension::subActions() { return {d, &d->subActions, &ActionExtensionPrivate::appendAction, &ActionExtensionPrivate::actionCount, &ActionExtensionPrivate::actionAt, &ActionExtensionPrivate::clearAction, &ActionExtensionPrivate::replaceAction, &ActionExtensionPrivate::removeLastAction }; } } // UkuiQuick #include "action-extension.moc" ukui-quick/items/icon-helper.cpp0000664000175000017500000000724215153755732015643 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "icon-helper.h" #include #include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #else #include #endif #include #include namespace UkuiQuick { QIcon IconHelper::getDefaultIcon() { QIcon icon; loadDefaultIcon(icon); return icon; } QIcon IconHelper::loadIcon(const QString &id) { QIcon icon; if (id.isEmpty()) { loadDefaultIcon(icon); return icon; } bool isOk = false; QString path = toLocalPath(id); if (!path.isEmpty()) { if(QFile::exists(path)) { icon.addFile(path); isOk = !icon.isNull(); } } else { isOk = loadThemeIcon(id, icon); if (!isOk) { isOk = loadXdgIcon(id, icon); } } if (!isOk) { loadDefaultIcon(icon); } return icon; } bool IconHelper::loadPixmap(const QString &path, QPixmap &pixmap) { if (!QFile::exists(path)) { qWarning() << "Error: loadPixmap, File dose not exists." << path; return false; } return pixmap.load(path); } bool IconHelper::loadThemeIcon(const QString &name, QIcon &icon) { if (!QIcon::hasThemeIcon(name)) { return false; } icon = QIcon::fromTheme(name); return true; } void IconHelper::loadDefaultIcon(QIcon &icon) { if (!loadThemeIcon("application-x-desktop", icon)) { QPixmap pixmap; if (loadPixmap(":/res/icon/application-x-desktop.png", pixmap)) { icon.addPixmap(pixmap); } } } bool IconHelper::loadXdgIcon(const QString &name, QIcon &icon) { icon = XdgIcon::fromTheme(name); if (icon.isNull()) { qWarning() << "Error: loadXdgIcon, icon dose not exists. name:" << name; return false; } return true; } // see: https://doc.qt.io/archives/qt-5.12/qurl.html#details QString IconHelper::toLocalPath(const QUrl &url) { if (url.isEmpty()) { return {}; } // file: if (url.isLocalFile()) { return url.path(); } QString schema = url.scheme(); if (schema.isEmpty()) { QString path = url.path(); if (path.startsWith("/") || path.startsWith(":")) { return path; } } else { // qrc example: the Path ":/images/cut.png" or the URL "qrc:///images/cut.png" // see: https://doc.qt.io/archives/qt-5.12/resources.html if (schema == "qrc") { //qrc path: :/xxx/xxx.png return ":" + url.path(); } } return {}; } bool IconHelper::isRemoteServerFile(const QUrl &url) { if (url.isEmpty() || url.scheme().isEmpty()) { return false; } return url.scheme() == "http" || url.scheme() == "https"; } bool IconHelper::isThemeIcon(const QString &name) { return QIcon::hasThemeIcon(name) || !XdgIcon::fromTheme(name).isNull(); } bool IconHelper::isLocalFile(const QUrl &url) { return !toLocalPath(url).isEmpty(); } } // UkuiQuick ukui-quick/items/views/0000775000175000017500000000000015153756415014061 5ustar fengfengukui-quick/items/views/tooltip.h0000664000175000017500000001206715153755732015733 0ustar fengfeng/* SPDX-FileCopyrightText: 2011 Marco Martin SPDX-FileCopyrightText: 2011 Artur Duque de Souza SPDX-FileCopyrightText: 2013 Sebastian Kügler SPDX-FileCopyrightText: 2024 iaom SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef UKUI_QUICK_ITEMS_TOOLTIP_H #define UKUI_QUICK_ITEMS_TOOLTIP_H class QQuickItem; #include "tooltip-dialog.h" namespace UkuiQuick { class Tooltip : public QQuickItem { Q_OBJECT /** * The item shown inside the tooltip. */ Q_PROPERTY(QQuickItem *mainItem READ mainItem WRITE setMainItem NOTIFY mainItemChanged) /** * The main text of this tooltip */ Q_PROPERTY(QString mainText READ mainText WRITE setMainText NOTIFY mainTextChanged) /** * Returns whether the mouse is inside the item */ Q_PROPERTY(bool containsMouse READ containsMouse NOTIFY containsMouseChanged) /** * Plasma Location of the dialog window. Useful if this dialog is a popup for a panel */ Q_PROPERTY(UkuiQuick::Dialog::PopupLocation location READ location WRITE setLocation NOTIFY locationChanged) /** * Property that controls if a tooltips will show on mouse over. * The default is true. */ Q_PROPERTY(bool active MEMBER m_active WRITE setActive NOTIFY activeChanged) /** * Timeout in milliseconds after which the tooltip will hide itself. * Set this value to -1 to never hide the tooltip automatically. */ Q_PROPERTY(int timeout MEMBER m_timeout WRITE setTimeout) Q_PROPERTY(bool posFollowCursor READ posFollowCursor WRITE setPosFollowCursor NOTIFY activeChanged) /** * Margin between tooltip and it's parent item, only work for posFollowCursor is false and location is set to * TopEdge, BottomEdge, LeftEdge or RightEdge. */ Q_PROPERTY(int margin READ margin WRITE setMargin NOTIFY marginChanged) /** * If interactive is false (default), the tooltip will automatically hide * itself as soon as the mouse leaves the tooltiparea, if is true, if the * mouse leaves tooltiparea and goes over the tooltip itself, the tooltip * won't hide, so it will be possible to interact with tooltip contents. */ Q_PROPERTY(bool interactive MEMBER m_interactive WRITE setInteractive NOTIFY interactiveChanged) /** * Returns true if the main text of this tooltip is likely to be rich text */ Q_PROPERTY(bool isRichText MEMBER m_isRichText NOTIFY isRichTextChanged) public: explicit Tooltip(QQuickItem *parent = nullptr); ~Tooltip() override; QQuickItem *mainItem() const; void setMainItem(QQuickItem *mainItem); QString mainText() const; void setMainText(const QString &mainText); UkuiQuick::Dialog::PopupLocation location() const; void setLocation(UkuiQuick::Dialog::PopupLocation location); bool containsMouse() const; void setContainsMouse(bool contains); bool posFollowCursor(); void setPosFollowCursor(bool follow); void setActive(bool active); void setTimeout(int timeout); int margin() const; void setMargin(int margin); void setInteractive(bool interactive); public Q_SLOTS: /** * Shows the tooltip. */ void showTooltip(); /** * Hides the tooltip after a grace period if shown. Does not affect whether the tooltip area is active. */ void hideTooltip(); /** * Hides the tooltip immediately, in comparison to hideTooltip. */ void hideImmediately(); protected: bool childMouseEventFilter(QQuickItem *item, QEvent *event) override; void hoverEnterEvent(QHoverEvent *event) override; void hoverLeaveEvent(QHoverEvent *event) override; void mousePressEvent(QMouseEvent *event) override; TooltipDialog *tooltipDialogInstance(); Q_SIGNALS: void mainItemChanged(); void mainTextChanged(); void containsMouseChanged(); void locationChanged(); void activeChanged(); /** * Emitted just before the tooltip dialog is shown. * */ void aboutToShow(); /** * Emitted when the tooltip's visibility changes. * */ void toolTipVisibleChanged(bool toolTipVisible); void posFollowCursorChanged(); void marginChanged(); void interactiveChanged(); void isRichTextChanged(); private: bool isValid() const; bool m_tooltipsEnabledGlobally; bool m_containsMouse; UkuiQuick::Dialog::PopupLocation m_location; QPointer m_mainItem; QTimer *m_showTimer; QString m_mainText; QVariant m_icon; bool m_active; int m_interval; int m_timeout; bool m_posFollowCursor; int m_margin; bool m_interactive; bool m_isRichText; // TooltipDialog is not a Q_GLOBAL_STATIC because QQuickwindows as global static // are deleted too later after some stuff in the qml runtime has already been deleted, // causing a crash on exit bool m_usingDialog : 1; static TooltipDialog *s_dialog; static int s_dialogUsers; bool m_deactivatedByClick; }; } // UkuiQuick #endif //UKUI_QUICK_ITEMS_TOOLTIP_H ukui-quick/items/views/qmldir0000664000175000017500000000013715153755732015276 0ustar fengfengmodule org.ukui.quick.views plugin ukui-quick-views-plugin depends org.ukui.quick.platform 1.0 ukui-quick/items/views/tooltip-dialog.h0000664000175000017500000000407115153755732017164 0ustar fengfeng/* SPDX-FileCopyrightText: 2013 Sebastian Kügler SPDX-FileCopyrightText: 2024 iaom SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef TOOLTIP_DIALOG_H #define TOOLTIP_DIALOG_H #include #include #include #include "windows/dialog.h" #include "shared-engine-component.h" class QQuickItem; namespace UkuiQuick { /** * Internally used by Tooltip */ class TooltipUtils : public QObject { Q_OBJECT public: static TooltipUtils & instance(); Q_INVOKABLE QString processedText(const QString &text); private: explicit TooltipUtils(QObject *parent = nullptr); int tooltipLength(const QString &text); }; class TooltipDialog : public UkuiQuick::Dialog { Q_OBJECT public: explicit TooltipDialog(QQuickItem *parent = nullptr); ~TooltipDialog() override; QQuickItem *loadDefaultItem(); void dismiss(); void keepalive(); bool interactive(); void setInteractive(bool interactive); int hideTimeout() const; void setHideTimeout(int timeout); /** * Basically the last one who has shown the dialog */ QObject *owner() const; void setOwner(QObject *owner); bool posFollowCursor() const; void setPosFollowCursor(bool follow); void setMargin(int margin); protected: void showEvent(QShowEvent *event) override; void hideEvent(QHideEvent *event) override; void resizeEvent(QResizeEvent *re) override; bool event(QEvent *e) override; QPoint popupPosition(QQuickItem *item, const QSize &size) override; private Q_SLOTS: void valueChanged(const QVariant &value); private: QPoint posByCursor(); SharedEngineComponent *m_qmlObject = nullptr; QTimer *m_showTimer; int m_hideTimeout; bool m_interactive; /** * HACK: prevent tooltips from being incorrectly dismissed (BUG439522) */ enum m_extendTimeoutFlags { None = 0x0, Resized = 0x1, Moved = 0x2, }; int m_extendTimeoutFlag; QObject *m_owner; bool m_posFollowCursor; }; } #endif ukui-quick/items/views/tooltip-dialog.cpp0000664000175000017500000001632715153755732017526 0ustar fengfeng/* SPDX-FileCopyrightText: 2013 Sebastian Kügler SPDX-FileCopyrightText: 2025 iaom SPDX-License-Identifier: GPL-2.0-or-later */ #include "tooltip-dialog.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace UkuiQuick; TooltipDialog::TooltipDialog(QQuickItem *parent) : Dialog(parent) , m_qmlObject(nullptr) , m_hideTimeout(4000) , m_interactive(false) , m_extendTimeoutFlag(m_extendTimeoutFlags::None) , m_owner(nullptr) , m_posFollowCursor(true) { setLocation(UkuiQuick::Dialog::Floating); setType(UkuiQuick::WindowType::ToolTip); setDecorationComponents(UkuiQuick::Decoration::Shadow | UkuiQuick::Decoration::Border | UkuiQuick::Decoration::RoundCorner); setEnableWindowBlur(true); m_showTimer = new QTimer(this); m_showTimer->setSingleShot(true); connect(m_showTimer, &QTimer::timeout, this, [this]() { setVisible(false); }); } TooltipDialog::~TooltipDialog() { } QQuickItem *TooltipDialog::loadDefaultItem() { if (!m_qmlObject) { m_qmlObject = new SharedEngineComponent(); } if (!m_qmlObject->rootObject()) { m_qmlObject->rootContext()->setContextProperty("tooltipUtils", &TooltipUtils::instance()); m_qmlObject->setSource(QUrl("qrc:/DefaultTooltip.qml")); } return qobject_cast(m_qmlObject->rootObject()); } void TooltipDialog::showEvent(QShowEvent *event) { if (m_hideTimeout > 0) { m_showTimer->start(m_hideTimeout); } Dialog::showEvent(event); } void TooltipDialog::hideEvent(QHideEvent *event) { m_showTimer->stop(); Dialog::hideEvent(event); } void TooltipDialog::resizeEvent(QResizeEvent *re) { Dialog::resizeEvent(re); } bool TooltipDialog::event(QEvent *e) { switch (e->type()) { case QEvent::Enter: if (m_interactive) { m_showTimer->stop(); } break; case QEvent::Leave: if (m_extendTimeoutFlag == (m_extendTimeoutFlags::Resized | m_extendTimeoutFlags::Moved)) { keepalive(); // HACK: prevent tooltips from being incorrectly dismissed (BUG439522) } else { dismiss(); } m_extendTimeoutFlag = m_extendTimeoutFlags::None; break; case QEvent::Resize: m_extendTimeoutFlag = m_extendTimeoutFlags::Resized; break; case QEvent::Move: m_extendTimeoutFlag |= m_extendTimeoutFlags::Moved; break; case QEvent::MouseMove: m_extendTimeoutFlag = m_extendTimeoutFlags::None; } bool ret = Dialog::event(e); Qt::WindowFlags flags = Qt::ToolTip | Qt::WindowDoesNotAcceptFocus | Qt::WindowStaysOnTopHint; setFlags(flags); return ret; } QObject *TooltipDialog::owner() const { return m_owner; } void TooltipDialog::setOwner(QObject *owner) { m_owner = owner; } void TooltipDialog::dismiss() { m_showTimer->start(m_hideTimeout / 20); // pretty short: 200ms } void TooltipDialog::keepalive() { m_showTimer->start(m_hideTimeout); } bool TooltipDialog::interactive() { return m_interactive; } void TooltipDialog::setInteractive(bool interactive) { m_interactive = interactive; } void TooltipDialog::valueChanged(const QVariant &value) { setPosition(value.toPoint()); } void TooltipDialog::setHideTimeout(int timeout) { m_hideTimeout = timeout; } int TooltipDialog::hideTimeout() const { return m_hideTimeout; } QPoint TooltipDialog::popupPosition(QQuickItem *item, const QSize &size) { if(m_posFollowCursor) { return posByCursor(); } return Dialog::popupPosition(item, size); } bool TooltipDialog::posFollowCursor() const { return m_posFollowCursor; } void TooltipDialog::setPosFollowCursor(bool follow) { m_posFollowCursor = follow; } QPoint TooltipDialog::posByCursor() { QPoint p = QCursor::pos(); const QScreen *screen =QGuiApplication::screenAt(QCursor::pos()); if (const QPlatformScreen *platformScreen = screen ? screen->handle() : nullptr) { QPlatformCursor *cursor = platformScreen->cursor(); const QSize nativeSize = cursor ? cursor->size() : QSize(16, 16); const QSize cursorSize = QHighDpi::fromNativePixels(nativeSize, platformScreen); QPoint offset(2, cursorSize.height()); if (cursorSize.height() > 2 * this->height()) { offset = QPoint(cursorSize.width() / 2, 0); } p += offset; QRect screenRect = screen->geometry(); if (p.x() + this->width() > screenRect.x() + screenRect.width()) p.rx() -= 4 + this->width(); if (p.y() + this->height() > screenRect.y() + screenRect.height()) p.ry() -= 24 + this->height(); if (p.y() < screenRect.y()) p.setY(screenRect.y()); if (p.x() + this->width() > screenRect.x() + screenRect.width()) p.setX(screenRect.x() + screenRect.width() - this->width()); if (p.x() < screenRect.x()) p.setX(screenRect.x()); if (p.y() + this->height() > screenRect.y() + screenRect.height()) p.setY(screenRect.y() + screenRect.height() - this->height()); } return p; } void TooltipDialog::setMargin(int margin) { Dialog::setMargin(margin); } TooltipUtils &TooltipUtils::instance() { static TooltipUtils tooltipUtils; return tooltipUtils; } QString TooltipUtils::processedText(const QString &text) { if (text.length() <= 30) { return text; } const int separator = tooltipLength(text); QString result; int charCounter = 0; QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, text); int start = 0; int end = 0; while (finder.toNextBoundary() != -1) { end = finder.position(); const QString word = text.mid(start, end - start); start = end; result.append(word); if (word == "\n") { charCounter = 0; } else { charCounter++; if (charCounter == separator) { result.append('\n'); charCounter = 0; } } } return result; } TooltipUtils::TooltipUtils(QObject *parent) : QObject(parent) { } int TooltipUtils::tooltipLength(const QString &text) { int count = 0; int targetCount = 0; QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, text); int start = 0; int end = 0; while (finder.toNextBoundary() != -1) { end = finder.position(); const auto ucs4 = text.mid(start, end - start).toUcs4(); for (const uint &codePoint : ucs4) { QChar::Script script = QChar::script(codePoint); if (script == QChar::Script_Han || script == QChar::Script_Bopomofo || //标点符号 (codePoint >= 0x3000 && codePoint <= 0x303F) || (codePoint >= 0xFF00 && codePoint <= 0xFFEF)) targetCount++; } start = end; count++; } return (targetCount > (count * 2) / 3) ? 30 : 60; } #include "moc_tooltip-dialog.cpp" ukui-quick/items/views/ukui-style-window.cpp0000664000175000017500000001126415153755732020212 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "ukui-style-window.h" #include #include #include namespace UkuiQuick { class UkuiStyleWindowPrivate { public: // 毛玻璃 bool enableWindowBlur {false}; WindowType::Type windowType {WindowType::Normal}; QRegion windowBlurRegion; QRect windowRect {0, 0, 1, 1}; WindowProxy *windowProxy = nullptr; }; UkuiStyleWindow::UkuiStyleWindow(QWindow *parent) : QQuickWindow(parent), d(new UkuiStyleWindowPrivate) { qRegisterMetaType(); d->windowProxy = new WindowProxy(this); } quint64 UkuiStyleWindow::getWinId() { return winId(); } UkuiStyleWindow::~UkuiStyleWindow() { if (d) { delete d; d = nullptr; } } bool UkuiStyleWindow::event(QEvent *event) { switch (event->type()) { case QEvent::Show: { // case QEvent::Expose: { // if (isExposed()) { updateBlurRegion(); // } } default: break; } return QQuickWindow::event(event); } bool UkuiStyleWindow::enableWindowBlur() const { return d->enableWindowBlur; } void UkuiStyleWindow::setEnableWindowBlur(bool enable) { if (d->enableWindowBlur == enable) { return; } d->enableWindowBlur = enable; updateBlurRegion(); Q_EMIT enableWindowBlurChanged(); } QRegion UkuiStyleWindow::blurRegion() const { return d->windowBlurRegion; } void UkuiStyleWindow::setBlurRegion(const QRegion& region) { if (d->windowBlurRegion == region) { return; } d->windowBlurRegion = region; updateBlurRegion(); } void UkuiStyleWindow::updateBlurRegion() { d->windowProxy->setBlurRegion(d->enableWindowBlur, d->windowBlurRegion); } void UkuiStyleWindow::updateGeometry() { QRect rect = windowGeometry(); setGeometry(rect); d->windowProxy->setGeometry(rect); } int UkuiStyleWindow::x() const { return d->windowRect.x(); } void UkuiStyleWindow::setX(int x) { if (d->windowRect.x() == x) { return; } setWindowGeometry(QRect(x, d->windowRect.y(), d->windowRect.width(), d->windowRect.height())); } int UkuiStyleWindow::y() const { return d->windowRect.y(); } void UkuiStyleWindow::setY(int y) { if (d->windowRect.y() == y) { return; } setWindowGeometry(QRect(d->windowRect.x(), y, d->windowRect.width(), d->windowRect.height())); } int UkuiStyleWindow::width() const { return d->windowRect.width(); } void UkuiStyleWindow::setWidth(int width) { if (d->windowRect.width() == width) { return; } setWindowGeometry(QRect(d->windowRect.x(), d->windowRect.y(), width, d->windowRect.height())); } int UkuiStyleWindow::height() const { return d->windowRect.height(); } void UkuiStyleWindow::setHeight(int height) { if (d->windowRect.height() == height) { return; } setWindowGeometry(QRect(d->windowRect.x(), d->windowRect.y(), d->windowRect.width(), height)); } QRect UkuiStyleWindow::windowGeometry() const { return d->windowRect; } void UkuiStyleWindow::setWindowGeometry(const QRect &rect) { if (d->windowRect == rect) { return; } QRect old = d->windowRect; d->windowRect = rect; updateGeometry(); if (old.x() != rect.x()) { Q_EMIT windowXChanged(); } if (old.y() != rect.y()) { Q_EMIT windowYChanged(); } if (old.width() != rect.width()) { Q_EMIT windowWidthChanged(); } if (old.height() != rect.height()) { Q_EMIT windowHeightChanged(); } Q_EMIT windowGeometryChanged(); } QString UkuiStyleWindow::graphBackend() const { return QQuickWindow::sceneGraphBackend(); } WindowType::Type UkuiStyleWindow::windowType() const { return d->windowType; } void UkuiStyleWindow::setWindowType(WindowType::Type windowType) { if (d->windowType == windowType) { return; } d->windowType = windowType; d->windowProxy->setWindowType(windowType); Q_EMIT windowTypeChanged(); } } // UkuiQuick ukui-quick/items/views/content-window.cpp0000664000175000017500000001336715153756415017556 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "content-window.h" #include "ukui/screen-area-utils.h" #include #include #include namespace UkuiQuick { // ====== ContentWindow ====== ContentWindow::ContentWindow(QWindow *parent) : UKUIWindow(parent), m_margin(new Margin(this)) { setColor(QColor(Qt::transparent)); setSkipTaskBar(true); setSkipSwitcher(true); connect(m_margin, &Margin::leftChanged, this, &ContentWindow::updateLocation); connect(m_margin, &Margin::topChanged, this, &ContentWindow::updateLocation); connect(m_margin, &Margin::rightChanged, this, &ContentWindow::updateLocation); connect(m_margin, &Margin::bottomChanged, this, &ContentWindow::updateLocation); } QScreen *ContentWindow::getScreen() const { return screen(); } void ContentWindow::setWindowScreen(QScreen *screen) { if (screen == UKUIWindow::screen()) { return; } setScreen(screen); updateLocation(); Q_EMIT screenChanged(); } QQuickItem *ContentWindow::content() const { return m_content; } void ContentWindow::setContent(QQuickItem *content) { if (m_content == content) { return; } if (m_content) { m_content->setParentItem(nullptr); m_content->disconnect(this, nullptr); } m_content = content; if (m_content) { m_content->setParentItem(contentItem()); onContentWidthChanged(); onContentHeightChanged(); updateLocation(); connect(m_content, &QQuickItem::widthChanged, this, &ContentWindow::onContentWidthChanged); connect(m_content, &QQuickItem::heightChanged, this, &ContentWindow::onContentHeightChanged); } emit contentChanged(); } Types::Pos ContentWindow::getPos() const { return m_pos; } void ContentWindow::setPos(Types::Pos pos) { if (m_pos == pos) { return; } m_pos = pos; updateLocation(); emit positionChanged(); } Margin *ContentWindow::margin() const { return m_margin; } bool ContentWindow::useAvailableGeometry() const { return m_useAvailableGeometry; } void ContentWindow::setUseAvailableGeometry(bool use) { if (m_useAvailableGeometry == use) { return; } m_useAvailableGeometry = use; Q_EMIT useAvailableGeometryChanged(); updateLocation(); Q_EMIT positionChanged(); } void ContentWindow::reLocated() { updateLocation(); } void ContentWindow::onContentWidthChanged() { setWidth(m_content->width()); updateLocation(); } void ContentWindow::onContentHeightChanged() { setHeight(m_content->height()); updateLocation(); } void ContentWindow::updateLocation() { if (m_pos == Types::NoPosition) { return; } QScreen *currentScreen = getScreen(); if (!currentScreen) { if (parent()) { currentScreen = parent()->screen(); } if (!currentScreen) { return; } } QRect ar = m_useAvailableGeometry? ScreenAreaUtils::instance()->getAvailableGeometry(currentScreen) : currentScreen->geometry(); int nx = 0, ny = 0; switch (m_pos) { case Types::TopLeft: nx = ar.left() + m_margin->left(); ny = ar.top() + m_margin->top(); break; case Types::TopRight: nx = ar.right() - width() - m_margin->right(); ny = ar.top() + m_margin->top(); break; case Types::BottomRight: nx = ar.right() - width() - m_margin->right(); ny = ar.bottom() - height() - m_margin->bottom(); break; case Types::BottomLeft: nx = ar.left() + m_margin->left(); ny = ar.bottom() - height() - m_margin->bottom(); break; case Types::Left: case Types::Top: nx = ar.left() + m_margin->left(); ny = ar.top() + m_margin->top(); break; case Types::Right: nx = ar.right() - width() - m_margin->right(); ny = ar.top() + m_margin->top(); break; case Types::Bottom: nx = ar.left() + m_margin->left(); ny = ar.bottom() - height() - m_margin->bottom(); break; case Types::Center: nx = ar.left() + ar.width()/2 - width()/2; nx = ar.top() + ar.height()/2 - height()/2; break; case Types::LeftCenter: nx = ar.left() + m_margin->left(); ny = ar.top() + ar.height()/2 - height()/2; break; case Types::TopCenter: nx = ar.left() + ar.width()/2 - width()/2; ny = ar.top() + m_margin->top(); break; case Types::RightCenter: nx = ar.right() - width() - m_margin->right(); ny = ar.top() + ar.height()/2 - height()/2; break; case Types::BottomCenter: nx = ar.left() + ar.width()/2 - width()/2; ny = ar.bottom() - height() - m_margin->bottom(); break; default: nx = ar.left(); ny = ar.top(); break; } setWindowPosition({nx, ny}); } } // UkuiQuick ukui-quick/items/views/tooltip.cpp0000664000175000017500000001626015153756415016264 0ustar fengfeng/* SPDX-FileCopyrightText: 2011 Marco Martin SPDX-FileCopyrightText: 2011 Artur Duque de Souza SPDX-FileCopyrightText: 2013 Sebastian Kügler SPDX-FileCopyrightText: 2024 iaom SPDX-License-Identifier: GPL-2.0-or-later */ #include "tooltip.h" #include namespace UkuiQuick { // ====== Tooltip ====== TooltipDialog *Tooltip::s_dialog = nullptr; int Tooltip::s_dialogUsers = 0; Tooltip::Tooltip(QQuickItem *parent) : QQuickItem(parent) , m_tooltipsEnabledGlobally(true) , m_containsMouse(false) , m_location(UkuiQuick::Dialog::PopupLocation::Floating) , m_active(true) , m_timeout(10000) , m_usingDialog(false) , m_interval(500) , m_posFollowCursor(true) , m_margin(0) , m_interactive(false) , m_isRichText(false) , m_deactivatedByClick(false) { setAcceptHoverEvents(true); setAcceptedMouseButtons(Qt::AllButtons); setFiltersChildMouseEvents(true); m_showTimer = new QTimer(this); m_showTimer->setSingleShot(true); connect(m_showTimer, &QTimer::timeout, this, [this]() { if (!m_deactivatedByClick) { showTooltip(); } }); } Tooltip::~Tooltip() { if (s_dialog && s_dialog->owner() == this) { s_dialog->setVisible(false); } if (m_usingDialog) { --s_dialogUsers; } if (s_dialogUsers == 0) { delete s_dialog; s_dialog = nullptr; } } QQuickItem *Tooltip::mainItem() const { return m_mainItem.data();; } void Tooltip::setMainItem(QQuickItem *mainItem) { if (m_mainItem.data() != mainItem) { m_mainItem = mainItem; Q_EMIT mainItemChanged(); if (!isValid() && s_dialog && s_dialog->owner() == this) { s_dialog->setVisible(false); } } } QString Tooltip::mainText() const { return m_mainText; } void Tooltip::setMainText(const QString &mainText) { if (mainText == m_mainText) { return; } m_mainText = mainText; Q_EMIT mainTextChanged(); if (m_isRichText != Qt::mightBeRichText(m_mainText)) { m_isRichText = !m_isRichText; Q_EMIT isRichTextChanged(); } if (!isValid() && s_dialog && s_dialog->owner() == this) { s_dialog->setVisible(false); } } UkuiQuick::Dialog::PopupLocation Tooltip::location() const { return m_location; } void Tooltip::setLocation(UkuiQuick::Dialog::PopupLocation location) { if (m_location == location) { return; } m_location = location; Q_EMIT locationChanged(); } bool Tooltip::containsMouse() const { return m_containsMouse; } void Tooltip::setContainsMouse(bool contains) { if (m_containsMouse != contains) { m_containsMouse = contains; Q_EMIT containsMouseChanged(); } if (!contains) { tooltipDialogInstance()->dismiss(); } } void Tooltip::setActive(bool active) { if (m_active == active) { return; } m_active = active; if (!active) { tooltipDialogInstance()->dismiss(); } Q_EMIT activeChanged(); } void Tooltip::setTimeout(int timeout) { m_timeout = timeout; } void Tooltip::showTooltip() { if (!m_active) { return; } Q_EMIT aboutToShow(); TooltipDialog *dlg = tooltipDialogInstance(); if (dlg->isVisible()) { dlg->setVisible(false); } if (!mainItem()) { setMainItem(dlg->loadDefaultItem()); } // Unset the dialog's old contents before reparenting the dialog. dlg->setMainItem(nullptr); UkuiQuick::Dialog::PopupLocation location = m_location; if (m_location == UkuiQuick::Dialog::PopupLocation::Floating) { QQuickItem *p = parentItem(); while (p) { if (p->property("location").isValid()) { location = (UkuiQuick::Dialog::PopupLocation)p->property("location").toInt(); break; } p = p->parentItem(); } } if (mainItem()) { mainItem()->setProperty("tooltip", QVariant::fromValue(this)); mainItem()->setVisible(true); } connect(dlg, &TooltipDialog::visibleChanged, this, &Tooltip::toolTipVisibleChanged, Qt::UniqueConnection); dlg->setHideTimeout(m_timeout); dlg->setOwner(this); dlg->setLocation(location); dlg->setPosFollowCursor(m_posFollowCursor); dlg->setMargin(m_margin); dlg->setVisualParent(this); dlg->setMainItem(mainItem()); dlg->setInteractive(m_interactive); dlg->setVisible(true); } void Tooltip::hideTooltip() { m_showTimer->stop(); tooltipDialogInstance()->dismiss(); } void Tooltip::hideImmediately() { m_showTimer->stop(); tooltipDialogInstance()->setVisible(false); } bool Tooltip::childMouseEventFilter(QQuickItem *item, QEvent *event) { if (event->type() == QEvent::MouseButtonPress) { hideTooltip(); } return QQuickItem::childMouseEventFilter(item, event); } void Tooltip::hoverEnterEvent(QHoverEvent *event) { setContainsMouse(true); m_deactivatedByClick = false; if (!m_tooltipsEnabledGlobally) { return QQuickItem::hoverEnterEvent(event); } if (!isValid()) { return QQuickItem::hoverEnterEvent(event); } if (tooltipDialogInstance()->isVisible()) { // We signal the tooltipmanager that we're "potentially interested, // and ask to keep it open for a bit, so other items get the chance // to update the content before the tooltip hides -- this avoids // flickering // It need to be considered only when other items can deal with tooltip area if (m_active && !m_deactivatedByClick) { tooltipDialogInstance()->keepalive(); // FIXME: showToolTip needs to be renamed in sync or something like that showTooltip(); } } else { m_showTimer->start(m_interval); } QQuickItem::hoverEnterEvent(event); } void Tooltip::hoverLeaveEvent(QHoverEvent *event) { Q_UNUSED(event) setContainsMouse(false); m_showTimer->stop(); } void Tooltip::mousePressEvent(QMouseEvent *event) { Q_UNUSED(event) m_deactivatedByClick = true; hideImmediately(); event->ignore(); QQuickItem::mousePressEvent(event); } TooltipDialog *Tooltip::tooltipDialogInstance() { if (!s_dialog) { s_dialog = new TooltipDialog; s_dialogUsers = 1; } if (!m_usingDialog) { s_dialogUsers++; m_usingDialog = true; } return s_dialog; } bool Tooltip::isValid() const { return m_mainItem || !mainText().isEmpty(); } bool Tooltip::posFollowCursor() { return m_posFollowCursor; } void Tooltip::setPosFollowCursor(bool follow) { if(m_posFollowCursor == follow) { return; } m_posFollowCursor = follow; Q_EMIT posFollowCursorChanged(); } int Tooltip::margin() const { return m_margin; } void Tooltip::setMargin(int margin) { if(m_margin != margin) { m_margin = margin; Q_EMIT marginChanged(); } } void Tooltip::setInteractive(bool interactive) { if (m_interactive == interactive) { return; } m_interactive = interactive; Q_EMIT interactiveChanged(); } } // UkuiQuick ukui-quick/items/views/ukui-style-window.h0000664000175000017500000000604415153755732017657 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_UKUI_STYLE_WINDOW_H #define UKUI_QUICK_UKUI_STYLE_WINDOW_H #include #include #include #include "window-helper.h" namespace UkuiQuick { class UkuiStyleWindowPrivate; class UkuiStyleWindow : public QQuickWindow { Q_OBJECT Q_PROPERTY(bool enableWindowBlur READ enableWindowBlur WRITE setEnableWindowBlur NOTIFY enableWindowBlurChanged) Q_PROPERTY(QRegion blurRegion READ blurRegion WRITE setBlurRegion NOTIFY blurRegionChanged) // reimpl Q_PROPERTY(int x READ x WRITE setX NOTIFY windowXChanged FINAL) Q_PROPERTY(int y READ y WRITE setY NOTIFY windowYChanged FINAL) Q_PROPERTY(int width READ width WRITE setWidth NOTIFY windowWidthChanged FINAL) Q_PROPERTY(int height READ height WRITE setHeight NOTIFY windowHeightChanged FINAL) Q_PROPERTY(QRect windowGeometry READ windowGeometry WRITE setWindowGeometry NOTIFY windowGeometryChanged) Q_PROPERTY(QString graphBackend READ graphBackend NOTIFY graphBackendChanged) Q_PROPERTY(UkuiQuick::WindowType::Type windowType READ windowType WRITE setWindowType NOTIFY windowTypeChanged) public: explicit UkuiStyleWindow(QWindow *parent = nullptr); ~UkuiStyleWindow() override; Q_INVOKABLE quint64 getWinId(); bool enableWindowBlur() const; void setEnableWindowBlur(bool enable); QRegion blurRegion() const; void setBlurRegion(const QRegion& region); WindowType::Type windowType() const; virtual void setWindowType(WindowType::Type windowType); int x() const; void setX(int x); int y() const; void setY(int y); int width() const; void setWidth(int width); int height() const; void setHeight(int height); QRect windowGeometry() const; void setWindowGeometry(const QRect &rect); QString graphBackend() const; Q_SIGNALS: void enableWindowBlurChanged(); void blurRegionChanged(); // geometry void windowXChanged(); void windowYChanged(); void windowWidthChanged(); void windowHeightChanged(); void windowGeometryChanged(); void graphBackendChanged(); void windowTypeChanged(); protected: bool event(QEvent *event) override; void updateBlurRegion(); void updateGeometry(); private: UkuiStyleWindowPrivate *d {nullptr}; }; } // UkuiQuick #endif //UKUI_QUICK_UKUI_STYLE_WINDOW_H ukui-quick/items/views/content-window.h0000664000175000017500000000705015153755732017214 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_ITEMS_CONTENT_WINDOW_H #define UKUI_QUICK_ITEMS_CONTENT_WINDOW_H #include "ukui-style-window.h" #include "margin.h" #include "types.h" #include "windows/ukui-window.h" class QScreen; namespace UkuiQuick { /** * @class ContentWindow * * 是基于屏幕可用区域进行定位的,需要设置窗口需要显示在屏幕可用区域的哪个位置 * 窗口的尺寸由显示的内容决定 * 位置(position)配合边距(margin)一起使用。 ContentWindow { id: dialog // 设置显示在哪个屏幕上 screen: QScreen* // 在屏幕上的位置,目前支持:TopLeft,TopRight,BottomLeft,BottomRight position: Types.BottomLeft // 到屏幕边缘边距 margin { left: 24 top: 24 right: 24 bottom: 24 } // 需要显示的内容,必须定义宽高,窗口尺寸与内容尺寸一致 Item { width: 500 height: 500 Text { anchors.fill: parent anchors.margins: 10 text: "test dialog" } } } * */ class ContentWindow : public UKUIWindow { Q_OBJECT Q_PROPERTY(UkuiQuick::Margin* margin READ margin CONSTANT) Q_PROPERTY(QQuickItem* content READ content WRITE setContent NOTIFY contentChanged FINAL) Q_PROPERTY(QScreen* screen READ getScreen WRITE setWindowScreen NOTIFY screenChanged FINAL) Q_PROPERTY(UkuiQuick::Types::Pos position READ getPos WRITE setPos NOTIFY positionChanged FINAL) Q_PROPERTY(bool useAvailableGeometry READ useAvailableGeometry WRITE setUseAvailableGeometry NOTIFY useAvailableGeometryChanged) Q_CLASSINFO("DefaultProperty", "content") public: explicit ContentWindow(QWindow *parent = nullptr); QScreen* getScreen() const; void setWindowScreen(QScreen* screen); QQuickItem* content() const; void setContent(QQuickItem* content); // 在屏幕上的位置,目前支持:TopLeft,TopRight,BottomLeft,BottomRight Types::Pos getPos() const; void setPos(Types::Pos pos); Margin* margin() const; // 设置窗口是否使用屏幕可用区域进行定位,默认为true bool useAvailableGeometry() const; void setUseAvailableGeometry(bool use); Q_INVOKABLE void reLocated(); private Q_SLOTS: void onContentWidthChanged(); void onContentHeightChanged(); void updateLocation(); private: bool m_enableBlurEffect {false}; QQuickItem *m_content {nullptr}; UkuiQuick::Margin *m_margin {nullptr}; Types::Pos m_pos {Types::NoPosition}; bool m_useAvailableGeometry {true}; Q_SIGNALS: void screenChanged(); void contentChanged(); void positionChanged(); void useAvailableGeometryChanged(); }; } // UkuiQuick #endif //UKUI_QUICK_ITEMS_CONTENT_WINDOW_H ukui-quick/items/tooltip-proxy.h0000664000175000017500000000320015153755732015742 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_ITEMS_TOOLTIP_PROXY_H #define UKUI_QUICK_ITEMS_TOOLTIP_PROXY_H #include #include #include #include namespace UkuiQuick { class ToolTip : public QObject { Q_OBJECT Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) public: explicit ToolTip(QObject *parent = nullptr); QString text() const; void setText(const QString &text); Q_INVOKABLE void show(int x = -1, int y = -1); Q_INVOKABLE void show(QPointF point); Q_INVOKABLE void show(QPoint point); Q_INVOKABLE void hide(); Q_SIGNALS: void textChanged(); private: QString m_text; }; class ToolTipAttached : public QObject { Q_OBJECT public: static ToolTip *qmlAttachedProperties(QObject *object); }; } // UkuiQuick QML_DECLARE_TYPEINFO(UkuiQuick::ToolTipAttached, QML_HAS_ATTACHED_PROPERTIES) #endif //UKUI_QUICK_ITEMS_TOOLTIP_PROXY_H ukui-quick/items/qmldir0000664000175000017500000000121315153755732014135 0ustar fengfengmodule org.ukui.quick.items plugin ukui-quick-items-plugin depends org.ukui.quick.platform 1.0 BlurItem 1.0 qml/BlurItem.qml StyleBackground 1.0 qml/StyleBackground.qml DtThemeBackground 1.0 qml/DtThemeBackground.qml DtThemeScrollBar 1.0 qml/DtThemeScrollBar.qml TouchSlider 1.0 qml/TouchSlider.qml StyleText 1.0 qml/StyleText.qml DtThemeText 1.0 qml/DtThemeText.qml Button 1.0 qml/Button.qml DtThemeButton 1.0 qml/DtThemeButton.qml Background 1.0 qml/Background.qml IconButton 1.0 qml/IconButton.qml DtDropShadow 1.0 qml/DtDropShadow.qml HoverButton 1.0 qml/HoverButton.qml SearchLineEdit 1.0 qml/SearchLineEdit.qml KBallonTip 1.0 qml/KBallonTip.qml ukui-quick/items/icon-helper.h0000664000175000017500000000376615153755732015317 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_ITEMS_ICON_HELPER_H #define UKUI_QUICK_ITEMS_ICON_HELPER_H #include #include #include #include namespace UkuiQuick { class IconHelper { public: // 判断函数 // 是否存在主题图标 static bool isThemeIcon(const QString &name); // 是否本地文件 static bool isLocalFile(const QUrl &url); // 远程文件: http or https static bool isRemoteServerFile(const QUrl &url); // 功能函数 /** * Url转换为本地文件 * 判断一个url是否本地文件。包括qrc * @param url * @return 返回可用于load的path,如果不是本地文件,返回空 */ static QString toLocalPath(const QUrl &url); // 图标相关 // 从某个路径加载图标,并存入引用中,返回加载是否成功 static bool loadPixmap(const QString &path, QPixmap &pixmap); // 从路径或者主题加载图标 static QIcon loadIcon(const QString &id); // 默认图标 static QIcon getDefaultIcon(); private: static bool loadThemeIcon(const QString &name, QIcon &icon); static bool loadXdgIcon(const QString &name, QIcon &icon); static void loadDefaultIcon(QIcon &icon); }; } // UkuiQuick #endif //UKUI_QUICK_ITEMS_ICON_HELPER_H ukui-quick/items/translations/0000775000175000017500000000000015167362353015444 5ustar fengfengukui-quick/items/translations/ky.ts0000664000175000017500000000216515167362353016443 0ustar fengfeng SearchLineEdit search ىزدۅۅ &Undo ارعادان قالتىرىش (&U) &Redo قايرا جاسوو ،اتقارۇۇ (&R) Cu&t قىيىش (&T) &Copy گۅچۉرۉش (&C) &Paste چاپتوو(&P) Delete ۅچۉرۉۉ Select All باردىعىن تاندا ukui-quick/items/translations/zh_CN.ts0000664000175000017500000000201515167362353017013 0ustar fengfeng SearchLineEdit search 搜索 &Undo 撤销(&U) &Redo 恢复(&R) Cu&t 剪切(&T) &Copy 复制(&C) &Paste 粘贴(&P) Delete 删除 Select All 选择全部 ukui-quick/items/translations/kk.ts0000664000175000017500000000213315167362353016420 0ustar fengfeng SearchLineEdit search ٸزدەۋ &Undo كۇشىنەن قالدىرۋ (&U) &Redo قاتە ەتۋ (&R) Cu&t كەسۋ(&T) &Copy كوشىرىڭىز(&C) &Paste شاپتاۋ(&P) Delete ٴوشىرۋ Select All جالپىسىن تالداۋ ukui-quick/items/translations/ms.ts0000664000175000017500000000177315167362353016443 0ustar fengfeng SearchLineEdit search cari &Undo &Batal &Redo &Buat semula Cu&t Ricih (>) &Copy &Salin &Paste &Tampal Delete Padam Select All Pilih Semua ukui-quick/items/translations/th.ts0000664000175000017500000000205215167362353016426 0ustar fengfeng SearchLineEdit search ค้นหา &Undo เลิกทำ &Redo ทำซ้ำ Cu&t ตัด &Copy คัดลอก &Paste วาง Delete ลบ Select All เลือกทั้งหมด ukui-quick/items/translations/ar.ts0000664000175000017500000000201215167362353016411 0ustar fengfeng SearchLineEdit search بحث &Undo &تراجع &Redo &إعادة Cu&t &قصّ &Copy &نسخ &Paste &لصق Delete حذف Select All تحديد الكل ukui-quick/items/translations/ug.ts0000664000175000017500000000213715167362353016432 0ustar fengfeng SearchLineEdit search ئىزدەش &Undo ئەمەلدىن قالدۇرۇش (&U) &Redo قايتا قىلىش (&R) Cu&t كېسىش (T) &Copy كۆچۈرۈش(&C) &Paste چاپلاش(&P) Delete ئۆچۈرۈش Select All ھەممىنى تاللا ukui-quick/items/translations/bo_CN.ts0000664000175000017500000000230415167362353016773 0ustar fengfeng SearchLineEdit search སྤྱིའི་་བཤེར་འཚོལ། &Undo ཕྱིར་འཐེན(&U) &Redo བསྐྱར་སྒྲུབ(&R) Cu&t གཏུབ་བཏོན།(&T) &Copy འདྲ་བཤུས།(&C) &Paste སྦྱར་བ།(&P) Delete སུབ། Select All ཡོངས་འདེམས་ ukui-quick/items/translations/vi.ts0000664000175000017500000000203615167362353016433 0ustar fengfeng SearchLineEdit search Tìm kiếm &Undo Hoàn tác (&U) &Redo Làm lại (&R) Cu&t cắt &Copy &Sao chép &Paste Dán(&P) Delete Xóa đánh dấu Select All Chọn tất cả ukui-quick/items/translations/mn.ts0000664000175000017500000000231615167362353016430 0ustar fengfeng SearchLineEdit search ᠪᠦᠬᠦ ᠳᠠᠯ᠎ᠠ ᠪᠡᠷ ᠬᠠᠢᠬᠤ &Undo ᠪᠤᠴᠠᠬᠤ (&U) &Redo ᠰᠡᠷᠬᠦᠬᠡᠬᠦ (&R) Cu&t ᠬᠠᠢᠴᠢᠯᠠᠬᠤ (&T) &Copy ᠺᠣᠫᠢᠳᠠᠬᠤ (᠎&C ) &Paste ᠨᠠᠭᠠᠬᠤ (&P) Delete ᠤᠰᠠᠳᠬᠠᠠᠬᠤ Select All ᠪᠦᠬᠦᠨ᠎ᠢ᠋ ᠰᠣᠩᠭᠣᠬᠤ ukui-quick/items/translations/zh_Hant.ts0000664000175000017500000000204315167362353017406 0ustar fengfeng SearchLineEdit search 搜索 &Undo 撤銷(&U) &Redo 恢復(&R) Cu&t 剪下(&T) &Copy 複製(&C) &Paste 貼上(&P) Delete 刪除 Select All 選擇全部 ukui-quick/items/menu.cpp0000664000175000017500000000367415153756415014406 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: qiqi49 * */ #include #include #include "menu.h" namespace UkuiQuick { Menu::Menu(QObject *prent) : QObject(prent) { m_menu = new QMenu(nullptr); connect(m_menu, &QMenu::aboutToHide, this, [=]() { Q_EMIT visibleChanged(); }); connect(m_menu, &QMenu::aboutToShow, this, [=]() { Q_EMIT visibleChanged(); }); } Menu::~Menu() { delete m_menu; } QQmlListProperty Menu::content() { return QQmlListProperty(this, &m_items); } void Menu::open(int x, int y) { m_menu->clear(); for (MenuItem *item : m_items) { m_menu->addAction(item->action()); } if(m_transientParent) { m_menu->winId(); if(m_menu->windowHandle()) { m_menu->windowHandle()->setTransientParent(m_transientParent->window()); } } if (x < 0 || y < 0) { m_menu->popup(QCursor::pos()); } else { m_menu->popup(QPoint(x, y)); } } void Menu::close() { m_menu->hide(); } bool Menu::visible() { return m_menu->isVisible(); } void Menu::setTransientParent(QQuickItem *item) { if(m_transientParent == item) { return; } m_transientParent = item; Q_EMIT transientParentChanged(); } } ukui-quick/items/window-blur-behind.h0000664000175000017500000000723715153755732016607 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #ifndef UKUI_QUICK_WINDOW_BLUR_BEHIND_H #define UKUI_QUICK_WINDOW_BLUR_BEHIND_H #include #include #include #include #include "windows/region.h" namespace UkuiQuick { /** * @class WindowBlurBehind * 注意:使用这个类需要为目标window初始化WindowProxy类,如果未曾手动初始化,会自动初始化一个默认的WindowProxy类 WindowBlurBehind { rectRegions: [ RectRegion { x: 0 y: 0 width: 100 height: 200 radius: 8 }, RectRegion { x: 100 y: 200 width: 100 height: 200 radius: 0 } ] enable: true } * * */ class WindowBlurBehind : public QQuickItem { Q_OBJECT Q_PROPERTY(QQmlListProperty rectRegions READ rectRegions) Q_PROPERTY(bool enable READ enable WRITE setEnable NOTIFY enableChanged) Q_PROPERTY(QWindow* window READ window WRITE setWindow NOTIFY windowChanged) Q_CLASSINFO("DefaultProperty", "rectRegions") public: explicit WindowBlurBehind(QQuickItem *parent = nullptr); QQmlListProperty rectRegions(); void setEnable(bool enable); bool enable() const; void setWindow(QWindow *window); QWindow* window(); /** * You cannot append elements to or clear a QQmlListProperty directly in QML. * To edit QQmlListProperty in QML, assign a new list to it. * * Note that objects cannot be individually added to or removed from the list once created; * to modify the contents of a list, it must be reassigned to a new list. */ static void appendRect(QQmlListProperty *list, RectRegion *rect); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) static qsizetype rectCount(QQmlListProperty *list); static RectRegion *rectAt(QQmlListProperty *list, qsizetype index); static void replaceRect(QQmlListProperty *list, qsizetype index, RectRegion *rect); #else static int rectCount(QQmlListProperty *list); static RectRegion *rectAt(QQmlListProperty *list, int index); static void replaceRect(QQmlListProperty *list, int index, RectRegion *rect); #endif static void clearRect(QQmlListProperty *list); static void removeLastRect(QQmlListProperty *list); Q_INVOKABLE void addRectRegion(RectRegion *rect); Q_INVOKABLE void clearRectRegion(); Q_SIGNALS: void enableChanged(); void windowChanged(); private: void updateBlurRegion(); QList m_rects; bool m_enable = false; QWindow *m_window = nullptr; QPointer m_windowProxy; }; } // UkuiQuick #endif //UKUI_QUICK_WINDOW_BLUR_BEHIND_H ukui-quick/items/shaders/0000775000175000017500000000000015153755732014356 5ustar fengfengukui-quick/items/shaders/uniforms.glsl0000664000175000017500000000106115153755732017101 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ layout(std140, binding = 0) uniform buf { highp mat4 matrix; // offset 0 lowp vec2 aspect; // offset 64 lowp float opacity; // offset 72 lowp float size; // offset 76 lowp vec4 radius; // offset 80 lowp vec4 color; // offset 96 lowp vec4 shadowColor; // offset 112 lowp vec2 offset; // offset 128 lowp float borderWidth; // offset 136 lowp vec4 borderColor; // offset 144 } ubuf; // size 160 ukui-quick/items/shaders/shadowedrectangle.vert0000664000175000017500000000072115153755732020743 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ uniform highp mat4 matrix; uniform lowp vec2 aspect; #ifdef CORE_PROFILE in highp vec4 in_vertex; in mediump vec2 in_uv; out mediump vec2 uv; #else attribute highp vec4 in_vertex; attribute mediump vec2 in_uv; varying mediump vec2 uv; #endif void main() { uv = (-1.0 + 2.0 * in_uv) * aspect; gl_Position = matrix * in_vertex; } ukui-quick/items/shaders/header_desktop.glsl0000664000175000017500000000105115153755732020217 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ // This file contains common directives needed for the shaders to work. // It is included as the very first bit in the shader. // Important: If a specific GLSL version is needed, it should be set in this // file. // This file is intended for desktop OpenGL version 2.1 or greater. #version 120 #ifndef lowp #define lowp #endif #ifndef mediump #define mediump #endif #ifndef highp #define highp mediump #endif ukui-quick/items/shaders/shadowedborderrectangle_lowpower.frag0000664000175000017500000000230215153755732024033 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ // See sdf.glsl for the SDF related functions. // This is a version of shadowedborderrectangle.frag for extremely low powered // hardware (PinePhone). It does not draw a shadow and also eliminates alpha // blending. uniform lowp float opacity; uniform lowp float size; uniform lowp vec4 radius; uniform lowp vec4 color; uniform lowp vec4 shadowColor; uniform lowp vec2 offset; uniform lowp vec2 aspect; uniform lowp float borderWidth; uniform lowp vec4 borderColor; #ifdef CORE_PROFILE in lowp vec2 uv; out lowp vec4 out_color; #else varying lowp vec2 uv; #define out_color gl_FragColor #define texture texture2D #endif void main() { lowp vec4 col = vec4(0.0); // Calculate the outer rectangle distance field and render it. lowp float outer_rect = sdf_rounded_rectangle(uv, aspect, radius); col = sdf_render(outer_rect, col, borderColor); // The inner distance field is the outer reduced by border width. lowp float inner_rect = outer_rect + borderWidth * 2.0; // Render it. col = sdf_render(inner_rect, col, color); out_color = col * opacity; } ukui-quick/items/shaders/shadowedbordertexture.frag0000664000175000017500000000515415153755732021641 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ // See sdf.glsl for the SDF related functions. // This shader renders a rectangle with rounded corners and a shadow below it. // In addition it renders a border around it. uniform lowp float opacity; uniform lowp float size; uniform lowp vec4 radius; uniform lowp vec4 color; uniform lowp vec4 shadowColor; uniform lowp vec2 offset; uniform lowp vec2 aspect; uniform lowp float borderWidth; uniform lowp vec4 borderColor; uniform sampler2D textureSource; #ifdef CORE_PROFILE in lowp vec2 uv; out lowp vec4 out_color; #else varying lowp vec2 uv; #define out_color gl_FragColor #define texture texture2D #endif const lowp float minimum_shadow_radius = 0.05; void main() { // Scaling factor that is the inverse of the amount of scaling applied to the geometry. lowp float inverse_scale = 1.0 / (1.0 + size + length(offset) * 2.0); // Correction factor to round the corners of a larger shadow. // We want to account for size in regards to shadow radius, so that a larger shadow is // more rounded, but only if we are not already rounding the corners due to corner radius. lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(radius, minimum_shadow_radius)); lowp vec4 shadow_radius = radius + size * size_factor; lowp vec4 col = vec4(0.0); // Calculate the shadow's distance field. lowp float shadow = sdf_rounded_rectangle(uv - offset * 2.0 * inverse_scale, aspect * inverse_scale, shadow_radius * inverse_scale); // Render it, interpolating the color over the distance. col = mix(col, shadowColor * sign(size), 1.0 - smoothstep(-size * 0.5, size * 0.5, shadow)); // Scale corrected corner radius lowp vec4 corner_radius = radius * inverse_scale; // Calculate the outer rectangle distance field and render it. lowp float outer_rect = sdf_rounded_rectangle(uv, aspect * inverse_scale, corner_radius); col = sdf_render(outer_rect, col, borderColor); // The inner rectangle distance field is the outer reduced by twice the border width. lowp float inner_rect = outer_rect + (borderWidth * inverse_scale) * 2.0; // Render the inner rectangle. col = sdf_render(inner_rect, col, color); // Sample the texture, then blend it on top of the background color. lowp vec2 texture_uv = ((uv / aspect) + (1.0 * inverse_scale)) / (2.0 * inverse_scale); lowp vec4 texture_color = texture(textureSource, texture_uv); col = sdf_render(inner_rect, col, texture_color, texture_color.a, sdf_default_smoothing); out_color = col * opacity; } ukui-quick/items/shaders/shadowedbordertexture_lowpower.frag0000664000175000017500000000310715153755732023573 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ // See sdf.glsl for the SDF related functions. // This shader renders a rectangle with rounded corners and a shadow below it. // In addition it renders a border around it. uniform lowp float opacity; uniform lowp float size; uniform lowp vec4 radius; uniform lowp vec4 color; uniform lowp vec4 shadowColor; uniform lowp vec2 offset; uniform lowp vec2 aspect; uniform lowp float borderWidth; uniform lowp vec4 borderColor; uniform sampler2D textureSource; #ifdef CORE_PROFILE in lowp vec2 uv; out lowp vec4 out_color; #else varying lowp vec2 uv; #define out_color gl_FragColor #define texture texture2D #endif const lowp float minimum_shadow_radius = 0.05; void main() { lowp vec4 col = vec4(0.0); // Calculate the outer rectangle distance field. lowp float outer_rect = sdf_rounded_rectangle(uv, aspect, radius); // Render it col = sdf_render(outer_rect, col, borderColor); // Inner rectangle distance field equals outer reduced by twice the border width lowp float inner_rect = outer_rect + borderWidth * 2.0; // Render it so we have a background for the image. col = sdf_render(inner_rect, col, color); // Sample the texture, then render it, blending with the background color. lowp vec2 texture_uv = ((uv / aspect) + 1.0) / 2.0; lowp vec4 texture_color = texture(textureSource, texture_uv); col = sdf_render(inner_rect, col, texture_color, texture_color.a, sdf_default_smoothing); out_color = col * opacity; } ukui-quick/items/shaders/shadowedborderrectangle.frag0000664000175000017500000000567715153755732022117 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ // See sdf.glsl for the SDF related functions. // This shader renders a rectangle with rounded corners and a shadow below it. // In addition it renders a border around it. uniform lowp float opacity; uniform lowp float size; uniform lowp vec4 radius; uniform lowp vec4 color; uniform lowp vec4 shadowColor; uniform lowp vec2 offset; uniform lowp vec2 aspect; uniform lowp float borderWidth; uniform lowp vec4 borderColor; uniform bool pureColor; //是否纯色 uniform lowp vec4 startColor; // 渐变起始颜色 uniform lowp vec4 endColor; // 渐变结束颜色 uniform lowp float angle; // 渐变角度(弧度) #ifdef CORE_PROFILE in lowp vec2 uv; out lowp vec4 out_color; #else varying lowp vec2 uv; #define out_color gl_FragColor #endif const lowp float minimum_shadow_radius = 0.05; void main() { // Scaling factor that is the inverse of the amount of scaling applied to the geometry. lowp float inverse_scale = 1.0 / (1.0 + size + length(offset) * 2.0); // Correction factor to round the corners of a larger shadow. // We want to account for size in regards to shadow radius, so that a larger shadow is // more rounded, but only if we are not already rounding the corners due to corner radius. lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(radius, minimum_shadow_radius)); lowp vec4 shadow_radius = radius + size * size_factor; lowp vec4 col = vec4(0.0); // Calculate the shadow's distance field. lowp float shadow = sdf_rounded_rectangle(uv - offset * inverse_scale, aspect * inverse_scale, shadow_radius * inverse_scale); // Render it, interpolating the color over the distance. col = mix(col, shadowColor * sign(size), 1.0 - smoothstep(-size * 0.5, size * 0.5, shadow)); // Scale corrected corner radius lowp vec4 corner_radius = radius * inverse_scale; // Calculate the outer rectangle distance field and render it. lowp float outer_rect = sdf_rounded_rectangle(uv, aspect * inverse_scale, corner_radius); col = sdf_render(outer_rect, col, borderColor); // The inner rectangle distance field is the outer reduced by twice the border size. lowp float inner_rect = outer_rect + (borderWidth * inverse_scale) * 2.0; if (pureColor) { col = sdf_render(inner_rect, col, color); } else { // 计算渐变方向 vec2 dir = normalize(vec2(cos(angle), sin(angle)) / aspect); // 转换到矩形本地坐标系 vec2 rectUV = uv / (aspect * inverse_scale); // 计算渐变插值因子 float t = clamp((dot(rectUV, dir) + 1.0) / 2.0, 0.0, 1.0); // 混合颜色 vec4 gradientColor = mix(startColor, endColor, t); col = sdf_render(inner_rect, col, gradientColor); } out_color = col * opacity; } ukui-quick/items/shaders/header_desktop_core.glsl0000664000175000017500000000067515153755732021242 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ // This file contains common directives needed for the shaders to work. // It is included as the very first bit in the shader. // Important: If a specific GLSL version is needed, it should be set in this // file. // This file is intended for desktop OpenGL version 4.5 or greater. #version 450 #define CORE_PROFILE ukui-quick/items/shaders/sdf_lowpower.glsl0000664000175000017500000001733715153755732017766 0ustar fengfeng// SPDX-FileCopyrightText: 2020 Arjen Hiemstra // SPDX-FileCopyrightText: 2017 Inigo Quilez // // SPDX-License-Identifier: MIT // // This file is based on // https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm //if not GLES // include "desktop_header.glsl" //else // include "es_header.glsl" // A maximum point count to be used for sdf_polygon input arrays. // Unfortunately even function inputs require a fixed size at declaration time // for arrays, unless we were to use OpenGL 4.5. // Since the polygon is most likely to be defined in a uniform, this should be // at least less than MAX_FRAGMENT_UNIFORM_COMPONENTS / 2 (since we need vec2). #define SDF_POLYGON_MAX_POINT_COUNT 400 /********************************* Shapes *********************************/ // Distance field for a circle. // // \param point A point on the distance field. // \param radius The radius of the circle. // // \return The signed distance from point to the circle. If negative, point is // inside the circle. lowp float sdf_circle(in lowp vec2 point, in lowp float radius) { return length(point) - radius; } // Distance field for a triangle. // // \param point A point on the distance field. // \param p0 The first vertex of the triangle. // \param p0 The second vertex of the triangle. // \param p0 The third vertex of the triangle. // // \note The ordering of the three vertices does not matter. // // \return The signed distance from point to triangle. If negative, point is // inside the triangle. lowp float sdf_triangle(in lowp vec2 point, in lowp vec2 p0, in lowp vec2 p1, in lowp vec2 p2) { lowp vec2 e0 = p1 - p0; lowp vec2 e1 = p2 - p1; lowp vec2 e2 = p0 - p2; lowp vec2 v0 = point - p0; lowp vec2 v1 = point - p1; lowp vec2 v2 = point - p2; lowp vec2 pq0 = v0 - e0 * clamp( dot(v0, e0) / dot(e0, e0), 0.0, 1.0 ); lowp vec2 pq1 = v1 - e1 * clamp( dot(v1, e1) / dot(e1, e1), 0.0, 1.0 ); lowp vec2 pq2 = v2 - e2 * clamp( dot(v2, e2) / dot(e2, e2), 0.0, 1.0 ); lowp float s = sign( e0.x*e2.y - e0.y*e2.x ); lowp vec2 d = min(min(vec2(dot(pq0,pq0), s*(v0.x*e0.y-v0.y*e0.x)), vec2(dot(pq1,pq1), s*(v1.x*e1.y-v1.y*e1.x))), vec2(dot(pq2,pq2), s*(v2.x*e2.y-v2.y*e2.x))); return -sqrt(d.x)*sign(d.y); } // Distance field for a rectangle. // // \param point A point on the distance field. // \param rect A vec2 with the size of the rectangle. // // \return The signed distance from point to rectangle. If negative, point is // inside the rectangle. lowp float sdf_rectangle(in lowp vec2 point, in lowp vec2 rect) { lowp vec2 d = abs(point) - rect; return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0); } // Distance field for a rectangle with rounded corners. // // \param point The point to calculate the distance of. // \param rect The rectangle to calculate the distance of. // \param radius A vec4 with the radius of each corner. Order is top right, bottom right, top left, bottom left. // // \return The signed distance from point to rectangle. If negative, point is // inside the rectangle. lowp float sdf_rounded_rectangle(in lowp vec2 point, in lowp vec2 rect, in lowp vec4 radius) { radius.xy = (point.x > 0.0) ? radius.xy : radius.zw; radius.x = (point.y > 0.0) ? radius.x : radius.y; lowp vec2 d = abs(point) - rect + radius.x; return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - radius.x; } /********************* Operators *********************/ // Convert a distance field to an annular (hollow) distance field. // // \param sdf The result of an sdf shape to convert. // \param thickness The thickness of the resulting shape. // // \return The value of sdf modified to an annular shape. lowp float sdf_annular(in lowp float sdf, in lowp float thickness) { return abs(sdf) - thickness; } // Union two sdf shapes together. // // \param sdf1 The first sdf shape. // \param sdf2 The second sdf shape. // // \return The union of sdf1 and sdf2, that is, the distance to both sdf1 and // sdf2. lowp float sdf_union(in lowp float sdf1, in lowp float sdf2) { return min(sdf1, sdf2); } // Subtract two sdf shapes. // // \param sdf1 The first sdf shape. // \param sdf2 The second sdf shape. // // \return sdf1 with sdf2 subtracted from it. lowp float sdf_subtract(in lowp float sdf1, in lowp float sdf2) { return max(sdf1, -sdf2); } // Intersect two sdf shapes. // // \param sdf1 The first sdf shape. // \param sdf2 The second sdf shape. // // \return The intersection between sdf1 and sdf2, that is, the area where both // sdf1 and sdf2 provide the same distance value. lowp float sdf_intersect(in lowp float sdf1, in lowp float sdf2) { return max(sdf1, sdf2); } // Smoothly intersect two sdf shapes. // // \param sdf1 The first sdf shape. // \param sdf2 The second sdf shape. // \param smoothing The amount of smoothing to apply. // // \return A smoothed version of the intersect operation. lowp float sdf_intersect_smooth(in lowp float sdf1, in lowp float sdf2, in lowp float smoothing) { lowp float h = clamp(0.5 - 0.5 * (sdf1 - sdf2) / smoothing, 0.0, 1.0); return mix(sdf1, sdf2, h) + smoothing * h * (1.0 - h); } // Round an sdf shape. // // \param sdf The sdf shape to round. // \param amount The amount of rounding to apply. // // \return The rounded shape of sdf. // Note that rounding happens by basically selecting an isoline of sdf, // therefore, the resulting shape may be larger than the input shape. lowp float sdf_round(in lowp float sdf, in lowp float amount) { return sdf - amount; } // Convert an sdf shape to an outline of its shape. // // \param sdf The sdf shape to turn into an outline. // // \return The outline of sdf. lowp float sdf_outline(in lowp float sdf) { return abs(sdf); } /******************** Convenience ********************/ // A constant to represent a "null" value of an sdf. // // Since 0 is a point exactly on the outline of an sdf shape, and negative // values are inside the shape, this uses a very large positive constant to // indicate a value that is really far away from the actual sdf shape. const lowp float sdf_null = 99999.0; // A constant for a default level of smoothing when rendering an sdf. // // This const lowp float sdf_default_smoothing = 0.625; // Render an sdf shape alpha-blended onto an existing color. // // This is an overload of sdf_render(float, vec4, vec4) that allows specifying a // blending amount and a smoothing amount. // // \param alpha The alpha to use for blending. // \param smoothing The amount of smoothing to apply to the sdf. // lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float alpha, in lowp float smoothing) { lowp float g = smoothing * fwidth(sdf); return mix(sourceColor, sdfColor, alpha * (1.0 - clamp(sdf / g, 0.0, 1.0))); } // Render an sdf shape. // // This will render the sdf shape on top of whatever source color is input, // making sure to apply smoothing if desired. // // \param sdf The sdf shape to render. // \param sourceColor The source color to render on top of. // \param sdfColor The color to use for rendering the sdf shape. // // \return sourceColor with the sdf shape rendered on top. lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor) { return sdf_render(sdf, sourceColor, sdfColor, 1.0, sdf_default_smoothing); } // Render an sdf shape. // // This is an overload of sdf_render(float, vec4, vec4) that allows specifying a // smoothing amount. // // \param smoothing The amount of smoothing to apply to the sdf. // lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float smoothing) { return sdf_render(sdf, sourceColor, sdfColor, 1.0, smoothing); } ukui-quick/items/shaders/shadowedtexture_lowpower.frag0000664000175000017500000000232415153755732022375 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ // See sdf.glsl for the SDF related functions. // This shader renders a texture on top of a rectangle with rounded corners and // a shadow below it. uniform lowp float opacity; uniform lowp float size; uniform lowp vec4 radius; uniform lowp vec4 color; uniform lowp vec4 shadowColor; uniform lowp vec2 offset; uniform lowp vec2 aspect; uniform sampler2D textureSource; #ifdef CORE_PROFILE in lowp vec2 uv; out lowp vec4 out_color; #else varying lowp vec2 uv; #define out_color gl_FragColor #define texture texture2D #endif void main() { lowp vec4 col = vec4(0.0); // Calculate the main rectangle distance field. lowp float rect = sdf_rounded_rectangle(uv, aspect, radius); // Render it, so we have a background for the image. col = sdf_render(rect, col, color); // Sample the texture, then render it, blending it with the background. lowp vec2 texture_uv = ((uv / aspect) + 1.0) / 2.0; lowp vec4 texture_color = texture(textureSource, texture_uv); col = sdf_render(rect, col, texture_color, texture_color.a, sdf_default_smoothing); out_color = col * opacity; } ukui-quick/items/shaders/shadowedtexture.frag0000664000175000017500000000425715153755732020446 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ // See sdf.glsl for the SDF related functions. // This shader renders a texture on top of a rectangle with rounded corners and // a shadow below it. uniform lowp float opacity; uniform lowp float size; uniform lowp vec4 radius; uniform lowp vec4 color; uniform lowp vec4 shadowColor; uniform lowp vec2 offset; uniform lowp vec2 aspect; uniform sampler2D textureSource; #ifdef CORE_PROFILE in lowp vec2 uv; out lowp vec4 out_color; #else varying lowp vec2 uv; #define out_color gl_FragColor #define texture texture2D #endif const lowp float minimum_shadow_radius = 0.05; void main() { // Scaling factor that is the inverse of the amount of scaling applied to the geometry. lowp float inverse_scale = 1.0 / (1.0 + size + length(offset) * 2.0); // Correction factor to round the corners of a larger shadow. // We want to account for size in regards to shadow radius, so that a larger shadow is // more rounded, but only if we are not already rounding the corners due to corner radius. lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(radius, minimum_shadow_radius)); lowp vec4 shadow_radius = radius + size * size_factor; lowp vec4 col = vec4(0.0); // Calculate the shadow's distance field. lowp float shadow = sdf_rounded_rectangle(uv - offset * 2.0 * inverse_scale, aspect * inverse_scale, shadow_radius * inverse_scale); // Render it, interpolating the color over the distance. col = mix(col, shadowColor * sign(size), 1.0 - smoothstep(-size * 0.5, size * 0.5, shadow)); // Calculate the main rectangle distance field and render it. lowp float rect = sdf_rounded_rectangle(uv, aspect * inverse_scale, radius * inverse_scale); col = sdf_render(rect, col, color); // Sample the texture, then blend it on top of the background color. lowp vec2 texture_uv = ((uv / aspect) + (1.0 * inverse_scale)) / (2.0 * inverse_scale); lowp vec4 texture_color = texture(textureSource, texture_uv); col = sdf_render(rect, col, texture_color, texture_color.a, sdf_default_smoothing); out_color = col * opacity; } ukui-quick/items/shaders/sdf.glsl0000664000175000017500000001734715153755732016031 0ustar fengfeng// SPDX-FileCopyrightText: 2020 Arjen Hiemstra // SPDX-FileCopyrightText: 2017 Inigo Quilez // // SPDX-License-Identifier: MIT // // This file is based on // https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm //if not GLES // include "desktop_header.glsl" //else // include "es_header.glsl" // A maximum point count to be used for sdf_polygon input arrays. // Unfortunately even function inputs require a fixed size at declaration time // for arrays, unless we were to use OpenGL 4.5. // Since the polygon is most likely to be defined in a uniform, this should be // at least less than MAX_FRAGMENT_UNIFORM_COMPONENTS / 2 (since we need vec2). #define SDF_POLYGON_MAX_POINT_COUNT 400 /********************************* Shapes *********************************/ // Distance field for a circle. // // \param point A point on the distance field. // \param radius The radius of the circle. // // \return The signed distance from point to the circle. If negative, point is // inside the circle. lowp float sdf_circle(in lowp vec2 point, in lowp float radius) { return length(point) - radius; } // Distance field for a triangle. // // \param point A point on the distance field. // \param p0 The first vertex of the triangle. // \param p0 The second vertex of the triangle. // \param p0 The third vertex of the triangle. // // \note The ordering of the three vertices does not matter. // // \return The signed distance from point to triangle. If negative, point is // inside the triangle. lowp float sdf_triangle(in lowp vec2 point, in lowp vec2 p0, in lowp vec2 p1, in lowp vec2 p2) { lowp vec2 e0 = p1 - p0; lowp vec2 e1 = p2 - p1; lowp vec2 e2 = p0 - p2; lowp vec2 v0 = point - p0; lowp vec2 v1 = point - p1; lowp vec2 v2 = point - p2; lowp vec2 pq0 = v0 - e0 * clamp( dot(v0, e0) / dot(e0, e0), 0.0, 1.0 ); lowp vec2 pq1 = v1 - e1 * clamp( dot(v1, e1) / dot(e1, e1), 0.0, 1.0 ); lowp vec2 pq2 = v2 - e2 * clamp( dot(v2, e2) / dot(e2, e2), 0.0, 1.0 ); lowp float s = sign( e0.x*e2.y - e0.y*e2.x ); lowp vec2 d = min(min(vec2(dot(pq0,pq0), s*(v0.x*e0.y-v0.y*e0.x)), vec2(dot(pq1,pq1), s*(v1.x*e1.y-v1.y*e1.x))), vec2(dot(pq2,pq2), s*(v2.x*e2.y-v2.y*e2.x))); return -sqrt(d.x)*sign(d.y); } // Distance field for a rectangle. // // \param point A point on the distance field. // \param rect A vec2 with the size of the rectangle. // // \return The signed distance from point to rectangle. If negative, point is // inside the rectangle. lowp float sdf_rectangle(in lowp vec2 point, in lowp vec2 rect) { lowp vec2 d = abs(point) - rect; return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0); } // Distance field for a rectangle with rounded corners. // // \param point The point to calculate the distance of. // \param rect The rectangle to calculate the distance of. // \param radius A vec4 with the radius of each corner. Order is top right, bottom right, top left, bottom left. // // \return The signed distance from point to rectangle. If negative, point is // inside the rectangle. lowp float sdf_rounded_rectangle(in lowp vec2 point, in lowp vec2 rect, in lowp vec4 radius) { radius.xy = (point.x > 0.0) ? radius.xy : radius.zw; radius.x = (point.y > 0.0) ? radius.x : radius.y; lowp vec2 d = abs(point) - rect + radius.x; return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - radius.x; } /********************* Operators *********************/ // Convert a distance field to an annular (hollow) distance field. // // \param sdf The result of an sdf shape to convert. // \param thickness The thickness of the resulting shape. // // \return The value of sdf modified to an annular shape. lowp float sdf_annular(in lowp float sdf, in lowp float thickness) { return abs(sdf) - thickness; } // Union two sdf shapes together. // // \param sdf1 The first sdf shape. // \param sdf2 The second sdf shape. // // \return The union of sdf1 and sdf2, that is, the distance to both sdf1 and // sdf2. lowp float sdf_union(in lowp float sdf1, in lowp float sdf2) { return min(sdf1, sdf2); } // Subtract two sdf shapes. // // \param sdf1 The first sdf shape. // \param sdf2 The second sdf shape. // // \return sdf1 with sdf2 subtracted from it. lowp float sdf_subtract(in lowp float sdf1, in lowp float sdf2) { return max(sdf1, -sdf2); } // Intersect two sdf shapes. // // \param sdf1 The first sdf shape. // \param sdf2 The second sdf shape. // // \return The intersection between sdf1 and sdf2, that is, the area where both // sdf1 and sdf2 provide the same distance value. lowp float sdf_intersect(in lowp float sdf1, in lowp float sdf2) { return max(sdf1, sdf2); } // Smoothly intersect two sdf shapes. // // \param sdf1 The first sdf shape. // \param sdf2 The second sdf shape. // \param smoothing The amount of smoothing to apply. // // \return A smoothed version of the intersect operation. lowp float sdf_intersect_smooth(in lowp float sdf1, in lowp float sdf2, in lowp float smoothing) { lowp float h = clamp(0.5 - 0.5 * (sdf1 - sdf2) / smoothing, 0.0, 1.0); return mix(sdf1, sdf2, h) + smoothing * h * (1.0 - h); } // Round an sdf shape. // // \param sdf The sdf shape to round. // \param amount The amount of rounding to apply. // // \return The rounded shape of sdf. // Note that rounding happens by basically selecting an isoline of sdf, // therefore, the resulting shape may be larger than the input shape. lowp float sdf_round(in lowp float sdf, in lowp float amount) { return sdf - amount; } // Convert an sdf shape to an outline of its shape. // // \param sdf The sdf shape to turn into an outline. // // \return The outline of sdf. lowp float sdf_outline(in lowp float sdf) { return abs(sdf); } /******************** Convenience ********************/ // A constant to represent a "null" value of an sdf. // // Since 0 is a point exactly on the outline of an sdf shape, and negative // values are inside the shape, this uses a very large positive constant to // indicate a value that is really far away from the actual sdf shape. const lowp float sdf_null = 99999.0; // A constant for a default level of smoothing when rendering an sdf. // // This const lowp float sdf_default_smoothing = 0.625; // Render an sdf shape alpha-blended onto an existing color. // // This is an overload of sdf_render(float, vec4, vec4) that allows specifying a // blending amount and a smoothing amount. // // \param alpha The alpha to use for blending. // \param smoothing The amount of smoothing to apply to the sdf. // lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float alpha, in lowp float smoothing) { lowp float g = fwidth(sdf); return mix(sourceColor, sdfColor, alpha * (1.0 - smoothstep(-smoothing * g, smoothing * g, sdf))); } // Render an sdf shape. // // This will render the sdf shape on top of whatever source color is input, // making sure to apply smoothing if desired. // // \param sdf The sdf shape to render. // \param sourceColor The source color to render on top of. // \param sdfColor The color to use for rendering the sdf shape. // // \return sourceColor with the sdf shape rendered on top. lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor) { return sdf_render(sdf, sourceColor, sdfColor, 1.0, sdf_default_smoothing); } // Render an sdf shape. // // This is an overload of sdf_render(float, vec4, vec4) that allows specifying a // smoothing amount. // // \param smoothing The amount of smoothing to apply to the sdf. // lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float smoothing) { return sdf_render(sdf, sourceColor, sdfColor, 1.0, smoothing); } ukui-quick/items/shaders/shaders.qrc0000664000175000017500000000230115153755732016512 0ustar fengfeng header_es.glsl header_desktop.glsl header_desktop_core.glsl sdf.glsl sdf_lowpower.glsl sdf.glsl shadowedrectangle.vert shadowedrectangle.vert shadowedrectangle.frag shadowedrectangle_lowpower.frag shadowedrectangle.frag shadowedborderrectangle.frag shadowedborderrectangle_lowpower.frag shadowedborderrectangle.frag shadowedtexture.frag shadowedtexture_lowpower.frag shadowedtexture.frag shadowedbordertexture.frag shadowedbordertexture_lowpower.frag shadowedbordertexture.frag ukui-quick/items/shaders/shadowedrectangle.frag0000664000175000017500000000501715153755732020705 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ // See sdf.glsl for the SDF related functions. // This shader renders a rectangle with rounded corners and a shadow below it. uniform lowp float opacity; uniform lowp float size; uniform lowp vec4 radius; uniform lowp vec4 color; uniform lowp vec4 shadowColor; uniform lowp vec2 offset; uniform lowp vec2 aspect; uniform bool pureColor; //是否纯色 uniform lowp vec4 startColor; // 渐变起始颜色 uniform lowp vec4 endColor; // 渐变结束颜色 uniform lowp float angle; // 渐变角度(弧度) #ifdef CORE_PROFILE in lowp vec2 uv; out lowp vec4 out_color; #else varying lowp vec2 uv; #define out_color gl_FragColor #endif const lowp float minimum_shadow_radius = 0.05; void main() { // Scaling factor that is the inverse of the amount of scaling applied to the geometry. lowp float inverse_scale = 1.0 / (1.0 + size + length(offset) * 2.0); // Correction factor to round the corners of a larger shadow. // We want to account for size in regards to shadow radius, so that a larger shadow is // more rounded, but only if we are not already rounding the corners due to corner radius. lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(radius, minimum_shadow_radius)); lowp vec4 shadow_radius = radius + size * size_factor; lowp vec4 col = vec4(0.0); // Calculate the shadow's distance field. lowp float shadow = sdf_rounded_rectangle(uv - offset * inverse_scale, aspect * inverse_scale, shadow_radius * inverse_scale); // Render it, interpolating the color over the distance. col = mix(col, shadowColor * sign(size), 1.0 - smoothstep(-size * 0.5, size * 0.5, shadow)); // Calculate the main rectangle distance field and render it. lowp float rect = sdf_rounded_rectangle(uv, aspect * inverse_scale, radius * inverse_scale); if (pureColor) { col = sdf_render(rect, col, color); } else { // 计算渐变方向 vec2 dir = normalize(vec2(cos(angle), sin(angle)) / aspect); // 转换到矩形本地坐标系 vec2 rectUV = uv / (aspect * inverse_scale); // 计算渐变插值因子 float t = clamp((dot(rectUV, dir) + 1.0) / 2.0, 0.0, 1.0); // 混合颜色 vec4 gradientColor = mix(startColor, endColor, t); col = sdf_render(rect, col, gradientColor); } out_color = col * opacity; } ukui-quick/items/shaders/header_es.glsl0000664000175000017500000000072215153755732017161 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ // This file contains common directives needed for the shaders to work. // It is included as the very first bit in the shader. // Important: If a specific GLSL version is needed, it should be set in this // file. // This file is intended for OpenGLES version 2.0 or greater. #version 100 #extension GL_OES_standard_derivatives : enable ukui-quick/items/shaders/shadowedrectangle_lowpower.frag0000664000175000017500000000316715153755732022647 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ // See sdf.glsl for the SDF related functions. // This is a version of shadowedrectangle.frag meant for very low power hardware // (PinePhone). It does not render a shadow and does not do alpha blending. uniform lowp float opacity; uniform lowp float size; uniform lowp vec4 radius; uniform lowp vec4 color; uniform lowp vec4 shadowColor; uniform lowp vec2 offset; uniform lowp vec2 aspect; // 添加渐变相关uniform变量 uniform bool pureColor; //是否纯色 uniform lowp vec4 startColor; // 渐变起始颜色 uniform lowp vec4 endColor; // 渐变结束颜色 uniform lowp float angle; // 渐变角度(弧度) #ifdef CORE_PROFILE in lowp vec2 uv; out lowp vec4 out_color; #else varying lowp vec2 uv; #define out_color gl_FragColor #endif void main() { lowp vec4 col = vec4(0.0); // Calculate the main rectangle distance field. lowp float rect = sdf_rounded_rectangle(uv, aspect, radius); // Render it. if (pureColor) { col = sdf_render(rect, col, color); } else { // 添加渐变渲染逻辑 // 计算渐变方向 vec2 dir = normalize(vec2(cos(angle), sin(angle)) / aspect); // 转换到矩形本地坐标系 vec2 rectUV = uv / aspect; // 计算渐变插值因子 float t = clamp((dot(rectUV, dir) + 1.0) / 2.0, 0.0, 1.0); // 混合颜色 vec4 gradientColor = mix(startColor, endColor, t); col = sdf_render(rect, col, gradientColor); } out_color = col * opacity; } ukui-quick/items/ukui-quick-items-plugin.h0000664000175000017500000000254515153755732017606 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #ifndef UKUI_QUICK_ITEMS_UKUI_QUICK_ITEMS_PLUGIN_H #define UKUI_QUICK_ITEMS_UKUI_QUICK_ITEMS_PLUGIN_H #include #include class UkuiQuickItemsPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) public: void registerTypes(const char *uri) override; ~UkuiQuickItemsPlugin(); void initializeEngine(QQmlEngine *engine, const char *uri) override; private: void installTranslations(); QTranslator* m_translator = nullptr; }; #endif //UKUI_QUICK_ITEMS_UKUI_QUICK_ITEMS_PLUGIN_H ukui-quick/items/menu-item.cpp0000664000175000017500000000574415153756415015342 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: qiqi49 * */ #include "menu-item.h" #include "icon-helper.h" namespace UkuiQuick { MenuItem::MenuItem(QObject *parent) { setAction(new QAction(this)); } QAction *MenuItem::action() const { return m_action; } void MenuItem::setAction(QAction *action) { if (m_action != action) { if (m_action) { disconnect(m_action, nullptr, this, nullptr); if (m_action->parent() == this) { delete m_action; m_action = nullptr; } } if (action) { m_action = action; } else { m_action = new QAction(this); m_action->setVisible(false); } setEnabled(m_action->isEnabled()); connect(m_action, &QAction::changed, this, &MenuItem::textChanged); connect(m_action, &QAction::changed, this, &MenuItem::checkableChanged); connect(m_action, &QAction::changed, this, &MenuItem::enabledChanged); connect(m_action, &QAction::toggled, this, &MenuItem::toggled); connect(m_action, &QAction::triggered, this, &MenuItem::clicked); connect(m_action, &QObject::destroyed, this, [this] { if (m_action->parent() != this) { m_action = new QAction(this); m_action->setVisible(false); Q_EMIT actionChanged(); } }); connect(this, &QObject::destroyed, this, &MenuItem::deleteLater); Q_EMIT actionChanged(); } } QString MenuItem::icon() const { return m_action->icon().name(); } void MenuItem::setIcon(const QString &icon) { m_action->setIcon(IconHelper::loadIcon(icon)); } QString MenuItem::text() const { return m_action->text(); } void MenuItem::setText(const QString &text) { if (m_action->text() != text) { m_action->setText(text); } } bool MenuItem::checkable() const { return m_action->isCheckable(); } void MenuItem::setCheckable(bool checkable) { m_action->setCheckable(checkable); } bool MenuItem::checked() const { return m_action->isChecked(); } void MenuItem::setChecked(bool checked) { m_action->setChecked(checked); } bool MenuItem::isEnabled() const { return m_action->isEnabled(); } void MenuItem::setEnabled(bool enabled) { m_action->setEnabled(enabled); } } ukui-quick/items/scenegraph/0000775000175000017500000000000015153755732015044 5ustar fengfengukui-quick/items/scenegraph/shadowed-texture-material.cpp0000664000175000017500000000463015153755732022643 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "shadowed-texture-material.h" #include QSGMaterialType ShadowedTextureMaterial::staticType; ShadowedTextureMaterial::ShadowedTextureMaterial() : ShadowedRectangleMaterial() { setFlag(QSGMaterial::Blending, true); } #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QSGMaterialShader *ShadowedTextureMaterial::createShader() const #else QSGMaterialShader *ShadowedTextureMaterial::createShader(QSGRendererInterface::RenderMode) const #endif { return new ShadowedTextureShader{shaderType}; } QSGMaterialType *ShadowedTextureMaterial::type() const { return &staticType; } int ShadowedTextureMaterial::compare(const QSGMaterial *other) const { auto material = static_cast(other); auto result = ShadowedRectangleMaterial::compare(other); if (result == 0) { if (material->textureSource == textureSource) { return 0; } else { return (material->textureSource < textureSource) ? 1 : -1; } } return result; } ShadowedTextureShader::ShadowedTextureShader(ShadowedRectangleMaterial::ShaderType shaderType) : ShadowedRectangleShader(shaderType) { setShader(shaderType, QStringLiteral("shadowedtexture")); } #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) void ShadowedTextureShader::initialize() { ShadowedRectangleShader::initialize(); program()->setUniformValue("textureSource", 0); } void ShadowedTextureShader::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) { ShadowedRectangleShader::updateState(state, newMaterial, oldMaterial); auto texture = static_cast(newMaterial)->textureSource; if (texture) { texture->bind(); } } #else void ShadowedTextureShader::updateSampledImage(QSGMaterialShader::RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) { Q_UNUSED(state); Q_UNUSED(oldMaterial); if (binding == 1) { *texture = static_cast(newMaterial)->textureSource; } } #endif ukui-quick/items/scenegraph/shadowed-border-rectangle-material.cpp0000664000175000017500000000610515153755732024361 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "shadowed-border-rectangle-material.h" #include QSGMaterialType ShadowedBorderRectangleMaterial::staticType; ShadowedBorderRectangleMaterial::ShadowedBorderRectangleMaterial() { setFlag(QSGMaterial::Blending, true); } #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QSGMaterialShader *ShadowedBorderRectangleMaterial::createShader() const #else QSGMaterialShader *ShadowedBorderRectangleMaterial::createShader(QSGRendererInterface::RenderMode) const #endif { return new ShadowedBorderRectangleShader{shaderType}; } QSGMaterialType *ShadowedBorderRectangleMaterial::type() const { return &staticType; } int ShadowedBorderRectangleMaterial::compare(const QSGMaterial *other) const { auto material = static_cast(other); auto result = ShadowedRectangleMaterial::compare(other); /* clang-format off */ if (result == 0 && material->borderColor == borderColor && qFuzzyCompare(material->borderWidth, borderWidth)) { /* clang-format on */ return 0; } return QSGMaterial::compare(other); } ShadowedBorderRectangleShader::ShadowedBorderRectangleShader(ShadowedRectangleMaterial::ShaderType shaderType) : ShadowedRectangleShader(shaderType) { setShader(shaderType, QStringLiteral("shadowedborderrectangle")); } #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) void ShadowedBorderRectangleShader::initialize() { ShadowedRectangleShader::initialize(); m_borderWidthLocation = program()->uniformLocation("borderWidth"); m_borderColorLocation = program()->uniformLocation("borderColor"); } void ShadowedBorderRectangleShader::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) { ShadowedRectangleShader::updateState(state, newMaterial, oldMaterial); auto p = program(); if (!oldMaterial || newMaterial->compare(oldMaterial) != 0 || state.isCachedMaterialDataDirty()) { auto material = static_cast(newMaterial); p->setUniformValue(m_borderWidthLocation, material->borderWidth); p->setUniformValue(m_borderColorLocation, material->borderColor); } } #else bool ShadowedBorderRectangleShader::updateUniformData(QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) { bool changed = ShadowedRectangleShader::updateUniformData(state, newMaterial, oldMaterial); QByteArray *buf = state.uniformData(); Q_ASSERT(buf->size() >= 196); if (!oldMaterial || newMaterial->compare(oldMaterial) != 0) { const auto material = dynamic_cast(newMaterial); memcpy(buf->data() + 188, &material->borderWidth, 4); float c[4]; material->borderColor.getRgbF(&c[0], &c[1], &c[2], &c[3]); memcpy(buf->data() + 144, c, 16); changed = true; } return changed; } #endif ukui-quick/items/scenegraph/shadowed-border-texture-material.h0000664000175000017500000000247415153755732023567 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #pragma once #include #include "shadowed-border-rectangle-material.h" class ShadowedBorderTextureMaterial : public ShadowedBorderRectangleMaterial { public: ShadowedBorderTextureMaterial(); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QSGMaterialShader *createShader() const override; #else QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override; #endif QSGMaterialType *type() const override; int compare(const QSGMaterial *other) const override; QSGTexture *textureSource = nullptr; static QSGMaterialType staticType; }; class ShadowedBorderTextureShader : public ShadowedBorderRectangleShader { public: ShadowedBorderTextureShader(ShadowedRectangleMaterial::ShaderType shaderType); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) void initialize() override; void updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; #else void updateSampledImage(QSGMaterialShader::RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; #endif }; ukui-quick/items/scenegraph/shadowed-border-rectangle-material.h0000664000175000017500000000306415153755732024027 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #pragma once #include "shadowed-rectangle-material.h" /** * A material rendering a rectangle with a shadow and a border. * * This material uses a distance field shader to render a rectangle with a * shadow below it, optionally with rounded corners and a border. */ class ShadowedBorderRectangleMaterial : public ShadowedRectangleMaterial { public: ShadowedBorderRectangleMaterial(); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QSGMaterialShader *createShader() const override; #else QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override; #endif QSGMaterialType *type() const override; int compare(const QSGMaterial *other) const override; float borderWidth = 0.0; QColor borderColor = Qt::black; static QSGMaterialType staticType; }; class ShadowedBorderRectangleShader : public ShadowedRectangleShader { public: ShadowedBorderRectangleShader(ShadowedRectangleMaterial::ShaderType shaderType); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) void initialize() override; void updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; private: int m_borderWidthLocation = -1; int m_borderColorLocation = -1; #else bool updateUniformData(QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; #endif }; ukui-quick/items/scenegraph/shadowed-rectangle-material.cpp0000664000175000017500000001527315153755732023114 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "shadowed-rectangle-material.h" #include QSGMaterialType ShadowedRectangleMaterial::staticType; ShadowedRectangleMaterial::ShadowedRectangleMaterial() { setFlag(QSGMaterial::Blending, true); } #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QSGMaterialShader *ShadowedRectangleMaterial::createShader() const #else QSGMaterialShader *ShadowedRectangleMaterial::createShader(QSGRendererInterface::RenderMode) const #endif { return new ShadowedRectangleShader{shaderType}; } QSGMaterialType *ShadowedRectangleMaterial::type() const { return &staticType; } int ShadowedRectangleMaterial::compare(const QSGMaterial *other) const { auto material = static_cast(other); /* clang-format off */ if (material->color == color && material->shadowColor == shadowColor && material->offset == offset && material->aspect == aspect && material->pureColor == pureColor && material->startColor == startColor && material->endColor == endColor && material->angle == angle && qFuzzyCompare(material->size, size) && qFuzzyCompare(material->radius, radius)) { /* clang-format on */ return 0; } return QSGMaterial::compare(other); } ShadowedRectangleShader::ShadowedRectangleShader(ShadowedRectangleMaterial::ShaderType shaderType) { setShader(shaderType, QStringLiteral("shadowedrectangle")); } #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) const char *const *ShadowedRectangleShader::attributeNames() const { static char const *const names[] = {"in_vertex", "in_uv", nullptr}; return names; } void ShadowedRectangleShader::initialize() { QSGMaterialShader::initialize(); m_matrixLocation = program()->uniformLocation("matrix"); m_aspectLocation = program()->uniformLocation("aspect"); m_opacityLocation = program()->uniformLocation("opacity"); m_sizeLocation = program()->uniformLocation("size"); m_radiusLocation = program()->uniformLocation("radius"); m_colorLocation = program()->uniformLocation("color"); m_shadowColorLocation = program()->uniformLocation("shadowColor"); m_offsetLocation = program()->uniformLocation("offset"); m_pureColorLocation = program()->uniformLocation("pureColor"); m_startColorLocation = program()->uniformLocation("startColor"); m_endColorLocation = program()->uniformLocation("endColor"); m_angleLocation = program()->uniformLocation("angle"); } void ShadowedRectangleShader::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) { auto p = program(); if (state.isMatrixDirty()) { p->setUniformValue(m_matrixLocation, state.combinedMatrix()); } if (state.isOpacityDirty()) { p->setUniformValue(m_opacityLocation, state.opacity()); } if (!oldMaterial || newMaterial->compare(oldMaterial) != 0 || state.isCachedMaterialDataDirty()) { auto material = static_cast(newMaterial); p->setUniformValue(m_aspectLocation, material->aspect); p->setUniformValue(m_sizeLocation, material->size); p->setUniformValue(m_radiusLocation, material->radius); p->setUniformValue(m_colorLocation, material->color); p->setUniformValue(m_shadowColorLocation, material->shadowColor); p->setUniformValue(m_offsetLocation, material->offset); p->setUniformValue(m_pureColorLocation, material->pureColor); p->setUniformValue(m_startColorLocation, material->startColor); p->setUniformValue(m_endColorLocation, material->endColor); p->setUniformValue(m_angleLocation, material->angle); } } #else bool ShadowedRectangleShader::updateUniformData(RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) { bool changed = false; QByteArray *buf = state.uniformData(); Q_ASSERT(buf->size() >= 196); if (state.isMatrixDirty()) { const QMatrix4x4 m = state.combinedMatrix(); memcpy(buf->data(), m.constData(), 64); changed = true; } if (state.isOpacityDirty()) { const float opacity = state.opacity(); memcpy(buf->data() + 176, &opacity, 4); changed = true; } if (!oldMaterial || newMaterial->compare(oldMaterial) != 0) { const auto material = static_cast(newMaterial); memcpy(buf->data() + 160, &material->aspect, 8); memcpy(buf->data() + 180, &material->size, 4); memcpy(buf->data() + 64, &material->radius, 16); float c[4]; material->color.getRgbF(&c[0], &c[1], &c[2], &c[3]); memcpy(buf->data() + 80, c, 16); material->shadowColor.getRgbF(&c[0], &c[1], &c[2], &c[3]); memcpy(buf->data() + 96, c, 16); memcpy(buf->data() + 168, &material->offset, 8); const bool pureColor = material->pureColor; memcpy(buf->data() + 192, &pureColor, 4); material->startColor.getRgbF(&c[0], &c[1], &c[2], &c[ 3]); memcpy(buf->data() + 112, c, 16); material->endColor.getRgbF(&c[0], &c[1], &c[2], &c[3]); memcpy(buf->data() + 128, c, 16); memcpy(buf->data() + 184, &material->angle, 4); changed = true; } return changed; } #endif void ShadowedRectangleShader::setShader(ShadowedRectangleMaterial::ShaderType shaderType, const QString &shader) { const auto shaderRoot = QStringLiteral(":/org/ukui/quick/shaders/"); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) auto header = QOpenGLContext::currentContext()->isOpenGLES() ? QStringLiteral("header_es.glsl") : QStringLiteral("header_desktop.glsl"); setShaderSourceFiles(QOpenGLShader::Vertex, {shaderRoot + header, shaderRoot + QStringLiteral("shadowedrectangle.vert")}); QString shaderFile = shader + QStringLiteral(".frag"); auto sdfFile = QStringLiteral("sdf.glsl"); if (shaderType == ShadowedRectangleMaterial::ShaderType::LowPower) { shaderFile = shader + QStringLiteral("_lowpower.frag"); sdfFile = QStringLiteral("sdf_lowpower.glsl"); } setShaderSourceFiles(QOpenGLShader::Fragment, {shaderRoot + header, shaderRoot + sdfFile, shaderRoot + shaderFile}); #else setShaderFileName(QSGMaterialShader::VertexStage, shaderRoot + QStringLiteral("shadowedrectangle.vert.qsb")); auto shaderFile = shader; if (shaderType == ShadowedRectangleMaterial::ShaderType::LowPower) { shaderFile += QStringLiteral("_lowpower"); } setShaderFileName(QSGMaterialShader::FragmentStage, shaderRoot + shaderFile + QStringLiteral(".frag.qsb")); #endif }ukui-quick/items/scenegraph/shadowed-texture-node.cpp0000664000175000017500000000456215153755732021776 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "shadowed-texture-node.h" #include "shadowed-border-texture-material.h" template inline void preprocessTexture(QSGMaterial *material, QSGTextureProvider *provider) { auto m = static_cast(material); // Since we handle texture coordinates differently in the shader, we // need to remove the texture from the atlas for now. if (provider->texture()->isAtlasTexture()) { // Blegh, I have no idea why "removedFromAtlas" doesn't just return // the texture when it's not an atlas. m->textureSource = provider->texture()->removedFromAtlas(); } else { m->textureSource = provider->texture(); } if (QSGDynamicTexture *dynamic_texture = qobject_cast(m->textureSource)) { dynamic_texture->updateTexture(); } } ShadowedTextureNode::ShadowedTextureNode() : ShadowedRectangleNode() { setFlag(QSGNode::UsePreprocess); } void ShadowedTextureNode::setTextureSource(QSGTextureProvider *source) { if (m_textureSource == source) { return; } if (m_textureSource) { m_textureSource->disconnect(); } m_textureSource = source; QObject::connect(m_textureSource.data(), &QSGTextureProvider::textureChanged, [this] { markDirty(QSGNode::DirtyMaterial); }); markDirty(QSGNode::DirtyMaterial); } void ShadowedTextureNode::preprocess() { if (m_textureSource && m_material && m_textureSource->texture()) { if (m_material->type() == borderlessMaterialType()) { preprocessTexture(m_material, m_textureSource); } else { preprocessTexture(m_material, m_textureSource); } } } ShadowedRectangleMaterial *ShadowedTextureNode::createBorderlessMaterial() { return new ShadowedTextureMaterial{}; } ShadowedBorderRectangleMaterial *ShadowedTextureNode::createBorderMaterial() { return new ShadowedBorderTextureMaterial{}; } QSGMaterialType *ShadowedTextureNode::borderlessMaterialType() { return &ShadowedTextureMaterial::staticType; } QSGMaterialType *ShadowedTextureNode::borderMaterialType() { return &ShadowedBorderTextureMaterial::staticType; } ukui-quick/items/scenegraph/shadowed-border-texture-material.cpp0000664000175000017500000000517215153755732024120 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "shadowed-border-texture-material.h" #include QSGMaterialType ShadowedBorderTextureMaterial::staticType; ShadowedBorderTextureMaterial::ShadowedBorderTextureMaterial() : ShadowedBorderRectangleMaterial() { setFlag(QSGMaterial::Blending, true); } #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QSGMaterialShader *ShadowedBorderTextureMaterial::createShader() const #else QSGMaterialShader *ShadowedBorderTextureMaterial::createShader(QSGRendererInterface::RenderMode) const #endif { return new ShadowedBorderTextureShader{shaderType}; } QSGMaterialType *ShadowedBorderTextureMaterial::type() const { return &staticType; } int ShadowedBorderTextureMaterial::compare(const QSGMaterial *other) const { auto material = static_cast(other); auto result = ShadowedBorderRectangleMaterial::compare(other); if (result == 0) { if (material->textureSource == textureSource) { return 0; } else { return (material->textureSource < textureSource) ? 1 : -1; } } return result; } ShadowedBorderTextureShader::ShadowedBorderTextureShader(ShadowedRectangleMaterial::ShaderType shaderType) : ShadowedBorderRectangleShader(shaderType) { setShader(shaderType, QStringLiteral("shadowedbordertexture")); } #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) void ShadowedBorderTextureShader::initialize() { ShadowedBorderRectangleShader::initialize(); program()->setUniformValue("textureSource", 0); } void ShadowedBorderTextureShader::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) { ShadowedBorderRectangleShader::updateState(state, newMaterial, oldMaterial); auto texture = static_cast(newMaterial)->textureSource; if (texture) { texture->bind(); } } #else void ShadowedBorderTextureShader::updateSampledImage(QSGMaterialShader::RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) { Q_UNUSED(state); Q_UNUSED(oldMaterial); if (binding == 1) { *texture = static_cast(newMaterial)->textureSource; } } #endifukui-quick/items/scenegraph/shadowed-rectangle-node.cpp0000664000175000017500000001657515153755732022251 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "shadowed-rectangle-node.h" #include "shadowed-border-rectangle-material.h" QColor premultiply(const QColor &color) { return QColor::fromRgbF(color.redF() * color.alphaF(), // color.greenF() * color.alphaF(), color.blueF() * color.alphaF(), color.alphaF()); } ShadowedRectangleNode::ShadowedRectangleNode() { m_geometry = new QSGGeometry{QSGGeometry::defaultAttributes_TexturedPoint2D(), 4}; setGeometry(m_geometry); setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial); } void ShadowedRectangleNode::setBorderEnabled(bool enabled) { // We can achieve more performant shaders by splitting the two into separate // shaders. This requires separating the materials as well. So when // borderWidth is increased to something where the border should be visible, // switch to the with-border material. Otherwise use the no-border version. if (enabled) { if (!m_material || m_material->type() == borderlessMaterialType()) { auto newMaterial = createBorderMaterial(); newMaterial->shaderType = m_shaderType; setMaterial(newMaterial); m_material = newMaterial; m_rect = QRectF{}; markDirty(QSGNode::DirtyMaterial); } } else { if (!m_material || m_material->type() == borderMaterialType()) { auto newMaterial = createBorderlessMaterial(); newMaterial->shaderType = m_shaderType; setMaterial(newMaterial); m_material = newMaterial; m_rect = QRectF{}; markDirty(QSGNode::DirtyMaterial); } } } void ShadowedRectangleNode::setRect(const QRectF &rect) { if (rect == m_rect) { return; } m_rect = rect; QVector2D newAspect{1.0, 1.0}; if (m_rect.width() >= m_rect.height()) { newAspect.setX(m_rect.width() / m_rect.height()); } else { newAspect.setY(m_rect.height() / m_rect.width()); } if (m_material->aspect != newAspect) { m_material->aspect = newAspect; markDirty(QSGNode::DirtyMaterial); m_aspect = newAspect; } } void ShadowedRectangleNode::setSize(qreal size) { auto minDimension = std::min(m_rect.width(), m_rect.height()); float uniformSize = (size / minDimension) * 2.0; if (!qFuzzyCompare(m_material->size, uniformSize)) { m_material->size = uniformSize; markDirty(QSGNode::DirtyMaterial); m_size = size; } } void ShadowedRectangleNode::setRadius(const QVector4D &radius) { float minDimension = std::min(m_rect.width(), m_rect.height()); auto uniformRadius = QVector4D{std::min(radius.x() * 2.0f / minDimension, 1.0f), std::min(radius.y() * 2.0f / minDimension, 1.0f), std::min(radius.z() * 2.0f / minDimension, 1.0f), std::min(radius.w() * 2.0f / minDimension, 1.0f)}; if (m_material->radius != uniformRadius) { m_material->radius = uniformRadius; markDirty(QSGNode::DirtyMaterial); m_radius = radius; } } void ShadowedRectangleNode::setColor(const QColor &color) { auto premultiplied = premultiply(color); if (m_material->color != premultiplied) { m_material->color = premultiplied; markDirty(QSGNode::DirtyMaterial); } } void ShadowedRectangleNode::setShadowColor(const QColor &color) { auto premultiplied = premultiply(color); if (m_material->shadowColor != premultiplied) { m_material->shadowColor = premultiplied; markDirty(QSGNode::DirtyMaterial); } } void ShadowedRectangleNode::setOffset(const QVector2D &offset) { auto minDimension = std::min(m_rect.width(), m_rect.height()); auto uniformOffset = offset / minDimension; if (m_material->offset != uniformOffset) { m_material->offset = uniformOffset; markDirty(QSGNode::DirtyMaterial); m_offset = offset; } } void ShadowedRectangleNode::setBorderWidth(qreal width) { if (m_material->type() != borderMaterialType()) { return; } auto minDimension = std::min(m_rect.width(), m_rect.height()); float uniformBorderWidth = width / minDimension; auto borderMaterial = static_cast(m_material); if (!qFuzzyCompare(borderMaterial->borderWidth, uniformBorderWidth)) { borderMaterial->borderWidth = uniformBorderWidth; markDirty(QSGNode::DirtyMaterial); m_borderWidth = width; } } void ShadowedRectangleNode::setBorderColor(const QColor &color) { if (m_material->type() != borderMaterialType()) { return; } auto borderMaterial = static_cast(m_material); auto premultiplied = premultiply(color); if (borderMaterial->borderColor != premultiplied) { borderMaterial->borderColor = premultiplied; markDirty(QSGNode::DirtyMaterial); } } void ShadowedRectangleNode::setPureColor(bool pureColor) { if( m_material->pureColor != pureColor) { m_material->pureColor = pureColor; markDirty(QSGNode::DirtyMaterial); } } void ShadowedRectangleNode::setStartColor(const QColor& color) { auto premultiplied = premultiply(color); if (m_material->startColor != premultiplied) { m_material->startColor = premultiplied; markDirty(QSGNode::DirtyMaterial); } } void ShadowedRectangleNode::setEndColor(const QColor& color) { auto premultiplied = premultiply(color); if (m_material->endColor != premultiplied) { m_material->endColor = premultiplied; markDirty(QSGNode::DirtyMaterial); } } void ShadowedRectangleNode::setAngle(const qreal& angle) { if (m_material->angle != angle) { m_material->angle = angle; markDirty(QSGNode::DirtyMaterial); } } void ShadowedRectangleNode::setShaderType(ShadowedRectangleMaterial::ShaderType type) { m_shaderType = type; } void ShadowedRectangleNode::updateGeometry() { auto rect = m_rect; if (m_shaderType == ShadowedRectangleMaterial::ShaderType::Standard) { rect = rect.adjusted(-m_size * m_aspect.x(), // -m_size * m_aspect.y(), m_size * m_aspect.x(), m_size * m_aspect.y()); auto offsetLength = m_offset.length(); rect = rect.adjusted(-offsetLength * m_aspect.x(), // -offsetLength * m_aspect.y(), offsetLength * m_aspect.x(), offsetLength * m_aspect.y()); } QSGGeometry::updateTexturedRectGeometry(m_geometry, rect, QRectF{0.0, 0.0, 1.0, 1.0}); markDirty(QSGNode::DirtyGeometry); } ShadowedRectangleMaterial *ShadowedRectangleNode::createBorderlessMaterial() { return new ShadowedRectangleMaterial{}; } ShadowedBorderRectangleMaterial *ShadowedRectangleNode::createBorderMaterial() { return new ShadowedBorderRectangleMaterial{}; } QSGMaterialType *ShadowedRectangleNode::borderlessMaterialType() { return &ShadowedRectangleMaterial::staticType; } QSGMaterialType *ShadowedRectangleNode::borderMaterialType() { return &ShadowedBorderRectangleMaterial::staticType; } ukui-quick/items/scenegraph/shadowed-rectangle-node.h0000664000175000017500000000502715153755732021704 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #pragma once #include #include #include #include #include "shadowed-rectangle-material.h" struct QSGMaterialType; class ShadowedBorderRectangleMaterial; /** * Scene graph node for a shadowed rectangle. * * This node will set up the geometry and materials for a shadowed rectangle, * optionally with rounded corners. * * \note You must call updateGeometry() after setting properties of this node, * otherwise the node's state will not correctly reflect all the properties. * * \sa ShadowedRectangle */ class ShadowedRectangleNode : public QSGGeometryNode { public: ShadowedRectangleNode(); /** * Set whether to draw a border. * * Note that this will switch between a material with or without border. * This means this needs to be called before any other setters. */ void setBorderEnabled(bool enabled); void setRect(const QRectF &rect); void setSize(qreal size); void setRadius(const QVector4D &radius); void setColor(const QColor &color); void setShadowColor(const QColor &color); void setOffset(const QVector2D &offset); void setBorderWidth(qreal width); void setBorderColor(const QColor &color); void setPureColor(bool pureColor); void setStartColor(const QColor &color); void setEndColor(const QColor &color); void setAngle(const qreal &angle); void setShaderType(ShadowedRectangleMaterial::ShaderType type); /** * Update the geometry for this node. * * This is done as an explicit step to avoid the geometry being recreated * multiple times while updating properties. */ void updateGeometry(); protected: virtual ShadowedRectangleMaterial *createBorderlessMaterial(); virtual ShadowedBorderRectangleMaterial *createBorderMaterial(); virtual QSGMaterialType *borderMaterialType(); virtual QSGMaterialType *borderlessMaterialType(); QSGGeometry *m_geometry; ShadowedRectangleMaterial *m_material = nullptr; ShadowedRectangleMaterial::ShaderType m_shaderType = ShadowedRectangleMaterial::ShaderType::Standard; private: QRectF m_rect; qreal m_size = 0.0; QVector4D m_radius = QVector4D{0.0, 0.0, 0.0, 0.0}; QVector2D m_offset = QVector2D{0.0, 0.0}; QVector2D m_aspect = QVector2D{1.0, 1.0}; qreal m_borderWidth = 0.0; QColor m_borderColor; }; ukui-quick/items/scenegraph/shadowed-rectangle-material.h0000664000175000017500000000466315153755732022562 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #pragma once #include #include #include #include /** * A material rendering a rectangle with a shadow. * * This material uses a distance field shader to render a rectangle with a * shadow below it, optionally with rounded corners. */ class ShadowedRectangleMaterial : public QSGMaterial { public: enum class ShaderType { Standard, LowPower, }; ShadowedRectangleMaterial(); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QSGMaterialShader *createShader() const override; #else QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override; #endif QSGMaterialType *type() const override; int compare(const QSGMaterial *other) const override; QVector2D aspect = QVector2D{1.0, 1.0}; float size = 0.0; QVector4D radius = QVector4D{0.0, 0.0, 0.0, 0.0}; QColor color = Qt::white; QColor shadowColor = Qt::black; bool pureColor = true; QColor startColor = Qt::white; QColor endColor = Qt::white; float angle = 0.0; QVector2D offset; ShaderType shaderType = ShaderType::Standard; static QSGMaterialType staticType; }; class ShadowedRectangleShader : public QSGMaterialShader { public: ShadowedRectangleShader(ShadowedRectangleMaterial::ShaderType shaderType); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) char const *const *attributeNames() const override; void initialize() override; void updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; #else bool updateUniformData(QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; #endif protected: void setShader(ShadowedRectangleMaterial::ShaderType shaderType, const QString &shader); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) private: int m_matrixLocation = -1; int m_opacityLocation = -1; int m_aspectLocation = -1; int m_sizeLocation = -1; int m_radiusLocation = -1; int m_colorLocation = -1; int m_shadowColorLocation = -1; int m_offsetLocation = -1; int m_pureColorLocation = -1; int m_startColorLocation = -1; int m_endColorLocation = -1; int m_angleLocation = -1; #endif }; ukui-quick/items/scenegraph/painted-rectangle-item.cpp0000664000175000017500000001042015153755732022067 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "painted-rectangle-item.h" #include #include #include PaintedRectangleItem::PaintedRectangleItem(QQuickItem *parent) : QQuickPaintedItem(parent) { } void PaintedRectangleItem::setRadius(const QVector4D& radius) { m_radius = radius; update(); } void PaintedRectangleItem::setPureColor(bool pureColor) { m_pureColor = pureColor; update(); } void PaintedRectangleItem::setStartColor(const QColor& color) { m_startColor = color; update(); } void PaintedRectangleItem::setEndColor(const QColor& color) { m_endColor = color; update(); } void PaintedRectangleItem::setAngle(qreal angle) { m_angle = angle; update(); } void PaintedRectangleItem::setColor(const QColor &color) { m_color = color; update(); } void PaintedRectangleItem::setBorderColor(const QColor &color) { m_borderColor = color; update(); } void PaintedRectangleItem::setBorderWidth(qreal width) { m_borderWidth = width; update(); } void PaintedRectangleItem::paint(QPainter *painter) { painter->setRenderHint(QPainter::Antialiasing, true); qreal borderWidth = qFloor(m_borderWidth); qreal halfBorder = borderWidth / 2.0; // 计算填充区域(向内缩进边框宽度的一半) QRectF fillRect = boundingRect().adjusted(halfBorder, halfBorder, -halfBorder, -halfBorder); // 计算实际可用尺寸 qreal effectiveWidth = fillRect.width(); qreal effectiveHeight = fillRect.height(); // 计算各圆角半径(确保不超过可用空间) float minDimension = qMin(effectiveWidth, effectiveHeight); qreal topLeftRadius = qMin(m_radius.w(), minDimension/2); qreal topRightRadius = qMin(m_radius.y(), minDimension/2); qreal bottomRightRadius = qMin(m_radius.x(), minDimension/2); qreal bottomLeftRadius = qMin(m_radius.z(), minDimension/2); // 构造圆角路径 QPainterPath path; // 左上角 path.moveTo(fillRect.left(), fillRect.top() + topLeftRadius); path.arcTo(fillRect.left(), fillRect.top(), topLeftRadius*2, topLeftRadius*2, 180, -90); // 右上角 path.lineTo(fillRect.right() - topRightRadius, fillRect.top()); path.arcTo(fillRect.right() - topRightRadius*2, fillRect.top(), topRightRadius*2, topRightRadius*2, 90, -90); // 右下角 path.lineTo(fillRect.right(), fillRect.bottom() - bottomRightRadius); path.arcTo(fillRect.right() - bottomRightRadius*2, fillRect.bottom() - bottomRightRadius*2, bottomRightRadius*2, bottomRightRadius*2, 0, -90); // 左下角 path.lineTo(fillRect.left() + bottomLeftRadius, fillRect.bottom()); path.arcTo(fillRect.left(), fillRect.bottom() - bottomLeftRadius*2, bottomLeftRadius*2, bottomLeftRadius*2, -90, -90); path.closeSubpath(); // 设置填充画刷 QBrush brush; if (m_pureColor) { brush.setColor(m_color); brush.setStyle(Qt::SolidPattern); } else { // 计算渐变参数(基于填充区域) qreal gradW = fillRect.width(); qreal gradH = fillRect.height(); // 计算渐变方向向量 qreal dirX = qCos(m_angle); qreal dirY = qSin(m_angle); // 计算起点终点(基于填充区域坐标系) QPointF startPoint( gradW * (0.5 - dirX * 0.5), gradH * (0.5 - dirY * 0.5) ); QPointF endPoint( gradW * (0.5 + dirX * 0.5), gradH * (0.5 + dirY * 0.5) ); // 创建线性渐变 QLinearGradient gradient( fillRect.topLeft() + startPoint, fillRect.topLeft() + endPoint ); gradient.setColorAt(0, m_startColor); gradient.setColorAt(1, m_endColor); brush = QBrush(gradient); } // 绘制填充 painter->fillPath(path, brush); // 绘制边框 if (borderWidth > 0) { QPen pen(m_borderColor, borderWidth); pen.setJoinStyle(Qt::RoundJoin); // 圆角连接 pen.setCapStyle(Qt::RoundCap); // 圆角线帽 painter->setPen(pen); painter->drawPath(path); } }ukui-quick/items/scenegraph/shadowed-texture-node.h0000664000175000017500000000234515153755732021440 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #pragma once #include #include #include "shadowed-rectangle-node.h" #include "shadowed-texture-material.h" /** * Scene graph node for a shadowed texture source. * * This node will set up the geometry and materials for a shadowed rectangle, * optionally with rounded corners, using a supplied texture source as the color * for the rectangle. * * \note You must call updateGeometry() after setting properties of this node, * otherwise the node's state will not correctly reflect all the properties. * * \sa ShadowedTexture */ class ShadowedTextureNode : public ShadowedRectangleNode { public: ShadowedTextureNode(); void setTextureSource(QSGTextureProvider *source); void preprocess() override; private: ShadowedRectangleMaterial *createBorderlessMaterial() override; ShadowedBorderRectangleMaterial *createBorderMaterial() override; QSGMaterialType *borderlessMaterialType() override; QSGMaterialType *borderMaterialType() override; QPointer m_textureSource; }; ukui-quick/items/scenegraph/shadowed-texture-material.h0000664000175000017500000000271715153755732022314 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #pragma once #include #include "shadowed-rectangle-material.h" /** * A material rendering a rectangle with a shadow. * * This material uses a distance field shader to render a rectangle with a * shadow below it, optionally with rounded corners. */ class ShadowedTextureMaterial : public ShadowedRectangleMaterial { public: ShadowedTextureMaterial(); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QSGMaterialShader *createShader() const override; #else QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override; #endif QSGMaterialType *type() const override; int compare(const QSGMaterial *other) const override; QSGTexture *textureSource = nullptr; static QSGMaterialType staticType; }; class ShadowedTextureShader : public ShadowedRectangleShader { public: ShadowedTextureShader(ShadowedRectangleMaterial::ShaderType shaderType); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) void initialize() override; void updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; #else void updateSampledImage(QSGMaterialShader::RenderState &state, int binding, QSGTexture **texture, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; #endif }; ukui-quick/items/scenegraph/painted-rectangle-item.h0000664000175000017500000000307715153755732021546 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef PAINTEDRECTANGLEITEM_H #define PAINTEDRECTANGLEITEM_H #include #include /** * A rectangle with a border and rounded corners, rendered through QPainter. * * This is a helper used by ShadowedRectangle as fallback for when software * rendering is used, which means our shaders cannot be used. * * Since we cannot actually use QSGPaintedNode, we need to do some trickery * using QQuickPaintedItem as a child of ShadowedRectangle. * * \warning This item is **not** intended as a general purpose item. */ class PaintedRectangleItem : public QQuickPaintedItem { Q_OBJECT public: explicit PaintedRectangleItem(QQuickItem *parent = nullptr); void setRadius(const QVector4D &radius); void setColor(const QColor &color); void setPureColor(bool pureColor); void setStartColor(const QColor &color); void setEndColor(const QColor &color); void setAngle(qreal angle); void setBorderColor(const QColor &color); void setBorderWidth(qreal width); void paint(QPainter *painter) override; private: QColor m_color; QVector4D m_radius = QVector4D{0.0, 0.0, 0.0, 0.0}; QColor m_borderColor; qreal m_borderWidth = 0.0; QColor m_shadowColor = Qt::black; bool m_pureColor = true; QColor m_startColor = Qt::white; QColor m_endColor = Qt::white; float m_angle = 0.0; }; #endif // PAINTEDRECTANGLEITEM_H ukui-quick/items/gradient-border-item.cpp0000664000175000017500000001227715153755732017446 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: amingamingaming * */ #include "gradient-border-item.h" #include #include namespace UkuiQuick { GradientBorderItem::GradientBorderItem(QQuickItem *parent) : QQuickPaintedItem(parent) { } void GradientBorderItem::setBorderWidth(qreal width) { if(m_borderWidth != width) { m_borderWidth = width; update(); // 触发重绘 Q_EMIT borderWidthChanged(); } } void GradientBorderItem::setBorderRadius(qreal radius) { if(m_borderRadius != radius) { m_borderRadius = radius; update(); // 触发重绘 Q_EMIT borderRadiusChanged(); } } void GradientBorderItem::setDeg(qreal deg) { if(m_deg != deg) { m_deg = deg; update(); // 触发重绘 Q_EMIT degChanged(); } } void GradientBorderItem::setEnableSpecificBorder(bool enable) { if(m_enableSpecificBorder != enable) { m_enableSpecificBorder = enable; startCurveAnimation(m_enableSpecificBorder); Q_EMIT enableSpecificBorderChanged(); } } void GradientBorderItem::setBorderColor(GradientColor *color) { if(m_borderColor == nullptr && isComponentComplete()) { disconnect(DtTheme::self(qmlEngine(this)), &DtTheme::kLineComponentNormalChanged, this, &QQuickItem::update); } if(color != nullptr && m_borderColor != color) { m_borderColor = color; update(); Q_EMIT borderColorChanged(); } } void GradientBorderItem::paint(QPainter *painter) { QRectF drawRect(m_borderWidth / 2.0, m_borderWidth / 2.0, width() - m_borderWidth, height() - m_borderWidth); // 启用抗锯齿,确保边缘平滑 painter->setRenderHint(QPainter::Antialiasing); painter->setRenderHint(QPainter::SmoothPixmapTransform); if(m_enableSpecificBorder) { // 根据角度计算渐变方向 QPointF start( (0.5 - 0.5 * qCos(m_deg * M_PI / 180)) * width(), (0.5 - 0.5 * qSin(m_deg * M_PI / 180)) * height() ); QPointF end( (0.5 + 0.5 * qCos(m_deg * M_PI / 180)) * width(), (0.5 + 0.5 * qSin(m_deg * M_PI / 180)) * height() ); QLinearGradient gradient(start,end); gradient.setColorAt(1.0, QColor("#0036F5")); gradient.setColorAt(0.66, QColor("#4E75FF")); gradient.setColorAt(0.29, QColor("#8A41FF")); gradient.setColorAt(0.0, QColor("#E888F8")); painter->setPen(QPen(gradient, m_borderWidth)); } else { GradientColor* borderColor = m_borderColor ? m_borderColor : DtTheme::self(qmlEngine(this))->kLineComponentNormal(); if(borderColor->colorType() == DtColorType::Type::Pure) { painter->setPen(QPen(borderColor->pureColor(), m_borderWidth)); } else { //设置渐变色的方向 QLinearGradient linearGradient(borderColor->getGradientStartPoint(borderColor->gradientDeg(), width(), height()) , borderColor->getGradientEndPoint(borderColor->gradientDeg(), width(), height())); //设置渐变色颜色 linearGradient.setColorAt(0.0, borderColor->mixBackGroundColor(borderColor->gradientStart(), borderColor->gradientBackground(), 1)); linearGradient.setColorAt(1.0, borderColor->mixBackGroundColor(borderColor->gradientEnd(), borderColor->gradientBackground(), 1)); painter->setPen(QPen(linearGradient, m_borderWidth)); } } // 绘制圆角矩形边框 painter->drawRoundedRect(drawRect, m_borderRadius, m_borderRadius); } void GradientBorderItem::startCurveAnimation(bool start) { if(m_animation == nullptr) { m_animation = new QPropertyAnimation(this, "deg", this); m_animation->setDuration(1200); m_animation->setStartValue(0); m_animation->setEndValue(540); // 设置贝塞尔曲线 cubic-bezier(0.3, 0.1, 0.3, 1) QEasingCurve curve(QEasingCurve::BezierSpline); curve.addCubicBezierSegment(QPointF(0.3, 0.1), QPointF(0.3, 1), QPointF(1, 1)); m_animation->setEasingCurve(curve); m_animation->setLoopCount(1); } if(start) { m_animation->start(); } else { if(m_animation->state() != QAbstractAnimation::Stopped) { m_animation->stop(); } update(); } } void GradientBorderItem::componentComplete() { QQuickItem::componentComplete(); if(m_borderColor == nullptr) { connect(DtTheme::self(qmlEngine(this)), &DtTheme::kLineComponentNormalChanged, this, &QQuickItem::update); } } } ukui-quick/items/iconThemeBackgroundConfig.json0000664000175000017500000017524015153755732020672 0ustar fengfeng{ "ukui-icon-theme-default" : { "containShadow" : true, "offsetX" : 0, "offsetY" : 0, "centerAreaRate" : 0.625, "16" : { "containShadow" : false, "radius" : 4, "outLineDistance" : [ 41.7732,41.0366,40.3113,41.7732,41.1096,40.4599,39.8246,39.2046, 38.6005,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.573,32.28,32.0156, 31.7805,31.5753,31.4006,31.257,31.1448,31.0644,31.0161,31, 31.0161,31.0644,31.1448,31.257,31.4006,31.5753,31.7805,32.0156, 32.28,32.573,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,39.2046, 39.8246,40.4599,41.1096,41.7732,42.45,41.0366,41.7732,42.5206, 42.45,41.7253,41.0122,42.5206,41.8688,41.2311,40.6079,40, 39.4081,38.833,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.176,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.249,32.1403,32.0624,32.0156,32, 32.0156,32.0624,32.1403,32.249,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.176,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.833,39.4081,40, 40.6079,41.2311,41.8688,42.5206,43.1856,41.7253,42.45,43.1856, 41.7732,41.0366,40.3113,41.7732,41.1096,40.4599,39.8246,39.2046, 38.6005,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.573,32.28,32.0156, 31.7805,31.5753,31.4006,31.257,31.1448,31.0644,31.0161,31, 31.0161,31.0644,31.1448,31.257,31.4006,31.5753,31.7805,32.0156, 32.28,32.573,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,39.2046, 39.8246,40.4599,41.1096,41.7732,42.45,41.0366,41.7732,42.5206, 42.45,41.7253,41.0122,42.5206,41.8688,41.2311,40.6079,40, 39.4081,38.833,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.176,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.249,32.1403,32.0624,32.0156,32, 32.0156,32.0624,32.1403,32.249,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.176,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.833,39.4081,40, 40.6079,41.2311,41.8688,42.5206,43.1856,41.7253,42.45,43.1856] }, "24" : { "containShadow" : false, "radius" : 6, "outLineDistance" : [ 39.8246,39.0512,38.2884,39.598,38.8973,38.2099,39.8246,39.2046, 38.6005,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.573,32.28,32.0156, 31.7805,31.5753,31.4006,31.257,31.1448,31.0644,31.0161,31, 31.0161,31.0644,31.1448,31.257,31.4006,31.5753,31.7805,32.0156, 32.28,32.573,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,39.2046, 39.8246,40.4599,38.8973,39.598,40.3113,39.0512,39.8246,40.6079, 40.4599,39.6989,38.9487,40.3113,39.6232,38.9487,40.6079,40, 39.4081,38.833,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.176,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.249,32.1403,32.0624,32.0156,32, 32.0156,32.0624,32.1403,32.249,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.176,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.833,39.4081,40, 40.6079,41.2311,39.6232,40.3113,41.0122,39.6989,40.4599,41.2311, 39.8246,39.0512,38.2884,39.598,38.8973,38.2099,39.8246,39.2046, 38.6005,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.573,32.28,32.0156, 31.7805,31.5753,31.4006,31.257,31.1448,31.0644,31.0161,31, 31.0161,31.0644,31.1448,31.257,31.4006,31.5753,31.7805,32.0156, 32.28,32.573,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,39.2046, 39.8246,40.4599,38.8973,39.598,40.3113,39.0512,39.8246,40.6079, 40.4599,39.6989,38.9487,40.3113,39.6232,38.9487,40.6079,40, 39.4081,38.833,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.176,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.249,32.1403,32.0624,32.0156,32, 32.0156,32.0624,32.1403,32.249,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.176,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.833,39.4081,40, 40.6079,41.2311,39.6232,40.3113,41.0122,39.6989,40.4599,41.2311] }, "32" : { "radius" : 8, "outLineDistance" : [ 35.3553,34.4819,36.4005,35.609,34.8281,35.3836,34.6554,35.3836, 34.7131,36.4005,35.805,35.2278,34.6699,34.1321,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.573,32.28,32.0156, 31.7805,31.5753,31.4006,31.257,31.1448,31.0644,31.0161,31, 31.0161,31.0644,31.1448,31.257,31.4006,31.5753,31.7805,32.0156, 32.28,32.573,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,34.6699,35.2278,35.805,36.4005,37.0135,35.3836, 36.0694,35.3836,36.1248,35.609,36.4005,37.2022,35.3553,36.2353, 35.8469,34.9857,37.0135,36.2353,35.4683,36.0694,35.3553,36.1248, 35.4683,37.2022,36.6197,36.0555,35.5106,34.9857,36.2353,35.7771, 35.3412,34.9285,34.5398,34.176,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.249,32.1403,32.0624,32.0156,32, 32.0156,32.0624,32.1403,32.249,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.176,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,35.5106,36.0555,36.6197,37.2022,37.8021,36.1248, 36.7967,36.0694,36.7967,36.2353,37.0135,37.8021,35.8469,36.7151, 35.3553,34.4819,36.4005,35.609,34.8281,35.3836,34.6554,35.3836, 34.7131,36.4005,35.805,35.2278,34.6699,34.1321,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.573,32.28,32.0156, 31.7805,31.5753,31.4006,31.257,31.1448,31.0644,31.0161,31, 31.0161,31.0644,31.1448,31.257,31.4006,31.5753,31.7805,32.0156, 32.28,32.573,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,34.6699,35.2278,35.805,36.4005,37.0135,35.3836, 36.0694,35.3836,36.1248,35.609,36.4005,37.2022,35.3553,36.2353, 35.8469,34.9857,37.0135,36.2353,35.4683,36.0694,35.3553,36.1248, 35.4683,37.2022,36.6197,36.0555,35.5106,34.9857,36.2353,35.7771, 35.3412,34.9285,34.5398,34.176,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.249,32.1403,32.0624,32.0156,32, 32.0156,32.0624,32.1403,32.249,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.176,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,35.5106,36.0555,36.6197,37.2022,37.8021,36.1248, 36.7967,36.0694,36.7967,36.2353,37.0135,37.8021,35.8469,36.7151] }, "48" : { "radius" : 12, "outLineDistance" : [ 34.8855,35.2278,35.2278,35.609,34.8281,36.0694,36.0694,35.3836, 36.2353,35.609,35,35.2278,34.6699,34.1321,33.6155,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.573,32.28,32.0156, 31.7805,31.5753,31.4006,31.257,31.1448,31.0644,31.0161,31, 31.0161,31.0644,31.1448,31.257,31.4006,31.5753,31.7805,32.0156, 32.28,32.573,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,34.1321,34.6699,35.2278,35.805,35.609,36.2353,36.8782, 36.0694,36.7696,36.7967,35.609,36.4005,36.0555,34.8855,35.7771, 35.3553,34.4819,35.805,36.2353,35.4683,36.7696,36.7967,36.1248, 37.0135,36.4005,35.805,36.0555,35.5106,34.9857,34.4819,35.7771, 35.3412,34.9285,34.5398,34.176,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.249,32.1403,32.0624,32.0156,32, 32.0156,32.0624,32.1403,32.249,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.176,34.5398,34.9285,35.3412,35.7771, 36.2353,34.9857,35.5106,36.0555,36.6197,36.4005,37.0135,37.6431, 36.7967,37.4833,37.4833,36.2353,37.0135,36.6197,35.3553,36.2353, 34.8855,34,35.2278,36.2353,35.4683,36.0694,36.0694,35.3836, 34.7131,35.609,35,35.2278,34.6699,34.1321,33.6155,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.573,32.28,32.0156, 31.7805,31.5753,31.4006,31.257,31.1448,31.0644,31.0161,31, 31.0161,31.0644,31.1448,31.257,31.4006,31.5753,31.7805,32.0156, 32.28,32.573,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,34.1321,34.6699,35.2278,35.805,35.609,36.2353,35.3836, 36.0694,36.7696,36.7967,36.2353,37.0135,36.0555,34.8855,35.7771, 35.3553,34.4819,35.805,36.8782,36.1248,36.7696,36.7967,36.1248, 35.4683,36.4005,35.805,36.0555,35.5106,34.9857,34.4819,35.7771, 35.3412,34.9285,34.5398,34.176,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.249,32.1403,32.0624,32.0156,32, 32.0156,32.0624,32.1403,32.249,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.176,34.5398,34.9285,35.3412,35.7771, 36.2353,34.9857,35.5106,36.0555,36.6197,36.4005,37.0135,36.1248, 36.7967,37.4833,37.4833,36.8782,37.6431,36.6197,35.3653,36.2353] }, "64" : { "radius" : 16, "outLineDistance" : [ 35.1870,36.0652,36.4093,36.2436,36.4608,35.7265,36.0760,36.5015, 36.2409,36.0048,36.2124,35.6417,35.5106,35.4163,34.9186,34.4428, 33.9897,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,34.4384,34.4428, 34.9186,35.4163,35.9350,36.0555,36.2124,36.8014,36.6244,36.8840, 37.1667,36.7764,36.4608,37.2074,37.0218,37.2109,36.9013,36.0721, 35.6651,36.6289,37.0218,36.8860,37.1400,36.4195,36.8030,37.2607, 37.0187,36.8014,37.0315,36.4736,36.3593,36.2810,35.7953,35.3313, 34.8898,34.9285,34.0777,34.1760,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,33.7088,34.5398,34.4716,35.3412,35.3313, 35.7953,36.2810,36.7875,36.8917,37.0315,37.6077,37.4078,37.6485, 37.9125,37.4898,37.1400,37.8733,37.6510,37.8105,37.4525,36.5386, 34.9308,36.0652,36.4093,36.2436,36.4608,35.7265,36.0760,36.5015, 36.2409,36.0048,36.2124,35.6417,35.9350,35.4163,34.9186,34.4428, 33.9897,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,34.4384,34.4428, 34.9186,35.4163,35.9350,36.4736,36.2124,36.8014,36.6244,36.8840, 37.1667,36.7764,36.4608,37.2074,37.0218,37.2109,36.9013,35.8218, 35.3989,36.6289,37.0218,36.8860,37.1400,36.4195,36.8030,37.2607, 37.0187,36.8014,37.0315,36.4736,36.7875,36.2810,35.7953,35.3313, 34.8898,34.9285,34.5398,34.1760,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.1760,34.5398,34.9285,35.3412,35.3313, 35.7953,36.2810,36.7875,37.3138,37.0315,37.6077,37.4078,37.6485, 37.9125,37.4898,37.1400,37.8733,37.6510,37.8105,37.4525,36.2784] }, "96": { "radius" : 24, "outLineDistance" : [ 34.4384,36.0555,36.4005,35.609,36.1248,36.0694,36.0694,36.1248, 35.4683,36.4005,35.805,36.0555,35.5106,34.9857,34.4819,34, 34.4384,34.0147,33.6155,33.2415,32.8938,32.573,32.28,32.0156, 31.7805,31.5753,31.4006,31.257,31.1448,31.0644,31.0161,31, 31.0161,31.0644,31.1448,31.257,31.4006,31.5753,31.7805,32.0156, 32.28,32.573,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 34.4819,34.9857,35.5106,36.0555,36.6197,36.4005,37.0135,36.1248, 36.7967,36.7696,36.7967,36.8782,36.4005,37.2022,36.8917,35.3412, 34.8855,36.6197,37.0135,36.2353,36.7967,36.7696,36.7967,36.8782, 36.2353,37.2022,36.6197,36.8917,36.3593,35.8469,35.3553,34.8855, 35.3412,34.9285,34.5398,34.176,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.249,32.1403,32.0624,32.0156,32, 32.0156,32.0624,32.1403,32.249,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.176,34.5398,34.9285,35.3412,35.7771, 35.3553,35.8469,36.3593,36.8917,37.4433,37.2022,37.8021,36.8782, 37.5366,37.4833,37.4833,37.5366,37.0135,37.8021,37.4433,35.7771, 34.4384,36.0555,36.4005,35.609,36.1248,36.0694,36.0694,36.1248, 35.4683,36.4005,35.805,36.0555,35.5106,34.9857,34.4819,34, 34.4384,34.0147,33.6155,33.2415,32.8938,32.573,32.28,32.0156, 31.7805,31.5753,31.4006,31.257,31.1448,31.0644,31.0161,31, 31.0161,31.0644,31.1448,31.257,31.4006,31.5753,31.7805,32.0156, 32.28,32.573,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 34.4819,34.9857,35.5106,36.0555,36.6197,36.4005,37.0135,36.1248, 36.7967,36.7696,36.7967,36.8782,36.4005,37.2022,36.8917,35.3412, 34.8855,36.6197,37.0135,36.2353,36.7967,36.7696,36.7967,36.8782, 36.2353,37.2022,36.6197,36.8917,36.3593,35.8469,35.3553,34.8855, 35.3412,34.9285,34.5398,34.176,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.249,32.1403,32.0624,32.0156,32, 32.0156,32.0624,32.1403,32.249,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.176,34.5398,34.9285,35.3412,35.7771, 35.3553,35.8469,36.3593,36.8917,37.4433,37.2022,37.8021,36.8782, 37.5366,37.4833,37.4833,37.5366,37.0135,37.8021,37.4433,35.7771] }, "128" : { "radius" : 32, "outLineDistance" : [ 34.6620,35.7831,36.1028,36.2353,36.1248,36.0694,36.0694,36.1248, 36.2353,36.0048,35.8050,35.2278,35.5106,34.9857,34.9186,34.4428, 34.4384,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,34.9857,35.5106,36.0555,35.8050,36.4005,37.0135,36.8782, 36.7967,36.7696,36.7967,36.8782,37.0135,36.9110,36.6255,35.5592, 34.8855,36.0555,36.4005,36.8782,36.7967,36.4195,36.7967,36.8782, 36.6244,36.4005,36.6197,36.0555,35.9350,35.8469,35.3553,34.8855, 34.8898,34.4716,34.0777,33.7088,33.3658,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,31.7530,31.6426,31.5634,31.5159,31.5000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.0665,32.2686,32.5002, 32.7608,33.0496,33.3658,34.1760,34.5398,34.4716,34.8898,34.8855, 35.7953,35.8469,36.3593,36.4736,36.6197,37.2022,37.0135,37.2607, 37.5366,37.4833,37.1400,37.5366,37.6431,37.2022,36.8917,35.7771, 35.1204,35.5106,36.1028,36.2353,36.1248,36.0694,36.0694,36.1248, 36.2353,36.0048,35.8050,35.6417,35.5106,34.9857,34.4819,34.4428, 34.4384,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,33.9897,34.8855, 34.4819,34.9857,35.5106,36.0555,35.8050,36.4005,36.2353,36.8782, 36.7967,36.4195,36.7967,36.8782,36.7070,36.6197,36.1031,34.4337, 35.3553,36.0555,37.0135,36.8782,36.7967,36.7696,36.7967,36.8782, 37.0135,36.8014,36.6197,36.4736,36.3593,35.8469,35.3553,35.3313, 35.3412,34.9285,34.5398,34.1760,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.1760,34.5398,34.9285,35.3412,35.7771, 35.3553,35.8469,36.3593,36.8917,36.6197,37.2022,37.0135,37.6431, 37.5366,37.1265,37.4833,37.5366,37.3283,37.2022,36.6255,35.3876] }, "512" : { "radius" : 32, "outLineDistance" : [ 34.6620,35.7831,36.1028,36.2353,36.1248,36.0694,36.0694,36.1248, 36.2353,36.0048,35.8050,35.2278,35.5106,34.9857,34.9186,34.4428, 34.4384,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,34.9857,35.5106,36.0555,35.8050,36.4005,37.0135,36.8782, 36.7967,36.7696,36.7967,36.8782,37.0135,36.9110,36.6255,35.5592, 34.8855,36.0555,36.4005,36.8782,36.7967,36.4195,36.7967,36.8782, 36.6244,36.4005,36.6197,36.0555,35.9350,35.8469,35.3553,34.8855, 34.8898,34.4716,34.0777,33.7088,33.3658,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,31.7530,31.6426,31.5634,31.5159,31.5000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.0665,32.2686,32.5002, 32.7608,33.0496,33.3658,34.1760,34.5398,34.4716,34.8898,34.8855, 35.7953,35.8469,36.3593,36.4736,36.6197,37.2022,37.0135,37.2607, 37.5366,37.4833,37.1400,37.5366,37.6431,37.2022,36.8917,35.7771, 35.1204,35.5106,36.1028,36.2353,36.1248,36.0694,36.0694,36.1248, 36.2353,36.0048,35.8050,35.6417,35.5106,34.9857,34.4819,34.4428, 34.4384,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,33.9897,34.8855, 34.4819,34.9857,35.5106,36.0555,35.8050,36.4005,36.2353,36.8782, 36.7967,36.4195,36.7967,36.8782,36.7070,36.6197,36.1031,34.4337, 35.3553,36.0555,37.0135,36.8782,36.7967,36.7696,36.7967,36.8782, 37.0135,36.8014,36.6197,36.4736,36.3593,35.8469,35.3553,35.3313, 35.3412,34.9285,34.5398,34.1760,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.1760,34.5398,34.9285,35.3412,35.7771, 35.3553,35.8469,36.3593,36.8917,36.6197,37.2022,37.0135,37.6431, 37.5366,37.1265,37.4833,37.5366,37.3283,37.2022,36.6255,35.3876] } }, "ukui-icon-theme-fashion" : { "containShadow" : false, "offsetX" : 0, "offsetY" : 0, "centerAreaRate" : 0.625, "16" : { "radius" : 2, "outLineDistance" : [ 43.8406,43.1393,42.45,41.7732,41.1096,40.4599,39.8246,39.2046, 38.6005,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.573,32.28,32.0156, 31.7805,31.5753,31.4006,31.257,31.1448,31.0644,31.0161,31, 31.0161,31.0644,31.1448,31.257,31.4006,31.5753,31.7805,32.0156, 32.28,32.573,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,39.2046, 39.8246,40.4599,41.1096,41.7732,42.45,43.1393,43.8406,44.5533, 44.5533,43.8634,43.1856,42.5206,41.8688,41.2311,40.6079,40, 39.4081,38.833,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.176,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.249,32.1403,32.0624,32.0156,32, 32.0156,32.0624,32.1403,32.249,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.176,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.833,39.4081,40, 40.6079,41.2311,41.8688,42.5206,43.1856,43.8634,44.5533,45.2548, 43.8406,43.1393,42.45,41.7732,41.1096,40.4599,39.8246,39.2046, 38.6005,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.573,32.28,32.0156, 31.7805,31.5753,31.4006,31.257,31.1448,31.0644,31.0161,31, 31.0161,31.0644,31.1448,31.257,31.4006,31.5753,31.7805,32.0156, 32.28,32.573,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,39.2046, 39.8246,40.4599,41.1096,41.7732,42.45,43.1393,43.8406,44.5533, 44.5533,43.8634,43.1856,42.5206,41.8688,41.2311,40.6079,40, 39.4081,38.833,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.176,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.249,32.1403,32.0624,32.0156,32, 32.0156,32.0624,32.1403,32.249,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.176,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.833,39.4081,40, 40.6079,41.2311,41.8688,42.5206,43.1856,43.8634,44.5533,45.2548] }, "24" : { "radius" : 3, "outLineDistance" : [ 41.7732,41.0366,40.3113,41.7732,41.1096,40.4599,39.8246,39.2046, 38.6005,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.573,32.28,32.0156, 31.7805,31.5753,31.4006,31.257,31.1448,31.0644,31.0161,31, 31.0161,31.0644,31.1448,31.257,31.4006,31.5753,31.7805,32.0156, 32.28,32.573,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,39.2046, 39.8246,40.4599,41.1096,41.7732,42.45,41.0366,41.7732,42.5206, 42.45,41.7253,41.0122,42.5206,41.8688,41.2311,40.6079,40, 39.4081,38.833,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.176,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.249,32.1403,32.0624,32.0156,32, 32.0156,32.0624,32.1403,32.249,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.176,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.833,39.4081,40, 40.6079,41.2311,41.8688,42.5206,43.1856,41.7253,42.45,43.1856, 41.7732,41.0366,40.3113,41.7732,41.1096,40.4599,39.8246,39.2046, 38.6005,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.573,32.28,32.0156, 31.7805,31.5753,31.4006,31.257,31.1448,31.0644,31.0161,31, 31.0161,31.0644,31.1448,31.257,31.4006,31.5753,31.7805,32.0156, 32.28,32.573,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,39.2046, 39.8246,40.4599,41.1096,41.7732,42.45,41.0366,41.7732,42.5206, 42.45,41.7253,41.0122,42.5206,41.8688,41.2311,40.6079,40, 39.4081,38.833,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.176,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.249,32.1403,32.0624,32.0156,32, 32.0156,32.0624,32.1403,32.249,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.176,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.833,39.4081,40, 40.6079,41.2311,41.8688,42.5206,43.1856,41.7253,42.45,43.1856] }, "32" : { "radius" : 4, "outLineDistance" : [ 40.4599,39.6989,41.0122,40.3113,39.6232,40.4599,39.8246,39.2046, 38.6005,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,39.2046, 39.8246,40.4599,41.1096,40.3113,41.0122,41.7253,40.4599,41.2311, 41.1096,40.3609,41.7253,41.0366,40.3609,41.2311,40.6079,40.0000, 39.4081,38.8330,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.1760,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.1760,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.8330,39.4081,40.0000, 40.6079,41.2311,41.8688,41.0366,41.7253,42.4264,41.1096,41.8688, 40.4599,39.6989,41.0122,40.3113,39.6232,40.4599,39.8246,39.2046, 38.6005,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,39.2046, 39.8246,40.4599,41.1096,40.3113,41.0122,41.7253,40.4599,41.2311, 41.1096,40.3609,41.7253,41.0366,40.3609,41.2311,40.6079,40.0000, 39.4081,38.8330,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.1760,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.1760,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.8330,39.4081,40.0000, 40.6079,41.2311,41.8688,41.0366,41.7253,42.4264,41.1096,41.8688] }, "48" : { "radius" : 5, "outLineDistance" : [ 39.8246,39.0512,40.3113,40.3113,39.6232,38.9487,39.8246,39.2046, 38.6005,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,39.2046, 39.8246,40.4599,39.6232,40.3113,41.0122,41.0366,39.8246,40.6079, 40.4599,39.6989,41.0122,41.0366,40.3609,39.6989,40.6079,40.0000, 39.4081,38.8330,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.1760,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.1760,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.8330,39.4081,40.0000, 40.6079,41.2311,40.3609,41.0366,41.7253,41.7253,40.4599,41.2311, 39.8246,39.0512,40.3113,40.3113,39.6232,38.9487,39.8246,39.2046, 38.6005,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,39.2046, 39.8246,40.4599,39.6232,40.3113,41.0122,41.0366,39.8246,40.6079, 40.4599,39.6989,41.0122,41.0366,40.3609,39.6989,40.6079,40.0000, 39.4081,38.8330,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.1760,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.1760,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.8330,39.4081,40.0000, 40.6079,41.2311,40.3609,41.0366,41.7253,41.7253,40.4599,41.2311] }, "64" : { "radius" : 6, "outLineDistance" : [ 39.2046,39.6989,40.3113,40.3113,39.6232,39.6989,39.0512,39.2046, 38.6005,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,39.2046, 39.8246,39.6989,40.3609,40.3113,41.0122,41.0366,40.4599,40.0000, 39.8246,40.3609,41.0122,41.0366,40.3609,40.4599,39.8246,40.0000, 39.4081,38.8330,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.1760,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.1760,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.8330,39.4081,40.0000, 40.6079,40.4599,41.1096,41.0366,41.7253,41.7253,41.1096,40.6079, 39.2046,39.6989,40.3113,40.3113,39.6232,39.6989,39.0512,39.2046, 38.6005,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,39.2046, 39.8246,39.6989,40.3609,40.3113,41.0122,41.0366,40.4599,40.0000, 39.8246,40.3609,41.0122,41.0366,40.3609,40.4599,39.8246,40.0000, 39.4081,38.8330,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.1760,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.1760,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.8330,39.4081,40.0000, 40.6079,40.4599,41.1096,41.0366,41.7253,41.7253,41.1096,40.6079] }, "96": { "radius" : 9, "outLineDistance" : [ 38.6005,39.0512,39.6232,39.5980,39.6232,38.9487,39.0512,38.4187, 38.6005,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,39.2046, 39.0512,39.6989,39.6232,40.3113,40.3113,40.3609,39.8246,39.4081, 39.2046,39.6989,40.3113,40.3113,40.3609,39.6989,39.8246,39.2046, 39.4081,38.8330,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.1760,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.1760,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.8330,39.4081,40.0000, 39.8246,40.4599,40.3609,41.0366,41.0122,41.0366,40.4599,40.0000, 38.6005,39.0512,39.6232,39.5980,39.6232,38.9487,39.0512,38.4187, 38.6005,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,39.2046, 39.0512,39.6989,39.6232,40.3113,40.3113,40.3609,39.8246,39.4081, 39.2046,39.6989,40.3113,40.3113,40.3609,39.6989,39.8246,39.2046, 39.4081,38.8330,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.1760,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.1760,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.8330,39.4081,40.0000, 39.8246,40.4599,40.3609,41.0366,41.0122,41.0366,40.4599,40.0000] }, "128" : { "radius" : 12, "outLineDistance" : [ 38.0132,39.0512,38.9487,39.5980,38.8973,38.9487,39.0512,38.4187, 37.8021,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,38.4187, 39.0512,39.6989,39.6232,39.5980,40.3113,39.6989,39.8246,38.8330, 38.6005,39.6989,39.6232,40.3113,39.6232,39.6989,39.8246,39.2046, 38.6005,38.8330,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.1760,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.1760,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.8330,39.4081,39.2046, 39.8246,40.4599,40.3609,40.3113,41.0122,40.3609,40.4599,39.4081, 38.0132,39.0512,38.9487,39.5980,38.8973,38.9487,39.0512,38.4187, 37.8021,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,38.4187, 39.0512,39.6989,39.6232,39.5980,40.3113,39.6989,39.8246,38.8330, 38.6005,39.6989,39.6232,40.3113,39.6232,39.6989,39.8246,39.2046, 38.6005,38.8330,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.1760,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.1760,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.8330,39.4081,39.2046, 39.8246,40.4599,40.3609,40.3113,41.0122,40.3609,40.4599,39.4081] }, "512" : { "radius" : 12, "outLineDistance" : [ 38.0132,39.0512,38.9487,39.5980,38.8973,38.9487,39.0512,38.4187, 37.8021,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,38.4187, 39.0512,39.6989,39.6232,39.5980,40.3113,39.6989,39.8246,38.8330, 38.6005,39.6989,39.6232,40.3113,39.6232,39.6989,39.8246,39.2046, 38.6005,38.8330,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.1760,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.1760,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.8330,39.4081,39.2046, 39.8246,40.4599,40.3609,40.3113,41.0122,40.3609,40.4599,39.4081, 38.0132,39.0512,38.9487,39.5980,38.8973,38.9487,39.0512,38.4187, 37.8021,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,38.4187, 39.0512,39.6989,39.6232,39.5980,40.3113,39.6989,39.8246,38.8330, 38.6005,39.6989,39.6232,40.3113,39.6232,39.6989,39.8246,39.2046, 38.6005,38.8330,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.1760,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.1760,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.8330,39.4081,39.2046, 39.8246,40.4599,40.3609,40.3113,41.0122,40.3609,40.4599,39.4081] } }, "ukui-icon-theme-classical" : { "containShadow" : true, "offsetX" : -0.015625, "offsetY" : -0.015625, "centerAreaRate" : 0.625, "16" : { "offsetX" : 0.03125, "offsetY" : -0.03125, "containShadow" : false, "outLineDistance" : [ 38.0132,37.2022,36.4005,35.6090,38.1838,37.4833,36.7967,36.1248, 35.4683,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,36.1248, 36.7967,37.4833,38.1838,38.8973,36.4005,37.2022,38.0132,38.8330, 38.6005,37.8021,37.0135,36.2353,38.8973,38.2099,37.5366,36.8782, 36.2353,38.8330,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.1760,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.1760,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.8330,39.4081,36.8782, 37.5366,38.2099,38.8973,39.5980,37.0135,37.8021,38.6005,39.4081, 38.0132,37.2022,36.4005,35.6090,38.1838,37.4833,36.7967,36.1248, 35.4683,38.0132,37.4433,36.8917,36.3593,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,36.8917,37.4433,38.0132,38.6005,36.1248, 36.7967,37.4833,38.1838,38.8973,36.4005,37.2022,38.0132,38.8330, 38.6005,37.8021,37.0135,36.2353,38.8973,38.2099,37.5366,36.8782, 36.2353,38.8330,38.2753,37.7359,37.2156,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.1760,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.1760,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,37.7359,38.2753,38.8330,39.4081,36.8782, 37.5366,38.2099,38.8973,39.5980,37.0135,37.8021,38.6005,39.4081] }, "24" : { "offsetX" : 0, "offsetY" : 0, "containShadow" : false, "outLineDistance" : [ 35.8469,34.9857,37.0135,36.2353,36.1248,36.7696,36.0694,36.1248, 37.0135,36.4005,35.805,35.2278,34.6699,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.573,32.28,32.0156, 31.7805,31.5753,31.4006,31.257,31.1448,31.0644,31.0161,31, 31.0161,31.0644,31.1448,31.257,31.4006,31.5753,31.7805,32.0156, 32.28,32.573,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,35.2278,35.805,36.4005,37.0135,37.6431, 36.7967,36.7696,37.4833,36.8782,37.0135,37.8021,35.8469,36.7151, 36.3593,35.5106,37.6431,36.8782,36.7967,37.4833,36.7967,36.8782, 37.8021,37.2022,36.6197,36.0555,35.5106,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.176,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.249,32.1403,32.0624,32.0156,32, 32.0156,32.0624,32.1403,32.249,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.176,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,36.0555,36.6197,37.2022,37.8021,38.4187, 37.5366,37.4833,38.1838,37.5366,37.6431,38.4187,36.3593,37.2156, 35.8469,34.9857,37.0135,36.2353,36.1248,36.7696,36.0694,36.1248, 37.0135,36.4005,35.805,35.2278,34.6699,35.8469,35.3553,34.8855, 34.4384,34.0147,33.6155,33.2415,32.8938,32.573,32.28,32.0156, 31.7805,31.5753,31.4006,31.257,31.1448,31.0644,31.0161,31, 31.0161,31.0644,31.1448,31.257,31.4006,31.5753,31.7805,32.0156, 32.28,32.573,32.8938,33.2415,33.6155,34.0147,34.4384,34.8855, 35.3553,35.8469,36.3593,35.2278,35.805,36.4005,37.0135,37.6431, 36.7967,36.7696,37.4833,36.8782,37.0135,37.8021,35.8469,36.7151, 36.3593,35.5106,37.6431,36.8782,36.7967,37.4833,36.7967,36.8782, 37.8021,37.2022,36.6197,36.0555,35.5106,36.7151,36.2353,35.7771, 35.3412,34.9285,34.5398,34.176,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.249,32.1403,32.0624,32.0156,32, 32.0156,32.0624,32.1403,32.249,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.176,34.5398,34.9285,35.3412,35.7771, 36.2353,36.7151,37.2156,36.0555,36.6197,37.2022,37.8021,38.4187, 37.5366,37.4833,38.1838,37.5366,37.6431,38.4187,36.3593,37.21563] }, "32" : { "offsetX" : 0, "offsetY" : 0, "outLineDistance" : [ 34.0147,33.1059,34.1321,33.2866,34.8281,34.0588,35.3553,34.6554, 33.9706,34.8281,34.2053,35.2278,34.6699,34.1321,33.6155,33.121, 32.6497,34.0147,33.6155,33.2415,32.8938,32.573,32.28,32.0156, 31.7805,31.5753,31.4006,31.257,31.1448,31.0644,31.0161,31, 31.0161,31.0644,31.1448,31.257,31.4006,31.5753,31.7805,32.0156, 32.28,32.573,32.8938,33.2415,33.6155,34.0147,34.4384,33.121, 33.6155,34.1321,34.6699,33.6006,34.2053,34.8281,35.4683,34.6554, 35.3553,36.0694,34.8281,35.609,35.2278,36.0555,34.0147,34.9285, 34.4384,33.541,35.805,35,35.4683,34.7131,36.0694,35.3836, 34.7131,35.609,35,34.4093,33.8378,34.9857,34.4819,34, 33.541,34.9285,34.5398,34.176,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.249,32.1403,32.0624,32.0156,32, 32.0156,32.0624,32.1403,32.249,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.176,34.5398,34.9285,35.3412,34, 34.4819,34.9857,35.5106,36.0555,36.6197,35.609,36.2353,35.3836, 36.0694,36.7696,35.4683,36.2353,34.6699,35.5106,34.4384,35.3412, 34.0147,33.1059,35.2278,34.4093,34.8281,34.0588,35.3553,34.6554, 33.9706,34.8281,34.2053,33.6006,33.0151,34.1321,33.6155,33.121, 32.6497,34.0147,33.6155,33.2415,32.8938,32.573,32.28,32.0156, 31.7805,31.5753,31.4006,31.257,31.1448,31.0644,31.0161,31, 31.0161,31.0644,31.1448,31.257,31.4006,31.5753,31.7805,32.0156, 32.28,32.573,32.8938,33.2415,33.6155,34.0147,34.4384,33.121, 33.6155,34.1321,34.6699,35.2278,35.805,34.8281,35.4683,34.6554, 35.3553,36.0694,34.8281,35.609,34.1321,34.9857,34.0147,34.9285, 34.4384,33.541,34.6699,33.8378,35.4683,34.7131,36.0694,35.3836, 34.7131,35.609,35,36.0555,35.5106,34.9857,34.4819,34, 33.541,34.9285,34.5398,34.176,33.8378,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.249,32.1403,32.0624,32.0156,32, 32.0156,32.0624,32.1403,32.249,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,34.176,34.5398,34.9285,35.3412,34, 34.4819,34.9857,35.5106,34.4093,35,35.609,36.2353,35.3836, 36.0694,36.7696,35.4683,36.2353,35.805,36.6197,34.4384,35.3412] }, "48" : { "offsetX" : -0.0104166666666667, "offsetY" : -0.0104166666666667, "outLineDistance" : [ 33.6155,34.4819,33.6155,34.7047,34.8281,34.0588,34.6554,34.6554, 33.9706,34.8281,34.6027,34.4093,33.8378,34.1362,34.4819,34.0000, 33.5410,33.1059,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,32.7763,32.6956,33.1059,33.5410,34.0000, 33.6193,34.1362,33.8378,34.0050,34.6027,34.8281,34.7195,34.2983, 35.0054,34.7212,34.5167,35.3045,34.6800,33.5410,34.4384,33.6820, 33.0677,34.0000,33.1210,34.4189,35.1482,34.3860,34.6630,35.0195, 34.3419,34.8339,35.0000,34.8186,34.2539,34.1321,34.4854,34.0033, 34.4384,34.0147,33.6155,33.2415,33.3658,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,33.7088,33.6155,34.0147,34.4384,34.8855, 34.4854,34.9895,34.6699,34.8186,35.4025,35.6090,35.4742,35.0195, 35.7124,35.3913,35.1482,35.9222,35.2375,34.0000,34.8855,34.0069, 33.6155,34.7338,33.8738,34.7047,34.8281,34.0588,34.6554,34.6554, 33.9706,34.8281,34.6027,34.4093,33.8378,33.2866,34.4819,34.0000, 33.5410,33.1059,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.8938,32.7763,32.6956,33.1059,33.5410,34.0000, 32.7567,33.2866,33.8378,34.0050,34.6027,34.8281,34.7195,34.2983, 35.0054,34.7212,34.5167,35.3045,34.6800,34.0115,34.8969,33.6820, 33.0677,34.4929,33.6266,34.4189,35.1482,34.3860,34.6630,35.0195, 34.3419,34.8339,35.0000,34.8186,34.2539,34.1321,33.6155,33.1210, 34.4384,34.0147,33.6155,33.2415,33.3658,33.5261,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.8378,33.7088,33.6155,34.0147,34.4384,34.8855, 33.6155,34.1321,34.6699,34.8186,35.4025,35.6090,35.4742,35.0195, 35.7124,35.3913,35.1482,35.9222,35.2375,34.4929,35.3662,34.0069] }, "64" : { "offsetX" : -0.0078125, "offsetY" : -0.0078125, "outLineDistance" : [ 32.7334,33.3235,33.8738,34.4189,34.8281,35.0484,35.3553,35.0195, 34.7131,34.8281,34.6027,34.0050,33.8378,33.7094,33.6155,33.1210, 33.0954,33.1059,32.6956,32.3110,32.4235,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.4235,32.3110,32.6956,33.1059,33.0954,33.1210, 33.6155,33.7094,33.8378,34.0050,34.2053,34.8281,35.0907,35.3836, 35.3553,35.3836,35.4683,35.3045,34.4010,34.2410,33.8151,33.3838, 32.7334,33.3235,33.8738,34.1236,35.1482,35.3836,35.3553,35.3836, 35.4683,35.2186,35.0000,34.4093,34.2539,34.1321,34.0487,34.0000, 33.5410,33.5603,33.6155,33.2415,32.8938,33.0496,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.3658,33.2415,33.6155,34.0147,33.9897,34.0000, 34.4819,34.5589,34.6699,34.8186,35.0000,35.6090,35.8518,36.1248, 36.0694,36.0694,36.1248,35.9222,34.9489,34.7338,34.2266,33.6820, 32.7334,33.3235,33.8738,34.4189,34.8281,35.0484,35.3553,35.0195, 34.7131,34.8281,34.6027,34.0050,33.8378,33.7094,33.6155,33.1210, 33.0954,33.1059,32.6956,32.3110,32.4235,32.5730,32.2800,32.0156, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 32.2800,32.5730,32.4235,32.3110,32.6956,33.1059,33.0954,33.1210, 33.6155,33.7094,33.8378,34.0050,34.2053,34.8281,35.0907,35.3836, 35.3553,35.3836,35.4683,35.3045,34.4010,34.2410,33.8151,33.3838, 32.7334,33.3235,33.8738,34.1236,35.1482,35.3836,35.3553,35.3836, 35.4683,35.2186,35.0000,34.4093,34.2539,34.1321,34.0487,34.0000, 33.5410,33.5603,33.6155,33.2415,32.8938,33.0496,33.2415,32.9848, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 33.2415,33.5261,33.3658,33.2415,33.6155,34.0147,33.9897,34.0000, 34.4819,34.5589,34.6699,34.8186,35.0000,35.6090,35.8518,36.1248, 36.0694,36.0694,36.1248,35.9222,34.9489,34.7338,34.2266,33.6820] }, "96": { "offsetX" : 0, "offsetY" : 0, "outLineDistance" : [ 31.7805,32.6956,33.1210,33.2866,33.6006,33.4215,33.9706,33.9411, 33.9706,33.3017,33.4215,33.6006,33.0151,33.2866,32.7567,33.1210, 32.6497,32.2025,32.6956,32.3110,31.9531,31.6228,31.3209,31.0483, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 31.3209,31.6228,31.9531,32.3110,32.6956,33.1059,32.6497,33.1210, 33.6155,33.2866,33.8378,33.6006,34.2053,34.0588,33.9706,34.6554, 34.6554,34.7131,34.2053,34.4093,34.1321,34.0000,33.6155,32.7567, 32.0156,33.1059,33.6155,33.8378,34.2053,34.0588,34.6554,34.6554, 34.7131,34.0588,34.2053,34.4093,33.8378,34.1321,33.6155,34.0000, 33.5410,33.1059,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,33.5410,34.0000, 34.4819,34.1321,34.6699,34.4093,35.0000,34.8281,34.7131,35.3836, 35.3553,35.3836,34.8281,35.0000,34.6699,34.4819,34.0147,32.9848, 31.7805,32.6956,33.1210,33.2866,33.6006,33.4215,33.9706,33.9411, 33.9706,33.3017,33.4215,33.6006,33.0151,33.2866,32.7567,33.1210, 32.6497,32.2025,32.6956,32.3110,31.9531,31.6228,31.3209,31.0483, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,32.0156, 31.3209,31.6228,31.9531,32.3110,32.6956,33.1059,32.6497,33.1210, 33.6155,33.2866,33.8378,33.6006,34.2053,34.0588,33.9706,34.6554, 34.6554,34.7131,34.2053,34.4093,34.1321,34.0000,33.6155,32.7567, 32.0156,33.1059,33.6155,33.8378,34.2053,34.0588,34.6554,34.6554, 34.7131,34.0588,34.2053,34.4093,33.8378,34.1321,33.6155,34.0000, 33.5410,33.1059,33.6155,33.2415,32.8938,32.5730,32.2800,32.0156, 32.7567,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.9848, 32.2800,32.5730,32.8938,33.2415,33.6155,34.0147,33.5410,34.0000, 34.4819,34.1321,34.6699,34.4093,35.0000,34.8281,34.7131,35.3836, 35.3553,35.3836,34.8281,35.0000,34.6699,34.4819,34.0147,32.9848] }, "128" : { "offsetX" : -0.015625, "offsetY" : -0.015625, "outLineDistance" : [ 31.7805,32.6956,33.1210,33.8378,34.2053,34.7131,34.6554,34.6554, 34.7131,34.0588,34.2053,33.6006,33.8378,33.2866,32.7567,33.1210, 32.6497,32.2025,32.6956,32.3110,31.9531,31.6228,31.3209,31.0483, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,31.0483, 31.3209,31.6228,31.9531,32.3110,32.6956,32.2025,32.6497,33.1210, 32.7567,33.2866,33.8378,33.6006,34.2053,34.0588,34.7131,34.6554, 34.6554,34.7131,34.8281,34.4093,34.1321,33.5410,33.2415,32.5576, 31.7805,32.6956,33.1210,33.8378,34.2053,34.7131,34.6554,34.6554, 34.7131,34.8281,34.2053,34.4093,33.8378,34.1321,33.6155,33.1210, 33.5410,33.1059,32.6956,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,33.1059,33.5410,34.0000, 33.6155,34.1321,34.6699,34.4093,35.0000,34.8281,35.4683,35.3836, 35.3553,35.3836,35.4683,35.0000,34.6699,34.0000,33.6155,32.7567, 31.7805,32.6956,33.1210,33.8378,34.2053,34.7131,34.6554,34.6554, 34.7131,34.0588,34.2053,33.6006,33.8378,33.2866,32.7567,33.1210, 32.6497,32.2025,32.6956,32.3110,31.9531,31.6228,31.3209,31.0483, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,31.0483, 31.3209,31.6228,31.9531,32.3110,32.6956,32.2025,32.6497,33.1210, 32.7567,33.2866,33.8378,33.6006,34.2053,34.0588,34.7131,34.6554, 34.6554,34.7131,34.8281,34.4093,34.1321,33.5410,33.2415,32.5576, 31.7805,32.6956,33.1210,33.8378,34.2053,34.7131,34.6554,34.6554, 34.7131,34.8281,34.2053,34.4093,33.8378,34.1321,33.6155,33.1210, 33.5410,33.1059,32.6956,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,33.1059,33.5410,34.0000, 33.6155,34.1321,34.6699,34.4093,35.0000,34.8281,35.4683,35.3836, 35.3553,35.3836,35.4683,35.0000,34.6699,34.0000,33.6155,32.7567] }, "512" : { "offsetX" : -0.015625, "offsetY" : -0.015625, "outLineDistance" : [ 31.7805,32.6956,33.1210,33.8378,34.2053,34.7131,34.6554,34.6554, 34.7131,34.0588,34.2053,33.6006,33.8378,33.2866,32.7567,33.1210, 32.6497,32.2025,32.6956,32.3112810,31.9531,31.6228,31.3209,31.0483, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,31.0483, 31.3209,31.6228,31.9531,32.3110,32.6956,32.2025,32.6497,33.1210, 32.7567,33.2866,33.8378,33.6006,34.2053,34.0588,34.7131,34.6554, 34.6554,34.7131,34.8281,34.4093,34.1321,33.5410,33.2415,32.5576, 31.7805,32.6956,33.1210,33.8378,34.2053,34.7131,34.6554,34.6554, 34.7131,34.8281,34.2053,34.4093,33.8378,34.1321,33.6155,33.1210, 33.5410,33.1059,32.6956,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,33.1059,33.5410,34.0000, 33.6155,34.1321,34.6699,34.4093,35.0000,34.8281,35.4683,35.3836, 35.3553,35.3836,35.4683,35.0000,34.6699,34.0000,33.6155,32.7567, 31.7805,32.6956,33.1210,33.8378,34.2053,34.7131,34.6554,34.6554, 34.7131,34.0588,34.2053,33.6006,33.8378,33.2866,32.7567,33.1210, 32.6497,32.2025,32.6956,32.3110,31.9531,31.6228,31.3209,31.0483, 31.7805,31.5753,31.4006,31.2570,31.1448,31.0644,31.0161,31.0000, 31.0161,31.0644,31.1448,31.2570,31.4006,31.5753,31.7805,31.0483, 31.3209,31.6228,31.9531,32.3110,32.6956,32.2025,32.6497,33.1210, 32.7567,33.2866,33.8378,33.6006,34.2053,34.0588,34.7131,34.6554, 34.6554,34.7131,34.8281,34.4093,34.1321,33.5410,33.2415,32.5576, 31.7805,32.6956,33.1210,33.8378,34.2053,34.7131,34.6554,34.6554, 34.7131,34.8281,34.2053,34.4093,33.8378,34.1321,33.6155,33.1210, 33.5410,33.1059,32.6956,33.2415,32.8938,32.5730,32.2800,32.0156, 31.7805,32.5576,32.3883,32.2490,32.1403,32.0624,32.0156,32.0000, 32.0156,32.0624,32.1403,32.2490,32.3883,32.5576,32.7567,32.0156, 32.2800,32.5730,32.8938,33.2415,33.6155,33.1059,33.5410,34.0000, 33.6155,34.1321,34.6699,34.4093,35.0000,34.8281,35.4683,35.3836, 35.3553,35.3836,35.4683,35.0000,34.6699,34.0000,33.6155,32.7567] } } } ukui-quick/items/action-extension.h0000664000175000017500000000570015153755732016367 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_ACTION_EXTENSION_H #define UKUI_QUICK_ACTION_EXTENSION_H #include #include #include namespace UkuiQuick { class ActionExtensionPrivate; /** * @class ActionExtension * QAction只能通过指定QICon设置图标 * 扩展指定图标名称设置图标功能,增加指定为分割线功能,增加设置子菜单功能 * @see https://doc.qt.io/qt-5/qtqml-referenceexamples-extended-example.html * Usage: Action { iconName: "file-manager" text: "菜单" subActions: [ Action { iconName: "file-manager" text: "子菜单 1" onTriggered: function (checked) { console.log("onTriggered 子菜单 1", checked); } }, Action { // 分割线 isSeparator: true }, Action { iconName: "file-manager" text: "子菜单 2" onTriggered: function (checked) { console.log("onTriggered 子菜单 2"); } } ] } * * */ class ActionExtension : public QObject { Q_OBJECT Q_PROPERTY(QString iconName READ iconName WRITE setIconName NOTIFY iconNameChanged) Q_PROPERTY(bool isSeparator READ isSeparator WRITE setSeparator NOTIFY isSeparatorChanged) Q_PROPERTY(QMenu *menu READ menu WRITE setMenu NOTIFY menuChanged) Q_PROPERTY(QQmlListProperty subActions READ subActions) Q_CLASSINFO("DefaultProperty", "subActions") public: explicit ActionExtension(QObject *parent = nullptr); ~ActionExtension() override; QString iconName() const; void setIconName(const QString &name); bool isSeparator() const; void setSeparator(bool isSeparator); // actions QQmlListProperty subActions(); QMenu *menu() const; void setMenu(QMenu *menu); Q_SIGNALS: void iconNameChanged(); void isSeparatorChanged(); void menuChanged(); private: ActionExtensionPrivate *d {nullptr}; }; } // UkuiQuick #endif //UKUI_QUICK_ACTION_EXTENSION_H ukui-quick/items/gradient-border-item.h0000664000175000017500000000550215153755732017104 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: amingamingaming * */ #ifndef GRADIENTBORDERITEM_H #define GRADIENTBORDERITEM_H #include #include #include #include "./ukui/dt-theme.h" namespace UkuiQuick { class GradientBorderItem : public QQuickPaintedItem { Q_OBJECT Q_PROPERTY(qreal borderWidth READ borderWidth WRITE setBorderWidth NOTIFY borderWidthChanged) Q_PROPERTY(qreal borderRadius READ borderRadius WRITE setBorderRadius NOTIFY borderRadiusChanged) Q_PROPERTY(qreal deg READ deg WRITE setDeg NOTIFY degChanged) Q_PROPERTY(bool enableSpecificBorder READ enableSpecificBorder WRITE setEnableSpecificBorder NOTIFY enableSpecificBorderChanged) Q_PROPERTY(GradientColor* borderColor READ borderColor WRITE setBorderColor NOTIFY borderColorChanged) public: explicit GradientBorderItem(QQuickItem *parent = nullptr); qreal borderWidth() const {return m_borderWidth;} qreal borderRadius() const {return m_borderRadius;} qreal deg() const {return m_deg;} bool enableSpecificBorder() const {return m_enableSpecificBorder;} GradientColor* borderColor() const {return m_borderColor;} public Q_SLOTS: void setBorderWidth(qreal width); void setBorderRadius(qreal radius); void setDeg(qreal deg); void setEnableSpecificBorder(bool enable); void setBorderColor(GradientColor* color); private: void startCurveAnimation(bool start); Q_SIGNALS: void borderWidthChanged(); void borderRadiusChanged(); void degChanged(); void enableSpecificBorderChanged(); void borderColorChanged(); protected: void paint(QPainter *painter) override; void componentComplete() override; private: qreal m_borderWidth = 2.0; qreal m_borderRadius = 6.0; //开启贝塞尔曲线动画后描边的渐变色角度 qreal m_deg = 0; //是否开启特殊的(非DT色)渐变色描边 bool m_enableSpecificBorder = false; //未开启贝塞尔曲线动画时的描边颜色 GradientColor* m_borderColor = nullptr; //控制描边渐变色角度的动画 QPropertyAnimation *m_animation = nullptr; }; } #endif // GRADIENTBORDERITEM_H ukui-quick/items/CMakeLists.txt0000664000175000017500000001204615153756415015467 0ustar fengfengcmake_minimum_required(VERSION 3.14) project(ukui-quick-items-plugin) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) #find QT modules find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Qml Quick Widgets Gui LinguistTools REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Qml Quick Widgets Gui LinguistTools REQUIRED) if (QT_VERSION_MAJOR EQUAL "5") set(KF_VERSION_MAJOR "5") find_package(KF5WindowSystem REQUIRED) elseif (QT_VERSION_MAJOR EQUAL "6") set(KF_VERSION_MAJOR "6") find_package(Qt6 REQUIRED NO_MODULE COMPONENTS ShaderTools) find_package(Qt6GuiPrivate REQUIRED) find_package(KF6WindowSystem REQUIRED) endif() find_package(PkgConfig REQUIRED) set(EXTERNAL_LIBS "") if (QT_VERSION_MAJOR EQUAL 5) set(PC_PKGS Qt5Xdg) elseif (QT_VERSION_MAJOR EQUAL 6) set(PC_PKGS Qt6Xdg) endif () foreach(PC_LIB IN ITEMS ${PC_PKGS}) pkg_check_modules(${PC_LIB} REQUIRED IMPORTED_TARGET ${PC_LIB}) if(${${PC_LIB}_FOUND}) include_directories(${${PC_LIB}_INCLUDE_DIRS}) link_directories(${${PC_LIB}_LIBRARY_DIRS}) list(APPEND EXTERNAL_LIBS PkgConfig::${PC_LIB}) endif() endforeach() set(VERSION_MAJOR 1) set(VERSION_MINOR 0) set(VERSION_MICRO 0) set(UKUI_QUICK_CORE_ITEMS_VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_MICRO}) set(PLUGIN_INSTALL_ROOT_PATH "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/qt${QT_VERSION_MAJOR}/qml/org/ukui/quick/items") set(PLUGIN_INSTALL_TRANSLATIONS_PATH "${PLUGIN_INSTALL_ROOT_PATH}/translations/") add_compile_definitions(PLUGIN_INSTALL_TRANSLATIONS_PATH="${PLUGIN_INSTALL_TRANSLATIONS_PATH}") include_directories(views) # items模块的功能不需要导出到include目录,仅使用qml的import语句进行导入使用 set (ITEMS_PLUGIN_SRCS ukui-quick-items-plugin.cpp ukui-quick-items-plugin.h menu.cpp menu.h menu-item.cpp menu-item.h theme-icon.cpp theme-icon.h tooltip-proxy.cpp tooltip-proxy.h icon-provider.cpp icon-provider.h icon.cpp icon.h icon-helper.cpp icon-helper.h color-mixer.cpp color-mixer.h action-extension.cpp action-extension.h gradient-border-item.cpp gradient-border-item.h line-edit-context-menu.cpp line-edit-context-menu.h views/content-window.cpp views/content-window.h views/tooltip.cpp views/tooltip.h views/ukui-style-window.cpp views/ukui-style-window.h views/tooltip-dialog.cpp views/tooltip-dialog.h window-blur-behind.cpp window-blur-behind.h shadowed-rectangle.cpp shadowed-rectangle.h shadowed-texture.cpp shadowed-texture.h scenegraph/painted-rectangle-item.cpp scenegraph/painted-rectangle-item.h scenegraph/shadowed-rectangle-node.cpp scenegraph/shadowed-rectangle-node.h scenegraph/shadowed-border-rectangle-material.cpp scenegraph/shadowed-border-rectangle-material.h scenegraph/shadowed-rectangle-material.cpp scenegraph/shadowed-rectangle-material.h scenegraph/shadowed-texture-node.cpp scenegraph/shadowed-texture-node.h scenegraph/shadowed-border-texture-material.cpp scenegraph/shadowed-border-texture-material.h scenegraph/shadowed-texture-material.cpp scenegraph/shadowed-texture-material.h ) set(QRC_FILES qml/qml.qrc) if (QT_VERSION_MAJOR EQUAL "5") list(APPEND QRC_FILES shaders/shaders.qrc) endif() file(GLOB TS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/translations/*.ts) if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) qt_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR}/qml ${TS_FILES} OPTIONS -no-ui-lines) else() qt5_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR}/qml ${TS_FILES} OPTIONS -no-ui-lines) endif() add_library(${PROJECT_NAME} SHARED ${ITEMS_PLUGIN_SRCS} ${QRC_FILES} ${QM_FILES}) #for qt6 add_subdirectory(shaders6) target_include_directories(${PROJECT_NAME} PRIVATE ../core) target_include_directories(${PROJECT_NAME} PRIVATE ../platform) target_include_directories(${PROJECT_NAME} PRIVATE ../framework) target_include_directories(${PROJECT_NAME} PRIVATE scenegraph) target_compile_definitions(${PROJECT_NAME} PRIVATE PLUGIN_IMPORT_URI="org.ukui.quick.items" PLUGIN_VERSION_MAJOR=${VERSION_MAJOR} PLUGIN_VERSION_MINOR=${VERSION_MINOR} ) target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Qml Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Gui Qt::GuiPrivate KF${KF_VERSION_MAJOR}::WindowSystem ukui-quick::core ukui-quick::platform ukui-quick::framework ${EXTERNAL_LIBS} ) install(DIRECTORY "qml" DESTINATION "${PLUGIN_INSTALL_ROOT_PATH}") install(FILES qmldir DESTINATION "${PLUGIN_INSTALL_ROOT_PATH}") install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION "${PLUGIN_INSTALL_ROOT_PATH}") install(FILES ${QM_FILES} DESTINATION "${PLUGIN_INSTALL_TRANSLATIONS_PATH}") install(FILES iconThemeBackgroundConfig.json DESTINATION "/usr/share/ukui/ukui-quick-items") ukui-quick/items/icon-provider.h0000664000175000017500000000237015153755732015660 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef UKUI_QUICK_ICON_PROVIDER_H #define UKUI_QUICK_ICON_PROVIDER_H #include #include #include class QSize; namespace UkuiQuick { /** * 为Image提供图标 * 注册的schema为:theme * see: https://doc.qt.io/archives/qt-5.12/qquickimageprovider.html#details */ class IconProvider : public QQuickImageProvider { friend class Icon; public: IconProvider(); QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override; }; } // UkuiQuick #endif // UKUI_QUICK_ICON_PROVIDER_H ukui-quick/items/theme-icon.cpp0000664000175000017500000002152015153755732015461 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "theme-icon.h" #include "icon-helper.h" #include #include #include #include #include #include #include #include #include #include #define COLOR_DIFFERENCE 10 namespace UkuiQuick { QColor ThemeIcon::symbolicColor = QColor(26, 26, 26); ThemeIcon::ThemeIcon(QQuickItem *parent) : QQuickPaintedItem(parent), m_mode(Icon::Normal) { m_keepAspectRatio = true; } void ThemeIcon::paint(QPainter *painter) { //默认居中绘制 QRectF rect({0, 0}, size().toSize()); QPixmap target; QRectF sourceRect; QRectF targetRect; if (m_keepAspectRatio) { if (!m_sourceSize.isEmpty()) { target = m_rawIcon.pixmap(m_sourceSize); target.setDevicePixelRatio(qApp->devicePixelRatio()); qreal widthHeightRatio = rect.width()/rect.height(); qreal sourceWidthHeightRatio = m_sourceSize.width() * 1.0/m_sourceSize.height(); if (sourceWidthHeightRatio > widthHeightRatio) { targetRect.setWidth(rect.width()); targetRect.setHeight(rect.width()/sourceWidthHeightRatio); targetRect.moveCenter(rect.center()); } else { targetRect.setHeight(rect.height()); targetRect.setWidth(rect.height()*sourceWidthHeightRatio); targetRect.moveCenter(rect.center()); } } else { int extent = qMin(rect.width(), rect.height()); target = m_rawIcon.pixmap(QSize(extent, extent)); target.setDevicePixelRatio(qApp->devicePixelRatio()); targetRect = target.rect(); targetRect.moveCenter(rect.center()); QPointF center = targetRect.center(); targetRect.setSize(targetRect.size()/qApp->devicePixelRatio()); targetRect.moveCenter(center); } } else { target = m_rawIcon.pixmap(size().toSize()); target.setDevicePixelRatio(qApp->devicePixelRatio()); targetRect = rect; QPointF center = targetRect.center(); targetRect.setSize(targetRect.size()/qApp->devicePixelRatio()); targetRect.moveCenter(center); } sourceRect = target.rect(); painter->save(); //抗锯齿,平滑过渡 painter->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); if (m_mode & Icon::Disabled) { QPainter p(&target); p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); p.setCompositionMode(QPainter::CompositionMode_SourceIn); p.fillRect(target.rect(), Theme::instance()->color(Theme::ButtonText, Theme::Disabled)); } else if (m_mode & Icon::Highlight) { bool isPureColor = true; if(!m_mode.testFlag(Icon::ForceHighlight)) { isPureColor = ThemeIcon::isPixmapPureColor(target); } if (isPureColor) { QPainter p(&target); p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); p.setCompositionMode(QPainter::CompositionMode_SourceIn); p.fillRect(target.rect(), Theme::instance()->color(Theme::HighlightedText)); } } if (m_radius > 0) { auto radius = qMin(m_radius * 1.0, qMin((rect.height() / 2), (rect.width() / 2))); QPainterPath path; path.addRoundedRect(rect, radius, radius); painter->setClipPath(path); } painter->drawPixmap(targetRect, target, sourceRect); painter->restore(); } QVariant ThemeIcon::getSource() { return m_source; } void ThemeIcon::setSource(const QVariant &source) { if (m_source == source) { return; } m_source = source; updateRawIcon(); } QString ThemeIcon::getFallBack() { return m_fallback; } void ThemeIcon::setFallBack(const QString &fallback) { if (fallback.isEmpty()) { qWarning() << "ThemeIcon: fallback is empty!"; return; } if (m_rawIcon.isNull()) { setSource(fallback); } } Icon::Mode ThemeIcon::mode() const { return m_mode; } void ThemeIcon::setMode(Icon::Mode mode) { if (m_mode == mode) { return; } m_mode = mode; if (m_mode & Icon::AutoHighlight) { updateMode(); connect(Theme::instance(), &Theme::themeNameChanged, this, &ThemeIcon::updateMode); } else { disconnect(Theme::instance(), nullptr, this, nullptr); update(); } } void ThemeIcon::updateMode() { if (Theme::instance()->isDarkTheme()) { m_mode |= Icon::Highlight; } else { m_mode &= ~Icon::Highlight; } update(); } //copy from ukui-platform-theme bool ThemeIcon::isPixmapPureColor(const QPixmap &pixmap) { if (pixmap.isNull()) { qWarning("pixmap is null!"); return false; } QImage image = pixmap.toImage(); QVector vector; int total_red = 0; int total_green = 0; int total_blue = 0; bool pure = true; for (int y = 0; y < image.height(); ++y) { for (int x = 0; x < image.width(); ++x) { if (image.pixelColor(x, y).alphaF() > 0.3) { QColor color = image.pixelColor(x, y); vector << color; total_red += color.red(); total_green += color.green(); total_blue += color.blue(); int dr = qAbs(color.red() - symbolicColor.red()); int dg = qAbs(color.green() - symbolicColor.green()); int db = qAbs(color.blue() - symbolicColor.blue()); if (dr > COLOR_DIFFERENCE || dg > COLOR_DIFFERENCE || db > COLOR_DIFFERENCE) pure = false; } } } if (pure) return true; qreal squareRoot_red = 0; qreal squareRoot_green = 0; qreal squareRoot_blue = 0; qreal average_red = total_red / vector.count(); qreal average_green = total_green / vector.count(); qreal average_blue = total_blue / vector.count(); for (QColor color : vector) { squareRoot_red += (color.red() - average_red) * (color.red() - average_red); squareRoot_green += (color.green() - average_green) * (color.green() - average_green); squareRoot_blue += (color.blue() - average_blue) * (color.blue() - average_blue); } qreal arithmeticSquareRoot_red = qSqrt(squareRoot_red / vector.count()); qreal arithmeticSquareRoot_green = qSqrt(squareRoot_green / vector.count()); qreal arithmeticSquareRoot_blue = qSqrt(squareRoot_blue / vector.count()); return arithmeticSquareRoot_red < 2.0 && arithmeticSquareRoot_green < 2.0 && arithmeticSquareRoot_blue < 2.0; } QColor ThemeIcon::getSymbolicColor() { return symbolicColor; } void ThemeIcon::updateRawIcon() { switch (m_source.userType()) { case QMetaType::QPixmap: m_rawIcon = QIcon(m_source.value()); m_sourceSize = m_source.value().size(); break; case QMetaType::QImage: m_rawIcon = QIcon(QPixmap::fromImage(m_source.value())); m_sourceSize = m_source.value().size(); break; case QMetaType::QBitmap: m_rawIcon = QIcon(QPixmap::fromImage(m_source.value().toImage())); m_sourceSize = m_source.value().size(); break; case QMetaType::QIcon: m_rawIcon = m_source.value(); m_sourceSize = QSize(); break; case QMetaType::QString: m_rawIcon = IconHelper::loadIcon(m_source.toString()); m_sourceSize = QSize(); break; default: m_rawIcon = IconHelper::getDefaultIcon(); m_sourceSize = QSize(); break; } if (m_rawIcon.isNull()) { QImage image = QImage(QSize(width(), height()), QImage::Format_Alpha8); image.fill(Qt::transparent); m_rawIcon = QIcon(QPixmap::fromImage(image)); } update(); } bool ThemeIcon::getKeepAspectRatio() const { return m_keepAspectRatio; } void ThemeIcon::setKeepAspectRatio(bool keepAspectRatio) { m_keepAspectRatio = keepAspectRatio; } int ThemeIcon::radius() { return m_radius; } void ThemeIcon::setRadius(int radius) { m_radius = radius < 0 ? 0 : radius; } } ukui-quick/items/icon.h0000664000175000017500000001432515153755732014033 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_ITEMS_ICON_H #define UKUI_QUICK_ITEMS_ICON_H #include #include #include #include #include "types.h" #include "ukui/ukui-theme-proxy.h" #include "ukui/dt-theme.h" #include "ukui/application-icon-proxy.h" class QSGTexture; class QSGImageNode; namespace UkuiQuick { class IconPrivate; /** * @class Icon "icon/Icon" * * Usage: * * Icon { * mode: Icon.Highlight * source: "xxx" * } * */ class Icon : public QQuickItem { Q_OBJECT Q_PROPERTY(UkuiQuick::Icon::Mode mode READ mode WRITE setMode NOTIFY modeChanged) Q_PROPERTY(QVariant source READ source WRITE setSource NOTIFY sourceChanged) Q_PROPERTY(QVariant fallbackSource READ fallbackSource WRITE setFallbackSource) Q_PROPERTY(int radius READ radius WRITE setRadius NOTIFY radiusChanged) Q_PROPERTY(QString pointText READ pointText WRITE setPointText NOTIFY pointTextChanged) Q_PROPERTY(bool forceRound READ forceRound WRITE setForceRound NOTIFY forceRoundChanged) Q_PROPERTY(UkuiQuick::Types::Pos pointPos READ pointPos WRITE setPointPos NOTIFY pointPosChanged) Q_PROPERTY(UkuiQuick::Icon::PointType pointType READ pointType WRITE setPointType NOTIFY pointTypeChanged) Q_PROPERTY(UkuiQuick::Theme::ColorRole pointColor READ pointColor WRITE setPointColor NOTIFY pointColorChanged) Q_PROPERTY(UkuiQuick::Theme::ColorRole highlightColor READ highlightColor WRITE setHighlightColor NOTIFY highlightColorChanged) Q_PROPERTY(GradientColor* dtThemeHighlightColor READ dtThemeHighlightColor WRITE setDtThemeHighlightColor NOTIFY dtThemeHighlightColorChanged) Q_PROPERTY(UkuiQuick::ApplicationIconProxy* proxy READ proxy CONSTANT FINAL) Q_PROPERTY(bool addUnifiedIconBox READ addUnifiedIconBox WRITE setAddUnifiedIconBox NOTIFY addUnifiedIconBoxChanged) public: enum Mod { Normal = 0x01, Highlight = 0x02, AutoHighlight = 0x04, /* 高亮 */ ForceHighlight = Highlight | 0x08, /* 高亮 */ Disabled = 0x80 /* 灰色 */ }; Q_DECLARE_FLAGS(Mode, Mod) Q_FLAG(Mode) enum PointType { Null, Point, Text }; Q_ENUM(PointType) enum ImageType { FirstParty, /* 第一方图标 */ RectangleHorizontal, /* 水平方向长方形图标 */ RectangleVertical, /* 垂直方向长方形图标 */ RectangleWithBack, /* 有底正方形图标 */ Other, /* 其他类型图标 */ Uninitialized /* 未初始化 */ }; explicit Icon(QQuickItem *parent = nullptr); ~Icon() override; Icon::Mode mode() const; void setMode(const Icon::Mode &mode); Icon::PointType pointType() const; void setPointType(Icon::PointType type); Types::Pos pointPos() const; void setPointPos(Types::Pos pos); Theme::ColorRole pointColor() const; void setPointColor(const Theme::ColorRole &color); QString pointText() const; void setPointText(const QString &text); Theme::ColorRole highlightColor() const; void setHighlightColor(const Theme::ColorRole &color); GradientColor* dtThemeHighlightColor() const; void setDtThemeHighlightColor(GradientColor *color); int radius() const; void setRadius(int radius); QVariant source() const; void setSource(const QVariant &source); QVariant fallbackSource() const; void setFallbackSource(const QVariant &fallback); bool forceRound() const; void setForceRound(bool forceRound); bool addUnifiedIconBox() const; void setAddUnifiedIconBox(bool addUnifiedIconBox); // 在该函数中修改图像的细节 virtual void imageCorrection(QImage &image); virtual QSGTexture *createTexture(); virtual void drawPoint(QImage &image); QPoint posPoint(const QSize &imageSize, const QSize &pointSize) const; inline void markTextureChanged(); ApplicationIconProxy *proxy(); Q_SIGNALS: void modeChanged(); void sourceChanged(); void radiusChanged(); void highlightColorChanged(); void pointTypeChanged(); void pointTextChanged(); void pointPosChanged(); void pointColorChanged(); void forceRoundChanged(); void dtThemeHighlightColorChanged(); void addUnifiedIconBoxChanged(); private Q_SLOTS: void onSizeChanged(); void updateMode(); void updateSourceFromProxy(); void onIconThemeNameChanged(); protected: QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *data) override; void componentComplete() override; void itemChange(ItemChange change, const ItemChangeData &value) override; private: bool applyVariantToIcon(const QVariant &source, QIcon &icon); QImage loadFallbackIcon(); IconPrivate *d {nullptr}; //第三方图标添加统一底板 QImage addIconBox(QImage &image); //图标裁剪圆角 QImage clipBackground(QImage &image, QImage targetImage, QRectF targetRect); //判断图标是否为透明背景 bool isTransparencyBackground(QImage image); //判断是否为第一方图标 bool isFirstPartyIcon(const QImage &effectArea, int size); //获取图标类型 void getImageType(const QImage &effectArea, const QImage &image, int imageSize, int standardEffectAreaSize); QImage getEffectiveArea(const QImage &image); //判断图标是否为纯色 bool isImagePureColor(QImage &image); QHash, int> getTransparentPoint(const QImage &image); }; } // UkuiQuick Q_DECLARE_METATYPE(UkuiQuick::Icon::PointType) Q_DECLARE_OPERATORS_FOR_FLAGS(UkuiQuick::Icon::Mode) #endif //UKUI_QUICK_ITEMS_ICON_H ukui-quick/items/shadowed-texture.h0000664000175000017500000000325315153755732016375 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #pragma once #include "shadowed-rectangle.h" /** * A rectangle with a shadow, using a QQuickItem as texture. * * This item will render a source item, with a shadow below it. The rendering is done * using distance fields, which provide greatly improved performance. The shadow is * rendered outside of the item's bounds, so the item's width and height are the * rectangle's width and height. * */ /* Add shadow for a round button: Button { id: roundButton width: 100 height: 100 background.radius: 50 visible: false } ShadowedTexture { source: roundButton anchors.fill: parent radius: 50 shadow { size: 20 color: Qt.rgba(0, 0, 0, 1) yOffset: 2 } border { width: 1 color: "blue" } } */ class ShadowedTexture : public ShadowedRectangle { Q_OBJECT /** * This property holds the source item that will get rendered with the * shadow. */ Q_PROPERTY(QQuickItem *source READ source WRITE setSource NOTIFY sourceChanged) public: ShadowedTexture(QQuickItem *parent = nullptr); ~ShadowedTexture() override; QQuickItem *source() const; void setSource(QQuickItem *newSource); Q_SIGNAL void sourceChanged(); protected: QSGNode *updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) override; private: QQuickItem *m_source = nullptr; bool m_sourceChanged = false; }; ukui-quick/items/menu.h0000664000175000017500000000426115153755732014045 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: qiqi49 * */ #ifndef UKUI_QUICK_ITEMS_MENU_H #define UKUI_QUICK_ITEMS_MENU_H #include "menu-item.h" #include #include #include #include namespace UkuiQuick { /** * Example : * import org.ukui.quick.items 1.0 * MouseArea { Menu { id: menu transientParent: parent content: [ MenuItem { text: "Click!!!!" onClicked: console.log("menu:Clicked!!") } ] } onClicked: { menu.open(); } } */ class Menu : public QObject { Q_OBJECT // 错误 // Q_PROPERTY(QQmlListProperty content READ content) // 正确 Q_PROPERTY(QQmlListProperty content READ content) Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged) Q_PROPERTY(QQuickItem* transientParent MEMBER m_transientParent WRITE setTransientParent NOTIFY transientParentChanged) public: explicit Menu(QObject *prent = nullptr); ~Menu(); QQmlListProperty content(); bool visible(); Q_INVOKABLE void open(int x = -1, int y = -1); Q_INVOKABLE void close(); void setTransientParent(QQuickItem* item); private: QList m_items; QMenu *m_menu = nullptr; QQuickItem *m_transientParent = nullptr; Q_SIGNALS: void visibleChanged(); void transientParentChanged(); }; } #endif //UKUI_QUICK_ITEMS_MENU_H ukui-quick/items/theme-icon.h0000664000175000017500000000425415153755732015133 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_ITEMS_THEME_ICON_H #define UKUI_QUICK_ITEMS_THEME_ICON_H #include #include #include "icon.h" namespace UkuiQuick { class ThemeIcon : public QQuickPaintedItem { Q_OBJECT Q_PROPERTY(QVariant source READ getSource WRITE setSource) Q_PROPERTY(QString fallback READ getFallBack WRITE setFallBack) Q_PROPERTY(int radius READ radius WRITE setRadius) Q_PROPERTY(UkuiQuick::Icon::Mode mode READ mode WRITE setMode) Q_PROPERTY(bool keepAspectRatio READ getKeepAspectRatio WRITE setKeepAspectRatio) public: explicit ThemeIcon(QQuickItem *parent = nullptr); static bool isPixmapPureColor(const QPixmap &pixmap); static QColor getSymbolicColor(); void paint(QPainter *painter) override; QVariant getSource(); void setSource(const QVariant& source); QString getFallBack(); void setFallBack(const QString &fallback); Icon::Mode mode() const; void setMode(Icon::Mode mode); int radius(); void setRadius(int radius); bool getKeepAspectRatio() const; void setKeepAspectRatio(bool keepAspectRatio); private Q_SLOTS: void updateMode(); private: void updateRawIcon(); private: int m_radius = 0; Icon::Mode m_mode; QIcon m_rawIcon; QVariant m_source; QString m_fallback; bool m_keepAspectRatio; QSize m_sourceSize; static QColor symbolicColor; }; } #endif //UKUI_QUICK_ITEMS_THEME_ICON_H ukui-quick/items/qml/0000775000175000017500000000000015153756415013515 5ustar fengfengukui-quick/items/qml/DtThemeButton.qml0000664000175000017500000000266715153756415016771 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: amingamingaming * */ import QtQuick 2.12 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 MouseArea { readonly property alias icon: iconBase readonly property alias background: backgroundBase hoverEnabled: true activeFocusOnTab: true DtThemeBackground { id: backgroundBase anchors.fill: parent border.width: parent.activeFocus ? 2 : 0 backgroundColor: GlobalTheme.buttonActive borderColor: GlobalTheme.highlightActive useStyleTransparency: false alpha: 1 Icon { id: iconBase anchors.centerIn: parent width: parent.width / 2 height: parent.height / 2 } } } ukui-quick/items/qml/TouchSlider.qml0000664000175000017500000001156515153756415016465 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: amingamingaming * */ import QtQuick 2.0 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.12 import QtQuick.Templates 2.12 as T import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 T.Slider { id: control property bool transparentMode: false property var backgroundColor : getBackgroundColor() property var borderColor : enabled ? GlobalTheme.kLineComponentNormal : GlobalTheme.kLineComponentDisable wheelEnabled: true Keys.onPressed: { if (event.key === Qt.Key_Left || event.key === Qt.Key_Down) { pressed = true if(mirrored) { increase() } else { decrease() } event.accepted = true; } else if (event.key === Qt.Key_Right || event.key === Qt.Key_Up) { pressed = true if(mirrored) { decrease() } else { increase() } event.accepted = true; } } Keys.onReleased: { pressed = false event.accepted = false; } MouseArea { anchors.fill: parent acceptedButtons: Qt.NoButton onWheel: { if (wheel.angleDelta.x == 0) { wheel.accepted = false; return; } if (wheel.angleDelta.x > 0) { decrease(); } else { increase(); } } } function getBackgroundColor() { if(transparentMode) { return enabled ? GlobalTheme.kComponentAlphaNormal : GlobalTheme.kComponentAlphaDisable } else { return enabled ? GlobalTheme.kComponentNormal : GlobalTheme.kComponentDisable } } //进度条 background: DtThemeBackground { id: sliderBackground x: control.leftPadding + (control.horizontal ? 0 : (control.availableWidth - width) / 2) y: control.topPadding + (control.horizontal ? (control.availableHeight - height) / 2 : 0) width: control.horizontal ? control.availableWidth : control.width height: control.horizontal ? control.height : control.availableHeight implicitWidth: control.width implicitHeight: control.height radius: control.horizontal ? height / 2 : width / 2 useStyleTransparency: false backgroundColor: control.backgroundColor borderColor: control.borderColor border.width: 1 scale: control.horizontal && control.mirrored ? -1 : 1 DtThemeBackground { id: top x: control.horizontal ? 0 : (parent.width - width) / 2 y: control.horizontal ? (parent.height - height) / 2 : control.visualPosition * (control.availableHeight - radius * 2) width: control.horizontal ? control.position * (control.availableWidth - radius * 2) + radius * 2 : control.width height: control.horizontal ? control.height : control.position * (control.availableHeight - radius * 2) + radius * 2 radius: sliderBackground.radius useStyleTransparency: false backgroundColor: enabled ? GlobalTheme.kBrandNormal : GlobalTheme.kBrandDisable borderColor: enabled ? GlobalTheme.kLineBrandNormal : GlobalTheme.kLineBrandDisable } } //滑块 handle: Item { id: sliderHandel x: control.horizontal ? control.visualPosition * (control.availableWidth - width) : control.availableWidth / 2 - width / 2 y: control.horizontal ? control.availableHeight / 2 - height / 2 : control.visualPosition * (control.availableHeight - height) implicitWidth: control.horizontal ? control.height : control.width implicitHeight: control.horizontal ? control.height : control.width DtThemeBackground { id: whiteSpot width: control.horizontal ? (control.height / 16) * 4 : (control.width / 16) * 6 height: control.horizontal ? (control.height / 16) * 6 : (control.width / 16) * 4 radius: control.horizontal ? width / 2 : height / 2 useStyleTransparency: false backgroundColor: GlobalTheme.kFontWhite anchors.centerIn: parent } } } ukui-quick/items/qml/StyleBackground.qml0000664000175000017500000000422415153756415017332 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ import QtQuick 2.12 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 Rectangle { //是否跟随系统主题颜色的透明度 property bool useStyleTransparency: true; //背景颜色枚举值,从调色板中取得 property int paletteRole: Theme.Window; property int paletteGroup: Theme.Active; property real alpha: 1.0; property real borderAlpha: 1.0; property int borderColor: Theme.Base; border.width: 0; clip: true; function updateColor() { if (useStyleTransparency) { color = Theme.colorWithThemeTransparency(paletteRole, paletteGroup); } else { color = Theme.colorWithCustomTransparency(paletteRole, paletteGroup, alpha); } } function updateBorderColor() { border.color = Theme.colorWithCustomTransparency(borderColor, Theme.Active, borderAlpha); } //监听系统主题变化 Theme.onPaletteChanged: { updateColor(); updateBorderColor(); } Theme.onThemeTransparencyChanged: updateColor(); onPaletteGroupChanged: { updateColor(); } onPaletteRoleChanged: { updateColor(); } onAlphaChanged: { updateColor(); } onBorderAlphaChanged: { updateBorderColor(); } onBorderColorChanged: { updateBorderColor(); } Component.onCompleted: { updateColor(); updateBorderColor(); } } ukui-quick/items/qml/Button.qml0000664000175000017500000000271415153756415015507 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ import QtQuick 2.12 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 MouseArea { readonly property alias icon: iconBase readonly property alias background: backgroundBase hoverEnabled: true activeFocusOnTab: true StyleBackground { id: backgroundBase anchors.fill: parent border.width: parent.activeFocus ? 2 : 0 paletteRole: Theme.Button borderColor: Theme.Highlight useStyleTransparency: false alpha: parent.containsPress ? 0.30 : parent.containsMouse ? 0.15 : 0.0 ThemeIcon { id: iconBase anchors.centerIn: parent width: parent.width / 2 height: parent.height / 2 } } } ukui-quick/items/qml/SearchLineEdit.qml0000664000175000017500000003155215153756415017061 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: amingamingaming * */ import QtQuick 2.15 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 GradientBorderItem { id: root property alias text: textInput.text property alias textInput: textInput //是否显示AI按钮与分隔线 property bool showAIButton: false //控件是否使用透明模式 property bool enableTransparentMode: true //默认的占位文本 property string placeholderText: qsTr("search") property bool enableContextMenu :false borderWidth: enableSpecificBorder ? 2 : textInput.activeFocus ? 2 : 1 borderRadius: GlobalTheme.kRadiusNormal borderColor: getBorderColor() function getBorderColor() { return textInput.activeFocus ? GlobalTheme.kBrandNormal : enabled ? GlobalTheme.kLineComponentNormal : GlobalTheme.kLineComponentDisable } function getBackgroundColor() { if(enableTransparentMode) { return textInput.activeFocus ? GlobalTheme.kComponentInputAlpha : mouseArea.containsPress ? GlobalTheme.kComponentAlphaClick : mouseArea.containsMouse ? GlobalTheme.kComponentAlphaHover : enabled ? GlobalTheme.kComponentAlphaNormal : GlobalTheme.kComponentAlphaDisable } else { return textInput.activeFocus ? GlobalTheme.kComponentInput : mouseArea.containsPress ? GlobalTheme.kComponentClick : mouseArea.containsMouse ? GlobalTheme.kComponentHover : enabled ? GlobalTheme.kComponentNormal : GlobalTheme.kComponentDisable } } DtThemeBackground { id: back clip: false anchors.centerIn: parent width: root.width - 2 * root.borderWidth height: root.height - 2 * root.borderWidth radius: root.borderRadius * (1 - 1 / 3 * root.borderWidth / root.borderRadius) useStyleTransparency: false backgroundColor: getBackgroundColor() border.width: 0 Item { id: defaultSearch width: searchIcon.width + defaultText.width height: parent.height anchors.verticalCenter: parent.verticalCenter x: getDefaultSearchPosition() Item { id: searchIcon width: height height: root.height anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left Icon { anchors.centerIn: parent width: height height: 16/32 * root.height source: "search-symbolic" mode: Icon.AutoHighlight } } DtThemeText { id: defaultText anchors.verticalCenter: parent.verticalCenter anchors.left: searchIcon.right text: placeholderText visible: textInput.text.length === 0 textColor: root.enabled ? GlobalTheme.kFontPlaceholderText : GlobalTheme.kFontPlaceholderTextDisable verticalAlignment: TextInput.AlignVCenter } Behavior on x { NumberAnimation { duration: 250 easing.type: Easing.Bezier easing.bezierCurve: [0.25, 0.1, 0.05, 1, 1, 1] } } function getDefaultSearchPosition() { let posX = text.length !== 0 ? 0 : textInput.activeFocus ? 0 : showAIButton ? (back.width - defaultSearch.width - searchIcon.width / 2 - aiButton.width) / 2 : (back.width - defaultSearch.width - searchIcon.width / 2) / 2 if (LayoutMirroring.enabled) { posX = back.width - defaultSearch.width - posX; } return posX; } } TextInput { id: textInput z: 1 clip: true width: showAIButton ? parent.width - searchIcon.width - clearButton.width - divider.width - aiButton.width : parent.width - searchIcon.width - clearButton.width height: parent.height selectByMouse: true verticalAlignment: TextInput.AlignVCenter font.pointSize: defaultText.font.pointSize font.family: defaultText.font.family focus: true activeFocusOnTab: false activeFocusOnPress: true color: GlobalTheme.kFontPrimary.pureColor selectionColor: GlobalTheme.highlightActive.pureColor x: getTextInputPosition() Behavior on x { NumberAnimation { duration: 250 easing.type: Easing.Bezier easing.bezierCurve: [0.25, 0.1, 0.05, 1, 1, 1] } } function getTextInputPosition() { let posX = text.length !== 0 ? searchIcon.width : textInput.activeFocus ? searchIcon.width : defaultSearch.x + searchIcon.width if (LayoutMirroring.enabled) { posX = back.width - textInput.width - posX; } return posX; } } DtThemeButton { id: clearButton z: 1 anchors.right: showAIButton ? divider.right : parent.right anchors.rightMargin: showAIButton ? 4 : 0 anchors.verticalCenter: parent.verticalCenter width: 24 / 32 * root.height height: 24 / 32 * root.height background.radius: width / 2 visible: textInput.length !== 0 activeFocusOnTab: false background.backgroundColor: GlobalTheme.kGrayAlpha0 icon.source: "edit-clear-symbolic" icon.mode: Icon.AutoHighlight icon.width: root.height / 2 icon.height: root.height / 2 onClicked: { textInput.clear(); } } DtThemeBackground { id: divider anchors.right: aiButton.left anchors.verticalCenter: parent.verticalCenter visible: showAIButton width: 1 height: root.height / 2 backgroundColor: GlobalTheme.kLineNormal } DtThemeButton { id: aiButton anchors.right: parent.right anchors.rightMargin: 4 anchors.verticalCenter: parent.verticalCenter width: 30 / 32 * root.height height: 30 / 32 * root.height background.radius: width / 2 visible: showAIButton activeFocusOnTab: false background.backgroundColor: GlobalTheme.kGrayAlpha0 icon.source: "ukui-nlp-symbolic" icon.width: root.height / 2 icon.height: root.height / 2 icon.mode: !root.enabled ? Icon.Disabled : enableSpecificBorder ? Icon.ForceHighlight : Icon.AutoHighlight icon.dtThemeHighlightColor: enableSpecificBorder ? GlobalTheme.kBrandNormal : GlobalTheme.highlightedTextActive onClicked: { enableSpecificBorder = !enableSpecificBorder; } } MouseArea { id: mouseArea anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter width: showAIButton ? root.width - aiButton.width : root.width height: parent.height cursorShape: Qt.IBeamCursor hoverEnabled: true acceptedButtons: Qt.LeftButton | Qt.RightButton propagateComposedEvents: true onClicked: (mouse)=> { if (mouse.button === Qt.LeftButton) { textInput.forceActiveFocus(Qt.MouseFocusReason) } else if (mouse.button === Qt.RightButton) { if(enableContextMenu) { textInput.forceActiveFocus(Qt.MouseFocusReason); menuLoader.item.show(-1,-1,root); } } } } Loader { id: menuLoader active: enableContextMenu sourceComponent: menuComponent } Component { id: menuComponent LineEditContextMenu { id: menu actions: [ Action { iconName: "edit-undo" text: qsTr("&Undo") + menu.getShortCutString(StandardKey.Undo) enabled: textInput.canUndo onTriggered: function() { textInput.undo(); } }, Action { iconName: "edit-redo" text: qsTr("&Redo") + menu.getShortCutString(StandardKey.Redo) enabled: textInput.canRedo onTriggered: function() { textInput.redo(); } }, Action { isSeparator: true }, Action { iconName: "edit-cut" text: qsTr("Cu&t") + menu.getShortCutString(StandardKey.Cut) enabled: !textInput.readOnly && textInput.echoMode === TextInput.Normal && textInput.selectedText !== "" onTriggered: function() { textInput.cut(); } }, Action { iconName: "edit-copy" text: qsTr("&Copy") + menu.getShortCutString(StandardKey.Copy) enabled: textInput.echoMode === TextInput.Normal && textInput.selectedText !== "" onTriggered: function() { textInput.copy(); } }, Action { iconName: "edit-paste" text: qsTr("&Paste") + menu.getShortCutString(StandardKey.Paste) enabled: !textInput.readOnly && textInput.canPaste onTriggered: function() { textInput.paste(); } }, Action { iconName: "edit-delete" text: qsTr("Delete") enabled: !textInput.readOnly && textInput.text !== "" && textInput.selectedText !== "" onTriggered: function() { textInput.remove(textInput.selectionStart, textInput.selectionEnd); } }, Action { isSeparator: true }, Action { iconName: "edit-select-all" text: qsTr("Select All") + menu.getShortCutString(StandardKey.SelectAll) enabled: textInput.text !== "" && textInput.selectedText !== textInput.text onTriggered: function() { textInput.selectAll(); } } ] } } } } ukui-quick/items/qml/KBallonTip.qml0000664000175000017500000001327515153756415016237 0ustar fengfengimport QtQuick 2.15 import QtQml 2.15 import QtQuick.Layouts 1.15 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 Item { id: root enum IconType { Error, Success, Warning, Info, Loading, None, Custom } property int iconType: KBallonTip.IconType.None property alias screen: window.screen property alias position: window.position property int leftMargin: 0 property int topMargin: 0 property int rightMargin: 0 property int bottomMargin: 0 property alias text: tipText.text property string customIconSource: "" property alias interval: hideTimer.interval property alias useAvailableGeometry: window.useAvailableGeometry readonly property bool windowVisible: window.visible function popup() { if (!window.visible) { window.reLocated(); window.show(); showAnimation.start(); hideTimer.start(); } } function hide() { showAnimation.stop(); hideAnimation.stop(); hideTimer.stop(); window.hide(); } Timer { id: hideTimer interval: 1750 repeat: false onTriggered: { hideAnimation.start(); } } Timer { id: loadingTimer interval: 100 repeat: true property int frame: 0 onTriggered: { if (iconType === KBallonTip.IconType.Loading) { if (frame < 7) { frame++; } else { frame = 0; } tipIcon.source = "ukui-loading-" + frame + ".symbolic"; } } } NumberAnimation { id: showAnimation target: backGround property: "opacity" from: 0 to: 1 easing.type: Easing.Bezier easing.bezierCurve: [0.25, 0.1, 0.25, 1] duration: 350 } NumberAnimation { id: hideAnimation target: backGround property: "opacity" from: 1 to: 0 easing.type: Easing.Bezier easing.bezierCurve: [0.25, 0.1, 0.25, 1] duration: 250 onFinished: { window.hide(); } } ContentWindow { id: window flags: Qt.Window | Qt.WindowDoesNotAcceptFocus | Qt.FramelessWindowHint windowType: WindowType.ToolTip enableBlurEffect: true margin.left: root.leftMargin- shadow.shadowSize margin.top: root.topMargin - shadow.shadowSize margin.right: root.rightMargin - shadow.shadowSize margin.bottom: root.bottomMargin - shadow.shadowSize Item { id: mainItem width: backGround.width + shadow.shadowSize * 2 height: backGround.height + shadow.shadowSize * 2 DtThemeBackground { id: backGround anchors.left: parent.left anchors.leftMargin: shadow.shadowSize anchors.top: parent.top anchors.topMargin: shadow.shadowSize radius: GlobalTheme.kRadiusMenu backgroundColor: GlobalTheme.kMenu borderColor: GlobalTheme.kLineWindowInactive useStyleTransparency: true width: childrenRect.width + 28 height: childrenRect.height + 28 RowLayout { anchors.left: parent.left anchors.leftMargin: 14 anchors.top: parent.top anchors.topMargin: 14 spacing: 6 Icon { id: tipIcon width: 24 height: 24 visible: iconType !== KBallonTip.IconType.None } DtThemeText { id: tipText Layout.alignment: Qt.AlignVCenter } } Component.onCompleted: { if (iconType === KBallonTip.IconType.Loading) { loadingTimer.start(); return; } else { loadingTimer.stop(); } if (iconType === KBallonTip.IconType.Custom) { tipIcon.source = Qt.binding(function() { return customIconSource; }); } else if (iconType === KBallonTip.IconType.Error) { tipIcon.source = "dialog-error"; } else if (iconType === KBallonTip.IconType.Success) { tipIcon.source = "ukui-dialog-success"; tipIcon.fallbackSource = "emblem-default"; } else if (iconType === KBallonTip.IconType.Warning) { tipIcon.source = "dialog-warning"; } else if (iconType === KBallonTip.IconType.Info) { tipIcon.source = "dialog-info"; } } } RectRegion { id: blurRegion x: mainItem.mapToItem(mainItem, backGround.x, backGround.y).x y: mainItem.mapToItem(mainItem, backGround.x, backGround.y).y width: backGround.width height: backGround.height radius: GlobalTheme.kRadiusMenu } Component.onCompleted: { window.blurRegion.appendRect(blurRegion); window.maskRegion.appendRect(blurRegion); } DtDropShadow { id: shadow anchors.fill: backGround shadowData: GlobalTheme.kShadowMenu shadowRadius: backGround.radius } } } } ukui-quick/items/qml/DtThemeBackground.qml0000664000175000017500000000442415153756415017566 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: amingamingaming * */ import QtQuick 2.15 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 ShadowedRectangle { //是否跟随系统主题颜色的透明度 property bool useStyleTransparency: true; //背景颜色 property var backgroundColor: GlobalTheme.windowActive; property var borderColor: GlobalTheme.baseActive property real borderAlpha: 1.0; property real alpha: 1.0 //阴影 property var shadowData border.width: 0; border.color : borderColor.setAlphaF(borderColor.pureColor,borderAlpha); color:backgroundColor.setAlphaF(backgroundColor.pureColor, useStyleTransparency ? GlobalTheme.transparency : alpha); pureColor: backgroundColor.colorType === DtColorType.Pure; startColor: backgroundColor.mixBackGroundColor(backgroundColor.gradientStart, backgroundColor.gradientBackground, useStyleTransparency ? GlobalTheme.transparency : alpha) endColor: backgroundColor.mixBackGroundColor(backgroundColor.gradientEnd, backgroundColor.gradientBackground, useStyleTransparency ? GlobalTheme.transparency : alpha) angle: backgroundColor.gradientDeg onShadowDataChanged: { if (shadowData === undefined) { shadow.size = 0; shadow.xOffset = 0.0; shadow.yOffset = 0.0; shadow.color = "black"; } else { shadow.size = shadowData.blurRadius + shadowData.spread; shadow.xOffset = shadowData.xOffset; shadow.yOffset = shadowData.yOffset; shadow.color = shadowData.shadowColor; } } } ukui-quick/items/qml/IconButton.qml0000664000175000017500000000413215153756415016314 0ustar fengfengimport QtQuick 2.0 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 DtThemeBackground { property bool isHighLight: false property bool disable: false property string iconSource: "" signal clicked() useStyleTransparency: false backgroundColor: isHighLight ? highLightBackGroundColor() : normalBackGroundColor() border.width: 1 borderColor: isHighLight ? highLightBorderColor() : normalBorderColor() function highLightBackGroundColor() { return mouseArea.containsPress ? GlobalTheme.kBrandClick : mouseArea.containsMouse ? GlobalTheme.kBrandHover : GlobalTheme.highlightActive } function normalBackGroundColor() { return mouseArea.containsPress ? GlobalTheme.kComponentAlphaClick : mouseArea.containsMouse ? GlobalTheme.kComponentAlphaHover : GlobalTheme.kComponentAlphaNormal } function highLightBorderColor() { return mouseArea.containsPress ? GlobalTheme.kLineBrandClick : mouseArea.containsMouse ? GlobalTheme.kLineBrandHover : GlobalTheme.kLineBrandNormal } function normalBorderColor() { return mouseArea.containsPress ? GlobalTheme.kLineComponentClick : mouseArea.containsMouse ? GlobalTheme.kLineComponentHover : GlobalTheme.kLineComponentNormal } Icon { width: parent.width/2 height: parent.width/2 anchors.centerIn: parent source: parent.iconSource mode: parent.isHighLight ? Icon.Highlight : Icon.AutoHighlight } MouseArea { id: mouseArea hoverEnabled: true anchors.fill: parent onClicked: { parent.clicked(); } } } ukui-quick/items/qml/DtThemeScrollBar.qml0000664000175000017500000000663015153756415017373 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: amingamingaming * */ import QtQuick 2.12 import QtQuick.Templates 2.12 as T import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 T.ScrollBar { id: control property bool visual: true property bool transparentMode: true hoverEnabled: true implicitWidth: control.horizontal ? Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding) : 16 implicitHeight: control.horizontal ? 16 : Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding) padding: (hovered || pressed) ? 4 : 6 Behavior on padding { NumberAnimation { duration: 200 easing.type: Easing.InOutQuad } } visible: control.policy !== T.ScrollBar.AlwaysOff minimumSize: orientation == Qt.Horizontal ? height / width : width / height contentItem: DtThemeBackground { implicitWidth: control.interactive ? 8 : 4 implicitHeight: control.interactive ? 8 : 4 radius: width / 2 backgroundColor: getBackgroundColor() opacity: 0.0 } states: State { name: "active" when: (control.policy !== T.ScrollBar.AlwaysOff || (control.active && control.size < 1.0)) && control.visual } transitions: [ Transition { to: "active" NumberAnimation { targets: [control.contentItem]; property: "opacity"; to: 1.0 } }, Transition { from: "active" SequentialAnimation { PropertyAction { targets: [control.contentItem]; property: "opacity"; value: 1.0 } NumberAnimation { targets: [control.contentItem]; property: "opacity"; to: 0.0 } } } ] function getBackgroundColor() { if(control.transparentMode) { return control.pressed ? GlobalTheme.kGrayAlpha11 : control.interactive && control.hovered ? GlobalTheme.kGrayAlpha13 : control.enabled ? GlobalTheme.kGrayAlpha9 : GlobalTheme.kGrayAlpha5 } else { return control.pressed ? GlobalTheme.kGray14 : control.interactive && control.hovered ? GlobalTheme.kGray13 : control.enabled ? GlobalTheme.kGray12 : GlobalTheme.kGray8 } } } ukui-quick/items/qml/Background.qml0000664000175000017500000000325115153756415016310 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ import QtQuick 2.12 import QtQml 2.15 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 Rectangle { property real borderAlpha: 1.0 property int borderColor: Theme.Base // colorMixer property alias colorMixer: mixer property alias mixMode: mixer.mixMode // 背景色 property alias backColorRole: mixer.backColorRole property alias backColorGroup: mixer.backColorGroup property alias backColorAlpha: mixer.backColorAlpha // 前景色 property alias foreColorRole: mixer.foreColorRole property alias foreColorGroup: mixer.foreColorGroup property alias foreColorAlpha: mixer.foreColorAlpha color: mixer.color ColorMix { id: mixer } border.width: 0 border.color: { return Theme.colorWithCustomTransparency(borderColor, Theme.Active, borderAlpha); } //监听系统主题变化 Theme.onPaletteChanged: { borderColorChanged(); } } ukui-quick/items/qml/HoverButton.qml0000664000175000017500000000424715153756415016516 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: amingamingaming * */ import QtQuick 2.12 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 MouseArea { readonly property alias icon: iconBase readonly property alias background: backgroundBase hoverEnabled: true DtThemeBackground { id: backgroundBase anchors.fill: parent border.width: 1 width: 24 height: 24 backgroundColor: getBackgroundColor() borderColor: getBorderdColor() useStyleTransparency: false radius: width/2 alpha: 1 DtDropShadow { anchors.fill: backgroundBase shadowData: GlobalTheme.kShadowMin shadowRadius: backgroundBase.width/2 } Icon { id: iconBase width: 16 height: 16 anchors.centerIn: parent mode: Icon.AutoHighlight } function getBackgroundColor() { return containsPress ? GlobalTheme.kContainClick : containsMouse ? GlobalTheme.kContainHover : GlobalTheme.kContainGeneralNormal } function getBorderdColor() { return containsPress ? GlobalTheme.kLineComponentClick : containsMouse ? GlobalTheme.kLineComponentHover : GlobalTheme.kLineComponentNormal } } } ukui-quick/items/qml/qml.qrc0000664000175000017500000000014115153755732015012 0ustar fengfeng DefaultTooltip.qml ukui-quick/items/qml/DtDropShadow.qml0000664000175000017500000000604215153756415016574 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: qiqi49 * */ /* Add shadow for a round button: Button { id: roundButton width: 100 height: 100 background.radius: 50 } DtDropShadow { anchors.fill: roundButton shadowRadius: 10 z: -1 shadowData: ShadowData { xOffset: 0 yOffset: 0 blurRadius: 50 spread: 0 shadowColor: Qt.rgba(0 / 255, 0 / 255, 0 / 255, 1) childShadow: ShadowData { xOffset: 0 yOffset: 12 blurRadius: 25 spread: 0 shadowColor: "blue" } } } */ import QtQuick 2.15 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 Item { id: root property var shadowData property int shadowSize: 0 property int shadowRadius: 0 property var activeShadows: [] Component.onCompleted: { updateShadow(); } onShadowDataChanged: { updateShadow(); } Component { id: shadowCompontent ShadowedRectangle { anchors.fill: root color: "transparent" radius: shadowRadius property var shadowRectData shadow { size: shadowRectData ? shadowRectData.blurRadius + shadowRectData.spread : 0 color: shadowRectData ? shadowRectData.shadowColor : "black" xOffset: shadowRectData ? shadowRectData.xOffset : 0 yOffset: shadowRectData ? shadowRectData.yOffset : 0 } } } function updateShadow() { clearShadow(); let currentShadow = shadowData; let maxSize = 0; while (currentShadow) { let instance = shadowCompontent.createObject(root, {shadowRectData: currentShadow}); if (instance) { activeShadows.push(instance); } let offset = Math.max(currentShadow.xOffset, currentShadow.yOffset); maxSize = Math.max(maxSize, (offset + currentShadow.blurRadius + currentShadow.spread)); if (currentShadow.isMultiShadow) { currentShadow = currentShadow.childShadow; } else { currentShadow = null; } } shadowSize = maxSize; } function clearShadow() { activeShadows.forEach(obj => { obj.destroy() } ); activeShadows = []; } } ukui-quick/items/qml/DefaultTooltip.qml0000664000175000017500000000321715153755732017173 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ import QtQuick 2.12 import QtQuick.Layouts 1.15 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 DtThemeBackground { id: root property Item tooltip backgroundColor: GlobalTheme.baseActive borderColor: GlobalTheme.shadowActive border.width: 0 width: toolTipText.contentWidth + 20 height: toolTipText.contentHeight + 20 Layout.minimumWidth: toolTipText.contentWidth + GlobalTheme.fontSize Layout.minimumHeight: toolTipText.contentHeight + GlobalTheme.fontSize LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft LayoutMirroring.childrenInherit: true DtThemeText { id: toolTipText anchors.centerIn: parent text: tooltip ? tooltip.isRichText ? tooltip.mainText : tooltipUtils.processedText(tooltip.mainText) : "" textFormat: tooltip ? tooltip.isRichText ? Text.RichText : Text.AutoText : Text.AutoText } } ukui-quick/items/qml/DtThemeText.qml0000664000175000017500000000251515153756415016432 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: amingamingaming * */ import QtQuick 2.12 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 Text { //DT主题字体颜色 property var textColor: GlobalTheme.textActive; property int pointSizeOffset: 0 property real alpha: 1.0; font { pointSize: { if ((GlobalTheme.fontSize + pointSizeOffset) < 0 ) { return GlobalTheme.fontSize; } else { return GlobalTheme.fontSize + pointSizeOffset; } } family: GlobalTheme.fontFamily } color: textColor.setAlphaF(textColor.pureColor, alpha) } ukui-quick/items/qml/StyleText.qml0000664000175000017500000000311015153756415016170 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ import QtQuick 2.12 import org.ukui.quick.items 1.0 import org.ukui.quick.platform 1.0 Text { //颜色枚举值,从调色板中取得 property int paletteRole: Theme.ButtonText; property int pointSizeOffset: 0 property real alpha: 1.0; font { pointSize: { if ((Theme.fontSize + pointSizeOffset) < 0 ) { return Theme.fontSize; } else { return Theme.fontSize + pointSizeOffset; } } family: Theme.fontFamily } function updateColor() { color = Theme.colorWithCustomTransparency(paletteRole, Theme.Active, alpha); } onPaletteRoleChanged: { updateColor(); } onAlphaChanged: { updateColor(); } Theme.onPaletteChanged: updateColor(); Component.onCompleted: { updateColor(); } } ukui-quick/items/qml/BlurItem.qml0000664000175000017500000000332515153756415015756 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ import QtQuick 2.15 import Qt5Compat.GraphicalEffects Rectangle { id: root property variant source property int samples: 9 property real blurRadius: Math.floor(samples / 2) property bool hideSource: false color: "transparent" ShaderEffectSource { id: effectSource sourceItem: root.source sourceRect: Qt.rect(root.x, root.y, root.width, root.height) hideSource: root.hideSource width: parent.width height: parent.height } GaussianBlur { anchors.fill: parent source: effectSource radius: root.blurRadius samples: root.samples layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: root.width height: root.height radius: root.radius } } } //TODO: //关闭特效时,仅绘制固定透明度的base色 } ukui-quick/items/line-edit-context-menu.cpp0000664000175000017500000000451715153755732017736 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: amingamingaming * */ #include "line-edit-context-menu.h" #include #include #include namespace UkuiQuick { LineEditContextMenu::LineEditContextMenu(QObject *prent) : QObject(prent) { } LineEditContextMenu::~LineEditContextMenu() { if(m_menu != nullptr) { delete m_menu; m_menu = nullptr; } } QQmlListProperty LineEditContextMenu::qmlActions() { return QQmlListProperty(this, &m_actions); } void LineEditContextMenu::show(int x, int y, QQuickItem* item) { if(m_menu == nullptr) { m_menu = new QMenu(); } //调用winId()强制创建原生窗口资源,否则QWidget控件的windowHandle为空 if(item) { m_menu->winId(); if(m_menu->windowHandle()) { m_menu->windowHandle()->setTransientParent(item->window()); } } m_menu->clear(); for (QAction *action : m_actions) { m_menu->addAction(action); } if (x < 0 || y < 0) { m_menu->popup(QCursor::pos()); } else { m_menu->popup(QPoint(x, y)); } } void LineEditContextMenu::hide() { m_menu->hide(); } QList LineEditContextMenu::actions() const { return m_actions; } QString LineEditContextMenu::getShortCutString(QKeySequence::StandardKey shortcut) { if(QCoreApplication::testAttribute(Qt::AA_DontShowShortcutsInContextMenus)) { return QString(); } QList list = QKeySequence::keyBindings(shortcut); if(list.isEmpty()) { return QString(); } return QLatin1Char('\t') + list.first().toString(QKeySequence::NativeText); } } ukui-quick/items/icon.cpp0000664000175000017500000014722515153756415014373 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "icon.h" #include "icon-helper.h" #include "icon-provider.h" #include "theme-icon.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace UkuiQuick { class IconBackgroundUtils; static IconBackgroundUtils *g_iconBackgroundUtils = nullptr; static std::once_flag onceFlag; #define COLOR_DIFFERENCE 15 class IconBackgroundUtils { public: static IconBackgroundUtils* instance(); void setIconThemeName(const QString& iconThemeName); bool isValid() const; QIcon iconBackground(); QIcon iconBackgroundWithoutShadow(); bool containShadow(uint size) const; qreal offsetX(uint size) const; qreal offsetY(uint size) const; QVarLengthArray outLineDistance(uint width); qreal centerAreaRate() const; private: IconBackgroundUtils(); void updateIconBackConfig(); void updateParam(); void reset(); QMap m_containShadow; //当前图标主题第三方图标统一背景图是否包含阴影 QMap m_offsetX; //当前图标主题第三方图标统一背景图X轴偏移量系数 QMap m_offsetY; //当前图标主题第三方图标统一背景图Y轴偏移量系数 qreal m_centerAreaRate = 0.625; //当前图标主题视觉中心区域占背景图总大小比值 QJsonObject m_config; QString m_themeName; QIcon m_background = QIcon::fromTheme(QStringLiteral("icon-background")); QIcon m_backgroundWithoutShadow = QIcon::fromTheme(QStringLiteral("icon-background-noshadow")); QMap> m_outLineDistance; //当前图标主题第三方图标统一背景图外边框轮廓 bool m_valid = false; }; IconBackgroundUtils* IconBackgroundUtils::instance() { std::call_once(onceFlag, [ & ] { g_iconBackgroundUtils = new IconBackgroundUtils(); }); return g_iconBackgroundUtils; } void IconBackgroundUtils::setIconThemeName(const QString &iconThemeName) { if (iconThemeName == m_themeName) { return; } m_themeName = iconThemeName; updateParam(); } bool IconBackgroundUtils::isValid() const { return m_valid; } void IconBackgroundUtils::updateIconBackConfig() { QFile file("/usr/share/ukui/ukui-quick-items/iconThemeBackgroundConfig.json"); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { qWarning() << "IconBackgroundUtils::updateIconBackConfig() failed to open config file" << file.errorString(); return; } QTextStream iStream(&file); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) iStream.setCodec("UTF-8"); #else iStream.setEncoding(QStringConverter::Utf8); #endif QJsonParseError parseError {}; QJsonDocument document = QJsonDocument::fromJson(iStream.readAll().toUtf8(), &parseError); file.close(); if (parseError.error != QJsonParseError::NoError) { qWarning() << "IconBackgroundUtils::updateIconBackConfig() failed to parse config file" << parseError.errorString(); return; } if (!document.isObject()) { qWarning() << "IconBackgroundUtils::updateIconBackConfig() failed to parse config file: Is not object."; return; } m_config = document.object(); } void IconBackgroundUtils::updateParam() { if (m_background.isNull() || m_background.name() != QStringLiteral("icon-background") || m_backgroundWithoutShadow.isNull() || m_backgroundWithoutShadow.name() != QStringLiteral("icon-background-noshadow")) { qWarning() << "IconBackgroundUtils::updateParam() failed to get icon background, background or icon background without shadow is null."; reset(); return; } auto currentThemeConfig = m_config.value(m_themeName).toObject(); if (currentThemeConfig.isEmpty()) { qWarning() << "IconBackgroundUtils::updateParam() failed to get theme config: theme config is empty."; reset(); return; } m_centerAreaRate = currentThemeConfig.value("centerAreaRate").toDouble(); QStringList sizeList = {"16", "24", "32", "48", "64", "96", "128", "512"}; for(auto size : sizeList) { auto sizeObject = currentThemeConfig.value(size).toObject(); if (sizeObject.isEmpty()) { qWarning() << QString("IconBackgroundUtils::updateParam() failed to get theme config: size = %1 config is empty.").arg(size); reset(); return; } qreal offsetX = sizeObject.contains("offsetX") ? sizeObject.value("offsetX").toDouble() : currentThemeConfig.value("offsetX").toDouble(); qreal offsetY = sizeObject.contains("offsetY") ? sizeObject.value("offsetY").toDouble() : currentThemeConfig.value("offsetY").toDouble(); m_offsetX.insert(size.toUInt(), offsetX); m_offsetY.insert(size.toUInt(), offsetY); bool containShadow = sizeObject.contains("containShadow") ? sizeObject.value("containShadow").toBool() : currentThemeConfig.value("containShadow").toBool(); m_containShadow.insert(size.toUInt(), containShadow); auto distanceArr = sizeObject.value("outLineDistance").toArray(); if(distanceArr.size() != 256) { qWarning() << QString("IconBackgroundUtils::updateParam() failed to get theme config: %1 outLineDistance size = %2 is invalid.").arg(m_themeName, size); reset(); return; } QVarLengthArray distance; for (auto val : distanceArr) { distance.append(val.toVariant().toReal()); } m_outLineDistance.insert(size.toUInt(), distance); } m_valid = true; } void IconBackgroundUtils::reset() { m_valid = false; m_containShadow.clear(); m_offsetX.clear(); m_offsetY.clear(); m_outLineDistance.clear(); } QIcon IconBackgroundUtils::iconBackground() { return m_background; } QIcon IconBackgroundUtils::iconBackgroundWithoutShadow() { return m_backgroundWithoutShadow; } bool IconBackgroundUtils::containShadow(uint size) const { if (m_backgroundWithoutShadow.availableSizes().contains(QSize(size, size)) || m_backgroundWithoutShadow.actualSize(QSize(size, size)) != QSize(size, size)) { if (size <= 16) { return m_containShadow.value(16); } else if (size <= 24) { return m_containShadow.value(24); } else if (size <= 32) { return m_containShadow.value(32); } else if (size <= 48) { return m_containShadow.value(48); } else if (size <= 64) { return m_containShadow.value(64); } else if (size <= 96) { return m_containShadow.value(96); } } return m_containShadow.value(128); } qreal IconBackgroundUtils::offsetX(uint size) const { if (m_backgroundWithoutShadow.availableSizes().contains(QSize(size, size)) || m_backgroundWithoutShadow.actualSize(QSize(size, size)) != QSize(size, size)) { if (size <= 16) { return m_offsetX.value(16); } else if (size <= 24) { return m_offsetX.value(24); } else if (size <= 32) { return m_offsetX.value(32); } else if (size <= 48) { return m_offsetX.value(48); } else if (size <= 64) { return m_offsetX.value(64); } else if (size <= 96) { return m_offsetX.value(96); } } return m_offsetX.value(128); } qreal IconBackgroundUtils::offsetY(uint size) const { if (m_backgroundWithoutShadow.availableSizes().contains(QSize(size, size)) || m_backgroundWithoutShadow.actualSize(QSize(size, size)) != QSize(size, size)) { if (size <= 16) { return m_offsetY.value(16); } else if (size <= 24) { return m_offsetY.value(24); } else if (size <= 32) { return m_offsetY.value(32); } else if (size <= 48) { return m_offsetY.value(48); } else if (size <= 64) { return m_offsetY.value(64); } else if (size <= 96) { return m_offsetY.value(96); } } return m_offsetY.value(128); } QVarLengthArray IconBackgroundUtils::outLineDistance(uint size) { if (size <= 20) { return m_outLineDistance.value(16); } else if (size > 20 && size <= 28) { return m_outLineDistance.value(24); } else if (size > 28 && size <= 40) { return m_outLineDistance.value(32); } else if (size > 40 && size <= 56) { return m_outLineDistance.value(48); } else if (size > 56 && size <= 80) { return m_outLineDistance.value(64); } else if (size <= 96) { return m_outLineDistance.value(96); } else if (size == 512) { return m_outLineDistance.value(512); } return m_outLineDistance.value(128); } qreal IconBackgroundUtils::centerAreaRate() const { return m_centerAreaRate; } IconBackgroundUtils::IconBackgroundUtils() { updateIconBackConfig(); // QObject::connect(ThemeGlobalConfig::instance(), &ThemeGlobalConfig::iconThemeNameChanged, [this]() { // setIconThemeName(ThemeGlobalConfig::instance()->iconThemeName()); // }); } class IconPrivate { public: int radius {0}; bool forceRound {false}; bool textureChanged {true}; QIcon icon; QVariant fallbackSource; QIcon fallbackIcon; bool fallbackIconLoaded {false}; Icon::Mode mode {Icon::Normal}; QString pointText {""}; Types::Pos pointPos {Types::TopRight}; Icon::PointType pointType {Icon::Null}; Theme::ColorRole pointColor {Theme::Highlight}; Theme::ColorRole highlightColor {Theme::HighlightedText}; GradientColor* dtThemeHighlightColor = nullptr; std::shared_ptr currentTexture; static QHash > > textureCache; ApplicationIconProxy* proxy = nullptr; bool addUnifiedIconBox {false}; //图标类型 Icon::ImageType imageType = {Icon::Uninitialized}; QImage imageCache; static const QColor symbolicColor; }; const QColor IconPrivate::symbolicColor = QColor(26, 26, 26); QHash > > IconPrivate::textureCache = {}; // ===== ICON ====== // Icon::Icon(QQuickItem *parent) : QQuickItem(parent), d(new IconPrivate) { setFlag(QQuickItem::ItemHasContents); connect(this, &QQuickItem::widthChanged, this, &Icon::onSizeChanged); connect(this, &QQuickItem::heightChanged, this, &Icon::onSizeChanged); } Icon::~Icon() { if (d) { delete d; d = nullptr; } } // ====== DRAW ====== // QSGNode *Icon::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) { QSGImageNode *imageNode = nullptr; if (node) { imageNode = static_cast(node); } else { imageNode = window()->createImageNode(); imageNode->setOwnsTexture(true); imageNode->setFiltering(QSGTexture::Linear); node = imageNode; } if (d->textureChanged) { imageNode->setTexture(createTexture()); imageNode->setSourceRect({QPoint(0, 0), imageNode->texture()->textureSize()}); d->textureChanged = false; } imageNode->setRect(boundingRect()); return node; } QSGTexture *Icon::createTexture() { QImage image; // 获取原始图像 if (d->icon.isNull()) { image = loadFallbackIcon(); } else { if (d->forceRound) { // 处理圆形图标 QSize rawSize = size().toSize(); if (!d->icon.availableSizes().isEmpty()) { auto sizes = d->icon.availableSizes(); rawSize = *std::max_element(sizes.begin(), sizes.end(), [](const QSize&a, const QSize&b) { return a.width() < b.width(); }); } QPixmap rawPixmap = d->icon.pixmap(rawSize); int diameter = std::min(rawPixmap.height(), rawPixmap.width()); QPixmap pixmap(diameter, diameter); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); QPainterPath path; path.addEllipse(0, 0, diameter, diameter); painter.setClipPath(path); QRect targetRect, sourceRect; targetRect = QRect(0, 0, diameter, diameter); if (rawPixmap.width() > rawPixmap.height()) { sourceRect = QRect((rawPixmap.width() - diameter) / 2, 0, diameter, diameter); } else { sourceRect = QRect(0, (rawPixmap.height() - diameter) / 2, diameter, diameter); } painter.drawPixmap(targetRect, rawPixmap, sourceRect); image = pixmap.toImage().scaled(size().toSize() * window()->devicePixelRatio(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } else { image = d->icon.pixmap(size().toSize()).toImage(); if (d->addUnifiedIconBox && IconBackgroundUtils::instance()->isValid()) { image = addIconBox(image); } if(image.isNull()) { qWarning() << "Icon::createTexture : Can't load icon with size " << size().toSize() * window()->devicePixelRatio(); image = loadFallbackIcon(); } } } // // 缩放大图像? // QSize imageSize = image.size(); // if (imageSize.width() > 512 || imageSize.height() > 512) { // image.scaled(); // imageSize = image.size(); // } // 修改图像 //imageCorrection(image); if (d->mode & Icon::Disabled) { // use style image = QIcon(QPixmap::fromImage(std::move(image))).pixmap(size().toSize(), QIcon::Disabled).toImage(); } else if (d->mode & Icon::Highlight) { bool isPureColor = true; if(image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } if (!d->mode.testFlag(Icon::ForceHighlight)) { // TODO: 更新判断纯色算法 isPureColor = isImagePureColor(image); } GradientColor* highlightColor = d->dtThemeHighlightColor ? d->dtThemeHighlightColor : DtTheme::self(qmlEngine(this))->highlightedTextActive(); if (isPureColor) { QPainter p(&image); p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); p.setCompositionMode(QPainter::CompositionMode_SourceIn); if (highlightColor->colorType() == DtColorType::Type::Pure) { p.fillRect(image.rect(), highlightColor->pureColor()); } else { //设置渐变色的方向 QLinearGradient linearGradient( highlightColor->getGradientStartPoint(highlightColor->gradientDeg(), image.width(), image.height()) , highlightColor->getGradientEndPoint(highlightColor->gradientDeg(), image.width(), image.height())); //设置渐变色颜色 linearGradient.setColorAt( 0.0, highlightColor->mixBackGroundColor(highlightColor->gradientStart(), highlightColor->gradientBackground(), 1)); linearGradient.setColorAt( 1.0, highlightColor->mixBackGroundColor(highlightColor->gradientEnd(), highlightColor->gradientBackground(), 1)); QBrush brush(linearGradient); p.fillRect(image.rect(), brush); } } else { int symbolicRed = d->symbolicColor.red(); int symbolicGreen = d->symbolicColor.green(); int symbolicBlue = d->symbolicColor.blue(); if (highlightColor->colorType() == DtColorType::Type::Pure) { QColor baseColor = highlightColor->pureColor(); uchar *pixels = image.bits(); int bytesPerLine = image.bytesPerLine(); for (int y = 0; y < image.height(); y++) { QRgb* line = reinterpret_cast(pixels + y * bytesPerLine); for (int x = 0; x < image.width(); x++) { QRgb &color = line[x]; if (qAlpha(color) > 0.3 * 256) { if (qAbs(qRed(color) - symbolicRed) < COLOR_DIFFERENCE && qAbs(qGreen(color) - symbolicGreen) < COLOR_DIFFERENCE && qAbs(qBlue(color) - symbolicBlue) < COLOR_DIFFERENCE) { color = qRgba(baseColor.red(),baseColor.green(),baseColor.blue(), qAlpha(color)); } } } } } else { QImage gradientImg(image.width(), image.height(), QImage::Format::Format_ARGB32); //设置渐变色的方向 QLinearGradient linearGradient( highlightColor->getGradientStartPoint(highlightColor->gradientDeg(), image.width(), image.height()) , highlightColor->getGradientEndPoint(highlightColor->gradientDeg(), image.width(), image.height())); //设置渐变色颜色 linearGradient.setColorAt( 0.0, highlightColor->mixBackGroundColor(highlightColor->gradientStart(), highlightColor->gradientBackground(), 1)); linearGradient.setColorAt( 1.0, highlightColor->mixBackGroundColor(highlightColor->gradientEnd(), highlightColor->gradientBackground(), 1)); QBrush brush(linearGradient); //将渐变色绘制到与图标相同大小的image上 QPainter painterImg(&gradientImg); painterImg.setBrush(brush); painterImg.drawRect(gradientImg.rect()); painterImg.end(); uchar *pixels = image.bits(); const uchar *gradientPixels = gradientImg.constBits(); int bytesPerLine = image.bytesPerLine(); int bytesPerLineGra = gradientImg.bytesPerLine(); for (int y = 0; y < image.height(); y++) { QRgb* line = reinterpret_cast(pixels + y * bytesPerLine); const QRgb* gradientLine = reinterpret_cast(gradientPixels + y * bytesPerLineGra); for (int x = 0; x < image.width(); x++) { QRgb &color = line[x]; if (qAlpha(color) > 0.3 * 256) { if (qAbs(qRed(color) - symbolicRed) < COLOR_DIFFERENCE && qAbs(qGreen(color) - symbolicGreen) < COLOR_DIFFERENCE && qAbs(qBlue(color) - symbolicBlue) < COLOR_DIFFERENCE) { color = qRgba(qRed(gradientLine[x]), qGreen(gradientLine[x]), qBlue(gradientLine[x]), qAlpha(color)); } } } } } } } //std::shared_ptr texturePtr; //qint64 cacheKey = image.cacheKey(); //auto hash = IconPrivate::textureCache[window()]; //if (hash.contains(cacheKey)) { // texturePtr = hash[cacheKey].lock(); // if (texturePtr) { // d->currentTexture = texturePtr; // return d->currentTexture.get(); // } //} QSGTexture *texture = window()->createTextureFromImage(image); //texturePtr = std::shared_ptr(texture, [this] (QSGTexture *texture) { // //hash.remove(cacheKey); // //if (hash.isEmpty()) { // // IconPrivate::textureCache.remove(window()); // //} // // delete texture; //}); //hash[cacheKey] = texturePtr; //d->currentTexture = texturePtr; return texture; } void Icon::imageCorrection(QImage &image) { // 画点 drawPoint(image); // 修改状态 } QPoint Icon::posPoint(const QSize &imageSize, const QSize &pointSize) const { QPoint point; switch (d->pointPos) { default: case Types::TopRight: point = {imageSize.width() - pointSize.width(), 0}; break; case Types::TopLeft: point = {0, 0}; break; case Types::BottomRight: point = {imageSize.width() - pointSize.width(), imageSize.height() - pointSize.height()}; break; case Types::BottomLeft: point = {0, imageSize.height() - pointSize.height()}; break; case Types::Center: point = {(imageSize.width() - pointSize.width()) / 2, (imageSize.height() - pointSize.height()) / 2}; break; case Types::LeftCenter: point = {0, (imageSize.height() - pointSize.height()) / 2}; break; case Types::TopCenter: point = {(imageSize.width() - pointSize.width()) / 2, 0}; break; case Types::RightCenter: point = {imageSize.width() - pointSize.width(), (imageSize.height() - pointSize.height()) / 2}; break; case Types::BottomCenter: point = {(imageSize.width() - pointSize.width()) / 2, imageSize.height() - pointSize.height()}; break; } if (point.x() < 0) { point.setX(0); } else if ((point.x() + pointSize.width()) > imageSize.width()) { point.setX(imageSize.width() - pointSize.width()); } if (point.y() < 0) { point.setY(0); } else if ((point.y() + pointSize.height()) > imageSize.height()) { point.setY(imageSize.height() - pointSize.height()); } return point; } void Icon::drawPoint(QImage &image) { if (d->pointType == Icon::Null) { return; } QPainter painter(&image); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); painter.setBrush(Theme::instance()->color(d->pointColor)); painter.setPen(Theme::instance()->color(d->pointColor)); if (d->pointType == Icon::Point) { QSize pointSize(qFloor(image.width() * 0.35), qFloor(image.height() * 0.35)); QRect rect(posPoint(image.size(), pointSize), pointSize); painter.drawEllipse(rect); } else if (d->pointType == Icon::Text) { QRect rect = {0, 0, qFloor(image.width() * 0.4), qFloor(image.height() * 0.4)}; QFont font = painter.font(); // 为了将两位数正常显示在框框中 font.setPointSize(qFloor(6.0 * rect.width() / 12.0)); font.setBold(true); QString text = d->pointText; QFontMetrics fontMetrics(font); QRect textRect = fontMetrics.boundingRect(rect, Qt::AlignVCenter | Qt::AlignHCenter, text); if (textRect.width() > rect.width()) { if (textRect.width() > image.width()) { rect.setWidth(image.width()); text = fontMetrics.elidedText(text, Qt::ElideRight, rect.width() - 4); } else { rect.setWidth(textRect.width() + 8); } } rect.moveTopLeft(posPoint(image.size(), rect.size())); // 底部圆 painter.save(); painter.setPen(Theme::instance()->colorWithCustomTransparency(Theme::BrightText, Theme::Active, 0.15)); painter.drawRoundedRect(rect, qFloor(rect.height() * 0.5), qFloor(rect.height() * 0.5)); painter.restore(); // draw text. painter.save(); // TODO: 优化字体显示 painter.setFont(font); painter.setPen(Theme::instance()->buttonText()); painter.drawText(rect, Qt::AlignVCenter | Qt::AlignHCenter, text); painter.restore(); } } void Icon::onSizeChanged() { markTextureChanged(); } // ====== METHODS ====== // QVariant Icon::source() const { return d->icon; } void Icon::setSource(const QVariant &source) { if (d->proxy && !d->proxy->icon().isEmpty()) { qInfo() << "This icon has been set a effective proxy, you can not set source explicitly." << "source attempted to set:" << source << "proxy icon:" << d->proxy->icon(); return; } bool applyIcon = applyVariantToIcon(source, d->icon); if (applyIcon) { markTextureChanged(); Q_EMIT sourceChanged(); } } QVariant Icon::fallbackSource() const { return d->fallbackSource; } void Icon::setFallbackSource(const QVariant &fallback) { if(d->fallbackSource != fallback) { d->fallbackSource = fallback; d->fallbackIcon = QIcon(); d->fallbackIconLoaded = false; markTextureChanged(); } } bool Icon::applyVariantToIcon(const QVariant &source, QIcon &icon) { switch (source.userType()) { case QMetaType::QIcon: icon = source.value(); break; case QMetaType::QImage: icon.addPixmap(QPixmap::fromImage(source.value())); break; case QMetaType::QPixmap: icon.addPixmap(source.value()); break; case QMetaType::QBitmap: icon.addPixmap(source.value()); break; case QMetaType::QString: { icon = IconHelper::loadIcon(source.value()); break; } default: break; } return !icon.isNull(); } QImage Icon::loadFallbackIcon() { if (d->fallbackSource.isNull()) { qWarning() << "Icon::loadFallbackIcon : fallback icon is Null"; QPixmap pixmap(size().toSize() * window()->devicePixelRatio()); pixmap.fill(Qt::transparent); return pixmap.toImage(); } if (!d->fallbackIconLoaded) { applyVariantToIcon(d->fallbackSource, d->fallbackIcon); d->fallbackIconLoaded = true; } if (!d->fallbackIcon.isNull()) { return d->fallbackIcon.pixmap(size().toSize() * window()->devicePixelRatio()).toImage(); } qWarning() << "Icon::loadFallbackIcon : failed to load fallback icon from" << d->fallbackSource; QPixmap pixmap(size().toSize() * window()->devicePixelRatio()); pixmap.fill(Qt::transparent); return pixmap.toImage(); } bool Icon::forceRound() const { return d->forceRound; } void Icon::setForceRound(bool forceRound) { if (d->forceRound == forceRound) { return; } d->forceRound = forceRound; markTextureChanged(); Q_EMIT forceRoundChanged(); } Icon::Mode Icon::mode() const { return d->mode; } void Icon::setMode(const Icon::Mode &mode) { if (d->mode == mode) { return; } d->mode = mode; if (d->mode & Icon::AutoHighlight) { updateMode(); connect(ThemeGlobalConfig::instance(), &ThemeGlobalConfig::styleColorChanged, this, &Icon::updateMode, Qt::UniqueConnection); connect(ThemeGlobalConfig::instance(), &ThemeGlobalConfig::themeNameChanged, this, &Icon::updateMode, Qt::UniqueConnection); } else { disconnect(ThemeGlobalConfig::instance(), &ThemeGlobalConfig::styleColorChanged, this, &Icon::updateMode); disconnect(ThemeGlobalConfig::instance(), &ThemeGlobalConfig::themeNameChanged, this, &Icon::updateMode); markTextureChanged(); } Q_EMIT modeChanged(); } Theme::ColorRole Icon::highlightColor() const { return d->highlightColor; } void Icon::setHighlightColor(const Theme::ColorRole &color) { if (d->highlightColor == color) { return; } d->highlightColor = color; markTextureChanged(); Q_EMIT highlightColorChanged(); } GradientColor* Icon::dtThemeHighlightColor() const { return d->dtThemeHighlightColor; } void Icon::setDtThemeHighlightColor(GradientColor *color) { if(d->dtThemeHighlightColor == nullptr && isComponentComplete()) { disconnect(DtTheme::self(qmlEngine(this)), &DtTheme::highlightedTextActiveChanged, this, &Icon::markTextureChanged); } if(color != nullptr && d->dtThemeHighlightColor != color) { d->dtThemeHighlightColor = color; markTextureChanged(); Q_EMIT dtThemeHighlightColorChanged(); } } int Icon::radius() const { return d->radius; } void Icon::setRadius(int radius) { if (d->radius == radius) { return; } d->radius = radius; markTextureChanged(); Q_EMIT radiusChanged(); } Icon::PointType Icon::pointType() const { return d->pointType; } void Icon::setPointType(Icon::PointType type) { if (d->pointType == type) { return; } d->pointType = type; markTextureChanged(); Q_EMIT pointTypeChanged(); } Theme::ColorRole Icon::pointColor() const { return d->pointColor; } void Icon::setPointColor(const Theme::ColorRole &color) { if (d->pointColor == color) { return; } d->pointColor = color; markTextureChanged(); Q_EMIT pointColorChanged(); } QString Icon::pointText() const { return d->pointText; } void Icon::setPointText(const QString &text) { if (d->pointText == text) { return; } d->pointText = text; markTextureChanged(); Q_EMIT pointTextChanged(); } Types::Pos Icon::pointPos() const { return d->pointPos; } void Icon::setPointPos(Types::Pos pos) { if (d->pointPos == pos) { return; } d->pointPos = pos; markTextureChanged(); Q_EMIT pointPosChanged(); } bool Icon::addUnifiedIconBox() const { return d->addUnifiedIconBox; } void Icon::setAddUnifiedIconBox(bool addUnifiedIconBox) { if(d->addUnifiedIconBox == addUnifiedIconBox) { return; } d->addUnifiedIconBox = addUnifiedIconBox; if (d->addUnifiedIconBox) { IconBackgroundUtils::instance()->setIconThemeName(ThemeGlobalConfig::instance()->iconThemeName()); QImage().swap(d->imageCache); } markTextureChanged(); Q_EMIT addUnifiedIconBoxChanged(); } inline void Icon::markTextureChanged() { d->textureChanged = true; update(); } ApplicationIconProxy* Icon::proxy() { if (d->proxy == nullptr) { d->proxy = new ApplicationIconProxy(this); updateSourceFromProxy(); connect(d->proxy, &ApplicationIconProxy::iconChanged, this, &Icon::updateSourceFromProxy); } return d->proxy; } void Icon::updateMode() { if (ThemeGlobalConfig::instance()->isDarkTheme()) { d->mode |= Icon::Highlight; } else { d->mode &= ~Icon::Highlight; } markTextureChanged(); } void Icon::updateSourceFromProxy() { if (!d->proxy->icon().isEmpty()) { d->icon = IconHelper::loadIcon(d->proxy->icon()); if (!d->icon.isNull()) { markTextureChanged(); } } } void Icon::onIconThemeNameChanged() { if (d->addUnifiedIconBox) { IconBackgroundUtils::instance()->setIconThemeName(ThemeGlobalConfig::instance()->iconThemeName()); QImage().swap(d->imageCache); } markTextureChanged(); } void Icon::componentComplete() { QQuickItem::componentComplete(); if(d->dtThemeHighlightColor == nullptr) { connect(DtTheme::self(qmlEngine(this)), &DtTheme::highlightedTextActiveChanged, this, &Icon::markTextureChanged); } connect(ThemeGlobalConfig::instance(), &ThemeGlobalConfig::iconThemeNameChanged, this, &Icon::onIconThemeNameChanged); } void Icon::itemChange(ItemChange change, const ItemChangeData &value) { if (change == ItemDevicePixelRatioHasChanged) markTextureChanged(); QQuickItem::itemChange(change, value); } QImage Icon::addIconBox(QImage &image) { if(image.isNull()) { return image; } QImage backImage = IconBackgroundUtils::instance()->iconBackground().pixmap(size().toSize()).toImage(); if(backImage.isNull()) { return image; } QImage backImageNoShadow = IconBackgroundUtils::instance()->iconBackgroundWithoutShadow().pixmap(size().toSize()).toImage(); QImage backImageEffectArea = getEffectiveArea(backImageNoShadow); //根据计算出来的非透明点找到有效区域并截取出来 QImage effectiveArea = getEffectiveArea(image); if (image != d->imageCache) { getImageType(effectiveArea, image, backImage.width(), backImageEffectArea.width()); d->imageCache = image; } bool cliped = false; switch (d->imageType) { //第一方图标和未定义类型图标不作处理 case Icon::Uninitialized : case Icon::FirstParty : { return image; } //不是正方形的图标,按比例将图标有效区域缩放到视觉中心区域并居中 case Icon::RectangleHorizontal : { effectiveArea = effectiveArea.scaledToWidth(backImage.width() * IconBackgroundUtils::instance()->centerAreaRate(), Qt::SmoothTransformation); break; } case Icon::RectangleVertical : { effectiveArea = effectiveArea.scaledToHeight(backImage.height() * IconBackgroundUtils::instance()->centerAreaRate(), Qt::SmoothTransformation); break; } //有底图标按照第一方规范裁剪圆角并缩放至背景区域大小 case Icon::RectangleWithBack : { if(IconBackgroundUtils::instance()->containShadow(image.width())) { effectiveArea = clipBackground(effectiveArea, backImageNoShadow, backImageEffectArea.rect()); cliped = true; } else { effectiveArea = effectiveArea.scaledToWidth(backImage.width() * backImageEffectArea.width() / backImageNoShadow.width(), Qt::SmoothTransformation); if(backImage.width() < 24) { if(effectiveArea.format() != QImage::Format_ARGB32) { effectiveArea = effectiveArea.convertToFormat(QImage::Format_ARGB32); } QHash, int> imageAlphaHash = getTransparentPoint(backImageEffectArea); QHash, int>::const_iterator it; for(it = imageAlphaHash.constBegin();it != imageAlphaHash.constEnd();it++) { QColor color = effectiveArea.pixelColor(it.key().first, it.key().second); color.setAlpha(it.value()); effectiveArea.setPixelColor(it.key().first, it.key().second, color); } } } break; } //默认将图标有效区域缩减到背景图的视觉中心区域 default: case Icon::Other : { effectiveArea = effectiveArea.scaledToWidth(backImage.width() * IconBackgroundUtils::instance()->centerAreaRate(), Qt::SmoothTransformation); break; } } qreal dpr = backImage.devicePixelRatio(); backImage.setDevicePixelRatio(1); QPainter painter(&backImage); painter.setCompositionMode(QPainter::CompositionMode_SourceAtop); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); if (!cliped) { qreal offsetX = IconBackgroundUtils::instance()->offsetX(backImage.width()) * backImage.width(); qreal offsetY = IconBackgroundUtils::instance()->offsetY(backImage.width()) * backImage.height(); painter.translate(offsetX, offsetY); } //将处理好的图标有效区域绘制到统一背景 painter.drawImage(QRectF(static_cast(backImage.width() - effectiveArea.width()) / 2, static_cast(backImage.height() - effectiveArea.height()) / 2,effectiveArea.width(), effectiveArea.height()), effectiveArea); painter.end(); backImage.setDevicePixelRatio(dpr); return backImage; } QImage Icon::clipBackground(QImage &image, QImage targetImage, QRectF targetRect) { image = image.scaledToWidth(targetRect.width(),Qt::SmoothTransformation); qreal offsetX = IconBackgroundUtils::instance()->offsetX(targetImage.width()) * targetImage.width(); qreal offsetY = IconBackgroundUtils::instance()->offsetY(targetImage.width()) * targetImage.height(); qreal dpr = targetImage.devicePixelRatio(); targetImage.setDevicePixelRatio(1); QPainter painter(&targetImage); painter.translate(offsetX, offsetY); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); painter.setCompositionMode(QPainter::CompositionMode_SourceIn); painter.drawImage(QRectF(static_cast(targetImage.width() - image.width()) / 2, static_cast(targetImage.height() - image.height()) / 2, image.width(), image.height()), image); painter.end(); targetImage.setDevicePixelRatio(dpr); return targetImage; } bool Icon::isTransparencyBackground(QImage image) { if (image.isNull()) { return false; } //找到图标有效区域四个定点的第一个不透明点 QPoint topLeft; QPoint topRight; QPoint leftTop; QPoint leftBottom; QPoint rightTop; QPoint rightBottom; QPoint bottomLeft; QPoint bottomRight; bool getMax = false; bool getMin = false; for (int y = 0; y < image.height(); ++y) { for (int x = 0; x < image.width(); ++x) { if (!getMin && qAlpha(image.pixel(x, y)) > 20) { topLeft.setX(x); topLeft.setY(y); getMin = true; } if (!getMax && qAlpha(image.pixel(image.width() - x - 1, y )) > 20) { topRight.setX(image.width() - x - 1); topRight.setY(y); getMax = true; } if(getMax && getMin) { break; } } if(getMax && getMin) { break; } } getMax = false; getMin = false; for (int y = image.height() - 1; y > 0; --y) { for (int x = 0; x < image.width(); ++x) { if (!getMin && qAlpha(image.pixel(x, y)) > 20) { bottomLeft.setX(x); bottomLeft.setY(y); getMin = true; } if (!getMax && qAlpha(image.pixel(image.width() - x - 1, y )) > 20) { bottomRight.setX(image.width() - x - 1); bottomRight.setY(y); getMax = true; } if(getMax && getMin) { break; } } if(getMax && getMin) { break; } } getMax = false; getMin = false; for (int x = 0; x < image.width(); ++x) { for (int y = 0; y < image.height(); ++y) { if (!getMin && qAlpha(image.pixel(x, y)) > 20) { leftTop.setX(x); leftTop.setY(y); getMin = true; } if (!getMax && qAlpha(image.pixel(x, image.height() - y - 1)) > 20) { leftBottom.setX(x ); leftBottom.setY(image.height() - y - 1); getMax = true; } if(getMax && getMin) { break; } } if(getMax && getMin) { break; } } getMax = false; getMin = false; for (int x = image.width() - 1; x > 0; --x) { for (int y = 0; y < image.height(); ++y) { if (!getMin && qAlpha(image.pixel(x, y)) > 20) { rightTop.setX(x); rightTop.setY(y); getMin = true; } if (!getMax && qAlpha(image.pixel(x, image.height() - y - 1)) > 20) { rightBottom.setX(x); rightBottom.setY(image.height() - y - 1); getMax = true; } if(getMax && getMin) { break; } } if(getMax && getMin) { break; } } //如果这四个点组成的4条线不平行或不相等则认为无底 if (topLeft.y() != topRight.y() || leftTop.x() != leftBottom.x() || bottomLeft.y() != bottomRight.y() || rightTop.x() != rightBottom.x()) { return true; } auto threshold = image.width() >= 32? 1 : 0; if (qAbs((topRight.x() - topLeft.x()) - (bottomRight.x() - bottomLeft.x())) > threshold || qAbs((leftBottom.y() - leftTop.y()) - (rightBottom.y() - rightTop.y())) > threshold) { return true; } int topOpaquePointCount = 0; int bottomOpaquePointCount = 0; int leftOpaquePointCount = 0; int rightOpaquePointCount = 0; for (int y = leftTop.y(); y <= leftBottom.y(); ++y) { if (qAlpha(image.pixel(leftTop.x(), y)) > 25) { ++leftOpaquePointCount; } } for (int x = topLeft.x(); x <= topRight.x(); ++x) { if (qAlpha(image.pixel(x, topLeft.y())) > 25) { ++topOpaquePointCount; } } for (int y = rightTop.y(); y <= rightBottom.y(); ++y) { if (qAlpha(image.pixel(rightTop.x(), y)) > 25) { ++rightOpaquePointCount; } } for (int x = bottomLeft.x(); x <= bottomRight.x(); ++x) { if (qAlpha(image.pixel(x, bottomLeft.y())) > 25) { ++bottomOpaquePointCount; } } //如果这四条线上的不透明点小于58%则认为无底 (58%来自一个典型的圆形图标 某歌浏览器) if (topOpaquePointCount < image.width() * 0.58 || bottomOpaquePointCount < image.width() * 0.58 || leftOpaquePointCount < image.height() * 0.58 || rightOpaquePointCount < image.height() * 0.58) { return true; } return false; } bool Icon::isFirstPartyIcon(const QImage &effectArea, int size) { //根据当前主题获取第一方图标的欧氏距离 QVarLengthArray distance; QSize itemSize = this->size().toSize(); QImage image; if (d->icon.availableSizes().contains(itemSize) || d->icon.actualSize(itemSize) != itemSize) { distance = IconBackgroundUtils::instance()->outLineDistance(size); image = effectArea.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation); } else { distance = IconBackgroundUtils::instance()->outLineDistance(512); image = getEffectiveArea(d->icon.pixmap(512).toImage()).scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation); } int contradictoryCount = 0; int transparencyAlpha = size >= 64 ? 0 : 3; //找到图片的最外层不透明边界,计算欧氏距离并和第一方图标模板进行比较,总体误差低于百分之10认为是第一方图标 int centerX = image.width()/2 - 1; int centerY = image.height()/2 - 1; const uchar *pixels = image.constBits(); const int bytesPerLine = image.bytesPerLine(); // 每行字节数 for(int x = 0; x < image.width(); x++) { for(int y = 0; y < image.height(); y++) { const uchar *line = pixels + y * bytesPerLine; if(qAlpha(*reinterpret_cast(line + x * 4)) > transparencyAlpha) { qreal dTemp = qSqrt(qPow(x - centerX, 2) + qPow(y - centerY, 2)); if(qAbs(distance[x] - dTemp) > 2) { ++contradictoryCount; } break; } } } for(int x = 0; x < image.width(); ++x) { for(int y = image.height() - 1; y >= 0; --y) { const uchar *line = pixels + y * bytesPerLine; if(qAlpha(*reinterpret_cast(line + x * 4)) > transparencyAlpha) { qreal dTemp = qSqrt(qPow(x - centerX, 2) + qPow(y - centerY, 2)); if(qAbs(distance[x + 64] - dTemp) > 2) { ++contradictoryCount; } break; } } } for(int y = 0; y < image.height(); ++y) { for(int x = 0; x < image.width(); ++x) { const uchar *line = pixels + y * bytesPerLine; if(qAlpha(*reinterpret_cast(line + x * 4)) > transparencyAlpha) { qreal dTemp = qSqrt(qPow(x - centerX, 2) + qPow(y - centerY, 2)); if(qAbs(distance[y + 128] - dTemp) > 2) { ++contradictoryCount; } break; } } } for(int y = 0; y < image.height(); ++y) { for(int x = image.width() - 1; x >= 0; --x) { const uchar *line = pixels + y * bytesPerLine; if(qAlpha(*reinterpret_cast(line + x * 4)) > transparencyAlpha) { qreal dTemp = qSqrt(qPow(x - centerX, 2) + qPow(y - centerY, 2)); if(qAbs(distance[y + 192] - dTemp) > 2) { ++contradictoryCount; } break; } } } return contradictoryCount <= 10; } void Icon::getImageType(const QImage &effectArea, const QImage &image, int imageSize, int standardEffectAreaSize) { if(effectArea.isNull()) { d->imageType = Icon::Uninitialized; } if (isFirstPartyIcon(effectArea, imageSize)) { if (!IconBackgroundUtils::instance()->containShadow(imageSize) && !(effectArea.width() == standardEffectAreaSize && effectArea.height() == effectArea.width())) { if(!isTransparencyBackground(image)) { d->imageType = RectangleWithBack; } else { d->imageType = Other; } return; } d->imageType = Icon::FirstParty; return; } if(qAbs(effectArea.width() - effectArea.height()) >= 2) { if(effectArea.width() > effectArea.height() + 1) { d->imageType = Icon::RectangleHorizontal; } else if (effectArea.width() + 1 < effectArea.height()) { d->imageType = Icon::RectangleVertical; } } else if(!isTransparencyBackground(image)) { d->imageType = Icon::RectangleWithBack; } else { d->imageType = Icon::Other; } } QImage Icon::getEffectiveArea(const QImage &image) { //从上到下,从下到上,从左到右,从右到左,找到图片中第一个非透明像素点 QPoint top(0,0), right(0,0), left(0,0), bottom(0,0); bool getMax = false; bool getMin = false; const uchar *pixels = image.constBits(); const int bytesPerLine = image.bytesPerLine(); // 每行字节数 int transparencyAlpha = IconBackgroundUtils::instance()->containShadow(image.width()) ? 2 : 4; for (int y = 0; y < image.height(); ++y) { const uchar *lineTop = pixels + y * bytesPerLine; const uchar *lineBottom = pixels + (image.height() - y - 1) * bytesPerLine; for (int x = 0; x < image.width(); ++x) { if (!getMax && qAlpha(*reinterpret_cast(lineTop + x * 4)) > transparencyAlpha) { top.setX(x); top.setY(y); getMax = true; } if (!getMin && qAlpha(*reinterpret_cast(lineBottom + x * 4)) > transparencyAlpha) { bottom.setX(x); bottom.setY(image.height() - y - 1); getMin = true; } if(getMax && getMin) { break; } } if(getMax && getMin) { break; } } getMax = false; getMin = false; for (int x = 0; x < image.width(); ++x) { for (int y = 0; y < image.height(); ++y) { const uchar *line = pixels + y * bytesPerLine; if (!getMax && qAlpha(*reinterpret_cast(line + x * 4)) > transparencyAlpha) { left.setX(x); left.setY(y); getMax = true; } if (!getMin && qAlpha(*reinterpret_cast(line + (image.width() - x - 1) * 4)) > transparencyAlpha) { right.setX(image.width() - x - 1); right.setY(y); getMin = true; } if(getMax && getMin) { break; } } if(getMax && getMin) { break; } } //在非透明像素点中找到x和y的最大值和最小值 auto xMax = std::max({top.x(),bottom.x(),left.x(),right.x()}); auto xMin = std::min({top.x(),bottom.x(),left.x(),right.x()}); auto yMax = std::max({top.y(),bottom.y(),left.y(),right.y()}); auto yMin = std::min({top.y(),bottom.y(),left.y(),right.y()}); //如果为透明图片,返回原图 if(xMax == 0 || yMax == 0) { return image; } //根据计算出来的非透明点找到有效区域并截取出来 return image.copy(xMin, yMin, xMax - xMin + 1, yMax - yMin + 1); } bool Icon::isImagePureColor(QImage &image) { if(image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } const uchar *pixels = image.constBits(); const int bytesPerLine = image.bytesPerLine(); // 每行字节数 bool isPureColor = true; QVector vector; int total_red = 0; int total_green = 0; int total_blue = 0; int symbolicRed = d->symbolicColor.red(); int symbolicGreen = d->symbolicColor.green(); int symbolicBlue = d->symbolicColor.blue(); for(int y = 0; y < image.height(); y++) { const uchar *line = pixels + y * bytesPerLine; for(int x = 0; x < image.width(); x++) { const QRgb* color = reinterpret_cast(line + x * 4); if(qAlpha(*color) > 0.3 * 256) { int red = qRed(*color); int green = qGreen(*color); int blue = qBlue(*color); vector.append(*color); total_red += red; total_green += green; total_blue += blue; if(!isPureColor) { continue; } if(qAbs(symbolicRed - red) > COLOR_DIFFERENCE || qAbs(symbolicGreen - green) > COLOR_DIFFERENCE || qAbs(symbolicBlue - blue) > COLOR_DIFFERENCE) { isPureColor = false; } } } } if(isPureColor || vector.count() == 0) { return true; } qreal squareRoot_red = 0; qreal squareRoot_green = 0; qreal squareRoot_blue = 0; qreal average_red = total_red / vector.count(); qreal average_green = total_green / vector.count(); qreal average_blue = total_blue / vector.count(); for (QRgb rgb : vector) { squareRoot_red += qPow(qRed(rgb) - average_red, 2); squareRoot_green += qPow(qGreen(rgb) - average_green, 2); squareRoot_blue += qPow(qBlue(rgb) - average_blue, 2); } qreal arithmeticSquareRoot_red = qSqrt(squareRoot_red / vector.count()); qreal arithmeticSquareRoot_green = qSqrt(squareRoot_green / vector.count()); qreal arithmeticSquareRoot_blue = qSqrt(squareRoot_blue / vector.count()); return arithmeticSquareRoot_red < 2.0 && arithmeticSquareRoot_green < 2.0 && arithmeticSquareRoot_blue < 2.0; } QHash, int> Icon::getTransparentPoint(const QImage &image) { QHash, int> imageAlphaList; const uchar *pixels = image.constBits(); int bytesPerLine = image.bytesPerLine(); for (int y = 0; y < image.height(); y++) { const QRgb* line = reinterpret_cast(pixels + y * bytesPerLine); for (int x = 0; x < image.width(); x++) { const QRgb &color = line[x]; if (qAlpha(color) != 255) { imageAlphaList.insert(QPair(x, y),qAlpha(color)); } } } return imageAlphaList; } } // UkuiQuick ukui-quick/items/ukui-quick-items-plugin.cpp0000664000175000017500000001653115153756415020140 0ustar fengfeng/* * * Copyright (C) 2023, KylinSoft Co., Ltd. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program 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 * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * * Authors: iaom * */ #include "ukui-quick-items-plugin.h" #include #include #include #include "margin.h" #include "types.h" #include "menu.h" #include "menu-item.h" #include "theme-icon.h" #include "tooltip-proxy.h" #include "icon.h" #include "icon-provider.h" #include "color-mixer.h" #include "content-window.h" #include "windows/dialog.h" #include "tooltip.h" #include "action-extension.h" #include "window-blur-behind.h" #include "gradient-border-item.h" #include "line-edit-context-menu.h" #include "shadowed-texture.h" #include "ukui/application-icon-proxy.h" void UkuiQuickItemsPlugin::registerTypes(const char *uri) { Q_ASSERT(QLatin1String(uri) == QLatin1String(PLUGIN_IMPORT_URI)); qmlRegisterModule(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); // value types qmlRegisterType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "Icon"); qmlRegisterType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "Menu"); qmlRegisterType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "MenuItem"); qmlRegisterType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "ThemeIcon"); qmlRegisterType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "ColorMix"); qmlRegisterType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "GradientBorderItem"); qmlRegisterType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "LineEditContextMenu"); qmlRegisterUncreatableType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "ToolTip", "Proxy tool pointing to QTooltip, accessed only through Attached property \"ToolTip\"."); qmlRegisterType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "Dialog"); qmlRegisterUncreatableType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "WindowProxy", "WindowProxy is a read-only interface used to access enumeration properties."); qmlRegisterUncreatableType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "SlideWindow", QStringLiteral("Used as grouped property")); qmlRegisterType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "Tooltip"); qmlRegisterType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "ContentWindow"); // 注册QAction并为它扩展属性 qmlRegisterExtendedType(uri, 1, 0, "Action"); qmlRegisterType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "RectRegion"); qmlRegisterType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "WindowBlurBehind"); qmlRegisterType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "ShadowedRectangle"); qmlRegisterType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "ShadowedTexture"); qmlRegisterUncreatableType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "BorderGroup", QStringLiteral("Used as grouped property")); qmlRegisterUncreatableType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "ShadowGroup", QStringLiteral("Used as grouped property")); qmlRegisterUncreatableType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "CornersGroup", QStringLiteral("Used as grouped property")); qmlRegisterUncreatableType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "ApplicationIconProxy", QStringLiteral("Used as grouped property")); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) qmlRegisterUncreatableType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "Types", "Types is a read-only interface used to access enumeration properties."); qmlRegisterUncreatableType(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "Margin", "Margin is a read-only attribute that can be accessed from the ContentWindow."); #else qmlRegisterUncreatableMetaObject(UkuiQuick::Types::staticMetaObject, uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "Types", "Types is a read-only interface used to access enumeration properties."); qmlRegisterUncreatableMetaObject(UkuiQuick::Margin::staticMetaObject, uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR, "Margin", "Margin is a read-only interface used to access enumeration properties."); #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) qmlRegisterRevision(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR); qmlRegisterRevision(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR); #else #if QT_VERSION >= QT_VERSION_CHECK(5, 3, 0) qmlRegisterRevision(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR); qmlRegisterRevision(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR); #else #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) qmlRegisterRevision(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR); qmlRegisterRevision(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR); #else qmlRegisterRevision(uri, PLUGIN_VERSION_MAJOR, PLUGIN_VERSION_MINOR); #endif #endif #endif } void UkuiQuickItemsPlugin::initializeEngine(QQmlEngine *engine, const char *uri) { Q_ASSERT(QLatin1String(uri) == QLatin1String(PLUGIN_IMPORT_URI)); engine->addImageProvider("theme", new UkuiQuick::IconProvider()); installTranslations(); } UkuiQuickItemsPlugin::~UkuiQuickItemsPlugin() { // 检查QCoreApplication是否仍然有效,防止在程序退出时访问已销毁的资源 if (QCoreApplication::instance() && m_translator != nullptr) { QCoreApplication::removeTranslator(m_translator); delete m_translator; m_translator = nullptr; } else if (m_translator != nullptr) { // 如果QCoreApplication已经无效,跳过避免崩溃 m_translator = nullptr; } } void UkuiQuickItemsPlugin::installTranslations() { const QString fileName = PLUGIN_INSTALL_TRANSLATIONS_PATH + QLocale::system().name(); m_translator = new QTranslator(QCoreApplication::instance()); if (m_translator->load(fileName)) { QCoreApplication::installTranslator(m_translator); } else { delete m_translator; m_translator = nullptr; } } ukui-quick/items/tooltip-proxy.cpp0000664000175000017500000000311715153755732016304 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "tooltip-proxy.h" #include #include namespace UkuiQuick { ToolTip::ToolTip(QObject *parent) : QObject(parent) { } QString ToolTip::text() const { return m_text; } void ToolTip::setText(const QString &text) { if (text == m_text) { return; } m_text = text; Q_EMIT textChanged(); } void ToolTip::show(int x, int y) { show(QPoint(x, y)); } void ToolTip::show(QPointF point) { show(QPoint(point.x(), point.y())); } void ToolTip::show(QPoint point) { if (point.isNull()) { QToolTip::showText(QCursor::pos(), m_text); } else { QToolTip::showText(point, m_text); } } void ToolTip::hide() { QToolTip::hideText(); } // ====== ToolTipAttached ====== // ToolTip *ToolTipAttached::qmlAttachedProperties(QObject *object) { return new ToolTip(object); } } // UkuiQuick ukui-quick/items/color-mixer.cpp0000664000175000017500000001070215153756415015670 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #include "color-mixer.h" namespace UkuiQuick { ColorMix::ColorMix(QObject *parent) : QObject(parent) { connect(Theme::instance(), &Theme::paletteChanged, this, &ColorMix::updateColor); connect(Theme::instance(), &Theme::themeTransparencyChanged, this, [this] { // 不在[0, 1]范围内,跟随主题透明度 if (m_backColorAlpha < 0 || m_backColorAlpha > 1 || m_foreColorAlpha < 0 || m_foreColorAlpha > 1) { updateColor(); } }); } QColor ColorMix::color() { return m_color; } ColorMix::MixMode ColorMix::mixMode() const { return m_mixMode; } void ColorMix::setMixMode(ColorMix::MixMode mixMode) { if (m_mixMode == mixMode) { return; } m_mixMode = mixMode; updateColor(); Q_EMIT mixModeChanged(); } void ColorMix::updateColor() { if (m_mixMode == NoMixer) { m_color = ColorMix::backColor(); } else { QColor fore = ColorMix::foreColor(); QColor back = ColorMix::backColor(); switch (m_mixMode) { default: case Normal: { qreal tiny = 1 - fore.alphaF(); qreal alpha = fore.alphaF() + back.alphaF() * tiny; qreal r = (fore.redF() * fore.alphaF() + back.redF() * back.alphaF() * tiny) / alpha; qreal g = (fore.greenF() * fore.alphaF() + back.greenF() * back.alphaF() * tiny) / alpha; qreal b = (fore.blueF() * fore.alphaF() + back.blueF() * back.alphaF() * tiny) / alpha; m_color = QColor::fromRgbF(r, g, b, alpha); break; } case Lighten: break; case Darken: break; case Deepen: break; case Dodge: break; case Overlay: break; case Exclusion: break; } } Q_EMIT colorChanged(); } Theme::ColorRole ColorMix::backColorRole() const { return m_backColorRole; } void ColorMix::setBackColorRole(Theme::ColorRole role) { if (m_backColorRole == role) { return; } m_backColorRole = role; updateColor(); } Theme::ColorRole ColorMix::foreColorRole() const { return m_foreColorRole; } void ColorMix::setForeColorRole(Theme::ColorRole role) { if (m_foreColorRole == role) { return; } m_foreColorRole = role; updateColor(); } Theme::ColorGroup ColorMix::backColorGroup() const { return m_backColorGroup; } void ColorMix::setBackColorGroup(Theme::ColorGroup group) { if (m_backColorGroup == group) { return; } m_backColorGroup = group; updateColor(); } Theme::ColorGroup ColorMix::foreColorGroup() const { return m_foreColorGroup; } void ColorMix::setForeColorGroup(Theme::ColorGroup group) { if (m_foreColorGroup == group) { return; } m_foreColorGroup = group; updateColor(); } qreal ColorMix::backColorAlpha() const { return m_backColorAlpha; } void ColorMix::setBackColorAlpha(qreal alpha) { if (m_backColorAlpha == alpha) { return; } m_backColorAlpha = alpha; updateColor(); } qreal ColorMix::foreColorAlpha() const { return m_foreColorAlpha; } void ColorMix::setForeColorAlpha(qreal alpha) { if (m_foreColorAlpha == alpha) { return; } m_foreColorAlpha = alpha; updateColor(); } QColor ColorMix::backColor() { return Theme::instance()->color(m_backColorRole, m_backColorGroup, m_backColorAlpha); } QColor ColorMix::foreColor() { return Theme::instance()->color(m_foreColorRole, m_foreColorGroup, m_foreColorAlpha); } } // UkuiQuick ukui-quick/items/color-mixer.h0000664000175000017500000000743515153755732015347 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: hxf * */ #ifndef UKUI_QUICK_ITEMS_COLOR_MIXER_H #define UKUI_QUICK_ITEMS_COLOR_MIXER_H #include "ukui/ukui-theme-proxy.h" #include #include namespace UkuiQuick { /** * @class ColorMix * * 通过设置前景色和背景色,根据特定的模式生成新的颜色。 * 颜色与主题色进行绑定,自动跟随主题切换。 * * 前景色,背景色,混合模式 */ class ColorMix : public QObject { Q_OBJECT Q_PROPERTY(QColor color READ color NOTIFY colorChanged) Q_PROPERTY(UkuiQuick::ColorMix::MixMode mixMode READ mixMode WRITE setMixMode NOTIFY mixModeChanged) Q_PROPERTY(qreal backColorAlpha READ backColorAlpha WRITE setBackColorAlpha) Q_PROPERTY(qreal foreColorAlpha READ foreColorAlpha WRITE setForeColorAlpha) Q_PROPERTY(UkuiQuick::Theme::ColorRole backColorRole READ backColorRole WRITE setBackColorRole) Q_PROPERTY(UkuiQuick::Theme::ColorRole foreColorRole READ foreColorRole WRITE setForeColorRole) Q_PROPERTY(UkuiQuick::Theme::ColorGroup backColorGroup READ backColorGroup WRITE setBackColorGroup) Q_PROPERTY(UkuiQuick::Theme::ColorGroup foreColorGroup READ foreColorGroup WRITE setForeColorGroup) public: enum MixMode { NoMixer = 0,/**> 禁用混合 */ Normal, /**> 普通混合 */ Lighten, /**> 变亮 */ Darken, /**> 变暗 */ Deepen, /**> 加深 */ Dodge, /**> 减淡 */ Overlay, /**> 叠加 */ Exclusion /**> 排除 */ }; Q_ENUM(MixMode) explicit ColorMix(QObject *parent = nullptr); QColor color(); QColor backColor(); QColor foreColor(); qreal backColorAlpha() const; void setBackColorAlpha(qreal alpha); qreal foreColorAlpha() const; void setForeColorAlpha(qreal alpha); ColorMix::MixMode mixMode() const; void setMixMode(ColorMix::MixMode mixMode); Theme::ColorRole backColorRole() const; void setBackColorRole(Theme::ColorRole role); Theme::ColorRole foreColorRole() const; void setForeColorRole(Theme::ColorRole role); Theme::ColorGroup backColorGroup() const; void setBackColorGroup(Theme::ColorGroup group); Theme::ColorGroup foreColorGroup() const; void setForeColorGroup(Theme::ColorGroup group); Q_SIGNALS: void colorChanged(); void mixModeChanged(); private Q_SLOTS: void updateColor(); private: MixMode m_mixMode {NoMixer}; QColor m_color {Qt::white}; qreal m_backColorAlpha {0}; // 不在[0, 1]范围内,跟随主题透明度 Theme::ColorRole m_backColorRole {Theme::Base}; Theme::ColorGroup m_backColorGroup {Theme::Active}; qreal m_foreColorAlpha {0}; Theme::ColorRole m_foreColorRole {Theme::Base}; Theme::ColorGroup m_foreColorGroup {Theme::Active}; }; //class ColorMixerAttached : public QObject //{ // Q_OBJECT // public: // static ColorMix *qmlAttachedProperties(QObject *object); //}; } // UkuiQuick Q_DECLARE_METATYPE(UkuiQuick::ColorMix::MixMode) //QML_DECLARE_TYPEINFO(UkuiQuick::ColorMixerAttached, QML_HAS_ATTACHED_PROPERTIES) #endif //UKUI_QUICK_ITEMS_COLOR_MIXER_H ukui-quick/items/shadowed-texture.cpp0000664000175000017500000000424515153755732016732 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "shadowed-texture.h" #include #include #include #if QT_CONFIG(opengl) || QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include "shadowed-texture-node.h" #endif ShadowedTexture::ShadowedTexture(QQuickItem *parentItem) : ShadowedRectangle(parentItem) { } ShadowedTexture::~ShadowedTexture() { } QQuickItem *ShadowedTexture::source() const { return m_source; } void ShadowedTexture::setSource(QQuickItem *newSource) { if (newSource == m_source) { return; } m_source = newSource; m_sourceChanged = true; if (m_source && !m_source->parentItem()) { m_source->setParentItem(this); } if (!isSoftwareRendering()) { update(); } Q_EMIT sourceChanged(); } QSGNode *ShadowedTexture::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) { Q_UNUSED(data) #if QT_CONFIG(opengl) || QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) auto shadowNode = static_cast(node); if (!shadowNode || m_sourceChanged) { m_sourceChanged = false; delete shadowNode; if (m_source) { shadowNode = new ShadowedTextureNode{}; } else { shadowNode = new ShadowedRectangleNode{}; } } shadowNode->setBorderEnabled(border()->isEnabled()); shadowNode->setRect(boundingRect()); shadowNode->setSize(shadow()->size()); shadowNode->setRadius(corners()->toVector4D(radius())); shadowNode->setOffset(QVector2D{float(shadow()->xOffset()), float(shadow()->yOffset())}); shadowNode->setColor(color()); shadowNode->setShadowColor(shadow()->color()); shadowNode->setBorderWidth(border()->width()); shadowNode->setBorderColor(border()->color()); if (m_source) { static_cast(shadowNode)->setTextureSource(m_source->textureProvider()); } shadowNode->updateGeometry(); return shadowNode; #else Q_UNUSED(node) return nullptr; #endif } ukui-quick/items/menu-item.h0000664000175000017500000000422015153755732014774 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: qiqi49 * */ #ifndef UKUI_QUICK_ITEMS_MENU_ITEM_H #define UKUI_QUICK_ITEMS_MENU_ITEM_H #include #include namespace UkuiQuick { class MenuItem : public QObject { Q_OBJECT Q_PROPERTY(QObject *parent READ parent WRITE setParent) Q_PROPERTY(QAction *action READ action WRITE setAction NOTIFY actionChanged) Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) Q_PROPERTY(QString icon READ icon WRITE setIcon NOTIFY iconChanged) Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) Q_PROPERTY(bool checkable READ checkable WRITE setCheckable NOTIFY checkableChanged) Q_PROPERTY(bool checked READ checked WRITE setChecked NOTIFY toggled) public: explicit MenuItem(QObject *parent = nullptr); QAction *action() const; void setAction(QAction *action); QString icon() const; void setIcon(const QString &icon); QString text() const; void setText(const QString &text); bool checkable() const; void setCheckable(bool checkable); bool checked() const; void setChecked(bool checked); bool isEnabled() const; void setEnabled(bool enabled); Q_SIGNALS: void clicked(); void toggled(bool checked); void actionChanged(); void iconChanged(); void textChanged(); void checkableChanged(); void enabledChanged(); private: QAction *m_action = nullptr; }; } #endif //UKUI_QUICK_ITEMS_MENU_ITEM_H ukui-quick/items/line-edit-context-menu.h0000664000175000017500000000306415153755732017377 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: amingamingaming * */ #ifndef UKUI_QUICK_ITEMS_LINE_EDIT_CONTENT_MENU_H #define UKUI_QUICK_ITEMS_LINE_EDIT_CONTENT_MENU_H #include #include #include #include namespace UkuiQuick { class LineEditContextMenu : public QObject { Q_OBJECT Q_PROPERTY(QQmlListProperty actions READ qmlActions) public: explicit LineEditContextMenu(QObject *prent = nullptr); ~LineEditContextMenu(); QQmlListProperty qmlActions(); Q_INVOKABLE void show(int x = -1, int y = -1, QQuickItem* item = nullptr); Q_INVOKABLE void hide(); Q_INVOKABLE QString getShortCutString(QKeySequence::StandardKey shortcut); QList actions() const; private: QList m_actions; QMenu *m_menu = nullptr; }; } #endif //UKUI_QUICK_ITEMS_LINE_EDIT_CONTENT_MENU_H ukui-quick/items/icon-provider.cpp0000664000175000017500000000247015153755732016214 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "icon-provider.h" #include "icon-helper.h" namespace UkuiQuick { static QSize defaultSize = QSize(128, 128); IconProvider::IconProvider() : QQuickImageProvider(QQmlImageProviderBase::Pixmap) { } QPixmap IconProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) { QIcon icon = IconHelper::loadIcon(id); QPixmap pixmap = icon.pixmap(requestedSize.isEmpty() ? defaultSize : requestedSize); if (size) { QSize pixmapSize = pixmap.size(); size->setWidth(pixmapSize.width()); size->setHeight(pixmapSize.height()); } return pixmap; } } // UkuiQuick ukui-quick/items/shadowed-rectangle.cpp0000664000175000017500000002441215153756415017173 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "shadowed-rectangle.h" #include #include #include #include #include "painted-rectangle-item.h" #if QT_CONFIG(opengl) || QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include "shadowed-rectangle-node.h" #endif BorderGroup::BorderGroup(QObject *parent) : QObject(parent) { } qreal BorderGroup::width() const { return m_width; } void BorderGroup::setWidth(qreal newWidth) { if (newWidth == m_width) { return; } m_width = newWidth; Q_EMIT changed(); } QColor BorderGroup::color() const { return m_color; } void BorderGroup::setColor(const QColor &newColor) { if (newColor == m_color) { return; } m_color = newColor; Q_EMIT changed(); } ShadowGroup::ShadowGroup(QObject *parent) : QObject(parent) { } qreal ShadowGroup::size() const { return m_size; } void ShadowGroup::setSize(qreal newSize) { if (newSize == m_size) { return; } m_size = newSize; Q_EMIT changed(); } qreal ShadowGroup::xOffset() const { return m_xOffset; } void ShadowGroup::setXOffset(qreal newXOffset) { if (newXOffset == m_xOffset) { return; } m_xOffset = newXOffset; Q_EMIT changed(); } qreal ShadowGroup::yOffset() const { return m_yOffset; } void ShadowGroup::setYOffset(qreal newYOffset) { if (newYOffset == m_yOffset) { return; } m_yOffset = newYOffset; Q_EMIT changed(); } QColor ShadowGroup::color() const { return m_color; } void ShadowGroup::setColor(const QColor &newColor) { if (newColor == m_color) { return; } m_color = newColor; Q_EMIT changed(); } CornersGroup::CornersGroup(QObject *parent) : QObject(parent) { } qreal CornersGroup::topLeft() const { return m_topLeft; } void CornersGroup::setTopLeft(qreal newTopLeft) { if (newTopLeft == m_topLeft) { return; } m_topLeft = newTopLeft; Q_EMIT changed(); } qreal CornersGroup::topRight() const { return m_topRight; } void CornersGroup::setTopRight(qreal newTopRight) { if (newTopRight == m_topRight) { return; } m_topRight = newTopRight; Q_EMIT changed(); } qreal CornersGroup::bottomLeft() const { return m_bottomLeft; } void CornersGroup::setBottomLeft(qreal newBottomLeft) { if (newBottomLeft == m_bottomLeft) { return; } m_bottomLeft = newBottomLeft; Q_EMIT changed(); } qreal CornersGroup::bottomRight() const { return m_bottomRight; } void CornersGroup::setBottomRight(qreal newBottomRight) { if (newBottomRight == m_bottomRight) { return; } m_bottomRight = newBottomRight; Q_EMIT changed(); } QVector4D CornersGroup::toVector4D(float all) const { return QVector4D{m_bottomRight < 0.0 ? all : m_bottomRight, m_topRight < 0.0 ? all : m_topRight, m_bottomLeft < 0.0 ? all : m_bottomLeft, m_topLeft < 0.0 ? all : m_topLeft}; } ShadowedRectangle::ShadowedRectangle(QQuickItem *parentItem) : QQuickItem(parentItem) , m_border(std::make_unique()) , m_shadow(std::make_unique()) , m_corners(std::make_unique()) { setFlag(QQuickItem::ItemHasContents, true); connect(m_border.get(), &BorderGroup::changed, this, &ShadowedRectangle::update); connect(m_shadow.get(), &ShadowGroup::changed, this, &ShadowedRectangle::update); connect(m_corners.get(), &CornersGroup::changed, this, &ShadowedRectangle::update); //workaround connect(this, &QQuickItem::activeFocusOnTabChanged, this, &ShadowedRectangle::activeFocusOnTabChanged1); } ShadowedRectangle::~ShadowedRectangle() { } BorderGroup *ShadowedRectangle::border() const { return m_border.get(); } ShadowGroup *ShadowedRectangle::shadow() const { return m_shadow.get(); } CornersGroup *ShadowedRectangle::corners() const { return m_corners.get(); } qreal ShadowedRectangle::radius() const { return m_radius; } void ShadowedRectangle::setRadius(qreal newRadius) { if (newRadius == m_radius) { return; } m_radius = newRadius; if (!isSoftwareRendering()) { update(); } Q_EMIT radiusChanged(); } QColor ShadowedRectangle::color() const { return m_color; } void ShadowedRectangle::setColor(const QColor &newColor) { if (newColor == m_color) { return; } m_color = newColor; if (!isSoftwareRendering()) { update(); } Q_EMIT colorChanged(); } bool ShadowedRectangle::pureColor() const { return m_pureColor; } void ShadowedRectangle::setPureColor(bool pureColor) { if (pureColor == m_pureColor) { return; } m_pureColor = pureColor; if (!isSoftwareRendering()) { update(); } Q_EMIT pureColorChanged(); } QColor ShadowedRectangle::startColor() const { return m_startColor; } void ShadowedRectangle::setStartColor(const QColor& startColor) { if (startColor == m_startColor) { return; } m_startColor = startColor; if (!isSoftwareRendering()) { update(); } Q_EMIT startColorChanged(); } QColor ShadowedRectangle::endColor() const { return m_endColor; } void ShadowedRectangle::setEndColor(const QColor& endColor) { if (endColor == m_endColor) { return; } m_endColor = endColor; if (!isSoftwareRendering()) { update(); } Q_EMIT endColorChanged(); } qreal ShadowedRectangle::angle() const { return m_angle; } void ShadowedRectangle::setAngle(qreal newAngle) { auto deg = newAngle / 180.0 * M_PI; if (m_angle == deg) { return; } m_angle = deg; if (!isSoftwareRendering()) { update(); } Q_EMIT angleChanged(); } ShadowedRectangle::RenderType ShadowedRectangle::renderType() const { return m_renderType; } void ShadowedRectangle::setRenderType(RenderType renderType) { if (renderType == m_renderType) { return; } m_renderType = renderType; update(); Q_EMIT renderTypeChanged(); } void ShadowedRectangle::componentComplete() { QQuickItem::componentComplete(); checkSoftwareItem(); } bool ShadowedRectangle::isSoftwareRendering() const { return (window() && window()->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) || m_renderType == RenderType::Software; } PaintedRectangleItem *ShadowedRectangle::softwareItem() const { return m_softwareItem; } void ShadowedRectangle::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) { if (change == QQuickItem::ItemSceneChange && value.window) { checkSoftwareItem(); // TODO: only conditionally emit? Q_EMIT softwareRenderingChanged(); } QQuickItem::itemChange(change, value); } QSGNode *ShadowedRectangle::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) { Q_UNUSED(data); #if QT_CONFIG(opengl) || QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) if (boundingRect().isEmpty()) { delete node; return nullptr; } auto shadowNode = static_cast(node); if (!shadowNode) { shadowNode = new ShadowedRectangleNode{}; if (m_renderType == RenderType::LowQuality) { shadowNode->setShaderType(ShadowedRectangleMaterial::ShaderType::LowPower); } } shadowNode->setBorderEnabled(m_border->isEnabled()); shadowNode->setRect(boundingRect()); shadowNode->setSize(m_shadow->size()); shadowNode->setRadius(m_corners->toVector4D(m_radius)); shadowNode->setOffset(QVector2D{static_cast(m_shadow->xOffset()), static_cast(m_shadow->yOffset())}); shadowNode->setColor(m_color); shadowNode->setPureColor(m_pureColor); shadowNode->setStartColor(m_startColor); shadowNode->setEndColor(m_endColor); shadowNode->setAngle(m_angle); shadowNode->setShadowColor(m_shadow->color()); shadowNode->setBorderWidth(m_border->width()); shadowNode->setBorderColor(m_border->color()); shadowNode->updateGeometry(); return shadowNode; #else Q_UNUSED(node) return nullptr; #endif } void ShadowedRectangle::checkSoftwareItem() { if (!m_softwareItem && isSoftwareRendering()) { m_softwareItem = new PaintedRectangleItem{this}; // The software item is added as a "normal" child item, this means it // will be part of the normal item sort order. Since there is no way to // control the ordering of children, just make sure to have a very low Z // value for the child, to force it to be the lowest item. m_softwareItem->setZ(-99.0); auto updateItem = [this]() { auto borderWidth = m_border->width(); auto rect = boundingRect(); m_softwareItem->setSize(rect.size()); m_softwareItem->setColor(m_color); m_softwareItem->setRadius(m_corners->toVector4D(m_radius)); m_softwareItem->setPureColor(m_pureColor); m_softwareItem->setStartColor(m_startColor); m_softwareItem->setEndColor(m_endColor); m_softwareItem->setAngle(m_angle); m_softwareItem->setBorderWidth(borderWidth); m_softwareItem->setBorderColor(m_border->color()); }; updateItem(); connect(this, &ShadowedRectangle::widthChanged, m_softwareItem, updateItem); connect(this, &ShadowedRectangle::heightChanged, m_softwareItem, updateItem); connect(this, &ShadowedRectangle::colorChanged, m_softwareItem, updateItem); connect(this, &ShadowedRectangle::radiusChanged, m_softwareItem, updateItem); connect(this, &ShadowedRectangle::pureColorChanged, m_softwareItem, updateItem); connect(this, &ShadowedRectangle::startColorChanged, m_softwareItem, updateItem); connect(this, &ShadowedRectangle::endColorChanged, m_softwareItem, updateItem); connect(this, &ShadowedRectangle::angleChanged, m_softwareItem, updateItem); connect(m_border.get(), &BorderGroup::changed, m_softwareItem, updateItem); connect(m_corners.get(), &CornersGroup::changed, m_softwareItem, updateItem); setFlag(QQuickItem::ItemHasContents, false); } }ukui-quick/items/window-blur-behind.cpp0000664000175000017500000001115315153755732017132 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom * */ #include #include #include "window-blur-behind.h" namespace UkuiQuick { WindowBlurBehind::WindowBlurBehind(QQuickItem *parent) : QQuickItem(parent) { } QQmlListProperty WindowBlurBehind::rectRegions() { return {this, &m_rects, &WindowBlurBehind::appendRect, &WindowBlurBehind::rectCount, &WindowBlurBehind::rectAt, &WindowBlurBehind::clearRect, &WindowBlurBehind::replaceRect, &WindowBlurBehind::removeLastRect }; } void WindowBlurBehind::setEnable(bool enable) { if(m_enable == enable) { return; } m_enable = enable; updateBlurRegion(); Q_EMIT enableChanged(); } bool WindowBlurBehind::enable() const { return m_enable; } void WindowBlurBehind::updateBlurRegion() { if (!m_window || !m_windowProxy) { return; } if (m_enable) { QPainterPath path; for (const RectRegion *rect : m_rects) { path.addRoundedRect(rect->x(), rect->y(), rect->width(), rect->height(), rect->radius(), rect->radius()); } if (path.isEmpty()) { m_windowProxy->setBlurRegion(false); } else { m_windowProxy->setBlurRegion(true, QRegion(path.toFillPolygon().toPolygon())); } } else { m_windowProxy->setBlurRegion(false); } } void WindowBlurBehind::appendRect(QQmlListProperty *list, RectRegion *rect) { if (!rect) { return; } auto wbb = qobject_cast(list->object); if (!wbb->m_rects.contains(rect)) { wbb->m_rects.append(rect); wbb->updateBlurRegion(); } } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) qsizetype WindowBlurBehind::rectCount(QQmlListProperty *list) #else int WindowBlurBehind::rectCount(QQmlListProperty *list) #endif { auto wbb = qobject_cast(list->object); return wbb->m_rects.size(); } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) RectRegion *WindowBlurBehind::rectAt(QQmlListProperty *list, qsizetype index) #else RectRegion *WindowBlurBehind::rectAt(QQmlListProperty *list, int index) #endif { auto wbb = qobject_cast(list->object); if (index < 0 || index >= wbb->m_rects.size()) { return nullptr; } return wbb->m_rects.at(index); } void WindowBlurBehind::clearRect(QQmlListProperty *list) { auto wbb = qobject_cast(list->object); wbb->m_rects.clear(); wbb->updateBlurRegion(); } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) void WindowBlurBehind::replaceRect(QQmlListProperty *list, qsizetype index, RectRegion *rect) #else void WindowBlurBehind::replaceRect(QQmlListProperty *list, int index, RectRegion *rect) #endif { if (!rect) { return; } auto wbb = qobject_cast(list->object); if (index < 0 || index >= wbb->m_rects.size()) { return; } wbb->m_rects.replace(index, rect); wbb->updateBlurRegion(); } void WindowBlurBehind::removeLastRect(QQmlListProperty *list) { auto wbb = qobject_cast(list->object); if (wbb->m_rects.isEmpty()) { return; } wbb->m_rects.removeLast(); wbb->updateBlurRegion(); } void WindowBlurBehind::addRectRegion(RectRegion *rect) { if (!rect) { return; } if (!m_rects.contains(rect)) { m_rects.append(rect); updateBlurRegion(); } } void WindowBlurBehind::setWindow(QWindow *window) { if(m_window == window) { return; } m_window = window; m_windowProxy = WindowProxy::fromWindow(window); Q_EMIT windowChanged(); updateBlurRegion(); } QWindow *WindowBlurBehind::window() { return m_window; } void WindowBlurBehind::clearRectRegion() { m_rects.clear(); updateBlurRegion(); } } // UkuiQuick ukui-quick/items/shaders6/0000775000175000017500000000000015153755732014444 5ustar fengfengukui-quick/items/shaders6/uniforms.glsl0000664000175000017500000000162615153755732017176 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ layout(std140, binding = 0) uniform buf { //64字节 - 4x4矩阵 highp mat4 matrix; // offset 0 //16字节 - vec4 lowp vec4 radius; // offset 64 lowp vec4 color; // offset 80 lowp vec4 shadowColor; // offset 96 lowp vec4 startColor; // offset 112 lowp vec4 endColor; // offset 128 lowp vec4 borderColor; // offset 144 //8字节 - vec2 lowp vec2 aspect; // offset 160 lowp vec2 offset; // offset 168 //4字节 - float和bool lowp float opacity; // offset 176 lowp float size; // offset 180 lowp float angle; // offset 184 lowp float borderWidth; // offset 188 bool pureColor; // offset 192 } ubuf; // size 196ukui-quick/items/shaders6/shadowedrectangle.vert0000664000175000017500000000075515153755732021040 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ #version 440 #extension GL_GOOGLE_include_directive: enable #include "uniforms.glsl" layout(location = 0) in highp vec4 in_vertex; layout(location = 1) in mediump vec2 in_uv; layout(location = 0) out mediump vec2 uv; out gl_PerVertex { vec4 gl_Position; }; void main() { uv = (-1.0 + 2.0 * in_uv) * ubuf.aspect; gl_Position = ubuf.matrix * in_vertex; } ukui-quick/items/shaders6/CMakeLists.txt0000664000175000017500000000124315153755732017204 0ustar fengfeng# SPDX-FileCopyrightText: 2022 Volker Krause # SPDX-License-Identifier: BSD-2-Clause if (QT_VERSION_MAJOR EQUAL "6") qt6_add_shaders(${PROJECT_NAME} "shaders6" BATCHABLE PRECOMPILE OPTIMIZED PREFIX "/org/ukui/quick/shaders/" FILES shadowedrectangle.vert shadowedrectangle.frag shadowedrectangle_lowpower.frag shadowedborderrectangle.frag shadowedborderrectangle_lowpower.frag shadowedtexture.frag shadowedtexture_lowpower.frag shadowedbordertexture.frag shadowedbordertexture_lowpower.frag ) endif() ukui-quick/items/shaders6/shadowedborderrectangle_lowpower.frag0000664000175000017500000000202315153755732024121 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ #version 440 #extension GL_GOOGLE_include_directive: enable #include "sdf_lowpower.glsl" // See sdf.glsl for the SDF related functions. // This is a version of shadowedborderrectangle.frag for extremely low powered // hardware (PinePhone). It does not draw a shadow and also eliminates alpha // blending. #include "uniforms.glsl" layout(location = 0) in lowp vec2 uv; layout(location = 0) out lowp vec4 out_color; void main() { lowp vec4 col = vec4(0.0); // Calculate the outer rectangle distance field and render it. lowp float outer_rect = sdf_rounded_rectangle(uv, ubuf.aspect, ubuf.radius); col = sdf_render(outer_rect, col, ubuf.borderColor); // The inner distance field is the outer reduced by border width. lowp float inner_rect = outer_rect + ubuf.borderWidth * 2.0; // Render it. col = sdf_render(inner_rect, col, ubuf.color); out_color = col * ubuf.opacity; } ukui-quick/items/shaders6/shadowedbordertexture.frag0000664000175000017500000000510415153755732021722 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #version 440 #extension GL_GOOGLE_include_directive: enable #include "sdf.glsl" // See sdf.glsl for the SDF related functions. // This shader renders a rectangle with rounded corners and a shadow below it. // In addition it renders a border around it. #include "uniforms.glsl" layout(binding = 1) uniform sampler2D textureSource; layout(location = 0) in lowp vec2 uv; layout(location = 0) out lowp vec4 out_color; const lowp float minimum_shadow_radius = 0.05; void main() { // Scaling factor that is the inverse of the amount of scaling applied to the geometry. lowp float inverse_scale = 1.0 / (1.0 + ubuf.size + length(ubuf.offset) * 2.0); // Correction factor to round the corners of a larger shadow. // We want to account for size in regards to shadow radius, so that a larger shadow is // more rounded, but only if we are not already rounding the corners due to corner radius. lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(ubuf.radius, minimum_shadow_radius)); lowp vec4 shadow_radius = ubuf.radius + ubuf.size * size_factor; lowp vec4 col = vec4(0.0); // Calculate the shadow's distance field. lowp float shadow = sdf_rounded_rectangle(uv - ubuf.offset * 2.0 * inverse_scale, ubuf.aspect * inverse_scale, shadow_radius * inverse_scale); // Render it, interpolating the color over the distance. col = mix(col, ubuf.shadowColor * sign(ubuf.size), 1.0 - smoothstep(-ubuf.size * 0.5, ubuf.size * 0.5, shadow)); // Scale corrected corner radius lowp vec4 corner_radius = ubuf.radius * inverse_scale; // Calculate the outer rectangle distance field and render it. lowp float outer_rect = sdf_rounded_rectangle(uv, ubuf.aspect * inverse_scale, corner_radius); col = sdf_render(outer_rect, col, ubuf.borderColor); // The inner rectangle distance field is the outer reduced by twice the border width. lowp float inner_rect = outer_rect + (ubuf.borderWidth * inverse_scale) * 2.0; // Render the inner rectangle. col = sdf_render(inner_rect, col, ubuf.color); // Sample the texture, then blend it on top of the background color. lowp vec2 texture_uv = ((uv / ubuf.aspect) + (1.0 * inverse_scale)) / (2.0 * inverse_scale); lowp vec4 texture_color = texture(textureSource, texture_uv); col = sdf_render(inner_rect, col, texture_color, texture_color.a, sdf_default_smoothing); out_color = col * ubuf.opacity; } ukui-quick/items/shaders6/shadowedbordertexture_lowpower.frag0000664000175000017500000000266115153755732023665 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ #version 440 #extension GL_GOOGLE_include_directive: enable #include "sdf_lowpower.glsl" // See sdf.glsl for the SDF related functions. // This shader renders a rectangle with rounded corners and a shadow below it. // In addition it renders a border around it. #include "uniforms.glsl" layout(binding = 1) uniform sampler2D textureSource; layout(location = 0) in lowp vec2 uv; layout(location = 0) out lowp vec4 out_color; const lowp float minimum_shadow_radius = 0.05; void main() { lowp vec4 col = vec4(0.0); // Calculate the outer rectangle distance field. lowp float outer_rect = sdf_rounded_rectangle(uv, ubuf.aspect, ubuf.radius); // Render it col = sdf_render(outer_rect, col, ubuf.borderColor); // Inner rectangle distance field equals outer reduced by twice the border width lowp float inner_rect = outer_rect + ubuf.borderWidth * 2.0; // Render it so we have a background for the image. col = sdf_render(inner_rect, col, ubuf.color); // Sample the texture, then render it, blending with the background color. lowp vec2 texture_uv = ((uv / ubuf.aspect) + 1.0) / 2.0; lowp vec4 texture_color = texture(textureSource, texture_uv); col = sdf_render(inner_rect, col, texture_color, texture_color.a, sdf_default_smoothing); out_color = col * ubuf.opacity; } ukui-quick/items/shaders6/shadowedborderrectangle.frag0000664000175000017500000000534715153755732022177 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #version 440 #extension GL_GOOGLE_include_directive: enable #include "sdf.glsl" // See sdf.glsl for the SDF related functions. // This shader renders a rectangle with rounded corners and a shadow below it. // In addition it renders a border around it. #include "uniforms.glsl" layout(location = 0) in lowp vec2 uv; layout(location = 0) out lowp vec4 out_color; const lowp float minimum_shadow_radius = 0.05; void main() { // Scaling factor that is the inverse of the amount of scaling applied to the geometry. lowp float inverse_scale = 1.0 / (1.0 + ubuf.size + length(ubuf.offset) * 2.0); // Correction factor to round the corners of a larger shadow. // We want to account for size in regards to shadow radius, so that a larger shadow is // more rounded, but only if we are not already rounding the corners due to corner radius. lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(ubuf.radius, minimum_shadow_radius)); lowp vec4 shadow_radius = ubuf.radius + ubuf.size * size_factor; lowp vec4 col = vec4(0.0); // Calculate the shadow's distance field. lowp float shadow = sdf_rounded_rectangle(uv - ubuf.offset * 2.0 * inverse_scale, ubuf.aspect * inverse_scale, shadow_radius * inverse_scale); // Render it, interpolating the color over the distance. col = mix(col, ubuf.shadowColor * sign(ubuf.size), 1.0 - smoothstep(-ubuf.size * 0.5, ubuf.size * 0.5, shadow)); // Scale corrected corner radius lowp vec4 corner_radius = ubuf.radius * inverse_scale; // Calculate the outer rectangle distance field and render it. lowp float outer_rect = sdf_rounded_rectangle(uv, ubuf.aspect * inverse_scale, corner_radius); col = sdf_render(outer_rect, col, ubuf.borderColor); // The inner rectangle distance field is the outer reduced by twice the border size. lowp float inner_rect = outer_rect + (ubuf.borderWidth * inverse_scale) * 2.0; // Finally, render the inner rectangle with gradient support. if (ubuf.pureColor) { col = sdf_render(inner_rect, col, ubuf.color); } else { // 计算渐变方向 vec2 dir = normalize(vec2(cos(ubuf.angle), sin(ubuf.angle)) / ubuf.aspect); // 转换到矩形本地坐标系 vec2 rectUV = uv / (ubuf.aspect * inverse_scale); // 计算渐变插值因子 float t = clamp((dot(rectUV, dir) + 1.0) / 2.0, 0.0, 1.0); // 混合颜色 vec4 gradientColor = mix(ubuf.startColor, ubuf.endColor, t); col = sdf_render(inner_rect, col, gradientColor); } out_color = col * ubuf.opacity; }ukui-quick/items/shaders6/sdf_lowpower.glsl0000664000175000017500000001733715153755732020054 0ustar fengfeng// SPDX-FileCopyrightText: 2020 Arjen Hiemstra // SPDX-FileCopyrightText: 2017 Inigo Quilez // // SPDX-License-Identifier: MIT // // This file is based on // https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm //if not GLES // include "desktop_header.glsl" //else // include "es_header.glsl" // A maximum point count to be used for sdf_polygon input arrays. // Unfortunately even function inputs require a fixed size at declaration time // for arrays, unless we were to use OpenGL 4.5. // Since the polygon is most likely to be defined in a uniform, this should be // at least less than MAX_FRAGMENT_UNIFORM_COMPONENTS / 2 (since we need vec2). #define SDF_POLYGON_MAX_POINT_COUNT 400 /********************************* Shapes *********************************/ // Distance field for a circle. // // \param point A point on the distance field. // \param radius The radius of the circle. // // \return The signed distance from point to the circle. If negative, point is // inside the circle. lowp float sdf_circle(in lowp vec2 point, in lowp float radius) { return length(point) - radius; } // Distance field for a triangle. // // \param point A point on the distance field. // \param p0 The first vertex of the triangle. // \param p0 The second vertex of the triangle. // \param p0 The third vertex of the triangle. // // \note The ordering of the three vertices does not matter. // // \return The signed distance from point to triangle. If negative, point is // inside the triangle. lowp float sdf_triangle(in lowp vec2 point, in lowp vec2 p0, in lowp vec2 p1, in lowp vec2 p2) { lowp vec2 e0 = p1 - p0; lowp vec2 e1 = p2 - p1; lowp vec2 e2 = p0 - p2; lowp vec2 v0 = point - p0; lowp vec2 v1 = point - p1; lowp vec2 v2 = point - p2; lowp vec2 pq0 = v0 - e0 * clamp( dot(v0, e0) / dot(e0, e0), 0.0, 1.0 ); lowp vec2 pq1 = v1 - e1 * clamp( dot(v1, e1) / dot(e1, e1), 0.0, 1.0 ); lowp vec2 pq2 = v2 - e2 * clamp( dot(v2, e2) / dot(e2, e2), 0.0, 1.0 ); lowp float s = sign( e0.x*e2.y - e0.y*e2.x ); lowp vec2 d = min(min(vec2(dot(pq0,pq0), s*(v0.x*e0.y-v0.y*e0.x)), vec2(dot(pq1,pq1), s*(v1.x*e1.y-v1.y*e1.x))), vec2(dot(pq2,pq2), s*(v2.x*e2.y-v2.y*e2.x))); return -sqrt(d.x)*sign(d.y); } // Distance field for a rectangle. // // \param point A point on the distance field. // \param rect A vec2 with the size of the rectangle. // // \return The signed distance from point to rectangle. If negative, point is // inside the rectangle. lowp float sdf_rectangle(in lowp vec2 point, in lowp vec2 rect) { lowp vec2 d = abs(point) - rect; return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0); } // Distance field for a rectangle with rounded corners. // // \param point The point to calculate the distance of. // \param rect The rectangle to calculate the distance of. // \param radius A vec4 with the radius of each corner. Order is top right, bottom right, top left, bottom left. // // \return The signed distance from point to rectangle. If negative, point is // inside the rectangle. lowp float sdf_rounded_rectangle(in lowp vec2 point, in lowp vec2 rect, in lowp vec4 radius) { radius.xy = (point.x > 0.0) ? radius.xy : radius.zw; radius.x = (point.y > 0.0) ? radius.x : radius.y; lowp vec2 d = abs(point) - rect + radius.x; return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - radius.x; } /********************* Operators *********************/ // Convert a distance field to an annular (hollow) distance field. // // \param sdf The result of an sdf shape to convert. // \param thickness The thickness of the resulting shape. // // \return The value of sdf modified to an annular shape. lowp float sdf_annular(in lowp float sdf, in lowp float thickness) { return abs(sdf) - thickness; } // Union two sdf shapes together. // // \param sdf1 The first sdf shape. // \param sdf2 The second sdf shape. // // \return The union of sdf1 and sdf2, that is, the distance to both sdf1 and // sdf2. lowp float sdf_union(in lowp float sdf1, in lowp float sdf2) { return min(sdf1, sdf2); } // Subtract two sdf shapes. // // \param sdf1 The first sdf shape. // \param sdf2 The second sdf shape. // // \return sdf1 with sdf2 subtracted from it. lowp float sdf_subtract(in lowp float sdf1, in lowp float sdf2) { return max(sdf1, -sdf2); } // Intersect two sdf shapes. // // \param sdf1 The first sdf shape. // \param sdf2 The second sdf shape. // // \return The intersection between sdf1 and sdf2, that is, the area where both // sdf1 and sdf2 provide the same distance value. lowp float sdf_intersect(in lowp float sdf1, in lowp float sdf2) { return max(sdf1, sdf2); } // Smoothly intersect two sdf shapes. // // \param sdf1 The first sdf shape. // \param sdf2 The second sdf shape. // \param smoothing The amount of smoothing to apply. // // \return A smoothed version of the intersect operation. lowp float sdf_intersect_smooth(in lowp float sdf1, in lowp float sdf2, in lowp float smoothing) { lowp float h = clamp(0.5 - 0.5 * (sdf1 - sdf2) / smoothing, 0.0, 1.0); return mix(sdf1, sdf2, h) + smoothing * h * (1.0 - h); } // Round an sdf shape. // // \param sdf The sdf shape to round. // \param amount The amount of rounding to apply. // // \return The rounded shape of sdf. // Note that rounding happens by basically selecting an isoline of sdf, // therefore, the resulting shape may be larger than the input shape. lowp float sdf_round(in lowp float sdf, in lowp float amount) { return sdf - amount; } // Convert an sdf shape to an outline of its shape. // // \param sdf The sdf shape to turn into an outline. // // \return The outline of sdf. lowp float sdf_outline(in lowp float sdf) { return abs(sdf); } /******************** Convenience ********************/ // A constant to represent a "null" value of an sdf. // // Since 0 is a point exactly on the outline of an sdf shape, and negative // values are inside the shape, this uses a very large positive constant to // indicate a value that is really far away from the actual sdf shape. const lowp float sdf_null = 99999.0; // A constant for a default level of smoothing when rendering an sdf. // // This const lowp float sdf_default_smoothing = 0.625; // Render an sdf shape alpha-blended onto an existing color. // // This is an overload of sdf_render(float, vec4, vec4) that allows specifying a // blending amount and a smoothing amount. // // \param alpha The alpha to use for blending. // \param smoothing The amount of smoothing to apply to the sdf. // lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float alpha, in lowp float smoothing) { lowp float g = smoothing * fwidth(sdf); return mix(sourceColor, sdfColor, alpha * (1.0 - clamp(sdf / g, 0.0, 1.0))); } // Render an sdf shape. // // This will render the sdf shape on top of whatever source color is input, // making sure to apply smoothing if desired. // // \param sdf The sdf shape to render. // \param sourceColor The source color to render on top of. // \param sdfColor The color to use for rendering the sdf shape. // // \return sourceColor with the sdf shape rendered on top. lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor) { return sdf_render(sdf, sourceColor, sdfColor, 1.0, sdf_default_smoothing); } // Render an sdf shape. // // This is an overload of sdf_render(float, vec4, vec4) that allows specifying a // smoothing amount. // // \param smoothing The amount of smoothing to apply to the sdf. // lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float smoothing) { return sdf_render(sdf, sourceColor, sdfColor, 1.0, smoothing); } ukui-quick/items/shaders6/shadowedtexture_lowpower.frag0000664000175000017500000000216315153755732022464 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ #version 440 #extension GL_GOOGLE_include_directive: enable #include "sdf_lowpower.glsl" // See sdf.glsl for the SDF related functions. // This shader renders a texture on top of a rectangle with rounded corners and // a shadow below it. #include "uniforms.glsl" layout(binding = 1) uniform sampler2D textureSource; layout(location = 0) in lowp vec2 uv; layout(location = 0) out lowp vec4 out_color; void main() { lowp vec4 col = vec4(0.0); // Calculate the main rectangle distance field. lowp float rect = sdf_rounded_rectangle(uv, ubuf.aspect, ubuf.radius); // Render it, so we have a background for the image. col = sdf_render(rect, col, ubuf.color); // Sample the texture, then render it, blending it with the background. lowp vec2 texture_uv = ((uv / ubuf.aspect) + 1.0) / 2.0; lowp vec4 texture_color = texture(textureSource, texture_uv); col = sdf_render(rect, col, texture_color, texture_color.a, sdf_default_smoothing); out_color = col * ubuf.opacity; } ukui-quick/items/shaders6/shadowedtexture.frag0000664000175000017500000000417415153755732020532 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ #version 440 #extension GL_GOOGLE_include_directive: enable #include "sdf.glsl" // See sdf.glsl for the SDF related functions. // This shader renders a texture on top of a rectangle with rounded corners and // a shadow below it. #include "uniforms.glsl" layout(binding = 1) uniform sampler2D textureSource; layout(location = 0) in lowp vec2 uv; layout(location = 0) out lowp vec4 out_color; const lowp float minimum_shadow_radius = 0.05; void main() { // Scaling factor that is the inverse of the amount of scaling applied to the geometry. lowp float inverse_scale = 1.0 / (1.0 + ubuf.size + length(ubuf.offset) * 2.0); // Correction factor to round the corners of a larger shadow. // We want to account for size in regards to shadow radius, so that a larger shadow is // more rounded, but only if we are not already rounding the corners due to corner radius. lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(ubuf.radius, minimum_shadow_radius)); lowp vec4 shadow_radius = ubuf.radius + ubuf.size * size_factor; lowp vec4 col = vec4(0.0); // Calculate the shadow's distance field. lowp float shadow = sdf_rounded_rectangle(uv - ubuf.offset * 2.0 * inverse_scale, ubuf.aspect * inverse_scale, shadow_radius * inverse_scale); // Render it, interpolating the color over the distance. col = mix(col, ubuf.shadowColor * sign(ubuf.size), 1.0 - smoothstep(-ubuf.size * 0.5, ubuf.size * 0.5, shadow)); // Calculate the main rectangle distance field and render it. lowp float rect = sdf_rounded_rectangle(uv, ubuf.aspect * inverse_scale, ubuf.radius * inverse_scale); col = sdf_render(rect, col, ubuf.color); // Sample the texture, then blend it on top of the background color. lowp vec2 texture_uv = ((uv / ubuf.aspect) + (1.0 * inverse_scale)) / (2.0 * inverse_scale); lowp vec4 texture_color = texture(textureSource, texture_uv); col = sdf_render(rect, col, texture_color, texture_color.a, sdf_default_smoothing); out_color = col * ubuf.opacity; } ukui-quick/items/shaders6/sdf.glsl0000664000175000017500000001734715153755732016117 0ustar fengfeng// SPDX-FileCopyrightText: 2020 Arjen Hiemstra // SPDX-FileCopyrightText: 2017 Inigo Quilez // // SPDX-License-Identifier: MIT // // This file is based on // https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm //if not GLES // include "desktop_header.glsl" //else // include "es_header.glsl" // A maximum point count to be used for sdf_polygon input arrays. // Unfortunately even function inputs require a fixed size at declaration time // for arrays, unless we were to use OpenGL 4.5. // Since the polygon is most likely to be defined in a uniform, this should be // at least less than MAX_FRAGMENT_UNIFORM_COMPONENTS / 2 (since we need vec2). #define SDF_POLYGON_MAX_POINT_COUNT 400 /********************************* Shapes *********************************/ // Distance field for a circle. // // \param point A point on the distance field. // \param radius The radius of the circle. // // \return The signed distance from point to the circle. If negative, point is // inside the circle. lowp float sdf_circle(in lowp vec2 point, in lowp float radius) { return length(point) - radius; } // Distance field for a triangle. // // \param point A point on the distance field. // \param p0 The first vertex of the triangle. // \param p0 The second vertex of the triangle. // \param p0 The third vertex of the triangle. // // \note The ordering of the three vertices does not matter. // // \return The signed distance from point to triangle. If negative, point is // inside the triangle. lowp float sdf_triangle(in lowp vec2 point, in lowp vec2 p0, in lowp vec2 p1, in lowp vec2 p2) { lowp vec2 e0 = p1 - p0; lowp vec2 e1 = p2 - p1; lowp vec2 e2 = p0 - p2; lowp vec2 v0 = point - p0; lowp vec2 v1 = point - p1; lowp vec2 v2 = point - p2; lowp vec2 pq0 = v0 - e0 * clamp( dot(v0, e0) / dot(e0, e0), 0.0, 1.0 ); lowp vec2 pq1 = v1 - e1 * clamp( dot(v1, e1) / dot(e1, e1), 0.0, 1.0 ); lowp vec2 pq2 = v2 - e2 * clamp( dot(v2, e2) / dot(e2, e2), 0.0, 1.0 ); lowp float s = sign( e0.x*e2.y - e0.y*e2.x ); lowp vec2 d = min(min(vec2(dot(pq0,pq0), s*(v0.x*e0.y-v0.y*e0.x)), vec2(dot(pq1,pq1), s*(v1.x*e1.y-v1.y*e1.x))), vec2(dot(pq2,pq2), s*(v2.x*e2.y-v2.y*e2.x))); return -sqrt(d.x)*sign(d.y); } // Distance field for a rectangle. // // \param point A point on the distance field. // \param rect A vec2 with the size of the rectangle. // // \return The signed distance from point to rectangle. If negative, point is // inside the rectangle. lowp float sdf_rectangle(in lowp vec2 point, in lowp vec2 rect) { lowp vec2 d = abs(point) - rect; return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0); } // Distance field for a rectangle with rounded corners. // // \param point The point to calculate the distance of. // \param rect The rectangle to calculate the distance of. // \param radius A vec4 with the radius of each corner. Order is top right, bottom right, top left, bottom left. // // \return The signed distance from point to rectangle. If negative, point is // inside the rectangle. lowp float sdf_rounded_rectangle(in lowp vec2 point, in lowp vec2 rect, in lowp vec4 radius) { radius.xy = (point.x > 0.0) ? radius.xy : radius.zw; radius.x = (point.y > 0.0) ? radius.x : radius.y; lowp vec2 d = abs(point) - rect + radius.x; return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - radius.x; } /********************* Operators *********************/ // Convert a distance field to an annular (hollow) distance field. // // \param sdf The result of an sdf shape to convert. // \param thickness The thickness of the resulting shape. // // \return The value of sdf modified to an annular shape. lowp float sdf_annular(in lowp float sdf, in lowp float thickness) { return abs(sdf) - thickness; } // Union two sdf shapes together. // // \param sdf1 The first sdf shape. // \param sdf2 The second sdf shape. // // \return The union of sdf1 and sdf2, that is, the distance to both sdf1 and // sdf2. lowp float sdf_union(in lowp float sdf1, in lowp float sdf2) { return min(sdf1, sdf2); } // Subtract two sdf shapes. // // \param sdf1 The first sdf shape. // \param sdf2 The second sdf shape. // // \return sdf1 with sdf2 subtracted from it. lowp float sdf_subtract(in lowp float sdf1, in lowp float sdf2) { return max(sdf1, -sdf2); } // Intersect two sdf shapes. // // \param sdf1 The first sdf shape. // \param sdf2 The second sdf shape. // // \return The intersection between sdf1 and sdf2, that is, the area where both // sdf1 and sdf2 provide the same distance value. lowp float sdf_intersect(in lowp float sdf1, in lowp float sdf2) { return max(sdf1, sdf2); } // Smoothly intersect two sdf shapes. // // \param sdf1 The first sdf shape. // \param sdf2 The second sdf shape. // \param smoothing The amount of smoothing to apply. // // \return A smoothed version of the intersect operation. lowp float sdf_intersect_smooth(in lowp float sdf1, in lowp float sdf2, in lowp float smoothing) { lowp float h = clamp(0.5 - 0.5 * (sdf1 - sdf2) / smoothing, 0.0, 1.0); return mix(sdf1, sdf2, h) + smoothing * h * (1.0 - h); } // Round an sdf shape. // // \param sdf The sdf shape to round. // \param amount The amount of rounding to apply. // // \return The rounded shape of sdf. // Note that rounding happens by basically selecting an isoline of sdf, // therefore, the resulting shape may be larger than the input shape. lowp float sdf_round(in lowp float sdf, in lowp float amount) { return sdf - amount; } // Convert an sdf shape to an outline of its shape. // // \param sdf The sdf shape to turn into an outline. // // \return The outline of sdf. lowp float sdf_outline(in lowp float sdf) { return abs(sdf); } /******************** Convenience ********************/ // A constant to represent a "null" value of an sdf. // // Since 0 is a point exactly on the outline of an sdf shape, and negative // values are inside the shape, this uses a very large positive constant to // indicate a value that is really far away from the actual sdf shape. const lowp float sdf_null = 99999.0; // A constant for a default level of smoothing when rendering an sdf. // // This const lowp float sdf_default_smoothing = 0.625; // Render an sdf shape alpha-blended onto an existing color. // // This is an overload of sdf_render(float, vec4, vec4) that allows specifying a // blending amount and a smoothing amount. // // \param alpha The alpha to use for blending. // \param smoothing The amount of smoothing to apply to the sdf. // lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float alpha, in lowp float smoothing) { lowp float g = fwidth(sdf); return mix(sourceColor, sdfColor, alpha * (1.0 - smoothstep(-smoothing * g, smoothing * g, sdf))); } // Render an sdf shape. // // This will render the sdf shape on top of whatever source color is input, // making sure to apply smoothing if desired. // // \param sdf The sdf shape to render. // \param sourceColor The source color to render on top of. // \param sdfColor The color to use for rendering the sdf shape. // // \return sourceColor with the sdf shape rendered on top. lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor) { return sdf_render(sdf, sourceColor, sdfColor, 1.0, sdf_default_smoothing); } // Render an sdf shape. // // This is an overload of sdf_render(float, vec4, vec4) that allows specifying a // smoothing amount. // // \param smoothing The amount of smoothing to apply to the sdf. // lowp vec4 sdf_render(in lowp float sdf, in lowp vec4 sourceColor, in lowp vec4 sdfColor, in lowp float smoothing) { return sdf_render(sdf, sourceColor, sdfColor, 1.0, smoothing); } ukui-quick/items/shaders6/shadowedrectangle.frag0000664000175000017500000000435215153755732020774 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ #version 440 #extension GL_GOOGLE_include_directive: enable #include "sdf.glsl" // See sdf.glsl for the SDF related functions. // This shader renders a rectangle with rounded corners and a shadow below it. #include "uniforms.glsl" layout(location = 0) in lowp vec2 uv; layout(location = 0) out lowp vec4 out_color; const lowp float minimum_shadow_radius = 0.05; void main() { // Scaling factor that is the inverse of the amount of scaling applied to the geometry. lowp float inverse_scale = 1.0 / (1.0 + ubuf.size + length(ubuf.offset) * 2.0); // Correction factor to round the corners of a larger shadow. // We want to account for size in regards to shadow radius, so that a larger shadow is // more rounded, but only if we are not already rounding the corners due to corner radius. lowp vec4 size_factor = 0.5 * (minimum_shadow_radius / max(ubuf.radius, minimum_shadow_radius)); lowp vec4 shadow_radius = ubuf.radius + ubuf.size * size_factor; lowp vec4 col = vec4(0.0); // Calculate the shadow's distance field. lowp float shadow = sdf_rounded_rectangle(uv - ubuf.offset * 2.0 * inverse_scale, ubuf.aspect * inverse_scale, shadow_radius * inverse_scale); // Render it, interpolating the color over the distance. col = mix(col, ubuf.shadowColor * sign(ubuf.size), 1.0 - smoothstep(-ubuf.size * 0.5, ubuf.size * 0.5, shadow)); // Calculate the main rectangle distance field and render it. lowp float rect = sdf_rounded_rectangle(uv, ubuf.aspect * inverse_scale, ubuf.radius * inverse_scale); if (ubuf.pureColor) { col = sdf_render(rect, col, ubuf.color); } else { // 计算渐变方向 vec2 dir = normalize(vec2(cos(ubuf.angle), sin(ubuf.angle)) / ubuf.aspect); // 转换到矩形本地坐标系 vec2 rectUV = uv / (ubuf.aspect * inverse_scale); // 计算渐变插值因子 float t = clamp((dot(rectUV, dir) + 1.0) / 2.0, 0.0, 1.0); // 混合颜色 vec4 gradientColor = mix(ubuf.startColor, ubuf.endColor, t); col = sdf_render(rect, col, gradientColor); } out_color = col * ubuf.opacity; }ukui-quick/items/shaders6/shadowedrectangle_lowpower.frag0000664000175000017500000000245515153755732022734 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.0-or-later */ #version 440 // See sdf.glsl for the SDF related functions. #extension GL_GOOGLE_include_directive: enable #include "sdf_lowpower.glsl" // This is a version of shadowedrectangle.frag meant for very low power hardware // (PinePhone). It does not render a shadow and does not do alpha blending. #include "uniforms.glsl" layout(location = 0) in lowp vec2 uv; layout(location = 0) out lowp vec4 out_color; void main() { lowp vec4 col = vec4(0.0); // Calculate the main rectangle distance field. lowp float rect = sdf_rounded_rectangle(uv, ubuf.aspect, ubuf.radius); // Render it with gradient support if (ubuf.pureColor) { col = sdf_render(rect, col, ubuf.color); } else { // 计算渐变方向 vec2 dir = normalize(vec2(cos(ubuf.angle), sin(ubuf.angle)) / ubuf.aspect); // 转换到矩形本地坐标系 vec2 rectUV = uv / ubuf.aspect; // 计算渐变插值因子 float t = clamp((dot(rectUV, dir) + 1.0) / 2.0, 0.0, 1.0); // 混合颜色 vec4 gradientColor = mix(ubuf.startColor, ubuf.endColor, t); col = sdf_render(rect, col, gradientColor); } out_color = col * ubuf.opacity; } ukui-quick/items/shadowed-rectangle.h0000664000175000017500000002744515153755732016652 0ustar fengfeng/* * SPDX-FileCopyrightText: 2020 Arjen Hiemstra * SPDX-FileCopyrightText: 2025 iaom * * SPDX-License-Identifier: LGPL-2.0-or-later */ #pragma once #include #include #include class PaintedRectangleItem; /** * @brief Grouped property for rectangle border. */ class BorderGroup : public QObject { Q_OBJECT /** * @brief This property holds the border's width in pixels. * * default: ``0``px */ Q_PROPERTY(qreal width READ width WRITE setWidth NOTIFY changed FINAL) /** * @brief This property holds the border's color. * * Full RGBA colors are supported. * * default: ``Qt::black`` */ Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY changed FINAL) public: explicit BorderGroup(QObject *parent = nullptr); qreal width() const; void setWidth(qreal newWidth); QColor color() const; void setColor(const QColor &newColor); Q_SIGNAL void changed(); inline bool isEnabled() const { return !qFuzzyIsNull(m_width); } private: qreal m_width = 0.0; QColor m_color = Qt::black; }; /** * @brief Grouped property for the rectangle's shadow. */ class ShadowGroup : public QObject { Q_OBJECT /** * @brief This property holds the shadow's approximate size in pixels. * @note The actual shadow size can be less than this value due to falloff. * * default: ``0``px */ Q_PROPERTY(qreal size READ size WRITE setSize NOTIFY changed FINAL) /** * @brief This property holds the shadow's offset in pixels on the X axis. * * default: ``0``px */ Q_PROPERTY(qreal xOffset READ xOffset WRITE setXOffset NOTIFY changed FINAL) /** * @brief This property holds the shadow's offset in pixels on the Y axis. * * default: ``0``px */ Q_PROPERTY(qreal yOffset READ yOffset WRITE setYOffset NOTIFY changed FINAL) /** * @brief This property holds the shadow's color. * * Full RGBA colors are supported. * * default: ``Qt::black`` */ Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY changed FINAL) public: explicit ShadowGroup(QObject *parent = nullptr); qreal size() const; void setSize(qreal newSize); qreal xOffset() const; void setXOffset(qreal newXOffset); qreal yOffset() const; void setYOffset(qreal newYOffset); QColor color() const; void setColor(const QColor &newShadowColor); Q_SIGNAL void changed(); private: qreal m_size = 0.0; qreal m_xOffset = 0.0; qreal m_yOffset = 0.0; QColor m_color = Qt::black; }; /** * @brief Grouped property for corner radius. */ class CornersGroup : public QObject { Q_OBJECT /** * @brief This property holds the top-left corner's radius in pixels. * * Setting this to ``-1`` indicates that the value should be ignored. * * default: ``-1``px */ Q_PROPERTY(qreal topLeftRadius READ topLeft WRITE setTopLeft NOTIFY changed FINAL) /** * @brief This property holds the top-right corner's radius in pixels. * * Setting this to ``-1`` indicates that the value should be ignored. * * default: ``-1``px */ Q_PROPERTY(qreal topRightRadius READ topRight WRITE setTopRight NOTIFY changed FINAL) /** * @brief This property holds the bottom-left corner's radius in pixels. * * Setting this to ``-1`` indicates that the value should be ignored. * * default: ``-1``px */ Q_PROPERTY(qreal bottomLeftRadius READ bottomLeft WRITE setBottomLeft NOTIFY changed FINAL) /** * @brief This property holds the bottom-right corner's radius in pixels. * * Setting this to ``-1`` indicates that the value should be ignored. * * default: ``-1``px */ Q_PROPERTY(qreal bottomRightRadius READ bottomRight WRITE setBottomRight NOTIFY changed FINAL) public: explicit CornersGroup(QObject *parent = nullptr); qreal topLeft() const; void setTopLeft(qreal newTopLeft); qreal topRight() const; void setTopRight(qreal newTopRight); qreal bottomLeft() const; void setBottomLeft(qreal newBottomLeft); qreal bottomRight() const; void setBottomRight(qreal newBottomRight); Q_SIGNAL void changed(); QVector4D toVector4D(float all) const; private: float m_topLeft = -1.0; float m_topRight = -1.0; float m_bottomLeft = -1.0; float m_bottomRight = -1.0; }; /** * @brief A rectangle with a shadow behind it. * * This item will render a rectangle, with a shadow below it. The rendering is done * using distance fields, which provide greatly improved performance. The shadow is * rendered outside of the item's bounds, so the item's width and height are the * rectangle's width and height. * * Example: * ShadowedRectangle { width: 100 height: 100 color: "red" radius: 50 shadow { size: 20 color: Qt.rgba(0, 0, 0, 0.5) yOffset: 2 } border { width: 1 color: "blue" } } */ class ShadowedRectangle : public QQuickItem { Q_OBJECT /** * @brief This property holds the radii of the rectangle's corners. * * This is the amount of rounding to apply to all of the rectangle's * corners, in pixels. Each corner can have a different radius. * * default: ``0`` * * @see corners */ Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged FINAL) /** * @brief This property holds the rectangle's color. * * Full RGBA colors are supported. * * default: ``Qt::white`` */ Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL) /** * @brief This property holds the rectangle's color is pure color or a gradient. * * default: ``true`` */ Q_PROPERTY(bool pureColor READ pureColor WRITE setPureColor NOTIFY pureColorChanged FINAL) /** * @brief This property holds the gradient start color of the rectangle. * This property is only used when pureColor is false. * default: ``Qt::white`` */ Q_PROPERTY(QColor startColor READ startColor WRITE setStartColor NOTIFY startColorChanged FINAL) /** * @brief This property holds the gradient end color of the rectangle. * This property is only used when pureColor is false. * default: ``Qt::white`` */ Q_PROPERTY(QColor endColor READ endColor WRITE setEndColor NOTIFY endColorChanged FINAL) /** * @brief This property holds the gradient angle of the rectangle. * This property is only used when pureColor is false. * default: ``0.0`` */ Q_PROPERTY(qreal angle READ angle WRITE setAngle NOTIFY angleChanged FINAL) /** * @brief This property holds the border's grouped property. * * Example usage: * @code * ShadowedRectangle { * border.width: 2 * border.color: Theme.textColor * } * @endcode * @see BorderGroup */ Q_PROPERTY(BorderGroup *border READ border CONSTANT FINAL) /** * @brief This property holds the shadow's grouped property. * * Example usage: * @code * ShadowedRectangle { * shadow.size: 20 * shadow.xOffset: 5 * shadow.yOffset: 5 * } * @endcode * * @see ShadowGroup */ Q_PROPERTY(ShadowGroup *shadow READ shadow CONSTANT FINAL) /** * @brief This property holds the corners grouped property * * Note that the values from this group override \property radius for the * corner they affect. * * Example usage: * @code * ShadowedRectangle { * corners.topLeftRadius: 4 * corners.topRightRadius: 5 * corners.bottomLeftRadius: 2 * corners.bottomRightRadius: 10 * @endcode * * @see CornersGroup */ Q_PROPERTY(CornersGroup *corners READ corners CONSTANT FINAL) /** * @brief This property holds the rectangle's render mode. * * default: ``RenderType::Auto`` * * @see RenderType */ Q_PROPERTY(RenderType renderType READ renderType WRITE setRenderType NOTIFY renderTypeChanged FINAL) /** * @brief This property tells whether software rendering is being used. * * default: ``false`` */ Q_PROPERTY(bool softwareRendering READ isSoftwareRendering NOTIFY softwareRenderingChanged FINAL) //workaround https://bugreports.qt.io/browse/QTBUG-44470 https://bugreports.qt.io/browse/QTBUG-40043 Q_PROPERTY(bool activeFocusOnTab1 READ activeFocusOnTab WRITE setActiveFocusOnTab NOTIFY activeFocusOnTabChanged1 FINAL) Q_PROPERTY(QObject *containmentMask READ containmentMask WRITE setContainmentMask NOTIFY containmentMaskChanged) public: ShadowedRectangle(QQuickItem *parent = nullptr); ~ShadowedRectangle() override; /** * @brief Available rendering types for ShadowedRectangle. */ enum RenderType { /** * @brief Automatically determine the optimal rendering type. * * This will use the highest rendering quality possible, falling back to * lower quality if the hardware doesn't support it. It will use software * rendering if the QtQuick scene graph is set to use software rendering. */ Auto, /** * @brief Use the highest rendering quality possible, even if the hardware might * not be able to handle it normally. */ HighQuality, /** * @brief Use the lowest rendering quality, even if the hardware could handle * higher quality rendering. * * This might result in certain effects being omitted, like shadows. */ LowQuality, /** * @brief Always use software rendering for this rectangle. * * Software rendering is intended as a fallback when the QtQuick scene * graph is configured to use software rendering. It will result in * a number of missing features, like shadows and multiple corner radii. */ Software }; Q_ENUM(RenderType) BorderGroup *border() const; ShadowGroup *shadow() const; CornersGroup *corners() const; qreal radius() const; void setRadius(qreal newRadius); QColor color() const; void setColor(const QColor &newColor); bool pureColor() const; void setPureColor(bool pureColor); QColor startColor() const; void setStartColor(const QColor &startColor); QColor endColor() const; void setEndColor(const QColor &endColor); qreal angle() const; void setAngle(qreal newAngle); RenderType renderType() const; void setRenderType(RenderType renderType); void componentComplete() override; bool isSoftwareRendering() const; Q_SIGNALS: void softwareRenderingChanged(); void activeFocusOnTabChanged1(); void radiusChanged(); void colorChanged(); void pureColorChanged(); void startColorChanged(); void endColorChanged(); void angleChanged(); void renderTypeChanged(); protected: PaintedRectangleItem *softwareItem() const; void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) override; QSGNode *updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *data) override; private: void checkSoftwareItem(); const std::unique_ptr m_border; const std::unique_ptr m_shadow; const std::unique_ptr m_corners; qreal m_radius = 0.0; QColor m_color = Qt::white; bool m_pureColor = true; QColor m_startColor = Qt::white; QColor m_endColor = Qt::white; qreal m_angle = 0.0; RenderType m_renderType = RenderType::Auto; PaintedRectangleItem *m_softwareItem = nullptr; }; ukui-quick/ukui-quick-config.cmake.in0000664000175000017500000000345415153755732016553 0ustar fengfeng if (NOT ukui-quick_FIND_COMPONENTS) set(ukui-quick_NOT_FOUND_MESSAGE "The ukui-quick package requires at least one component") set(ukui-quick_FOUND False) return() endif() set(_ukui-quick_FIND_PARTS_REQUIRED) if (ukui-quick_FIND_REQUIRED) set(_ukui-quick_FIND_PARTS_REQUIRED REQUIRED) endif() set(_ukui-quick_FIND_PARTS_QUIET) if (ukui-quick_FIND_QUIETLY) set(_ukui-quick_FIND_PARTS_QUIET QUIET) endif() get_filename_component(_ukui-quick_install_prefix "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) set(_ukui-quick_root_dir ${_ukui-quick_install_prefix}) set(_ukui-quick_module_paths ${_ukui-quick_install_prefix}) set(_at @) set(_ukui-quick_module_location_template ${_ukui-quick_install_prefix}/ukui-quick-${_at}module${_at}/ukui-quick-${_at}module${_at}-config.cmake) unset(_at) set(_ukui-quick_NOTFOUND_MESSAGE) foreach(module ${ukui-quick_FIND_COMPONENTS}) find_package(ukui-quick-${module} ${_ukui-quick_FIND_PARTS_QUIET} ${_ukui-quick_FIND_PARTS_REQUIRED} PATHS ${_ukui-quick_module_paths} NO_DEFAULT_PATH ) if (NOT ukui-quick-${module}_FOUND) string(CONFIGURE ${_ukui-quick_module_location_template} _expected_module_location @ONLY) if (ukui-quick_FIND_REQUIRED_${module}) set(_ukui-quick_NOTFOUND_MESSAGE "${_ukui-quick_NOTFOUND_MESSAGE}Failed to find ukui-quick component \"${module}\" config file at \"${_expected_module_location}\"\n") elseif(NOT ukui-quick_FIND_QUIETLY) message(WARNING "Failed to find ukui-quick component \"${module}\" config file at \"${_expected_module_location}\"") endif() unset(_expected_module_location) endif() endforeach() if (_ukui-quick_NOTFOUND_MESSAGE) set(ukui-quick_NOT_FOUND_MESSAGE "${_ukui-quick_NOTFOUND_MESSAGE}") set(ukui-quick_FOUND False) endif()