
У наших заказчиков нередко появляется потребность в использовании различного рода графических интерфейсов для вывода графиков, таблиц, различных показателей и метрик их ФПО, а также элементов управления.
С помощью библиотеки facefull можно создавать современные графические пользовательские интерфейсы с использованием технологий HTML, CSS и JS как для веб, так и для нативных приложений. Библиотека содержит более 30 различных визуальных компонентов с огромными возможностями кастомизации. Все компоненты адаптивные и отлично подходят для использования с разными разрешениями экрана, а также с тачскринами.
Библиотека обладает исчерпывающей документацией, а ее исходный код доступен в публичном репозитории.
В случае с нативными приложениями, в качестве рендера интерфейса выступает системный веб-движок, в случае Нейтрино — это WebKit. В Нейтрино имеется поддержка Qt5, поэтому самый простой способ отображения такого интерфейса — использование компонента QWebView. Недавно мы рассказывали о нашем инструменте мониторинга аномальной активности, пользовательский интерфейс графического приложения разработан с использованием facefull.
Введение
Отличие использования facefull для разработки интерфейсов для нативных приложений от классических случаев применения web-based UI в том, что в данном случае в webview переносится только графический интерфейс (и логика его работы), а вся основная логика приложения остаётся написанной на нативных языках С\С++. Это означает, что приложение не теряет в функциональных возможностях и скорости работы.
Для того, чтобы связать нативный код на С\С++ и код facefull требуется реализация специального механизма обмена сообщениями, который называется bridge. Для этих целей была разработана библиотека facefull-bridge, которая реализует этот механизм для различных фреймворков, в том числе и Qt5WebKit. Она включает в себя все компоненты библиотеки facefull. Библиотека facefull-bridge была портирована под ОС Нейтрино и доступна "из коробки". Её исходный код также доступен в публичном репозитории.
Схематически процесс организации взаимодействия компонентов внутри приложения выглядит следующим образом:

Таким образом, применение facefull для построения графических интерфейсов в нативных приложениях сводится к трём простым шагам.
Шаг 1. Создание главного окна Qt с виджетом QWebView
Обычно главное окно в Qt создаётся с помощью наследования от класса QMainWindow. На главном окне необходимо разместить только виджет QWebView и ряд вспомогательный компонентов. Типовой вариант заголовочного файла mainwindow.h выглядит так:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QVBoxLayout>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow {
Q_OBJECT
private:
Ui::MainWindow *ui;
QVBoxLayout *MainLayout;
QWidget *MainWidget;
QWebView *WebView;
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
};
#endif //MAINWINDOW_HРеализация класса в файле mainwindow.cpp содержит создание объекта класса QWebView и размещение виджета на главном окне:
#include <iostream>
#include <QDir>
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
ui -> setupUi(this);
// Если нужно убрать системную рамку и заголовок окна
setWindowFlags(Qt::FramelessWindowHint);
std::cout << "WebKit version: " << qWebKitVersion().toStdString() << std::endl;
MainLayout = new QVBoxLayout();
MainLayout -> addSpacing(0);
MainLayout -> setContentsMargins(0, 0, 0, 0);
WebView = new QWebView(this);
MainLayout -> addWidget(WebView);
MainWidget = new QWidget();
MainWidget -> setLayout(MainLayout);
setCentralWidget(MainWidget);
}
MainWindow::~MainWindow() {
delete ui;
}Шаг 2. Инициализация bridge
Для функционирования bridge необходимо подключить подходящий заголовочный файл с реализацией интерфейса и выполнить создание объекта, передав конструктору нужные параметры. Также потребуются некоторые вспомогательные методы.
В описание класса в файле mainwindow.h нужно внести следующие изменения:
#include <facefull/bridge/qt5webkit.hpp>
class MainWindow : public QMainWindow {
Q_OBJECT
private:
...
FacefullBridgeQt5WebKit *Bridge;
...
protected:
bool eventFilter(QObject* object, QEvent* event) override;
public slots:
void doBridgeEventReceive(const QString&) const;
signals:
void BridgeEventHandler(QString, QString);
...
};Перегруженный метод eventFilter необходим для реализации перемещения окна с помощью кастомного заголовка, а методы doBridgeEventReceive и BridgeEventHandler — слот и сигнал для обработки событий от QWebView.
В файле mainwindow.cpp добавится создание объекта класса FacefullBridgeQt5WebKit и реализация некоторых из указанных методов:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
...
Bridge = new FacefullBridgeQt5WebKit(this, WebView, QUrl("путь к html странице"));
...
}
void MainWindow::doBridgeEventReceive(const QString &data) const {
Bridge -> doEventCatch(data.toStdString());
}
bool MainWindow::eventFilter(QObject* object, QEvent* event) {
Bridge -> doMoveWindow((QMouseEvent*)event);
return false;
}Здесь переменная respath содержит путь к странице, реализующей графический интерфейс.
Шаг 3. Реализация UI
Этот шаг сводится к созданию трёх компонентов: HTML страницы, стилей (CSS), и основного JS-скрипта интерфейса приложения. Традиционно файлы называются window.html, style.css и app.js соответственно. Библиотека facefull в свою очередь предоставляет реализацию функционала визуальных компонентов, реализацию внутренней составляющей bridge и стили.
Описание HTML страницы
Файл window.html содержит описание компонентов на языке разметки HTML5, которые должны быть отображены в приложении, а также подключает все необходимые ресурсы. Простейший пример страницы выглядит следующим образом:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Facefull test</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<!-- Подключение необходимых ресурсов -->
<script src="facefull/facefull.min.js" charset="utf-8"></script>
<script src="src/app.js" charset="utf-8"></script>
<link rel="stylesheet" href="facefull/facefull.min.css">
<link rel="stylesheet" href="src/style.css">
</head>
<body>
<!-- Контейнер окна, обязательно должен иметь id="W" -->
<div id="W" class="Window">
<!-- Определение заголовка окна, обязательно должен иметь id="WH" -->
<div id="WH" class="WindowHeader" onselectstart="return false">
<div class="WindowIcon"></div>
<div class="WindowCaption">Facefull app example</div>
<div class="WindowMover"></div>
<div class="WindowControlsBlock">
<div class="WindowControl Min"><div></div></div>
<div class="WindowControl Max" id="WCM"><div></div></div>
<div class="WindowControl Close"><div></div></div>
</div>
</div>
<!-- Контейнер рабочей зоны окна, обязательно должен иметь id="G" -->
<div id="G" class="GlobalArea">
<!-- Определение главного бокового меню -->
<div class="MainMenu">
<div id="MMI" class="MainMenuItems">
<div class="TooltipTarget" data-pagename="Page1" data-tooltip-text="Page 1" data-tooltip-width="130" data-tooltip-pos="right"></div>
<div class="TooltipTarget" data-pagename="Page2" data-tooltip-text="Page 2" data-tooltip-width="120" data-tooltip-pos="right"></div>
</div>
</div>
<!-- Рабочая область окна -->
<div class="WorkArea">
<!-- Определение первой вкладки. В id указывается желаемое id вкладки с префиксом P (т.е. P<id вкладки>). Оно автоматически связывается с элементами главного меню -->
<div id="PPage1" class="Page">
<div class="Title">
<div class="TitleText"><div>Вкладка 1</div>
<div class="Subtitle">Подзаголовок вкладки 1</div>
</div>
</div>
<div class="Box PageBody Scrolling" data-scrollboxname="P1SB">
<div class="Scrolldata">
<!---->
</div>
</div>
</div>
<!-- Определение второй вкладки -->
<div id="PPage2" class="Page">
<div class="Title">
<div class="TitleText"><div>Вкладка 2</div>
<div class="Subtitle">Подзаголовок вкладки 2</div>
</div>
</div>
<div class="Box PageBody Scrolling" data-scrollboxname="P1SB">
<div class="Scrolldata">
<!---->
</div>
</div>
</div>
</div>
</div>
<!-- Определения дополнительных элементов окна: -->
<!-- Определение всплывающей подсказки -->
<div id="TT" class="Tooltip"></div>
<!-- Определение затеняющего оверлея для всплывающих сообщений -->
<div id="OV" class="Overlay"></div>
<!-- Определение стандартного окна всплывающих сообщений -->
<div id="AE" class="Alert Hidden Rounded">
<div class="AlertCaption"></div>
<div class="AlertText"></div>
<div class="AlertButtons">
<div id="AB-OK" class="Button Rounded">OK</div>
<div id="AB-Y" class="Button Rounded">Yes</div>
<div id="AB-N" class="Button Rounded">No</div>
</div>
</div>
</div>
</body>
</html>
Описание стилей
Стандартный файл стилей facefull.min.css содержит все необходимые стили для стандартных визуальных компонентов библиотеки facefull, но их можно переопределить в собственном CSS файле. Например, можно задать значки пунктам главного меню и значок в заголовке окна:
.MainMenu *[data-pagename="Page1"]::before {
content: '\F056E';
}
.MainMenu *[data-pagename="Page2"]::before {
content: '\F0D7C';
}
.WindowIcon {
font-family: "Material Design Icons";
font-size: 28px;
}
.WindowIcon::before {
content: '\F126F';
}В состав библиотеки facefull входит шрифт Material Design Icons, содержащий сотни значков в минималистичном стиле.
Описание основного JS скрипта
Теперь необходимо описать скрипт инициализации графического интерфейса. JS файл (app.js) должен содержать следующие обязательные определения:
// Инициализация объекта facefull. Все взаимодействия с библиотекой осуществляются через этот объект
facefullCreate(true);
// Запуск инициализации интерфейса после загрузки страницы
window.addEventListener('load', function () {
App();
});
function App() {
// Инициализация компонентов facefull
facefull.doInit();
// ...
// Иницализация графического интерфейса всегда должна заканчиваться отправкой в bridge сообщения doWindowReady. Это сообщение генерирует событие, которое означает, что интерфейс проинициализирован и готов к работе. После этого можно отправлять и получать сообщения через bridge.
facefull.doEventSend("doWindowReady");
}
Использование QRC для сборки ресурсов
Все ресурсы графического интерфейса можно использовать как напрямую с файловой системы, так и через QRC. QRC позволяет "вкомпилировать" их в бинарный файл, что удобнее при распространении приложения. Пример файла описания ресурсов выглядит следующим образом:
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
<file>ui/window.html</file>
<file>ui/app.js</file>
<file>ui/style.css</file>
<file>ui/facefull/facefull.min.js</file>
<file>ui/facefull/facefull.min.css</file>
<file>ui/facefull/theme-light.min.css</file>
<file>ui/facefull/fonts/md-embedded.woff</file>
</qresource>
</RCC>Таким образом, все ресурсы будут доступны из основного кода приложения через префикс qrc:. Теперь нужно указать в конструкторе при создании bridge правильный путь к html странице графического интерфейса:
Bridge = new FacefullBridgeQt5WebKit(this, WebView, QUrl("qrc:/ui/window.html"));Теперь после запуска приложения можно увидеть получившийся результат:

Взаимодействие с UI через bridge
Со стороны нативного кода отправка сообщений через bridge осуществляется с помощью метода doEventSend, для приёма сообщений необходимо создать обработчик события с помощью метода doEventAttach. Аналогичным образом взаимодействие осуществляется и со стороны UI — отправка и приём выполняется с помощью методов facefull.doEventSendи facefull.doEventHandlerAttach соответственно.
Рассмотрим пример. Чтобы отправить тестовое сообщение в UI в нативном коде (например в конструкторе класса MainWindow) выполняем соответствующий вызов:
// Навешиваем обработчик на событие готовности окна и отправляем сообщение
Bridge -> doEventAttach("doWindowReady", [this](const std::string& data) {
Bridge -> doEventSend("doTestMessage", "Тестовое сообщение");
});В свою очередь в app.js добавляем обработчик события:
facefull.doEventHandlerAttach("doTestMessage", function(data) {
AlertShow("Сообщение", data, "info", "OK");
});Теперь при запуске приложения будет появляться всплывающее сообщение с текстом "Тестовое сообщение":

Заключение
Библиотека facefull-bridge (библиотека визуальных компонентов facefull входит в её состав) является open-source проектом, который был портирован и адаптирован под использование в ОС Нейтрино. Описанный в статье подход позволяет с минимальными усилиями начать создавать современные графические интерфейсы, например, для вывода таблиц, графиков, элементов управления или других важных показателей ФПО. При этом сохраняется производительность и функциональность этого ФПО, так как нативный код приложения остаётся нативным.
Полный исходный код описанного в статье примера использования библиотеки facefull-bridge доступен в публичном репозитории СВД ВС. Компоненты библиотеки станут доступны потребителям ОС Нейтрино с релизом редакции 2024, однако сейчас библиотеку можно собрать руками из оригинального репозитория.
Список поддерживаемых визуальных компонентов, доступных "из коробки", постоянно расширяется. Сейчас доступны различные кнопки, переключатели, списки, графики, поля ввода, ��еню и много другое. Кроме визуальных компонентов facefull предоставляет и другие возможности. Менеджер тем оформления, с помощью которого можно достаточно просто управлять стилями оформления и создавать новые (стандартные визуальные компоненты "из коробки" доступны в двух стилях — тёмном и светлом); менеджер локализаций, позволяющий управлять локализациями интерфейса; а также менеджер отображения, предоставляющий возможность описывать правила поведения графического интерфейса при изменении разрешения и устройства отображения.
Использование библиотеки не требует специальных знаний (только чтение документации на API), так как применяются стандартные HTML5, CSS и JS. Полученный графический интерфейс приложения легко переносится из нативного режима в браузер, если потребуется создание web-приложения на его основе.
Подписывайтесь на наш канал, чтобы быть в курсе свежих новостей


