Нам известно, что на протяжении нескольких лет поддержка мульти-тач в Qt Quick была неадекватна во многих случаях её использования. У нас есть PinchArea , для обработки масштабирования, вращения и перетаскивания двумя пальцами; и MultiPointTouchArea , которая может использоваться для отображения какой-либо интерактивной обратной связи для точек касания, или, может быть, вы могли бы написать машину состояний в JavaScript, чтобы распознать какой-то жест. Что касается остальной части Qt Quick, то основными проблемами являются: 1) поддержка событий мыши; 2) Qt предполагает, что есть только одна мышь («основной указатель мыши»); 3) QMouseEvent и QTouchEvent (и еще несколько) не имеют подходящего промежуточного базового класса, поэтому они доставляются независимо друг от друга; 4), что было сложным на раннем этапе, чтобы рассматривать события касания как события мыши и доставлять их одинаково. Таким образом, результат заключается в том, что вы не можете взаимодействовать с двумя MouseAreas или Flickables одновременно, например. Также это означает, что вы не можете одновременно нажать две кнопки или перетащить два слайдера одновременно, если они реализованы с помощью MouseArea.
Сначала я надеялся исправить это, сделав MouseArea и Flickable отдельными событиями касания. Патчи для этого были довольно сложными, и добавляли много дублированной логики для полного параллельного пути доставки: QMouseEvent взял бы один путь, а QTouchEvent другой, надеясь, что взаимодействие работало бы как можно лучше. Это были месяцы работы, и в конце она в основном работала ... но было сложно сохранить прохождение всех существующих автотестов, и коллеги обеспокоились тем, что это является изменением поведения. MouseArea, провозглашенная своим именем, обрабатывает события мыши, поэтому, как только она начала обрабатывать события касания отдельно, ее имя стало неправильно употребимым. Внезапно, вы могли бы одновременно нажать две кнопки, вкладки или радиокнопки в приложениях и наборах элементов управления, которые не были предназначены для этого. (Итак, мы попробовали добавить свойство bool, которое можно было бы задавать, но надобность его установки в каждой MouseArea была бы уродлива.) MouseArea и Flickable также должны много взаимодействовать, поэтому изменения должны были быть сделаны вместе, чтобы все работало. Это было возможно, но не было добавлено в Qt 5.5 из-за неопределенности.
Поэтому в конце концов мы выбрали другой путь, после того как мы нашли разумное сочетание предложенных идей.
Одна из идей заключалась в том, что, поскольку мы не можем реорганизовать иерархию QEvent (пока не можем) из-за мандата совместимости с двоичными данными, мы могли бы вместо этого создать классы-оболочки, которые делают события похожими на то, что мы хотим, в комплекте с свойствами на благо QML, и доставлять эти классы-оболочки вместо обычных, используя единый путь доставки событий в QQuickWindow и QQuickItem.
Другая идея заключалась в осознании того, что динамическое создание и уничтожение этих событий-оболочек глупо: вместо этого мы стали использовать пул экземпляров, как мы это делали в других случаях, когда объект «событие» излучается сигналом (например, объект, испускаемый MouseArea.positionChanged всегда является одним и тем же экземпляром, начиная с Qt 5.8). С этой оптимизацией перенос одного события на другой больше не является большим снижением производительности.
Была высказана и другая идея: может быть, было бы неплохо, если бы обработка события с указывающего устройства была такой же простой, как использование ключей, с которыми связано свойство: например, Mouse.onClicked: {...} или PointingDevice.onTapped: {...} Но вскоре после этого пришло осознание того, что может быть только один экземпляр прикрепленного свойства на элемент, к которому он прикреплен. Одна из проблем с MouseArea заключается в том, что он пытается делать слишком много, поэтому не имеет смысла просто повторно реализовывать все функции в монолитной MouseAttached . Мы хотели иметь способность обрабатывать клик или нажатие, не заботясь о том, от какого устройства они произошли, потому что это то, что нужно сделать каждому элементу управления Button. То же самое с любым жестом, который может быть выполнен с помощью мыши или одного пальца. Возможно, следует быть одному прикрепленному свойству на жест, а не одному на тип устройства?
Поскольку QML является декларативным языком, приятно иметь возможность объявлять ограничения, а не писать операторы if / else в обратных вызовах JavaScript. Если объект, который обрабатывает события, создан так, что он не заботиться о том, от какого устройства пришел жест, все же будут случаи, когда ваше приложение позаботится об этом: вы хотите выполнить различные действия в зависимости от того, нажато ли оно на сенсорном экране или щелкнуто правой кнопкой мыши, или вы хотите сделать что-то другое, если клавиша управления удерживается при перетаскивании объекта. Предоставляя несколько экземпляров этих объектов-обработчиков, мы можем объявить ограничения на них, установив свойства. Должно быть хорошо, чтобы было столько случаев, сколько вам нужно. Каждый экземпляр должен быть легким, чтобы вы не боялись иметь слишком много экземпляров. Реализация должна быть на C++, и она должна быть простой и понятной. Каждый обработчик должен делать одно, или, самое большее, несколько очень близких вещей, и делать их хорошо.
На данный момент эти проблемы отвлекли нас от идеи использования прикрепленных свойств. Вместо этого мы имеем новое семейство классов в Qt Quick: обработчики указателей.
Обработчик указателя - это тип объекта, который вы можете объявить внутри любого элемента, который обрабатывает события от указывающих устройств от имени этого элемента. Вы можете объявить столько из них, сколько вам нужно: как правило, по одному для каждого сценария взаимодействия. Все общие ограничения, о которых мы могли думать, являются декларируемыми: вы можете заставить обработчика реагировать только в том случае, если это событие касания, только для определенных кнопок мыши, только если правильное количество пальцев нажато в пределах элемента, только если специальная клавиша-модификатор и т. д.
Перетаскиваемые шары
Обычные жесты представлены их собственными типами обработчиков. Например, если вы объявляете
Rectangle { width: 50; height: 50; color: "green" DragHandler { } }
то у вас есть прямоугольник, который можно перетаскивать по сцене с помощью мыши или касания, без написания Javascript и даже без необходимости привязывать обработчик к его родителям. Он имеет свойство target , а значение по умолчанию является его родитель. (Но, установив цель на другой элемент, вы можете захватывать события внутри одного элемента, но манипулировать другим.)
Конечно, если у вас есть два этих зеленых прямоугольников с DragHandlers внутри, вы можете перетащить их обоих одновременно разными пальцами.
Каждый обработчик указателя - это QObject, но это не QQuickItem, и он не имеет слишком много собственных переменных, поэтому каждый экземпляр примерно так же мал, как и подкласс QObject.
У каждого одноточечного обработчика есть свойство point , чтобы предоставить все детали о точке касания или нажатия мыши, которые мы можем найти. Существуют свойства pressure и ellipseDiameters : некоторые устройства могут иметь датчики силы, в то время как другие могут измерять размер контактного пути (но многие устройства этого не предоставляют). Такие устройства обладают свойством velocity , которое гарантированно определяется: мы вычисляем и усредняем скорость за последние несколько движений для слегка более плавной реакции. Наличие доступной скорости может потенциально активировать чувствительные к скорости жесты: возможно, щелчок должен выполняться с определенной минимальной скоростью. (Это правильный способ отличить щелчок от перетаскивания? Было не так просто сделать это различие раньше.) Или, если у вас есть скорость во время отпускания, жест перетаскивания может закончиться импульсом: объект перемещается на короткое расстояние в одном направлении. Это делает ваш интерфейс более живым. До сих пор мы не формализировали MomentumAnimation в поддерживаемый тип анимации, но есть прототип pure-QML в tests/manual/pointer/content/MomentumAnimation.qml.
Чечетка
TapHandler обрабатывает все жесты нажатия и отпускания: одиночные быстрые нажатия или щелчки, двойные нажатия, другое количество нажатий, удержание нажатия в течение настраиваемого периода времени или удержание его в течение различных периодов времени всеми уникальными способами , (Когда вы касаетесь сенсорного экрана, он часто не производит никакого звука, но вы можете нажать кнопку мыши, поэтому мы подумали, что «tap» - это более надежное будущее для этого жеста, чем «щелчок».) Вы можете показать обратную связь пропорционально тому, насколько долгим является нажатие (расширяющийся круг, индикатор выполнения или что-то в этом роде).
Ущипни меня, если это правда
Существует PinchHandler . Если вы объявите его внутри элемента, вы сможете масштабировать, поворачивать и перетаскивать этот предмет с помощью жестов "щипок". Вы можете масштабировать любую часть элемента, которая вам понравится (улучшение в PinchArea). Он также может обрабатывать большее количество пальцев: вы можете объявить PinchHandler {minimumTouchPoints: 3} , чтобы потребовать трех-пальцевый жест "щипок". Все преобразования затем происходят относительно центральной точки между тремя пальцами, а масштабирование относится к среднему увеличению или уменьшению распространения между ними. Идея исходила из того, что некоторые версии Ubuntu используют трех-пальцевый щипок для управления окнами: видимо, они думали, что содержимое в окне может быть использовано для жестов с двумя пальцами, но большинство приложений не используют три пальца, так что это нормально, чтобы зарезервировать трех-пальцевый щипок для масштабирования и перемещения окон вокруг рабочего стола. Теперь, поскольку вы можете написать композитор Wayland в QML, вы можете легко воссоздать этот опыт.
Добираемся до точек
Наконец, есть PointHandler . В отличие от других, он не манипулирует своим целевым элементом: он существует только для того, чтобы предоставить свойство point. Он похож на отдельный TouchPoint в MultiPointTouchArea и может использоваться с той же целью: обеспечить интерактивную обратную связь в качестве точек касания или курсора мыши, перемещающегося по сцене. В отличие от MultiPointTouchArea, он захватывает точки касания или мышь не эксклюзивно, поэтому наличие этой интерактивной обратной связи не препятствует взаимодействию с другими обработчиками в других элементах в одно и то же время. В анимации на этой странице он используется, чтобы заставить спрайты пальцев следить за моими пальцами.
Это уже все?
Итак, теперь я перейду к причинам, почему этот материал находится в Tech Preview в 5.10. Одна из причин заключается в том, что он неполный: у нас по-прежнему отсутствует поддержка наведения указателя мыши, колеса мыши и стилусов планшетов (на данный момент стилус по-прежнему рассматривается как мышь). Ни один из обработчиков не чувствителен к скорости. Мы можем представить еще несколько обработчиков, которые могли бы быть написаны. Должен существовать открытый C++ API, чтобы вы могли создавать свои собственные. Обработчики и Flickable немного ладят, но Flickable - сложный монолитный Предмет, и мы думаем, что он может быть реорганизован позже. Существует ручной тест FakeFlickable , который показывает, как можно воссоздать большую часть своей функциональности в QML с двумя обычными элементами, а также с DragHandler и несколькими анимациями.
Другая причина - именование. «Обработчики указателей» звучат нормально в изоляции, но существует уже существовавшая терминология, которая делает ее запутанной: указатель может быть переменной, указывающей на ячейку памяти (но это не то, что мы имеем в виду здесь), и обработчик может быть сортировкой функции обратного вызова, которую вы пишете в JavaScript. Если вы пишете TapHandler {onActiveChanged: ...} , тогда вы скажете, что у вашего обработчика есть обработчик? Вместо этого мы могли бы использовать слово «обратный вызов», но в некоторых кругах это анахронизм, а в QML наши привычки сейчас трудно изменить.
Другая причина заключается в том, что QtLocation имеет некоторые сложные варианты использования, которые мы хотим использовать в качестве примера, чтобы доказать, что можно перемещать карту (и любой интерактивный контент на нее) с небольшим количеством читаемого кода.
Возможно, я продолжу это позже с другого сообщения о другом способе распознавания жестов и о том, как это может повлиять на то, как мы будем думать о обработчиках указателей в будущем. Я еще не объяснил концепцию пассивного захвата. Существует также больше сведений о том, как создавать компоненты, которые внутренне используют обработчики указателей, но при этом автору авторских прав при необходимости можно переопределить поведение.
Итак, в данный момент обработчики указателей находятся в Tech Preview в 5.10. Мы надеемся, что вы будете долго с ними играться. Особенно, если у вас есть какие-то старые разочарования в отношении сценариев взаимодействия, которые раньше не были возможны. Более того, если вы когда-либо хотели создать сенсорный пользовательский интерфейс (возможно, даже многопользовательский?) Без написания C++ event-forwarding и QObject-event-filtering грязи. Нам нужно начать получать отзывы от всех с уникальными вариантами использования, в то время как у нас есть свобода вносить большие изменения, если это необходимо, чтобы облегчить вам работу.
До сих пор примеры использования в основном были в tests/manual/pointer . Тестовый исходный код не входит в пакет сборки релизов, поэтому, чтобы попробовать их, вам нужно загрузить исходные пакеты Qt или получить их из репозитория git . Некоторые из них можно превратить в примеры для предстоящих выпусков.
Так как это в техническом предпросмотре, реализация продолжит совершенствоваться во время серии 5.10, поэтому следуйте за веткой 5.10 git , чтобы не отставать от последних функций и исправлений ошибок.
Статья написана: Shawn Rutledge | Четверг, Ноябрь 23, 2017г.