From 8489f36322c585ec78199e6eb183000c74afae19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antti=20H=C3=B6ltt=C3=A4?= Date: Tue, 19 Feb 2019 15:36:39 +0100 Subject: Add redirect feature for manually fine tuning the cursor's movement Cursornavigation now has a property redirects, that allows defining exceptions to the navigation behaviour. A redirect allows defining a starting and an ending angle and a target object. If the move command's direction falls between the limits, the algorithm is bypassed and cursor is moved to the target object. --- DemoApplication/main.qml | 4 ++ DemoApplication/pages/Redirects.qml | 124 ++++++++++++++++++++++++++++++++++++ plugin/cursornavigation.cpp | 30 ++++++--- plugin/cursornavigation.h | 4 +- plugin/cursornavigationattached.cpp | 61 +++++++++++++----- plugin/cursornavigationattached.h | 12 +++- plugin/plugin.cpp | 2 + plugin/plugin.pro | 6 +- plugin/redirect.cpp | 68 ++++++++++++++++++++ plugin/redirect.h | 41 ++++++++++++ 10 files changed, 321 insertions(+), 31 deletions(-) create mode 100644 DemoApplication/pages/Redirects.qml create mode 100644 plugin/redirect.cpp create mode 100644 plugin/redirect.h diff --git a/DemoApplication/main.qml b/DemoApplication/main.qml index f149a69..a242e47 100644 --- a/DemoApplication/main.qml +++ b/DemoApplication/main.qml @@ -100,6 +100,9 @@ ApplicationWindow { CNTabButton { text: qsTr("Map") } + CNTabButton { + text: qsTr("Redirects") + } } contentData: Item { @@ -112,6 +115,7 @@ ApplicationWindow { FlipButtons { } Lists { } MapView { } + Redirects { } } CursorNavigation.acceptsCursor: false diff --git a/DemoApplication/pages/Redirects.qml b/DemoApplication/pages/Redirects.qml new file mode 100644 index 0000000..28873d7 --- /dev/null +++ b/DemoApplication/pages/Redirects.qml @@ -0,0 +1,124 @@ +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import CursorNavigation 1.0 +import controls 1.0 + +Item { + width: parent.width + height: parent.height + + Text { + id: text + text: "CursorNavigation allows overriding the navigation algorithm with the redirects -property. You may define angle sectors that bypass the algorithm, and instead move the cursor or focus to a predefined target object. This is useful for example implementing wrapping. Moving the cursor past the end of the button rows will wrap the cursor to the other end. The row in the bottom is done using a repeater and redirects have been set dynamically. Movement between the button rows has been redirected to go via the button in the center." + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 10 + wrapMode: Text.WordWrap + } + + Item { + anchors.top: text.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + RowLayout { + id: upperRow + anchors.right: parent.right + anchors.left: parent.left + anchors.top: parent.top + + CNButton { + id: firstButton + text: "Button 1" + + CursorNavigation.redirects: [ + Redirect { start: 45; end: 135; target: centerButton }, + Redirect { start: 135; end: 225; target: lastButton } + ] + } + + CNButton { + text: "Button 2" + + CursorNavigation.redirects: [ + Redirect { start: 45; end: 135; target: centerButton } + ] + } + + CNButton { + text: "Button 3" + + CursorNavigation.redirects: [ + Redirect { start: 45; end: 135; target: centerButton } + ] + } + + CNButton { + id: lastButton + text: "Button 4" + + CursorNavigation.redirects: [ + Redirect { start: 45; end: 135; target: centerButton }, + Redirect { start: 315; end: 45; target: firstButton } + ] + } + + } + + CNButton { + id: centerButton + text: "Center button" + anchors.centerIn: parent + } + + RowLayout { + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + Repeater { + id: buttonRepeater + model: 6 + + CNButton { + Layout.minimumWidth: 110 + Layout.minimumHeight: 110 + text: "Button " + index + + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + + CursorNavigation.redirects: [ + Redirect { start: 225; end: 315; target: centerButton } + ] + + Component.onCompleted: { + if (index == buttonRepeater.count-1) { + console.info("creating redirects for repeater buttons, count =" + buttonRepeater.count) + buttonRepeater.itemAt(0).CursorNavigation.redirects.push( + Qt.createQmlObject( + "import CursorNavigation 1.0; + Redirect { start: 135; end: 225; + target: buttonRepeater.itemAt(buttonRepeater.count-1) }", + buttonRepeater.itemAt(0) + )); + buttonRepeater.itemAt(buttonRepeater.count-1).CursorNavigation.redirects.push( + Qt.createQmlObject( + "import CursorNavigation 1.0; + Redirect { start: 315; end: 45; + target: buttonRepeater.itemAt(0) }", + buttonRepeater.itemAt(buttonRepeater.count-1) + )); + } + } + } + + + + } + } + + } +} diff --git a/plugin/cursornavigation.cpp b/plugin/cursornavigation.cpp index bf2d390..e069efc 100644 --- a/plugin/cursornavigation.cpp +++ b/plugin/cursornavigation.cpp @@ -3,6 +3,7 @@ #include "spatialnavigation4dir.h" #include #include +#include const char CursorNavigation::windowPropertyName[] = "cursor_navigation"; @@ -22,27 +23,38 @@ CursorNavigation::CursorNavigation(QQuickWindow *parent) bool CursorNavigation::move(qreal angle, qreal tolerance, bool discrete) { - CursorNavigationAttached *nextItem = find(angle, tolerance, discrete); + QQuickItem *foundItem = find(angle, tolerance, discrete); + CursorNavigationAttached *nextItem = cursorNavigationAttachment(foundItem); if (nextItem) { setCursorOnItem(nextItem); return true; + } else if (foundItem) { + foundItem->forceActiveFocus(); + return true; } return false; } -CursorNavigationAttached *CursorNavigation::find(qreal angle, qreal tolerance, bool discrete) +QQuickItem *CursorNavigation::find(qreal angle, qreal tolerance, bool discrete) { + if (!m_currentItem) + return defaultItem()->item(); + + if (m_currentItem->m_redirects.size()) { + for (auto redirect : m_currentItem->m_redirects) { + if (redirect->angleIsIncluded(angle)) { + if (!redirect->target()) + qWarning() << "Redirect target is null"; + return redirect->target(); + } + } + } + CursorNavigationAttached *nextItem = nullptr; CursorNavigationAttached *parent = m_currentItem ? m_currentItem->m_parentNavigable : m_rootItem; - - if (!m_currentItem) - return defaultItem(); - - //qWarning() << "find next item, angle = " << angle << " tolerance = " << tolerance << " discrete = " << discrete; - QList candidates; do { @@ -64,7 +76,7 @@ CursorNavigationAttached *CursorNavigation::find(qreal angle, qreal tolerance, b nextItem = m_navigation360.getNextCandidate(candidates, m_currentItem, cmd); } - return nextItem; + return nextItem ? nextItem->item() : nullptr; } bool CursorNavigation::action(Action action) diff --git a/plugin/cursornavigation.h b/plugin/cursornavigation.h index a32a357..ad1cf31 100644 --- a/plugin/cursornavigation.h +++ b/plugin/cursornavigation.h @@ -25,10 +25,8 @@ public: //void setMagnitude(const QVector2D& vector); //move the cursor bool move(qreal angle, qreal tolerance, bool discrete); - bool move(const QVector2D& vector, qreal tolerance, bool discrete); //find the next item without moving the cursor - CursorNavigationAttached *find(qreal angle, qreal tolerance, bool discrete); - CursorNavigationAttached *find(const QVector2D& vector, qreal tolerance, bool discrete); + QQuickItem *find(qreal angle, qreal tolerance, bool discrete); bool action(Action action); static CursorNavigationAttached *qmlAttachedProperties(QObject *object); diff --git a/plugin/cursornavigationattached.cpp b/plugin/cursornavigationattached.cpp index a897327..133890e 100644 --- a/plugin/cursornavigationattached.cpp +++ b/plugin/cursornavigationattached.cpp @@ -92,10 +92,10 @@ void CursorNavigationAttached::setMagnitude(QVector2D vector) void CursorNavigationAttached::move(qreal angle, qreal tolerance) { - qWarning() << "move"; - qreal a = qDegreesToRadians(angle); - qreal t = qDegreesToRadians(qFabs(std::fmod(tolerance, 180))); if (m_cursorNavigation) { + qWarning() << "move"; + qreal a = qDegreesToRadians(angle); + qreal t = qDegreesToRadians(qFabs(std::fmod(tolerance, 180))); CursorNavigationAttached *item = m_cursorNavigation->m_currentItem; if (m_cursorNavigation->move(a, t, false) && item) item->moved(a,t); @@ -104,10 +104,10 @@ void CursorNavigationAttached::move(qreal angle, qreal tolerance) void CursorNavigationAttached::move(QVector2D vector, qreal tolerance) { - qWarning() << "move (vector)"; - qreal a = qAtan2(vector.y(), vector.x()); - qreal t = qDegreesToRadians(qFabs(std::fmod(tolerance, 180))); if (m_cursorNavigation) { + qWarning() << "move (vector)"; + qreal a = qAtan2(vector.y(), vector.x()); + qreal t = qDegreesToRadians(qFabs(std::fmod(tolerance, 180))); CursorNavigationAttached *item = m_cursorNavigation->m_currentItem; if (m_cursorNavigation->move(a, t, false) && item) item->moved(a,t); @@ -116,24 +116,20 @@ void CursorNavigationAttached::move(QVector2D vector, qreal tolerance) QQuickItem *CursorNavigationAttached::find(qreal angle, qreal tolerance) { - qreal a = qDegreesToRadians(angle); - qreal t = qDegreesToRadians(qFabs(std::fmod(tolerance, 180))); if (m_cursorNavigation) { - CursorNavigationAttached *item = m_cursorNavigation->find(a, t, false); - if (item) - return item->item(); + qreal a = qDegreesToRadians(angle); + qreal t = qDegreesToRadians(qFabs(std::fmod(tolerance, 180))); + return m_cursorNavigation->find(a, t, false); } return nullptr; } QQuickItem *CursorNavigationAttached::find(QVector2D vector, qreal tolerance) { - qreal a = qAtan2(vector.y(), vector.x()); - qreal t = qDegreesToRadians(qFabs(std::fmod(tolerance, 180))); if (m_cursorNavigation) { - CursorNavigationAttached *item = m_cursorNavigation->find(a, t, false); - if (item) - return item->item(); + qreal a = qAtan2(vector.y(), vector.x()); + qreal t = qDegreesToRadians(qFabs(std::fmod(tolerance, 180))); + return m_cursorNavigation->find(a, t, false); } return nullptr; } @@ -234,6 +230,15 @@ QQuickItem *CursorNavigationAttached::escapeTarget() const return m_escapeTarget; } +QQmlListProperty CursorNavigationAttached::redirects() +{ + return QQmlListProperty(this, this, + &CursorNavigationAttached::appendRedirect, + &CursorNavigationAttached::redirectCount, + &CursorNavigationAttached::redirect, + &CursorNavigationAttached::clearRedirects); +} + bool CursorNavigationAttached::available() const { if (m_acceptsCursor && item()->isVisible() && item()->isEnabled()) { @@ -252,3 +257,27 @@ void CursorNavigationAttached::setHasCursor(bool hasCursor) } } +void CursorNavigationAttached::appendRedirect(QQmlListProperty *property, Redirect *redirect) +{ + CursorNavigationAttached *cna = static_cast(property->object); + cna->m_redirects.append(redirect); +} + +int CursorNavigationAttached::redirectCount(QQmlListProperty *property) +{ + CursorNavigationAttached *cna = static_cast(property->object); + return cna->m_redirects.count(); +} + +Redirect *CursorNavigationAttached::redirect(QQmlListProperty *property, int index) +{ + CursorNavigationAttached *cna = static_cast(property->object); + return cna->m_redirects.at(index); +} + +void CursorNavigationAttached::clearRedirects(QQmlListProperty *property) +{ + CursorNavigationAttached *cna = static_cast(property->object); + cna->m_redirects.clear(); +} + diff --git a/plugin/cursornavigationattached.h b/plugin/cursornavigationattached.h index f3c729d..7c5e99a 100644 --- a/plugin/cursornavigationattached.h +++ b/plugin/cursornavigationattached.h @@ -4,6 +4,8 @@ //#include #include #include +#include +#include "redirect.h" class CursorNavigation; class QQuickItem; @@ -22,7 +24,7 @@ class CursorNavigationAttached : public QObject Q_PROPERTY(bool trapsCursor READ trapsCursor WRITE setTrapsCursor NOTIFY trapsCursorChanged) //item to select when Q_PROPERTY(QQuickItem *escapeTarget READ escapeTarget WRITE setEscapeTarget NOTIFY escapeTargetChanged) - + Q_PROPERTY(QQmlListProperty redirects READ redirects) public: CursorNavigationAttached(QQuickItem *parent); @@ -32,6 +34,8 @@ public: bool hasCursor() const; bool trapsCursor() const; QQuickItem *escapeTarget() const; + QQmlListProperty redirects(); + /* indicates if the item is currently available for the navigation. * item might not be availble if it is disabled, invisible, outside of its * parent's geometry or simply not accepting cursor @@ -93,6 +97,11 @@ private: void setHasCursor(bool hasCursor); //QList &siblings(); + static void appendRedirect(QQmlListProperty *property, Redirect *redirect); + static int redirectCount(QQmlListProperty *property); + static Redirect *redirect(QQmlListProperty *property, int index); + static void clearRedirects(QQmlListProperty *property); + CursorNavigation *m_cursorNavigation; CursorNavigationAttached *m_parentNavigable; QList m_children; @@ -103,6 +112,7 @@ private: friend class CursorNavigation; QQuickItem * m_escapeTarget; + QVector m_redirects; }; #endif // CURSORNAVIGATIONATTACHED_H diff --git a/plugin/plugin.cpp b/plugin/plugin.cpp index 26d156a..87cc430 100644 --- a/plugin/plugin.cpp +++ b/plugin/plugin.cpp @@ -1,5 +1,6 @@ #include "plugin.h" #include "cursornavigation.h" +#include "redirect.h" #include "qqml.h" CursorNavigationPlugin::CursorNavigationPlugin() @@ -10,4 +11,5 @@ void CursorNavigationPlugin::registerTypes(const char *uri) { qmlRegisterUncreatableType(uri, 1, 0, "CursorNavigation", QStringLiteral("CursorNavigation is not creatable, use the attached properties.")); + qmlRegisterType(uri, 1, 0, "Redirect"); } diff --git a/plugin/plugin.pro b/plugin/plugin.pro index 4d7fed9..b5cdee7 100644 --- a/plugin/plugin.pro +++ b/plugin/plugin.pro @@ -16,7 +16,8 @@ SOURCES += \ cursornavigationalgorithm.cpp \ spatialnavigation4dir.cpp \ inputtypes.cpp \ - spatialnavigation360.cpp + spatialnavigation360.cpp \ + redirect.cpp HEADERS += \ plugin.h \ @@ -26,7 +27,8 @@ HEADERS += \ inputtypes.h \ cursornavigationalgorithm.h \ spatialnavigation4dir.h \ - spatialnavigation360.h + spatialnavigation360.h \ + redirect.h pluginfiles.files += qmldir diff --git a/plugin/redirect.cpp b/plugin/redirect.cpp new file mode 100644 index 0000000..3c33571 --- /dev/null +++ b/plugin/redirect.cpp @@ -0,0 +1,68 @@ +#include "redirect.h" +#include +#include +#include "inputtypes.h" + +Redirect::Redirect(QObject *parent) +:QObject(parent) +,m_start(-1) +,m_end(-1) +,m_target(nullptr) +{ +} + +Redirect::~Redirect() +{ +} + +qreal Redirect::start() const +{ + return m_start; +} + +qreal Redirect::end() const +{ + return m_end; +} + +QQuickItem *Redirect::target() const +{ + return m_target; +} + +void Redirect::setStart(qreal start) +{ + m_start = start; + m_startR = CursorNavigationCommand::fitAngle(qDegreesToRadians(start)); +} + +void Redirect::setEnd(qreal end) +{ + m_end = end; + m_endR = CursorNavigationCommand::fitAngle(qDegreesToRadians(end)); +} + +void Redirect::setTarget(QQuickItem *target) +{ + if (m_target) { + disconnect(m_target, &QObject::destroyed, this, &Redirect::onTargetDestroyed); + } + m_target = target; + if (m_target) { + connect(m_target, &QObject::destroyed, this, &Redirect::onTargetDestroyed); + } +} + +bool Redirect::angleIsIncluded(qreal angle) +{ + if (m_startR > m_endR) + return angle >= m_startR || angle <= m_endR; + else + return angle >= m_startR && angle <= m_endR; +} + +void Redirect::onTargetDestroyed() +{ + m_target = nullptr; +} + diff --git a/plugin/redirect.h b/plugin/redirect.h new file mode 100644 index 0000000..232f07b --- /dev/null +++ b/plugin/redirect.h @@ -0,0 +1,41 @@ +#ifndef REDIRECT_H +#define REDIRECT_H + +#include + +class QQuickItem; + +class Redirect : public QObject +{ + Q_OBJECT + + Q_PROPERTY(qreal start READ start WRITE setStart) + Q_PROPERTY(qreal end READ end WRITE setEnd) + Q_PROPERTY(QQuickItem *target READ target WRITE setTarget) +public: + Redirect(QObject *parent = nullptr); + virtual ~Redirect(); + + qreal start() const; + qreal end() const; + QQuickItem *target() const; + + void setStart(qreal start); + void setEnd(qreal end); + void setTarget(QQuickItem *target); + + bool angleIsIncluded(qreal angle); + +private slots: + void onTargetDestroyed(); + +private: + qreal m_start; + qreal m_end; + //fitted angles in radians + qreal m_startR; + qreal m_endR; + QQuickItem *m_target; +}; + +#endif // REDIRECT_H -- cgit v1.2.3