aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAntti Hölttä <AHoelttae@luxoft.com>2019-02-19 15:36:39 +0100
committerAntti Hölttä <AHoelttae@luxoft.com>2019-03-18 16:42:31 +0100
commit8489f36322c585ec78199e6eb183000c74afae19 (patch)
tree8989b7cf6275b8b6cbffaa3b44467f5bd4ecdbf5
parenta5120f26d509a3464c79404de84e9428b8ddc690 (diff)
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.
-rw-r--r--DemoApplication/main.qml4
-rw-r--r--DemoApplication/pages/Redirects.qml124
-rw-r--r--plugin/cursornavigation.cpp30
-rw-r--r--plugin/cursornavigation.h4
-rw-r--r--plugin/cursornavigationattached.cpp61
-rw-r--r--plugin/cursornavigationattached.h12
-rw-r--r--plugin/plugin.cpp2
-rw-r--r--plugin/plugin.pro6
-rw-r--r--plugin/redirect.cpp68
-rw-r--r--plugin/redirect.h41
10 files changed, 321 insertions, 31 deletions
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 <QQuickWindow>
#include <QQuickItem>
+#include <QtMath>
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<CursorNavigationAttached*> 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<Redirect> CursorNavigationAttached::redirects()
+{
+ return QQmlListProperty<Redirect>(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<Redirect> *property, Redirect *redirect)
+{
+ CursorNavigationAttached *cna = static_cast<CursorNavigationAttached*>(property->object);
+ cna->m_redirects.append(redirect);
+}
+
+int CursorNavigationAttached::redirectCount(QQmlListProperty<Redirect> *property)
+{
+ CursorNavigationAttached *cna = static_cast<CursorNavigationAttached*>(property->object);
+ return cna->m_redirects.count();
+}
+
+Redirect *CursorNavigationAttached::redirect(QQmlListProperty<Redirect> *property, int index)
+{
+ CursorNavigationAttached *cna = static_cast<CursorNavigationAttached*>(property->object);
+ return cna->m_redirects.at(index);
+}
+
+void CursorNavigationAttached::clearRedirects(QQmlListProperty<Redirect> *property)
+{
+ CursorNavigationAttached *cna = static_cast<CursorNavigationAttached*>(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 <qqml.h>
#include <QObject>
#include <QList>
+#include <QQmlListProperty>
+#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<Redirect> redirects READ redirects)
public:
CursorNavigationAttached(QQuickItem *parent);
@@ -32,6 +34,8 @@ public:
bool hasCursor() const;
bool trapsCursor() const;
QQuickItem *escapeTarget() const;
+ QQmlListProperty<Redirect> 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<CursorNavigationAttached*> &siblings();
+ static void appendRedirect(QQmlListProperty<Redirect> *property, Redirect *redirect);
+ static int redirectCount(QQmlListProperty<Redirect> *property);
+ static Redirect *redirect(QQmlListProperty<Redirect> *property, int index);
+ static void clearRedirects(QQmlListProperty<Redirect> *property);
+
CursorNavigation *m_cursorNavigation;
CursorNavigationAttached *m_parentNavigable;
QList<CursorNavigationAttached*> m_children;
@@ -103,6 +112,7 @@ private:
friend class CursorNavigation;
QQuickItem * m_escapeTarget;
+ QVector<Redirect*> 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<CursorNavigation>(uri, 1, 0, "CursorNavigation",
QStringLiteral("CursorNavigation is not creatable, use the attached properties."));
+ qmlRegisterType<Redirect>(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 <QQuickItem>
+#include <QtMath>
+#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 <QObject>
+
+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