aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md9
-rw-r--r--plugin/cursornavigation.cpp35
-rw-r--r--plugin/cursornavigation.h6
-rw-r--r--plugin/cursornavigationalgorithm.h3
-rw-r--r--plugin/cursornavigationattached.cpp64
-rw-r--r--plugin/cursornavigationattached.h17
-rw-r--r--plugin/plugin.pro2
-rw-r--r--plugin/spatialnavigation360.cpp4
-rw-r--r--plugin/spatialnavigation4dir.cpp4
9 files changed, 99 insertions, 45 deletions
diff --git a/README.md b/README.md
index e11511d..fc3774f 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,9 @@
+## Notice
+
+This module, the plugin and the examples are all very much work in progress, so changes to the code, naming and API are to be expected!
+
+
## What is it for?
Cursor is an indicator for the target of user interactions in a GUI. In this case, it's more specifically a selector for individual UI elements, such as buttons, and not completely freely moving, like a mouse pointer. The purpose of the CursorNavigation plugin is to provide a generic way of enabling cursor navigation in QML UIs with minimal configuration and ease of use in mind. Minimal configuration, in this case, would be in comparison to eg. qt's existing key navigation, where the navigation path has to be defined explicitly on a per element basis. CursorNavigation ships as a plugin for Qt and is loaded by simply importing it in QML, usable right from the beginning of the ui development.
@@ -6,7 +11,7 @@ Cursor is an indicator for the target of user interactions in a GUI. In this cas
## How does it work?
-CursorNavigation works by allowing the developer to define individual QML elements as navigable. The framework's backend keeps track of the navigable items and their parent-child relations and position and geometry on the UI. Moving the cursor is handled by algorithms, and is based just on the location and geometry of the items. There are 2 algorithm for spatial navigation: 4-way and free-directional. The simpler 4-way navigation is well suited for traditional, rectangular and structured UIs, while the 360-algorithm allows moving freely to any direction.
+CursorNavigation works by allowing the developer to define individual QML elements as navigable. The framework's backend keeps track of the navigable items and their parent-child relations and position and geometry on the UI. Moving the cursor is handled by algorithms, and is based just on the location and geometry of the items. There are 2 algorithms for spatial navigation: 4-way and free-directional. The simpler 4-way navigation is well suited for traditional, rectangular and structured UIs, while the 360-algorithm allows moving freely to any direction.
There may be one instance of the CursorNavigation backend per window and one item per window having the cursor. Moving the cursor is also moving the active focus between the items. This means that inputs are directed on the item that has the cursor and that the cursor movement can be refined using Qt's FocusScopes.
@@ -106,7 +111,7 @@ void movedBack();
void escaped();
```
-## Using CursorNavigation
+## Examples of using CursorNavigation
### Basics
diff --git a/plugin/cursornavigation.cpp b/plugin/cursornavigation.cpp
index 71ea2d8..ebcd064 100644
--- a/plugin/cursornavigation.cpp
+++ b/plugin/cursornavigation.cpp
@@ -44,6 +44,8 @@
#include <QQuickItem>
#include <QtMath>
+Q_LOGGING_CATEGORY(cursorNavigationLog, "cursor.navigation")
+
const char CursorNavigation::windowPropertyName[] = "cursor_navigation";
CursorNavigation::CursorNavigation(QQuickWindow *parent)
@@ -60,9 +62,9 @@ CursorNavigation::CursorNavigation(QQuickWindow *parent)
onActiveFocusItemChanged();
}
-bool CursorNavigation::move(qreal angle, qreal tolerance, bool discrete)
+bool CursorNavigation::move(qreal angle, qreal offset, qreal tolerance, bool discrete)
{
- QQuickItem *foundItem = find(angle, tolerance, discrete);
+ QQuickItem *foundItem = find(angle + offset, tolerance, discrete);
CursorNavigationAttached *nextItem = cursorNavigationAttachment(foundItem);
if (nextItem) {
@@ -77,14 +79,16 @@ bool CursorNavigation::move(qreal angle, qreal tolerance, bool discrete)
QQuickItem *CursorNavigation::find(qreal angle, qreal tolerance, bool discrete)
{
- if (!m_currentItem)
- return defaultItem()->item();
+ if (!m_currentItem) {
+ auto defaultItemRef = defaultItem();
+ return defaultItemRef ? defaultItemRef->item() : nullptr;
+ }
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";
+ qCWarning(cursorNavigationLog) << "Redirect target is null";
return redirect->target();
}
}
@@ -120,7 +124,7 @@ QQuickItem *CursorNavigation::find(qreal angle, qreal tolerance, bool discrete)
bool CursorNavigation::action(Action action)
{
- qWarning() << "handleActionCommand, action= " << action;
+ qCWarning(cursorNavigationLog) << "handleActionCommand, action= " << action;
switch (action) {
case Forward:
break;
@@ -145,7 +149,7 @@ bool CursorNavigation::action(Action action)
}
escapeTarget = m_currentItem->m_parentNavigable->m_parentNavigable->item();
}
- qWarning() << "escaping, target = " << escapeTarget;
+ qCWarning(cursorNavigationLog) << "escaping, target = " << escapeTarget;
setCursorOnItem(nullptr);
escapeTarget->forceActiveFocus();
onActiveFocusItemChanged();
@@ -167,13 +171,12 @@ CursorNavigationAttached *CursorNavigation::qmlAttachedProperties(QObject *objec
}
if (!qobject_cast<QQuickItem *>(object)) {
- qWarning("Cannot manage cursor for a non-Item!");
+ qCWarning(cursorNavigationLog) << "Cannot manage cursor for a non-Item!";
return nullptr;
}
QQuickItem *item = static_cast<QQuickItem *>(object);
- qWarning() << "Created a new CN attachment";
return new CursorNavigationAttached(item);
}
@@ -186,7 +189,7 @@ CursorNavigation *CursorNavigation::cursorNavigationForWindow(QQuickWindow *wind
if (!oldCursorNavigation.isNull())
return oldCursorNavigation.value<CursorNavigation *>();
- qWarning() << "Created a new CN engine";
+ qCWarning(cursorNavigationLog) << "Created a new CN engine";
CursorNavigation *cursorNavigation = new CursorNavigation(window);
window->setProperty(windowPropertyName, QVariant::fromValue(cursorNavigation));
@@ -194,7 +197,7 @@ CursorNavigation *CursorNavigation::cursorNavigationForWindow(QQuickWindow *wind
/*if (QQmlEngine *engine = cn->qmlEngine(window)) {
engine->rootContext()->setContextProperty("_cursorNavigation", cn);
} else {
- qDebug() << "Couldn't find QQmlEngine";
+ qCDebug(cursorNavigationLog) << "Couldn't find QQmlEngine";
}*/
return cursorNavigation;
@@ -207,7 +210,6 @@ CursorNavigationAttached *CursorNavigation::cursorNavigationAttachment(QQuickIte
void CursorNavigation::setCursorOnItem(CursorNavigationAttached *item)
{
- qWarning() << "set cursor on item " << item << " , currentItem " << m_currentItem;
if (item != m_currentItem) {
if (m_currentItem) {
m_currentItem->setHasCursor(false);
@@ -216,10 +218,8 @@ void CursorNavigation::setCursorOnItem(CursorNavigationAttached *item)
if (item && item->acceptsCursor()) {
item->setHasCursor(true);
m_currentItem = item;
- qWarning() << "Set cursor to " << item->item();
m_currentItem->item()->forceActiveFocus();
} else {
- qWarning() << "Set cursor to NULL";
m_currentItem = nullptr;
}
}
@@ -227,7 +227,8 @@ void CursorNavigation::setCursorOnItem(CursorNavigationAttached *item)
void CursorNavigation::onActiveFocusItemChanged()
{
- qWarning() << "onActiveFocusItemChanged, item:" << m_window->activeFocusItem();
+ qCWarning(cursorNavigationLog) << "onActiveFocusItemChanged, item:"
+ << m_window->activeFocusItem();
QQuickItem *item = m_window->activeFocusItem();
while (item) {
@@ -243,7 +244,7 @@ void CursorNavigation::onActiveFocusItemChanged()
void CursorNavigation::registerItem(CursorNavigationAttached* item)
{
- //qWarning() << "register item " << item;
+ //qCWarning(cursorNavigationLog) << "register item " << item;
if (!item)
return;
@@ -285,7 +286,7 @@ void CursorNavigation::registerItem(CursorNavigationAttached* item)
void CursorNavigation::unregisterItem(CursorNavigationAttached* item)
{
- //qWarning() << "unregister item " << item;
+ //qCWarning(cursorNavigationLog) << "unregister item " << item;
if (item == m_currentItem)
setCursorOnItem(nullptr);
diff --git a/plugin/cursornavigation.h b/plugin/cursornavigation.h
index 5829ff9..c73684e 100644
--- a/plugin/cursornavigation.h
+++ b/plugin/cursornavigation.h
@@ -49,8 +49,10 @@
#include <QObject>
#include <qqml.h>
#include <QStack>
+#include <QtCore/QLoggingCategory>
-//TODO remove useless debug prints and change over to categorized logging
+
+Q_DECLARE_LOGGING_CATEGORY(cursorNavigationLog)
class QQuickItem;
@@ -65,7 +67,7 @@ public:
//void setMagnitude(qreal angle, qreal magnitude);
//void setMagnitude(const QVector2D& vector);
//move the cursor
- bool move(qreal angle, qreal tolerance, bool discrete);
+ bool move(qreal angle, qreal offset, qreal tolerance, bool discrete);
//find the next item without moving the cursor
QQuickItem *find(qreal angle, qreal tolerance, bool discrete);
bool action(Action action);
diff --git a/plugin/cursornavigationalgorithm.h b/plugin/cursornavigationalgorithm.h
index b0c1efd..06dbb21 100644
--- a/plugin/cursornavigationalgorithm.h
+++ b/plugin/cursornavigationalgorithm.h
@@ -42,6 +42,9 @@
#include <QList>
#include "inputtypes.h"
+#include <QtCore/QLoggingCategory>
+
+Q_DECLARE_LOGGING_CATEGORY(cursorNavigationLog)
class CursorNavigationAttached;
diff --git a/plugin/cursornavigationattached.cpp b/plugin/cursornavigationattached.cpp
index 6e208d5..c6a450f 100644
--- a/plugin/cursornavigationattached.cpp
+++ b/plugin/cursornavigationattached.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2018, 2019 Luxoft Sweden AB. All rights reserved.
+** Copyright (C) 2018 - 2020 Luxoft Sweden AB. All rights reserved.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the cursor management module of the Qt Toolkit.
@@ -41,12 +41,14 @@
#include "cursornavigation.h"
#include <QQuickItem>
#include <QQuickWindow>
+#include <QVector2D>
#include <QtMath>
CursorNavigationAttached::CursorNavigationAttached(QQuickItem *parent)
:QObject(parent),
m_cursorNavigation(nullptr),
m_parentNavigable(nullptr),
+m_angleOffset(0.0),
m_acceptsCursor(false),
m_hasCursor(false),
m_trapsCursor(false),
@@ -57,18 +59,36 @@ m_escapeTarget(nullptr)
if (parent && item() && item()->window())
{
- qDebug() << "Item has a window already";
+ qCDebug(cursorNavigationLog) << "Item has a window already";
onWindowChanged(item()->window());
}
}
CursorNavigationAttached::~CursorNavigationAttached()
{
- qWarning() << "~CursorNavigationAttached";
+ qCWarning(cursorNavigationLog) << "~CursorNavigationAttached";
+
+ for (auto && child: m_children) {
+ child->m_parentNavigable = nullptr;
+ }
+
if (m_cursorNavigation)
m_cursorNavigation->unregisterItem(this);
}
+double CursorNavigationAttached::angleOffset() const
+{
+ return m_angleOffset;
+}
+
+void CursorNavigationAttached::setAngleOffset(double angleOffset)
+{
+ if (angleOffset != m_angleOffset) {
+ m_angleOffset = angleOffset;
+ emit angleOffsetChanged(m_angleOffset);
+ qCWarning(cursorNavigationLog) << "rotation changed " << m_angleOffset;
+ }
+}
bool CursorNavigationAttached::acceptsCursor() const
{
return m_acceptsCursor;
@@ -122,7 +142,7 @@ void CursorNavigationAttached::setMagnitude(qreal angle, qreal magnitude)
m_cursorNavigation->m_currentItem->magnitudeChanged(angle, magnitude);
}
-void CursorNavigationAttached::setMagnitude(QVector2D vector)
+void CursorNavigationAttached::setMagnitude(const QVector2D &vector)
{
if (m_cursorNavigation && m_cursorNavigation->m_currentItem)
m_cursorNavigation->m_currentItem->magnitudeChanged(
@@ -132,23 +152,25 @@ void CursorNavigationAttached::setMagnitude(QVector2D vector)
void CursorNavigationAttached::move(qreal angle, qreal tolerance)
{
if (m_cursorNavigation) {
- qWarning() << "move";
+ qCWarning(cursorNavigationLog) << "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)
+ double offset = item ? item->angleOffset() : 0.0;
+ if (m_cursorNavigation->move(a, offset, t, false) && item)
item->moved(a,t);
}
}
-void CursorNavigationAttached::move(QVector2D vector, qreal tolerance)
+void CursorNavigationAttached::move(const QVector2D &vector, qreal tolerance)
{
if (m_cursorNavigation) {
- qWarning() << "move (vector)";
+ qCWarning(cursorNavigationLog) << "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)
+ double offset = item ? item->angleOffset() : 0.0;
+ if (m_cursorNavigation->move(a, offset, t, false) && item)
item->moved(a,t);
}
}
@@ -163,7 +185,7 @@ QQuickItem *CursorNavigationAttached::find(qreal angle, qreal tolerance)
return nullptr;
}
-QQuickItem *CursorNavigationAttached::find(QVector2D vector, qreal tolerance)
+QQuickItem *CursorNavigationAttached::find(const QVector2D &vector, qreal tolerance)
{
if (m_cursorNavigation) {
qreal a = qAtan2(vector.y(), vector.x());
@@ -177,7 +199,8 @@ void CursorNavigationAttached::moveUp()
{
if (m_cursorNavigation) {
CursorNavigationAttached *item = m_cursorNavigation->m_currentItem;
- if (m_cursorNavigation->move(qDegreesToRadians(-90.0f), 0, true) && item)
+ double offset = item ? item->angleOffset() : 0.0;
+ if (m_cursorNavigation->move(qDegreesToRadians(-90.0f), offset, 0, true) && item)
item->movedUp();
}
}
@@ -186,7 +209,8 @@ void CursorNavigationAttached::moveDown()
{
if (m_cursorNavigation) {
CursorNavigationAttached *item = m_cursorNavigation->m_currentItem;
- if (m_cursorNavigation->move(qDegreesToRadians(90.0f), 0, true) && item)
+ double offset = item ? item->angleOffset() : 0.0;
+ if (m_cursorNavigation->move(qDegreesToRadians(90.0f), offset, 0, true) && item)
item->movedDown();
}
}
@@ -195,7 +219,8 @@ void CursorNavigationAttached::moveRight()
{
if (m_cursorNavigation) {
CursorNavigationAttached *item = m_cursorNavigation->m_currentItem;
- if (m_cursorNavigation->move(qDegreesToRadians(0.0f), 0, true) && item)
+ double offset = item ? item->angleOffset() : 0.0;
+ if (m_cursorNavigation->move(qDegreesToRadians(0.0f), offset, 0, true) && item)
item->movedRight();
}
}
@@ -204,7 +229,8 @@ void CursorNavigationAttached::moveLeft()
{
if (m_cursorNavigation) {
CursorNavigationAttached *item = m_cursorNavigation->m_currentItem;
- if (m_cursorNavigation->move(qDegreesToRadians(180.0f), 0, true) && item)
+ double offset = item ? item->angleOffset() : 0.0;
+ if (m_cursorNavigation->move(qDegreesToRadians(180.0f), offset, 0, true) && item)
item->movedLeft();
}
}
@@ -242,9 +268,17 @@ void CursorNavigationAttached::escape()
}
}
+QQuickItem *CursorNavigationAttached::currentItem()
+{
+ if (m_cursorNavigation && m_cursorNavigation->m_currentItem) {
+ return m_cursorNavigation->m_currentItem->item();
+ }
+ return nullptr;
+}
+
void CursorNavigationAttached::onWindowChanged(QQuickWindow *window)
{
- qDebug() << "window changed, window = " << window;
+ qCDebug(cursorNavigationLog) << "window changed, window = " << window;
if (m_cursorNavigation && m_acceptsCursor)
m_cursorNavigation->unregisterItem(this);
diff --git a/plugin/cursornavigationattached.h b/plugin/cursornavigationattached.h
index a005d7c..25a6769 100644
--- a/plugin/cursornavigationattached.h
+++ b/plugin/cursornavigationattached.h
@@ -49,10 +49,13 @@
class CursorNavigation;
class QQuickItem;
class QQuickWindow;
+class QVector2D;
class CursorNavigationAttached : public QObject
{
Q_OBJECT
+ //is used to add offsets to any directional movement of the cursor navigation
+ Q_PROPERTY(double angleOffset READ angleOffset WRITE setAngleOffset NOTIFY angleOffsetChanged)
//is included for cursor navigation
Q_PROPERTY(bool acceptsCursor READ acceptsCursor WRITE setAcceptsCursor NOTIFY acceptsCursorChanged)
//indicates if item is currently selected, indicated also by activeFocus property
@@ -67,6 +70,7 @@ public:
CursorNavigationAttached(QQuickItem *parent);
~CursorNavigationAttached();
+ double angleOffset() const;
bool acceptsCursor() const;
bool hasCursor() const;
bool trapsCursor() const;
@@ -82,6 +86,7 @@ public:
QQuickItem *item() const;
public slots:
+ void setAngleOffset(double angleOffset);
void setAcceptsCursor(bool acceptsCursor);
void setTrapsCursor(bool trapsCursor);
void setEscapeTarget(QQuickItem * escapeTarget);
@@ -90,14 +95,14 @@ public slots:
* events are received by the item that currently has the cursor
*/
void setMagnitude(qreal angle, qreal magnitude);
- void setMagnitude(QVector2D vector);
+ void setMagnitude(const QVector2D &vector);
void move(qreal angle, qreal tolerance = 0);
- void move(QVector2D vector, qreal tolerance = 0);
+ void move(const QVector2D &vector, qreal tolerance = 0);
//find the next item with this move, without moving
QQuickItem *find(qreal angle, qreal tolerance = 0);
- QQuickItem *find(QVector2D vector, qreal tolerance = 0);
+ QQuickItem *find(const QVector2D &vector, qreal tolerance = 0);
void moveUp();
void moveDown();
@@ -107,9 +112,12 @@ public slots:
void moveForward();
void moveBack();
void escape();
-
+ /* Returns the item that currently has the cursor
+ */
+ QQuickItem *currentItem();
signals:
+ void angleOffsetChanged(double angleOffset);
void acceptsCursorChanged(bool acceptsCursor);
void hasCursorChanged(bool hasCursor);
void trapsCursorChanged(bool trapsCursor);
@@ -142,6 +150,7 @@ private:
CursorNavigationAttached *m_parentNavigable;
QList<CursorNavigationAttached*> m_children;
+ double m_angleOffset;
bool m_acceptsCursor;
bool m_hasCursor;
bool m_trapsCursor;
diff --git a/plugin/plugin.pro b/plugin/plugin.pro
index b5cdee7..952b52a 100644
--- a/plugin/plugin.pro
+++ b/plugin/plugin.pro
@@ -2,7 +2,7 @@ TARGET = cursornavigationplugin
TEMPLATE = lib
QT -= gui
-QT += qml quick
+QT += qml quick core
CONFIG += plugin
diff --git a/plugin/spatialnavigation360.cpp b/plugin/spatialnavigation360.cpp
index 12f19ee..cc1f73e 100644
--- a/plugin/spatialnavigation360.cpp
+++ b/plugin/spatialnavigation360.cpp
@@ -154,7 +154,7 @@ CursorNavigationAttached* SpatialNavigation360::getNextCandidate(
* -remember to use current item's coord system as the reference!!!
*/
- //qWarning() << "##### navigation360: start, angle = " << cmd.angle << " tolerance = " << cmd.angleTolerance;
+ //qCWarning(cursorNavigationLog) << "##### navigation360: start, angle = " << cmd.angle << " tolerance = " << cmd.angleTolerance;
if (candidates.isEmpty())
return nullptr;
@@ -218,7 +218,7 @@ CursorNavigationAttached* SpatialNavigation360::getNextCandidate(
}
}
- //qWarning() << "##### end, directHit = " <<
+ //qCWarning(cursorNavigationLog) << "##### end, directHit = " <<
// directHitItem << " withinTolerances = " << withinToleranceItem;
if (directHitItem)
diff --git a/plugin/spatialnavigation4dir.cpp b/plugin/spatialnavigation4dir.cpp
index 5bd5b52..89b4a3a 100644
--- a/plugin/spatialnavigation4dir.cpp
+++ b/plugin/spatialnavigation4dir.cpp
@@ -69,10 +69,10 @@ CursorNavigationAttached* SpatialNavigation4Dir::getNextCandidate(
if (candidates.isEmpty())
return nullptr;
- qDebug() << "4-way algortihm called, no of candidates=" << candidates.count();
+ qCDebug(cursorNavigationLog) << "4-way algortihm called, no of candidates=" << candidates.count();
if (!currentItem && candidates.size()) {
- qDebug() << "the spatial chooser falling back to first child" << candidates.first();
+ qCDebug(cursorNavigationLog) << "the spatial chooser falling back to first child" << candidates.first();
return candidates.first();
}