Русский
Русский
English
Статистика
Реклама

Qml

Разработка круговых интерфейсов. Часть 2. Перенос библиотеки с JavaScript на QML

12.11.2020 20:10:42 | Автор: admin

Оглавление


Введение
Библиотека круговых интерфейсов v2.0
Круговая CAPTCHA
Перенос библиотеки с JavaScript на QML
Демонстрационное мобильное приложение
Заключение


Введение


Предыдущая статья была вводной к вопросу разработки круговых интерфейсов. В ней рассмотрены определение, классификация, принципы построения круговых интерфейсов, а также подход к их проектированию. Данная статья описывает основные изменения в библиотеке, которую я разрабатываю для облегчения и ускорения процесса создания круговых интерфейсов.


Во-первых, вышла вторая версия библиотеки на JavaScript, в которой реализованы круговые элементы управления.


Во-вторых, разработанная библиотека получила реализацию на QML, для демонстрации возможностей которой выпущено мобильное приложение под Android.


Библиотека круговых интерфейсов v2.0


Новые базовые и вспомогательные элементы


В первой версии библиотеки были реализованы базовые элементы круговых интерфейсов, комбинация которых позволяла получать необычные графические композиции. Во второй версии библиотеки этот набор расширен элементами, представленными на рисунке 1.


image
Рис. 1 Новые базовые и вспомогательные элементы


Segment Grid Сегментная сетка (рис. 1A). Это сегмент с прорисованной полярной координатной сеткой. Применяется при построении таких графических элементов как, например, радар или мишень.


Segment Spiral Сегментная спираль (рис. 1B). Это часть сегмента, отделенная кривой, построенной по уравнению спирали. Наиболее часто применяется для индикации изменяемого значения, например, громкости.


Segment Gradient типа Conic Конический градиент (рис. 1C). Это способ заливки фигуры, при котором цвет меняется вдоль дуги окружности. Позволяет получать дополнительный визуальный эффект и используется, например, для имитации локатора в графическом элементе Радар.


Круговые элементы управления


Основным нововведением второй версии библиотеки является набор наиболее часто используемых элементов управления и визуализации информации.


Progress Bar Круговой индикатор прогресса. В компактном виде показывает степень выполнения определенного процесса (рис. 2).


image
Рис. 2 Индикатор прогресса


Timer Таймер в виде циферблата со стрелкой или с индикатором в виде сегмента, массива сегментов или массива точек (рис. 3).


image
Рис. 3 Таймер


Gauge Измерительный прибор (рис. 4). Например, спидометр или барометр. Особенностями реализации является наличие измерительной шкалы с числовыми отметками и стрелки-указателя.


image
Рис. 4 Измерительный прибор


Chart Круговая диаграмма. Представлены 3 типа: Пирог, Радиальная, Гистограмма (рис. 5 слева направо).


image
Рис. 5 Круговая диаграмма


Equalizer Эквалайзер (рис. 6). Графический элемент для визуализации частотных характеристик сигнала.


image
Рис. 6 Эквалайзер


Knob Поворотная ручка (рис. 7).


image
Рис. 7 Поворотная ручка


Volume Control круговой регулятор громкости (рис. 8). Реализованы два типа регуляторов: с индикатором в виде массива сегментов и в виде сегментной спирали.


image
Рис. 8 Регулятор громкости


Radar Радар. Графический элемент для визуализации взаимного расположения объектов (рис. 9). Основными конструктивными элементами радара являются координатная сетка, локатор и индикаторы целей.


image
Рис. 9 Радар


Круговая CAPTCHA


Отдельно хочу отметить новый элемент CAPTCHA (Рис. 10).


image
Рис. 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:

image


В отличие от 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).

image
Рис. 11 Проблема с заливкой кольца


Так как в JavaScript-версии библиотеки элемент SegmentTimer наследуется от элемента SegmentProgressBar, то я сначала предположил, что проблема заключается в обработке свойства r_in, т.е. внутреннего радиуса индикатора, выполненного в виде сегмента, и возникла в процессе переноса кода. Но проверка эту гипотезу не подтвердила.


Тогда я пришел к тому, что проблема может заключаться в функции отрисовки элемента Segment, который используется для реализации индикатора. Для проверки я создал отдельный экземпляр элемента Segment с углом 360, который дает полное кольцо. И увидел тоже самое некорректное поведение. Следовательно, проблема была в элементе Segment.


Затем я предположил, что подход к рисованию сегмента кольца по точкам с помощью 2-х отрезков и 2-х дуг (рис. 12) не подходит для QML-версии элемента Canvas, точнее для реализации контекста Context2D. Пришлось даже сделать отдельное тестовое приложение.


image
Рис. 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 доступна для свободного скачивания и использования в репозитории.


Демонстрационное мобильное приложение


Для более быстрого и удобного ознакомления с элементами библиотеки я разработал демонстрационное мобильное приложение. В нем реализованы примеры базовых элементов круговых интерфейсов и круговых элементов управления. Продемонстрированы возможности анимации и взаимодействия пользователя с круговым интерфейсом. Также в приложении доступен раздел с документацией для теоретического изучения возможностей библиотеки.


imageimageimage
Рис. 13 Мобильное приложение


Демо-приложение доступно в Google Play по ссылке.


Заключение


Помимо уже сделанных выводов, хотелось бы еще раз отметить наличие в составе библиотеки готового компонента Круговая CAPTCHA, которую можно применять для идентификации пользователя в качестве человека или бота.


Для себя я сделал вывод, что графические JavaScript библиотеки и отдельные классы могут быть перенесены на QML для использования в настольных и мобильных приложениях с допустимыми затратами времени и сил.
Теперь можно переходить к оптимизации и расширению библиотеки, в том числе созданию редактора круговых интерфейсов.


Также есть просьба к читателям: т.к. у меня не было возможности собрать демо-приложение на QML под iOS, если кто-то сделает это, сообщите, пожалуйста, о результатах по почте igor@tiunovs.com.


Спасибо!

Подробнее..

Портирование приложений с QWidget на QML под Desktop

07.07.2020 14:13:48 | Автор: admin
Привет, Хабровчане!

Последнее время часто встречаю проекты для desktop, которые необходимо портировать с QWidget на QML. Кто-то хочет написать с нуля, кто-то перенести старые наработки. В любом из сценариев это популяризация QML, чему я очень рад. Я решил тоже побыть полезным и поделиться своими наработками и ситуациями из личной практики. Возможно, некоторые из них покажутся вам простыми и понятными, однако если я что-то упустил, буду рад почитать об этом в комментариях.

Кратко о QML и QWidget


QML это декларативный язык программирования. Он позволяет разработчикам и дизайнерам создавать удобные, плавно анимированные и визуально привлекательные приложения. QML предлагает читаемый декларативный JSON-подобный синтаксис с поддержкой императивных выражений JavaScript в сочетании с динамическими привязками свойств.

QWidget это предшествующая QML технология для создания пользовательского интерфейса, основанная на использовании заранее созданных desktop-style компонентов и предоставляющая API для взаимодействия с ними.

Пользовательский опыт


Около года назад я нашел прекрасный инструмент для работы с гитом gitAhead и в целом я доволен большей частью функционала и он мне часто помогает. Но на этом примере прекрасно видно, что виджетам не хватает гибкости и функционала. Из-за этого невозможно покрыть больше пользовательских сценариев и сделать более дружелюбный интерфейс. В итоге вместо отличного дерева коммитов и дифов, мы имеем огромное количество ограничений.

Стандартное окно(кликабельно)


Дизайн приложения и его влияние на портирование


Классически Qt нам советует придерживаться паттернов MVC\MVP, где всё взаимодействие с UI вынесено в отдельные сущности, что позволяет нам изменять их, не затрагивая остальные части приложений и бизнес логику. Абстрагирование элементов и возможность встраивать QML-компонент в стандартный QWidget позволяет гибко подойти к плану дальнейшей работы. Если вы сейчас на этом пункте, то я бы хотел вам посоветовать продумать требования и функциональность будущего приложения, нарисовать его макет, посоветоваться с дизайнерами, опросить пользователей или заказчика. В целом решить как вы будете жить ближайшее время, и какие вопросы будете решать. И выбрать один из двух путей, предложенных ниже.

План А С чистого листа


Мы убираем весь старый UI, виджеты, зависимости, legacy код и оставляем только требования и функциональность, которые нам необходимо будет восстановить. Хорошая возможность улучшить архитектуру, подготовить почву для новых изменений. При подготовке необходимо учитывать следующие вещи, которые изменяются при переходе от одной библиотеке к другой и лучше продумать их заранее:

  1. Библиотека элементов. Создайте свою начальную базу элементов, где пропишите базовые настройки для элементов, их вид и состояния по умолчанию. Если есть возможность, оберните их тестами. В одной из конфигураций проекта сделайте их копируемыми в папку проекта, а не компилируемыми в бинарный*.qrc, это позволит вам править эти сборки в runtime без дополнительных компиляций и подключать qmllive и его аналоги.
  2. Отсутствие QAction. В приложениях на QWidget, частым гостем является и QAction, расширяющий возможности перехвата действий пользователя как по нажатию кнопки на экране, так и перехватом горячих клавиш. К сожалению в QML QAction переехал в несколько ином виде и позволяет только из QML обертки перехватывать заранее определенные горячие клавиши, что накладывает ограничения на использование его внутри C++ или создавать динамически. Здесь вы можете написать свою реализацию, использовать event-filter от qobject или использовать сторонние проекты, такие как QHotkey .

В остальном здесь всё типично для приложения на C++ и QML.

План Б У нас типо agile


Так как для Agile одной из главной ценностью является работающий продукт, то мы должны делать переезд поэтапно, без регресса функционала. Для этого мы делим весь UI на логические сегменты с минимальным количеством взаимозависимостей и реализуем их отдельно. На примере gitAhead можно рассмотреть следующий вариант(выделил зеленым):

Разделение UI на сегменты(кликабельно)


Для каждой из областей мы создадим родительский виджет, в который и положим корневой QML элемент. Это можно сделать двумя способами.

Создать QQuickWidget

QQuickWidget *view = new QQuickWidget;view->setSource(QUrl::fromLocalFile("myqmlfile.qml"));view->show();

Или вызывать метод QWidget::createWindowContainer(...)

QQuickView *view = new QQuickView();...QWidget *container = QWidget::createWindowContainer(view);container->setMinimumSize(...);container->setMaximumSize(...);...widgetLayout->addWidget(container);

Разница между предложенными способами в том что QQuickWidget является более гибкой альтернативой чем QWidget::createWindowContainer(...), ведя себя больше как обычный QWidget. При этом большая функциональность достигается из-за незначительных потерь в производительности.

Дальнейшая работа производится как с обычным QQuickView, за исключением следующих особенностей:

  1. Ограниченный контекст. У каждого родительского QWidget будет свой QML-контекст, что в свое очередь накладывает ограничения на область видимости. Свои property, функции, объекты и т.д. Каждый widget по сути становится независимой песочницей
  2. Ограничение по размеру виджета. В ходе создания виджета, можно создать правило чтобы размеры виджета следовали за размерами контента внутри и по сути это правильно. Но это распространяется только на корневые элементы внутри этого QML, а все временные объекты с размерами превышающими виджет, будут обрезаться границами. Хорошим примером тут может служить всплывающая подсказка для кнопок, если кнопки маленькие, при это есть большое описание, то часть подсказки скроется за границей виджета. Если размеры виджета-контейнера поставлены не по размеру контента, то это так же может привести к проблемам при масштабировании интерфейса. Частично эта проблема решается элементами из lab.

    Пример выхода за границы и неправильной верстки
    image

  3. 3D графика. Здесь всё усложнено и полно ограничений, рекомендуется использовать QQuickWidget и есть не маленькая вероятность что именно ваш сценарий либо потребует костылей, либо больших трудовложений, поэтому ознакомьтесь с ограничениями заранее, либо сразу реализуйте под использование в QML

Стоит напомнить что приведенные выше ограничения распространяются только на тот период, пока вы переносите свою элементную базу. Описанные же в плане А ограничения распространяются на всё приложение. Последним элементом будет являться базовое окно приложения, где необходимо будет всё расположить в соответствии с дизайном, работой фокуса и др. требованиями. Останется удалить последние зависимости приложения от QWidget библиотеки, убрать ее из поставки и всё, работа закончена. Вы молодцы!

Итоги


Над gitAhead мне предстоит еще много работы, и не только по UI, но приблизительно как это происходит я описал. Если вам понравилась идея и вам хочется поучаствовать, то буду рад сотрудничеству. Мой репозиторий будет тут.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru