Оглавление
Введение
Библиотека круговых интерфейсов v2.0
Круговая CAPTCHA
Перенос библиотеки с JavaScript на QML
Демонстрационное мобильное приложение
Заключение
Введение
Предыдущая статья была вводной к вопросу разработки круговых интерфейсов. В ней рассмотрены определение, классификация, принципы построения круговых интерфейсов, а также подход к их проектированию. Данная статья описывает основные изменения в библиотеке, которую я разрабатываю для облегчения и ускорения процесса создания круговых интерфейсов.
Во-первых, вышла вторая версия библиотеки на JavaScript, в которой реализованы круговые элементы управления.
Во-вторых, разработанная библиотека получила реализацию на QML, для демонстрации возможностей которой выпущено мобильное приложение под Android.
Библиотека круговых интерфейсов v2.0
Новые базовые и вспомогательные элементы
В первой версии библиотеки были реализованы базовые элементы круговых интерфейсов, комбинация которых позволяла получать необычные графические композиции. Во второй версии библиотеки этот набор расширен элементами, представленными на рисунке 1.
Рис. 1 – Новые базовые и вспомогательные элементы
Segment Grid – Сегментная сетка (рис. 1A). Это сегмент с прорисованной полярной координатной сеткой. Применяется при построении таких графических элементов как, например, радар или мишень.
Segment Spiral – Сегментная спираль (рис. 1B). Это часть сегмента, отделенная кривой, построенной по уравнению спирали. Наиболее часто применяется для индикации изменяемого значения, например, громкости.
Segment Gradient типа “Conic” — Конический градиент (рис. 1C). Это способ заливки фигуры, при котором цвет меняется вдоль дуги окружности. Позволяет получать дополнительный визуальный эффект и используется, например, для имитации локатора в графическом элементе «Радар».
Круговые элементы управления
Основным нововведением второй версии библиотеки является набор наиболее часто используемых элементов управления и визуализации информации.
Progress Bar – Круговой индикатор прогресса. В компактном виде показывает степень выполнения определенного процесса (рис. 2).
Рис. 2 – Индикатор прогресса
Timer – Таймер в виде циферблата со стрелкой или с индикатором в виде сегмента, массива сегментов или массива точек (рис. 3).
Рис. 3 – Таймер
Gauge – Измерительный прибор (рис. 4). Например, спидометр или барометр. Особенностями реализации является наличие измерительной шкалы с числовыми отметками и стрелки-указателя.
Рис. 4 – Измерительный прибор
Chart – Круговая диаграмма. Представлены 3 типа: «Пирог», «Радиальная», «Гистограмма» (рис. 5 слева направо).
Рис. 5 – Круговая диаграмма
Equalizer – Эквалайзер (рис. 6). Графический элемент для визуализации частотных характеристик сигнала.
Рис. 6 – Эквалайзер
Knob – Поворотная ручка (рис. 7).
Рис. 7 – Поворотная ручка
Volume Control – круговой регулятор громкости (рис. 8). Реализованы два типа регуляторов: с индикатором в виде массива сегментов и в виде сегментной спирали.
Рис. 8 – Регулятор громкости
Radar – Радар. Графический элемент для визуализации взаимного расположения объектов (рис. 9). Основными конструктивными элементами радара являются координатная сетка, локатор и индикаторы целей.
Рис. 9 – Радар
Круговая CAPTCHA
Отдельно хочу отметить новый элемент CAPTCHA (Рис. 10).
Рис. 10 – Круговой тест CAPTCHA
CAPTCHA — это тест, позволяющий идентифицировать пользователя в качестве человека или компьютера (бота, программы). Обычно в качестве такого теста пользователю предлагается ввести цифры, буквы или слова с картинки, или выбрать из набора картинок те, которые соответствуют определенному критерию, например, на которых изображены велосипеды.
Круговая CAPTCHA предполагает, что пользователь должен совместить разрывы свободных концентрических колец с разрывом базового неповоротного кольца. Тогда как для бота это будет нетривиальной задачей, человек справится с этой проверкой за несколько секунд.
В качестве базового кольца может быть установлено внутреннее или внешнее кольцо.
Внешний вид элемента может быть настроен за счет изменения следующих параметров:
- цвет или градиент заливки базового и поворотных колец;
- толщина и цвет границ базового и поворотных колец;
- толщина колец и расстояние между кольцами;
- размер разрыва колец в градусах.
Также может быть настроена необходимая точность совмещения колец в градусах.
В случае успешной проверки, элемент запускает соответствующее событие.
Вторая версия Библиотеки круговых интерфейсов на JavaScript доступна для свободного скачивания и использования в репозитории.
Перенос библиотеки с JavaScript на QML
Перенос библиотеки был обусловлен желанием получить инструмент, ускоряющий разработку круговых интерфейсов на QML для настольных и мобильных кроссплатформенных приложений.
При этом реализовать перенос хотелось с минимально возможным изменением исходного кода, или, хотя бы, с максимальным его переиспользованием.
Для запуска компонентов библиотеки на QML потребовалось выполнить следующие действия.
1. Инициализировать все свойства компонента
JavaScript позволяет добавлять атрибуты объекта по ходу выполнения программы без явного указания типа. В QML все атрибуты компонента должны быть объявлены заранее и должны иметь определенный тип. Поэтому первым делом после переноса кода из JS-скриптов в QML-компоненты проходим по коду и инициализируем все используемые атрибуты объектов.
2. Заменить JavaScript события на QML сигналы
Аналогом JavaScript событий в QML являются сигналы. Объявляем все необходимые сигналы при создании компонента. А в скопированном JavaScript коде находим все строки, содержащие dispatchEvent, и заменяем их на вызов соответствующих сигналов.
3. Использовать QML Canvas вместо соответствующего HTML элемента
QML предоставляет собственную реализацию компонента Canvas c API, очень похожим на HTML-версию данного элемента. Для отрисовки графических объектов используем этот компонент.
При создании графических объектов нашей библиотеки на JavaScript в функцию-конструктор передается контекст рендеринга в качестве параметра. В QML реализации для того, чтобы ссылаться на контекст рендеринга через свойство context компонента Canvas, контекст должен быть активен. Для этого в обработчике сигнала paint должен быть реализован сам алгоритм рисования или должна вызываться функция, реализующая этот алгоритм.
Поэтому при кастомизации компонента Canvas добавляем проверку того, что контекст получен и может быть использован. При первом запуске onPaint контекст элемента Canvas назначается параметру context соответствующего графического элемента (в примере ниже — элемента Segment), а пользовательский флаг initialized приобретает значение true. При последующих вызовах обработчика onPaint будет сразу вызываться метод draw, отвечающий за отрисовку графического элемента на контексте рендеринга.
QML:
В отличие от JavaScript, в QML функция планирования анимации requestAnimationFrame предоставляется не в качестве метода объекта window, а в качестве метода компонента Canvas. Поэтому в исходном коде заменяем соответствующие вызовы данной функции, получая доступ к Canvas через контекст. Например:
QML:
segment.context.canvas.requestAnimationFrame(appearAnim);
4. Реализовать метод setTimeout
QML не предоставляет метод setTimeout в готовом виде. Чтобы снизить количество изменений в исходном JavaScript коде, реализуем данный метод с помощью компонента Timer в качестве вспомогательного в разделе Utilities нашей библиотеки.
Утилиты библиотеки подключаем к компонентам графических объектов
QML:
Utilities { id: utilities }
Остается заменить вызовы метода setTimeout на utilities.setTimeout.
5. Адаптировать заголовки функций
Объявление функций меняет форму, например,
JS:
Segment.prototype.calc = function() { … }
превращается в
QML:
function calc() { … }
6. Адаптировать код создания дочерних элементов в составных графических объектах
В исходном JavaScript коде (преимущественно в функциях первичного построения графических объектов build) меняется способ создания дочерних объектов, например:
JS:
let segment = new Segment (‘id’, context, cx, cy, r_in, thickness, init_angle, angle);QML:
let component = Qt.createComponent("Segment.qml");
let segment = component.createObject(parent_id, { id: ‘id’, context: this.context,
cx: this.cx, cy: this.cy, r_in: segment_r_in, thickness: segment_thickness,
init_angle: new_init_angle, angle: segment_angle });
7. Реализовать обработку событий мыши через MouseArea
Вместо подключения соответствующих обработчиков JavaScript событий addEventListener, в QML необходимо обрабатывать события мыши через компонент MouseArea. Следовательно, в исходном JavaScript коде находим функции, в которые передаются события мыши, передаем в них объект ‘mouse’ или ‘wheel’ вместо ‘e’, а также корректируем свойства этих событий, например:
JS:
e.offsetX
e.offsetY
e.deltaY
меняется соответственно на:
QML:
e.x
e.y
e.angleDelta.y
8. Проверить правила заливки фигур
Реализовывая примеры использования компонентов библиотеки в мобильном приложении, я столкнулся с неприятной проблемой:
Элементы SegmentProgressBar и SegmentTimer с индикаторами в виде сегмента вели себя неправильно. При 100% значении или максимальном значении времени, т.е. когда индикатор должен был быть в форме полного кольца, элемент отрисовывался в виде круга, а не кольца (рис. 11).
Рис. 11 – Проблема с заливкой кольца
Так как в JavaScript-версии библиотеки элемент SegmentTimer наследуется от элемента SegmentProgressBar, то я сначала предположил, что проблема заключается в обработке свойства r_in, т.е. внутреннего радиуса индикатора, выполненного в виде сегмента, и возникла в процессе переноса кода. Но проверка эту гипотезу не подтвердила.
Тогда я пришел к тому, что проблема может заключаться в функции отрисовки элемента Segment, который используется для реализации индикатора. Для проверки я создал отдельный экземпляр элемента Segment с углом 360°, который дает полное кольцо. И увидел тоже самое некорректное поведение. Следовательно, проблема была в элементе Segment.
Затем я предположил, что подход к рисованию сегмента кольца по точкам с помощью 2-х отрезков и 2-х дуг (рис. 12) не подходит для QML-версии элемента Canvas, точнее для реализации контекста Context2D. Пришлось даже сделать отдельное тестовое приложение.
Рис. 12 – Геометрия сегмента кольца
Но сегмент с углом меньше 360° отрисовывался верно и на JavaScript, и на QML. Проблема возникала только при 360°.
При более детальном изучении документации, выяснилось, что для функции заливки фигуры, нарисованной на контексте 2D рендеринга в JavaScript, правило заливки fillRule может принимать значения:
• "nonzero": Правило ненулевого индекса
• "evenodd": Четно-нечетное правило
Причем по умолчанию используется правило “nonzero”.
Однако, в документации также указано, что при рисовании контура с помощью beginPath, функция fill рисует фигуру с заливкой внутренней области. Это означает, что при рисовании сегмента, в том числе замкнутого в кольцо, используется правило “evenodd”.
Для объекта Context2D в QML также возможны 2 значения свойства fillRule:
• Qt.OddEvenFill
• Qt.WindingFill
Причем по умолчанию используется значение “Qt.WindingFill”.
В JavaScript-версии код работал корректно без указания правила заливки фигуры. Но в QML-версии, чтобы кольцо было залито верно, понадобилось отдельно прописать правило:
QML:
context.fillRule = Qt.OddEvenFill;
Таким образом, опытным путем, я пришел к верной реализации базового графического элемента Segment на QML, и поведение сегмента, индикатора прогресса и таймера стало ожидаемым.
Библиотека круговых интерфейсов на QML доступна для свободного скачивания и использования в репозитории.
Демонстрационное мобильное приложение
Для более быстрого и удобного ознакомления с элементами библиотеки я разработал демонстрационное мобильное приложение. В нем реализованы примеры базовых элементов круговых интерфейсов и круговых элементов управления. Продемонстрированы возможности анимации и взаимодействия пользователя с круговым интерфейсом. Также в приложении доступен раздел с документацией для теоретического изучения возможностей библиотеки.
Рис. 13 — Мобильное приложение
Демо-приложение доступно в Google Play по ссылке.
Заключение
Помимо уже сделанных выводов, хотелось бы еще раз отметить наличие в составе библиотеки готового компонента «Круговая CAPTCHA», которую можно применять для идентификации пользователя в качестве человека или бота.
Для себя я сделал вывод, что графические JavaScript библиотеки и отдельные классы могут быть перенесены на QML для использования в настольных и мобильных приложениях с допустимыми затратами времени и сил.
Теперь можно переходить к оптимизации и расширению библиотеки, в том числе созданию редактора круговых интерфейсов.
Также есть просьба к читателям: т.к. у меня не было возможности собрать демо-приложение на QML под iOS, если кто-то сделает это, сообщите, пожалуйста, о результатах по почте igor@tiunovs.com.
Спасибо!
QtRoS
Интересная работа проделана, но смущает подход с самописной setTimeout. В QML все хорошо с анимациями, у встроенных типов явно будет выше производительность, чем у реализации через таймер. Это временное решение или планируете остаться на нем?
TiunovIgor Автор
В основном анимация управляется через метод requestAnimationFrame QML-компонента Canvas. Самописная setTimeout используется только для задержки начала выполнения анимации, в том числе при последовательной анимации элементов в массиве.
В целом планируется оптимизация библиотеки как на JavaScript, так и на QML, с учетом особенностей каждого из этих инструментов.