简 述: DTK
基础知识,尤其使用 DTK
开发应用软件或者参与维护 DTK
库,此都是一个良好的入门级别的知识;且本文档着重讲解 “DTK自定义控件” 的规范。 此处为一个搬运,为了易于搜索和自己做一个记录,收录进自己目录中。
给 DTK 库加了一个基础知识性的 WiKi,里面有一些 DTK 绘画基础知识、绘画原理流程、自定义控件规范、图标的使用。 其由 zccrs 所编写。
[TOC]
QStyle
阅读此文档需要点此先熟悉以下类:
DTK Style
DTK Style插件的实现可以划分为两部分:
- Qt 库的内置控件
- DTK 自定义控件
本文档着重讲解“DTK自定义控件”的规范。DTK Widget 模块跟控件样式相关的主要有三个类:
DStyle
DStyle 继承于 QCommonStyle,在基类(QStyle)的基础上添加了一些枚举值以及绘制、加工类的函数。Qt 所有内置控件均在 paintEvent 中使用 QStyle 中提供的各种 drawXXX 接口进行绘制,另外,QStyle 中定义了大量的枚举类型,如 QStyle::PrimitiveElement、QStyle::ControlElement、QStyle::SubControl 等,这些枚举类型用于控件的绘制、布局、标志位等抽象接口的定义中。QStyle 满足了 Qt 原生控件自定义风格的需求,同理,DStyle 需要在此基础上满足 DTK 控件自定义风格的需求。
DStyleOption
用于扩展 QStyleOption。如 DStyleOptionButton 需要对 QStyleOptionButton 就行扩展,它需要同时继承 DStyleOption 和 QStyleOptionButton。
DPalette
控件的行为虽然不同,但是它们的配色有很多共同的地方,在 Qt 的 GUI 模块中,QPalette 类负责管理所有通用型颜色。每一个 QWidget 对象都有对应的 QPalette,当没有使用 QWidget::setPalette 指定控件使用的调色板时,其将跟随 QApplicatioin::palette。
QPalette 中定义了两个枚举类型:
- ColorGroup 颜色的分组,包含:Disabled、Active(Normal)、Inactive 三个值
- ColorRole 颜色的类型,包含:Background、Base、Foreground 等一系列值
我们可以把 ColorRole 定义的颜色理解为 c++ 中的基本数据类型(int double等),则控件的设计类似于 c++ 类的设计,一个是封装基本数据类型定义一个类,另一个则是通过对基础颜色的组合创建一个控件。
QPalette 包含十几种常用的颜色,能满足 Qt 所有内置控件的绘制需要。但是 DTK 控件的设计在配色上更加的丰富,QPalette::ColorRole 中定义的基本颜色无法满足需求,因此在 dtkwidget 库中定义了 DPalette 类,并且添加了 DPalette::ColorType 的枚举类型,用于为 DTK 控件添加基本色的定义。
QStyle 和 QPalette 的关系
QApplication 会在初始化阶段(当使用 QApplication::setStyle 后也会执行以下步骤)调用 QStyle::standardPalette 获取一个标准的 QPalette 对象,这个对象在各个控件的 paintEvent 中被初始化给 QStyleOption (用于存储绘制控件所需要的所有数据),之后在 QStyle 绘制控件时则从 QStyleOption::palette 中获取定义的各种颜色值。对于 QApplication、QStyleOptin、QWidget 而言,它们只识别 QPalette,因此,DPalette 的初始化目前是放置在 QStyle::polish(QApplication*) 中,由 DPalette 自己管理 QApplication 和 QWidget 对象所对应的 DPalette 数据,在 style 插件的实现中则通过 DPalette::get 静态函数获取调色板对象。
DTK 控件设计
在 DDEV20 的设计中,有以下几个特点。首先先看一张图
每个控件分为五个状态:
- Normal
- Hover
- Pressed
- Disabled
- Focus
于此相关的是,主窗口层面还分为 Active 和 Inactive 状态,也就是窗口是否被激活的状态。窗口的状态可以和控件状态相互叠加,因此,叠加后的控件状态共有十种组合。不过,在控件中其实不用过于关心窗口的状态,窗口 Active 时控件都是默认样式,Inactive 时则把所有颜色的不透明度降低 40%。同理 Disabled 状态则把所有颜色的不透明度降低 60%。因此,除去这两个特殊状态后,控件中还剩余 Normal Hover Pressed Focus 四个状态需要关心。
Normal
控件默认状态,使用 DPalette 的 Normal 组中各个 ColorRole/ColorType 的定义即可。
Hover
控件的 hover 状态可以由 normal 状态经过颜色变换(调色:如提供颜色亮度等),可使用 DStyle::generatedBrush 获取加工后的颜色值。DStyle 定义了 StyleState 用于指定需要加工的目标颜色类型,默认 Normal 可以不进行任何加工。
Pressed
和 Hover 状态一样,也是由 normal 状态经过颜色变换得到。
代码流程描述
字号使用规范
字号分为十个等级,T1-T10,默认为 T6 级别,当系统中字号大小改变时,所有级别基于默认级别进行同值增减。例如,T6 当前对应的字号为 16px,在控制中心将字号调整为 20px 后,T1-T10 的字号都将增加 4px。所有的控件设计中均不可直接指定字号大小,如果需要对特定的 QWidget 对象设置字号等级,可以使用 DFontSizeManager::bind 接口绑定到对应的级别。
如何实现一个全新的控件
首先为此控件添加需要的枚举值,如 DSuggestButton,QStyle 中已经包含了 QStyle::CE_PushButton 的定义,这部分不用再进行扩展,查看 QStyleOptionButton,它在 ButtonFeatures 中定义了按钮的一些特性,因此我们需要继承 QStyleOptionButton 进行扩展。
class DStyleOptionButton : public QStyleOptionButton, public DStyleOption
{
public:
enum ButtonFeature {
SuggestButton = (CommandLinkButton << 1),
WarningButton = (SuggestButton << 1)
};
void init(QWidget *widget) override;
};
这是 DStyleOptionButton 的代码实现,添加了两个枚举值 SuggestButton、WarningButton。在 DSuggestButton 的 paintEvent 中将使用这些新加的值绘制自定义的按钮。
void DSuggestButton::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QStylePainter p(this);
DStyleOptionButton option;
option.features |= DStyleOptionButton::ButtonFeatures(DStyleOptionButton::SuggestButton);
initStyleOption(&option);
option.init(this);
p.drawControl(QStyle::CE_PushButton, option);
}
最终,对 QStylePainter::drawControl 的调用将变为对 QStyle::drawControl 的调用。
XXX::drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w = nullptr)
{
switch(element) {
case CE_PushButton:
...
QStyleOptionButton *opt_button = static_case<QStyleOptionButton*>(opt);
if (opt_button->features.testFlag(DStyleOptionButton::SuggestButton)) {
// draw suggest button
}
...
break;
}
}
这样即完成了对 DSuggestButton 的绘制。
如何自绘控件
有些控件在 Qt 以及 DTK 库中均不存在,在应用程序的开发中需要自己绘制实现,如控制中心个性化模块中的颜色选择控件:
paintEvent 代码:
void paintEvent(QPaintEvent *event)
{
QPainter pa(this);
QStyleOption opt;
opt.initFrom(this);
int borderWidth = style()->pixelMetric(DStyle::PM_FocusBorderWidth, &opt, this);
int borderSpacing = style()->pixelMetric(DStyle::PM_FocusBorderSpacing, &opt, this);
const QMargins margins(borderWidth + borderSpacing, borderWidth + borderSpacing, borderWidth + borderSpacing, borderWidth + borderSpacing);
pa.fillRect(rect().marginRemoved(margins), color); // 填充颜色背景
pa.setPen(QPen(borderWidth, opt.palette.highlight())); // 设置边框的宽度和颜色
pa.drawEllipse(rect()); // 绘制选中的边框
}
从例子中可以看出,所有和颜色相关的数据均从 QPalette 中读取,所有和大小相关的数据均从 style 对应的接口中读取。
如何自定义控件的一些参数
如更改文本颜色:
QLabel label("test");
QPalette pa = label.palette();
pa.setColor(QPalette::Text, Qt::red);
label.setPalette(pa);
如何添加图标
一个完整的应用程序主题需要关注的不仅仅是控件的样式,有时,程序中对图标的使用也需要区分深色还是浅色。在 Qt 库中,查找和使用图标都可以通过 QIcon 完成,为了方便图标自动跟随程序主题,DDE 桌面环境在 Qt platform theme 主题插件中添加了一个内置的图标主题,路径为:"qrc:/icons/deepin/builtin/[light/dark]"
,其中,light
和dark
子目录为可选,它们分别为应用程序提供亮色和暗色图标的存储,如果图标本身为通用型(在任何主题下都可使用),则图标文件直接放置到"qrc:/icons/deepin/builtin"
目录即可。
图标分为三种类型:
- TextType 纯文本性图标,其颜色会跟随画笔的前景色变化(和文字颜色保持一致)。文件放置路径:
"qrc:/icons/deepin/builtin/[light/dark]/texts"
- ActionType 动作型图标,其颜色会在其 Mode 改变时跟随画笔前景色(Normal模式图标颜色不会发生变化)。文件放置路径:
"qrc:/icons/deepin/builtin/[light/dark]/actions"
- IconType 图标型图标,其颜色在任何模式下都不会变化。文件放置路径:
"qrc:/icons/deepin/builtin/[light/dark]/icons"
图标名称规范:
名称前面需要添加能标示当前程序的前缀,例如控制中心
的图标文件 dcc_xxx_32px.svg(设计大小为32)每一个都会有 “dcc_” 的前缀,其后跟图标s名称(图标名称单词间使用 “_” 符号链接,且全部使用小写字母),再接着,”_32px” 为图标大小标识,标识此图标默认大小,最后的 “.svg” 为图标文件后缀名。
后记:
qrc 表示是一个 Qt 资源文件,此文件需要应用程序中自行创建。”/icons/deepin/builtin” 为路径的固定前缀,随后的目录则为三种类型主题对应的目录。”actions” 为图标的类型目录,目前支持 “texts”、”actions” 和 “icons” 三种类型,”texts” 和 “actions” 目录用于放置一些工具性图标(一般都比较小,且颜色单一),”icons” 则用于放置一些颜色丰富的图标。
添加图标文件后,在程序中只需要使用 QIcon::fromTheme(“dcc_xxx”) 即可获取当前主题对应的图标对象,”dcc_xxx” 为图标文件名称,不包含图标大小标识和文件后缀名!
此规范仅用于约定应用程序中图标的存储和使用,其它图片资源不可使用此规范!
icon_demo.zip #示例程序
下载地址:
欢迎 star
和 fork
这个系列的 QT / DTK 学习,附学习由浅入深的目录。