qt using font library to realize dynamic skin changing of icons

introduction

In order to make the program beautiful and conform to the simple design, icons will be introduced into the interface for button or label. With the continuous evolution of the project and the increase of business logic, the number of icons also expands.

Common icon usage steps

1. Design cut

2.qrc add picture resources

3.css add style

button style

QPushButton#tmp_btn{
    background: transparent;
    border-image: url(:/skin/btn_img.png) 0 60 0 0;
}

QPushButton#tmp_btn:hover{
    border-image: url(:/skin/btn_img.png) 0 40 0 20;
}

QPushButton#tmp_btn:pressed{
    border-image: url(:/skin/btn_img.png) 0 20 0 40;
}

QPushButton#tmp_btn:disabled{
    border-image: url(:/skin/btn_img.png) 0 0 0 60;
}

label style

QLabel#tmp_label{
    background: transparent;
    border-image: url(:/skin/labele_img.png);
}

Above are two commonly used style sheets, btn_img is generally a group diagram of four states, and the style sheet is divided by Jiugong grid, as follows:

In most cases, the four states only have different colors, and the content shapes are exactly the same. In most cases, there is no gradient color. In another way, the button icon is actually a monochrome icon in a certain state, but the icon colors in different states are different. If we can abstract the shape of the icon and set different colors in different states, we can realize the reuse of icons in different states.

If there are customized requirements in the project, that is, different customers require different overall styles. For example, the overall style of the previous software interface is blue. Now it is necessary to switch to red. It is easier to change the background color and modify the css through the script. However, the icons are limited to the traditional implementation method, and a group of red icons need to be prepared.

One or two customization needs are good. When there are five or six customers, the number of icons needs to be expanded to five or six times, and each additional customer needs to prepare pictures again, which consumes the energy of the UI. At the same time, it also slows down the development of subsequent functions. Adding a new icon button requires adding five or six icons.

There is usually negligence in this process. Too many variables lead to the omission of icons in some places at the end, and this error is very obvious to customers. Therefore, there is an urgent need for another way to use icons to avoid waste in this process.

Implementation logic

Referring to the implementation of the web, the icon is converted into text, and the color is controlled by the font color of the style sheet, which can meet the above requirements of reusing the icon shape and controlling the icon color through code.

1. Icon management and font generation using iconfont

Use Alibaba's iconfont for icon management. Iconfont is convenient for adding, viewing, modifying, etc. After the icon is added, it is directly downloaded to the local. There is the font file ttf we need. In addition to the font, we also need to write the encoded value into a macro for the code to call. You can enter it manually. I use code to read the css file in the compressed package and generate macros. The code is as follows:

	if(ui->iconfontPath->text().isEmpty())
        return;
    QFile file(ui->iconfontPath->text());
    if(!file.exists())
        return;
    if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
        return;
    QTextStream stream(&file);

    QString exportPath = "iconfontCssConvert.txt";
    QFile::remove(exportPath);
    QFile exportFile(exportPath);
    if(!exportFile.open(QIODevice::WriteOnly | QIODevice::Text))
        return;
    QTextStream exportStrem(&exportFile);


    QString tmpStr;
    QString rowStr;
    QString outFormat("ICON_%1 = 0x%2,\n");
    while (!stream.atEnd()) {
        rowStr = stream.readLine();
        if(!rowStr.isEmpty()){
            tmpStr += rowStr;
        }
        else{
            if(tmpStr.contains("content")){
                QString name = tmpStr.mid(tmpStr.indexOf("-") + 1, tmpStr.indexOf(":") - tmpStr.indexOf("-") - 1).toUpper().replace("-", "_");
                QString val = tmpStr.mid(tmpStr.indexOf("\\") + 1, tmpStr.lastIndexOf("\"") - tmpStr.indexOf("\\") - 1);
                exportStrem << outFormat.arg(name).arg(val);
            }
            tmpStr.clear();
        }
    }

    file.close();
    exportFile.close();

css file:

Generate file:

2. Font management

After the font library is generated, the code needs to actually use the font library, and find the required icon through the coding value for display.

2.1 font class

#include <QString>
#include <QFont>
#include <QMutex>
#include <QResizeEvent>
#include <QPushButton>
#include <QLabel>

class IconFont
{
public:
    //Add as you use
    enum ICON_INDEX{
        ICON_ADD = 0xe664,
        ICON_ADD_CIRCLE = 0xe665,
        ICON_ADJUST = 0xe666,
        ICON_BROWSE = 0xe667,
        ICON_CAMERA = 0xe669,
        ICON_BINGBAO2 = 0xe6b4,
    };

    explicit IconFont(const QString &_name);
    QFont getIconFont()const{return m_iconFont;}
    QString getIconName()const{return m_name;}

private:
    QFont m_iconFont;
    QString m_name;
};
#include <QFileDialog>
#include <QTextStream>
#include <QFontDatabase>

#define FONT_PATH ":/font/"

IconFont::IconFont(const QString &name)
{
    m_name = name;
    //Load the font to get the ID in the font library
    int fontId = QFontDatabase::addApplicationFont(FONT_PATH + name + QString(".ttf"));
    //Get font name based on ID
    QString fontName = QFontDatabase::applicationFontFamilies(fontId).at(0);
    m_iconFont = QFont(fontName);
}

2.2 font management

Use a singleton to provide an interface for the subsequent dynamic replacement of the font library

class IconManager : public QObject
{
    Q_OBJECT

public:
    static IconManager* instance()
    {
        static QMutex mutex;
        if(!m_iconManager)
        {
            QMutexLocker locker(&mutex);
            if(!m_iconManager)
            {
                m_iconManager=new IconManager;
            }
        }
        return m_iconManager;
    }

private:
    IconManager();
    ~IconManager();

public:
    void setFont(const IconFont &newFont);
    void restoreFont(const QString& fontName);
    QFont getFont() { return m_iconFont.getIconFont(); }
    IconFont getIconFont() { return m_iconFont; }

private:
    static IconManager* m_iconManager;
    IconFont m_iconFont;
};

IconManager* IconManager::m_iconManager = nullptr;

IconManager::IconManager()
    : m_iconFont(IconFont("iconfont"))
{

}

IconManager::~IconManager()
{

}

void IconManager::setFont(const IconFont &newFont)
{
    if (newFont.getIconName() == m_iconFont.getIconName())
        return;
    m_iconFont = newFont;
}

void IconManager::restoreFont(const QString& fontName)
{
    if (IconFont(fontName).getIconName() == m_iconFont.getIconName())
        return;
    m_iconFont = IconFont(fontName);
}

2.3 help

Provides public functions for changing icons, icon size, and icon to image

namespace IconHelper {
    //Set the widget to display the characters represented by iconIndex in the current font library
    template<typename WidgetType>
    void setIcon(WidgetType *wig, int iconIndex)
    {
        QChar c(iconIndex);
        wig->setFont(IconManager::instance()->getFont());
        wig->setText(c);
    }

    template<typename WidgetType>
    void setIconSize(WidgetType *wig, int iconPxSize)
    {
        QFont font = wig->font();
        font.setPixelSize(iconPxSize);
        wig->setFont(font);
    }

    //Font Icon to pixel
    QPixmap transFontToPixmap(int size, int fontSize, int iconIndex, const QColor & iconColor);
    QPixmap transFontToPixmap(int w, int h, int fontSize, int iconIndex, const QColor & iconColor);
}
QPixmap IconHelper::transFontToPixmap(int size, int fontSize, int iconIndex, const QColor & iconColor)
{
    return transFontToPixmap(size, size, fontSize, iconIndex, iconColor);
}

QPixmap IconHelper::transFontToPixmap(int w, int h, int fontSize, int iconIndex, const QColor & iconColor)
{
    QString iconStyle = QString("QLabel{color:rgba(%1,%2,%3,%4)};").arg(iconColor.red()).arg(iconColor.green()).arg(iconColor.blue()).arg(iconColor.alpha());

    QLabel widget;
    widget.setAttribute(Qt::WA_TranslucentBackground);
    widget.setFixedSize(w, h);
    widget.setAlignment(Qt::AlignCenter);
    widget.setText(QChar(iconIndex));
    widget.setStyleSheet(iconStyle);

    QFont font = IconManager::instance()->getFont();
    font.setPointSize(fontSize);
    widget.setFont(font);

    return QPixmap::grabWidget(&widget, widget.rect());
}

3. Basic controls

Two common controls (buttons and labels) are implemented, mainly buttons. Considering the polymorphism of button icons (such as different icons in the selected state), qt state machine is introduced to simplify the code. The effects are as follows:

#define ICON_METHOD \
    Q_PROPERTY(int iconIndex READ icon WRITE setIcon)\
public:\
    void setIcon(int index) {\
        if (index == m_iconIndex)\
            return;\
        m_iconIndex = index;\
        IconHelper::setIcon(this, index);\
        emit sig_iconIndexChanged(index);\
    }\
    void setIconSize(int pxSize) {\
        m_pxSize = pxSize;\
        IconHelper::setIconSize(this, m_pxSize);\
    }\
    int icon() { return m_iconIndex; }\
protected:\
    virtual void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE { adjustIconSize(); }\
    void adjustIconSize() {\
        IconHelper::setIconSize(this, m_pxSize ? m_pxSize : qMin(this->width(), this->height()));\
    }\
    int m_pxSize;\
    int m_iconIndex;\

class IconButton : public QPushButton
{
    Q_OBJECT
    ICON_METHOD

public:
    explicit IconButton(QWidget *parent = nullptr);
    explicit IconButton(int iconIndex, QWidget *parent = nullptr);
    explicit IconButton(const QList<int>& iconList, QWidget *parent = nullptr);

signals:
    void sig_iconIndexChanged(int index);
};

class IconLabel : public QLabel
{
    Q_OBJECT
    ICON_METHOD

public:
    explicit IconLabel(QWidget *parent = nullptr);
    explicit IconLabel(int iconIndex, QWidget *parent = nullptr);

signals:
    void sig_iconIndexChanged(int index);
};
#include <QState>
#include <QStateMachine>

IconButton::IconButton(QWidget *parent)
    : QPushButton(parent)
    , m_pxSize(0)
{
    IconHelper::setIcon(this, IconFont::ICON_BINGBAO2);
}

IconButton::IconButton(int iconIndex, QWidget *parent)
    : QPushButton(parent)
    , m_pxSize(0)
{
    IconHelper::setIcon(this, iconIndex);
}

IconButton::IconButton(const QList<int>& iconList, QWidget *parent /*= nullptr*/)
    : QPushButton(parent)
    , m_pxSize(0)
{
    if (!iconList.isEmpty()) {
        QList<QState*> stateList;
        foreach(int index, iconList) {
            auto state = new QState;
            state->assignProperty(this, "iconIndex", index);
            stateList << state;
        }

        auto stateMachine = new QStateMachine(this);
        for (int i=0; i < stateList.count(); i++){
            stateList.at(i)->addTransition(this, &QPushButton::clicked, stateList.at((i + 1) % stateList.count()));
            stateMachine->addState(stateList.at(i));
        }

        stateMachine->setInitialState(stateList.first());
        stateMachine->start();
    }
    else {
        this->setIcon(IconFont::ICON_BINGBAO2);
    }
}

/
IconLabel::IconLabel(QWidget *parent /*= 0*/)
    : QLabel(parent)
    , m_pxSize(0)
{
    IconHelper::setIcon(this, IconFont::ICON_BINGBAO2);
    this->setAlignment(Qt::AlignCenter);
}

IconLabel::IconLabel(int iconIndex, QWidget *parent /*= nullptr*/)
    : QLabel(parent)
    , m_pxSize(0)
{
    IconHelper::setIcon(this, iconIndex);
    this->setAlignment(Qt::AlignCenter);
}

4. Dynamic skin change

Here, the dynamic skin changing is done by directly replacing the style sheet, or through the attribute selector of css. The effects are as follows:

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;

private:
    IconButton* m_iconBtn;
    IconLabel* m_iconLabel;
    QPushButton* m_changeBtn;

    QString m_btnStyle;
    QString m_labelStyle;
    QColor m_color;
};
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    setFixedSize(400,400);

    ui->setupUi(this);
    //m_iconBtn = new IconButton(QList<int>() << IconFont::ICON_BINGBAO2 << IconFont::ICON_CAMERA << IconFont::ICON_BROWSE,this);
    m_iconBtn = new IconButton(this);
    m_iconLabel = new IconLabel(IconFont::ICON_CAMERA, this);

    m_btnStyle = "IconButton {\
        color:%1;\
        border:0px;\
        background-color:transparent;}\
        IconButton:hover {\
            color:%2;}\
        IconButton:pressed {\
            color:%3;}";

    m_labelStyle = "IconLabel {\
            color:%1;\
            border:0px;\
            background-color:transparent;}";

    m_changeBtn = new QPushButton("change style",this);
    connect(m_changeBtn, &QPushButton::clicked, this, [this]{
        m_color = m_color == QColor(Qt::red) ? QColor(Qt::green) : QColor(Qt::red);
        auto color1 = m_color;
        color1.setAlphaF(0.2);
        auto color2 = color1;
        color1.setAlphaF(0.6);
        auto color3 = color1;
        m_iconBtn->setStyleSheet(m_btnStyle.arg(m_color.name(QColor::HexArgb)).arg(color2.name(QColor::HexArgb)).arg(color3.name(QColor::HexArgb)));

        m_iconLabel->setStyleSheet(m_labelStyle.arg(m_color.name()));
    });

    auto widget = new QWidget(this);
    setCentralWidget(widget);
    auto mainLayout = new QGridLayout(widget);
    mainLayout->addWidget(m_iconBtn,0,0,1,1);
    mainLayout->addWidget(m_iconLabel,0,1,1,1);
    mainLayout->addWidget(m_changeBtn,1,0,1,2);
}

MainWindow::~MainWindow()
{
    delete ui;
}

Keywords: Qt

Added by newbtophp on Wed, 19 Jan 2022 23:54:08 +0200