前军教程网

中小站长与DIV+CSS网页布局开发技术人员的首选CSS学习平台

【干货分享】ukui-quick-widget桌面插件开发指南

01


简介


在openKylin系统默认搭载的桌面环境UKUI中,实现了一个基于Qt Quick的UI开发功能集合,其中包含提供在UKUI上开发QML应用所需要的接口和功能模块—ukui-quick。

ukui-quick为开发QML应用提供了多种组件,包括各种基础UI组件,窗口组件,以及对系统主题(颜色,光标,字体)、窗口系统接口和UKUIFrameWork各种能力的访问接口。此外,ukui-quick中还提供了一个基于插件模式的UI开发框架,目的在于实现对桌面通用插件的定义、加载以及统一配置管理等功能。

其特点包括:

  • 可实现桌面组件一定程度上的功能解耦与功能复用;
  • 插件和宿主应用(Host)之间不会强制有二进制依赖关系,插件的开发相对更加独立,不易产生版本迭代时插件不兼容的情况;
  • 插件开发门槛低,无需了解宿主应用全部功能即可开发插件。
  • 通用性,可实现不同桌面组件之间无缝共享插件;
  • 统一的配置管理模块,便于应用和插件的统一配置管理。

ukui-quick-widget是基于ukui-quick框架的通用插件合集。这些插件以统一的结构形式来实现不同的UI和功能,如日历、网速显示等。浏览源码戳这里(https://gitee.com/openkylin/ukui-quick-widgets),这些插件都可以加载到基于ukui-quick开发的应用中。

接下来,一起看看如何开发一个ukui-quick-widget插件吧!


02


开发一个通用插件

ukui-quick插件以一个目录的形式安装到系统中,首先,我们来看看插件的目录结构。

1.插件的目录结构

/*ukui-quick插件命名前缀为“org.ukui” */
/*文件夹 org.ukui.myplugin 是整个插件项目的顶层文件夹*/


├── org.ukui.myplugin     #插件顶层文件夹
│    ├── metadata.json    #插件描述文件
│    ├── translations
│    │    └── *.qm        #多语言翻译文件
│    └── ui
│         └── main.qml    #插件主入口文件,插件界面

其中,metadata.json是插件的描述文件;translations目录存放翻译文件;ui目录存放插件的界面文件,主要包括qml文件,界面用到的相关资源文件也会安装到此目录,main.qml是插件的主入口文件。

由此可见,开发一个插件程序,只需要按照规则组织好目录结构,创建metadata.json和main.qml即可。

接下来,我们以“数字时钟”为例,逐步开发一个通用插件。

2.开发示例:数字时钟

数字时钟可以拆分为功能和界面两部分,功能部分需要实现系统时间的获取;界面部分则需要实现系统时间的显示。本示例工程使用CMake构建,需要手动编写一个CMakeLists.txt。

编写 CMakeLists.txt

在CMakeLists.txt 中定义数字时钟插件名称和版本号,查找并载入必要的Qt模块,链接对应的库。这里我们定义插件名称为ukui-digitalclock。

#CMakeLists.txt
cmake_minimum_required(VERSION 3.14)


project(ukui-digitalclock)
set(VERSION_MAJOR 1)
set(VERSION_MINOR 0)
set(VERSION_MICRO 0)
set(UKUI_DIGITALCLOCK_VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_MICRO})


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 Gui Widgets Quick REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Widgets Quick REQUIRED)


target_link_libraries(${PROJECT_NAME}
        PRIVATE
        Qt${QT_VERSION_MAJOR}::Core
        Qt${QT_VERSION_MAJOR}::Quick
        Qt${QT_MAJOR_VERSION}::Gui
        Qt${QT_VERSION_MAJOR}::Widgets
)

编写插件类

编写QML 模块定义文件:qmldir,声明模块标识符和插件名称。

module org.ukui.digitalclock
plugin ukui-digitalclock

插件类继承自QQmlExtensionPlugin,在这里重写函数 registerTypes() ,将"org.ukui.digitalclock"注册为 qml 模块。

//头文件 ukui-digitalclock-plugin.h
#ifndef UKUIDIGITALCLOCKPLUGIN_H
#define UKUIDIGITALCLOCKPLUGIN_H


#include <QQmlExtensionPlugin>


class UkuiDigitalclockPlugin : public QQmlExtensionPlugin
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
public:
    void registerTypes(const char *uri) override;
};


#endif // UKUIDIGITALCLOCKPLUGIN_H
//ukui-digitalclock-plugin.cpp
#include "ukui-digitalclock-plugin.h"
#include "digitalclock.h"


#include <QQmlEngine>
#include <QQmlContext>


void UkuiDigitalclockPlugin::registerTypes(const char *uri)
{
    Q_ASSERT(QString(uri) == QLatin1String("org.ukui.digitalclock"));
    qmlRegisterModule(uri, 1, 0);
    qmlRegisterType<Digitalclock>(uri, 1, 0, "Digitalclock");
}

Digitalclock为时钟功能类,在这里实现获取系统时间的功能,具体实现如下:

//头文件 digitalclock.h
#ifndef DIGITALCLOCK_H
#define DIGITALCLOCK_H


#include <QObject>
#include <QTimer>


class Digitalclock : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString time READ time NOTIFY timeUpdated)


public:
    explicit Digitalclock(QObject *parent = nullptr);
    ~Digitalclock() = default;
    QString time() const;
private:
    QTimer * m_timer = nullptr;


Q_SIGNALS:
    void timeUpdated();
};


#endif // DIGITALCLOCK_H
//digitalclock.cpp
#include "digitalclock.h"
#include <QTime>


Digitalclock::Digitalclock(QObject *parent)
{
    m_timer = new QTimer(this);
    m_timer->start(1000);
    connect(m_timer, &QTimer::timeout, this, &Digitalclock::timeUpdated);
}


QString Digitalclock::time() const
{
    return QTime::currentTime().toString(Qt::DateFormat::TextDate);
}

编写 widget 描述文件 metadata.json

"Id": "org.ukui.digitalclock"是插件(widget)的标识,加载插件时需要用到。“Contents”: {“Main”: "ui/main.qml"} 用于加载插件的界面 main.qml。

“Contents”: {“I18n”: " "} 用于加载插件的多语言翻译。该字段写入翻译文件路径或文件名,插件就可以自动加载翻译文件,不需要使用 QTranslator加载。如果没有翻译文件,该字段可以为空,或者不写该字段。

{
  "Authors": [
    {
      "Name": "",
      "Email": ""
    }
  ],
  "Id": "org.ukui.digitalclock",
  "Icon": "",
  "Name": "ukui-digitalclock",
  "Name[zh_CN]": "数字时钟",
  "Tooltip": "Digital clock",
  "Tooltip[zh_CN]": "数字时钟",
  "Description": "Digital clock",
  "Description[zh_CN]": "数字时钟",
  "Version": "1.0",
  "Website": "https://ukui.org",
  "BugReport": "https://gitee.com/openkylin/ukui-quick-widgets/issues",


  "Contents": {
    "Main": "ui/main.qml",
    "I18n": ""
  }
}

编写 QML 界面

QML界面分为主入口qml文件和插件界面qml文件。主入口qml文件是插件被加载时实例化的第一个对象。

需要注意的是,主入口的顶级对象必须是“ WidgetItem ”。代码实现如下:

// main.qml
import QtQuick 2.15               // 导入qtquick模块
import QtQuick.Layouts 1.15       // 导入qml布局模块
import org.ukui.quick.widgets 1.0 // 导入ukui-quick插件框架模块


// 定义顶级对象,该对象必须是 " WidgetItem "
WidgetItem {
    id: rootWidget
    // 在此处设置Widget的尺寸策略
    // 截至目前,只有任务栏会尊重Widget的Layout请求,并给予相应的尺寸
    Layout.preferredWidth: digitalclock.width
    Layout.fillHeight: true
    clip: false


    //此处是我们实现的插件界面
    DigitalClockItem {
        id: digitalclock
        anchors.centerIn: parent
    }
}

插件界面代码实现如下:

//DigitalClockItem.qml
import QtQuick 2.15
import org.ukui.digitalclock 1.0 //导入自定义数字时钟模块


Rectangle {
    implicitWidth: 106
    implicitHeight: 40
    radius: 6
    color: Qt.rgba(255, 255, 255, 0.5)
    border.color: "black"


    Digitalclock {
        id: clock
    }


    Text {
        anchors.centerIn: parent
        text: clock.time
        font.pointSize: 18
    }
}

补全 CMakeLists.txt 并安装

当我们编写完插件的主要逻辑后,还需要将插件安装到文件系统中,这样才能被ukui-quick应用加载。

通用插件有两个主要的安装路径:

# 系统路径
/usr/share/ukui/widgets
# 用户路径
${HOME}/.local/share/ukui/widgets

这里我们将插件安装到了系统路径。CMakeLists.txt需要补全的内容如下:

#CMakeLists.txt
set(PROJECT_SOURCES
    plugin/ukui-digitalclock-plugin.h plugin/ukui-digitalclock-plugin.cpp
    plugin/digitalclock.h plugin/digitalclock.cpp
)


set(UKUI_DIGITALCLOCK_DATA_DIR "/usr/share/ukui/widgets/org.ukui.digitalclock")
set(UKUI_DIGITALCLOCK_TRANSLATION_DIR "${UKUI_DIGITALCLOCK_DATA_DIR}/translations")


if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
    add_library(${PROJECT_NAME} SHARED MANUAL_FINALIZATION ${PROJECT_SOURCES})
else()
    add_library(${PROJECT_NAME} SHARED ${PROJECT_SOURCES})
endif()


#安装路径
install(DIRECTORY "widget/" DESTINATION ${UKUI_DIGITALCLOCK_DATA_DIR})
install(FILES "plugin/qmldir" DESTINATION "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/qt5/qml/org/ukui/digitalclock")
install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION  "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/qt5/qml/org/ukui/digitalclock")

数字时钟源码:https://gitee.com/zy-yuan1/ukui-quick-widgets/tree/digital-clock

将工程编译安装,包括在/usr/share/ukui/widgets/目录下安装我们的时钟插件,以及 /usr/lib/x86_64-linux-gnu/qt5/qml/org/ukui/digitalclock 路径安装qml module文件(用于在插件中improt插件库文件 libukui-digitalclock.so)。


03


加载插件到任务栏

1.插件加载机制

Ukui-quick framework 实现了一套插件的接口定义,使得应用通过配置文件(Config)加载和管理插件。更多信息可以参见源码:

https://gitee.com/openkylin/ukui-quick/blob/upstream/framework/config/config.h。

配置文件通过专门的配置文件加载器(ConfigLoader)获取,加载器负责初始化配置并处理节点映射等工作。

/*
* ConfigLoader 负责处理配置文件路径映射等功能,从文件系统加载配置文件
* 配置文件有两个域:
* 根据Domain进行映射:domain {
*     Local: ~/.config/org.ukui/appid/id.json
*     Global: ~/.config/org.ukui/_ukui-config-global/id.json
* }
*/


static Config *getConfig(const QString &id, Domain domain = Global, const QString &appid = QString());

ukui-panel作为一个ukui-quick应用,主界面视图Panel继承自 UkuiQuick::IslandView,构造时定义了id为“panel”,appid为“ukui-panel”,可以确定任务栏的配置文件为~/.config/org.ukui/ukui-panel/panel.json。

IslandView 绑定了配置文件Config,可以通过接口读取配置信息。以下是任务栏加载插件的代码,mainView() 为主界面的根视图,通过 mainView()->config() 可以读取配置文件。

mainView()->config()->children(QStringLiteral("widgets")) 读取了配置文件的 “widgets” 信息,即任务栏需要加载的插件列表。之后,遍历插件列表,向根视图添加插件。

//https://gitee.com/openkylin/ukui-panel/blob/upstream/panel/src/view/panel.cpp
Panel::Panel(Screen *screen, const QString &id, QWindow *parent)
  : UkuiQuick::IslandView(QStringLiteral("panel"), QStringLiteral("ukui-panel")),
    m_id(id)
{
    ......
    
    void Panel::initWidgets()
    {
        // 加载Widget
        QStringList allWidgets;
        UkuiQuick::ConfigList children = mainView()->config()->children(QStringLiteral("widgets"));
        for (const auto &config : children) {
        const QString widgetId = config->getValue(QStringLiteral("id")).toString();
        allWidgets << widgetId;


        if (m_disabledWidgets.contains(widgetId)) {
            continue;
        }
        // 向视图添加插件的widget
        mainView()->addWidget(widgetId, config->id().toInt());
    }
        ......
    }
}

2.插件加载示例

我们已经知道任务栏的配置文件为panel.json ,存储了任务栏加载的插件信息。接下来,把添加数字时钟“org.ukui.digitalclock”添加到在 panel.json 中的 “widgets” 模块,即可实现把数字时钟加载到任务栏上。

"widgetsOrder"代表插件顺序,数字为widgets instanceId。加载数字时钟插件前,任务栏左侧依次加载了开始菜单、搜索、多任务视图等。

我们将"org.ukui.digitalclock"的"instanceId"设置10,并将其添加到"widgetsOrder"中,如下所示:

//任务栏配置文件 .config/org.ukui/ukui-panel/panel.json 
//“widgets” 模块
"widgets": [
                {
                    "id": "org.ukui.menu.starter",
                    "instanceId": 0
                },
                {
                    "id": "org.ukui.panel.search",
                    "instanceId": 1
                },
                {
                    "id": "org.ukui.panel.taskView",
                    "instanceId": 2
                },
                
                ......
                
                {
                    "id": "org.ukui.digitalclock",
                    "instanceId": 10
                }
            ]?,
            "widgetsOrder": [
                0,
                1,
                10,
                2,
                
                ......
                            
           ]
        }
    ],

加载数字时钟插件后的任务栏:

进一步地,我们为数字时钟添加背景图片。可以将背景图片,可以将背景图片放在 plugin/res 文件夹,并添加为资源文件。

#CMakeLists.txt
#添加资源文件,需要注意的是,在插件?注册时要使用 Q_INIT_RESOURCE(res) 来初始化资源
qt5_add_resources(QRC_FILES plugin/res.qrc)


if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
    add_library(${PROJECT_NAME} SHARED MANUAL_FINALIZATION ${QRC_FILES})
else()
    add_library(${PROJECT_NAME} SHARED ${QRC_FILES})
endif()

在插件注册时使用 Q_INIT_RESOURCE(res) 来初始化资源:

//ukui-digitalclock-plugin.cpp
......


void UkuiDigitalclockPlugin::registerTypes(const char *uri)
{
    ......
    Q_INIT_RESOURCE(res) 
}

在 DigitalClockItem.qml中添加以下代码,为数字时钟添加背景图片。

//DigitalClockItem.qml
    
    //背景图片
    Image {
        id: backgroundImg
        anchors.fill: parent
        fillMode: Image.Stretch
        source: "qrc:///res/background.png"
        smooth: true
        visible: false
    }
    //圆角遮罩
    Rectangle {
        id: mask
        anchors.centerIn: parent
        //宽高各减2是为了显示数字时钟的1像素边框
        width: parent.width - 2
        height: parent.height - 2
        radius: 6
        visible: false
    }
    //遮罩后的图片
    OpacityMask {
        anchors.centerIn: parent
        width: parent.width - 2
        height: parent.height - 2
        source: backgroundImg
        maskSource: mask
    }

成果展示:

各位小伙伴,你学会了吗?更多疑问,可在文章下方留言咨询哦~

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言