aboutsummaryrefslogtreecommitdiffstats
path: root/plugin
diff options
context:
space:
mode:
authorAntti Hölttä <AHoelttae@luxoft.com>2018-07-23 14:53:11 +0200
committerAntti Hölttä <AHoelttae@luxoft.com>2019-03-18 16:29:30 +0100
commitffd151ecf3971fd8601d8b1d4e2f04fb5793e364 (patch)
treecd3b947f04781d59bc61306dd34ef15f8c3a24fd /plugin
Initial commit, demoable poc
Sort of working with 4 dir navigation, check demoapp
Diffstat (limited to 'plugin')
-rw-r--r--plugin/cursornavigation.cpp87
-rw-r--r--plugin/cursornavigation.h45
-rw-r--r--plugin/cursornavigationalgorithm.cpp13
-rw-r--r--plugin/cursornavigationalgorithm.h26
-rw-r--r--plugin/cursornavigationattached.cpp82
-rw-r--r--plugin/cursornavigationattached.h54
-rw-r--r--plugin/inputadapter.cpp83
-rw-r--r--plugin/inputadapter.h40
-rw-r--r--plugin/inputtypes.cpp23
-rw-r--r--plugin/inputtypes.h33
-rw-r--r--plugin/itemregister.cpp36
-rw-r--r--plugin/itemregister.h30
-rw-r--r--plugin/plugin.cpp13
-rw-r--r--plugin/plugin.h16
-rw-r--r--plugin/plugin.pro36
-rw-r--r--plugin/qmldir2
-rw-r--r--plugin/spatialnavigation4dir.cpp161
-rw-r--r--plugin/spatialnavigation4dir.h17
18 files changed, 797 insertions, 0 deletions
diff --git a/plugin/cursornavigation.cpp b/plugin/cursornavigation.cpp
new file mode 100644
index 0000000..5cb9545
--- /dev/null
+++ b/plugin/cursornavigation.cpp
@@ -0,0 +1,87 @@
+#include "cursornavigation.h"
+#include "cursornavigationalgorithm.h"
+#include "spatialnavigation4dir.h"
+#include <QQuickWindow>
+#include <QQuickItem>
+
+const char CursorNavigation::windowPropertyName[] = "cursor_navigation";
+
+CursorNavigation::CursorNavigation(QQuickWindow *parent)
+:QObject(parent)
+,m_inputAdapter(parent, this)
+,m_currentItem(nullptr)
+{
+ m_algorithms.push_back(new SpatialNavigation4Dir(&m_itemRegister));
+}
+
+bool CursorNavigation::inputCommand(CursorNavigationCommand cmd)
+{
+ QQuickItem *nextItem;
+
+ for (auto alg : m_algorithms) {
+ nextItem = alg->getNextCandidate(m_itemRegister.items(), m_currentItem, cmd);
+ if (nextItem)
+ break;
+ }
+
+ if (nextItem) {
+ if (m_currentItem) {
+ CursorNavigationAttached *current=cursorNavigationAttachment(m_currentItem);
+ Q_ASSERT(current);
+ current->setHasCursor(false);
+ }
+ CursorNavigationAttached *next=cursorNavigationAttachment(nextItem);
+ Q_ASSERT(next);
+ next->setHasCursor(true);
+ m_currentItem = nextItem;
+ }
+}
+
+CursorNavigationAttached *CursorNavigation::qmlAttachedProperties(QObject *object)
+{
+ // if the object is a window, use its contentItem instead
+ if (auto win = qobject_cast<QQuickWindow *>(object)) {
+ object = win->contentItem();
+ }
+
+ if (!qobject_cast<QQuickItem *>(object)) {
+ qWarning("Cannot manage focus for a non-Item!");
+ return nullptr;
+ }
+
+ QQuickItem *item = static_cast<QQuickItem *>(object);
+
+ // TODO: what if an object, with an already attached object, gets reparented (say, in another window?)
+ // with or without a focus system.
+
+ return new CursorNavigationAttached(item);
+}
+
+CursorNavigation *CursorNavigation::cursorNavigationForWindow(QQuickWindow *window)
+{
+ if (!window)
+ return nullptr;
+
+ const QVariant &oldCursorNavigation = window->property(windowPropertyName);
+ if (!oldCursorNavigation.isNull())
+ return oldCursorNavigation.value<CursorNavigation *>();
+
+ CursorNavigation *cursorNavigation = new CursorNavigation(window);
+ window->setProperty(windowPropertyName, QVariant::fromValue(cursorNavigation));
+
+ //why would the context property be needed?
+ /*if (QQmlEngine *engine = cn->qmlEngine(window)) {
+ engine->rootContext()->setContextProperty("_cursorNavigation", cn);
+ } else {
+ qDebug() << "Couldn't find QQmlEngine";
+ }*/
+
+ return cursorNavigation;
+}
+
+CursorNavigationAttached *CursorNavigation::cursorNavigationAttachment(QQuickItem *item)
+{
+ Q_ASSERT(item);
+ return static_cast<CursorNavigationAttached *>(qmlAttachedPropertiesObject<CursorNavigation>(item, false));
+}
+
diff --git a/plugin/cursornavigation.h b/plugin/cursornavigation.h
new file mode 100644
index 0000000..a9309fb
--- /dev/null
+++ b/plugin/cursornavigation.h
@@ -0,0 +1,45 @@
+#ifndef CURSORNAVIGATION_H
+#define CURSORNAVIGATION_H
+
+#include "cursornavigationattached.h"
+#include "itemregister.h"
+#include "inputtypes.h"
+#include "inputadapter.h"
+
+#include <QObject>
+#include <qqml.h>
+
+class QQuickItem;
+class CursorNavigationAlgorithm;
+
+class CursorNavigation : public QObject
+{
+ Q_OBJECT
+
+public:
+ CursorNavigation(QQuickWindow *parent);
+
+ bool inputCommand(CursorNavigationCommand cmd);
+
+ static CursorNavigationAttached *qmlAttachedProperties(QObject *object);
+
+ static CursorNavigation *cursorNavigationForWindow(QQuickWindow *window);
+
+private:
+ void setCursorOnItem(QQuickItem *item);
+ static CursorNavigationAttached *cursorNavigationAttachment(QQuickItem *item);
+
+private:
+ static const char windowPropertyName[];
+ InputAdapter m_inputAdapter;
+ QQuickItem *m_currentItem; //item that currently has the cursor
+ QList<CursorNavigationAlgorithm*> m_algorithms;
+ ItemRegister m_itemRegister;
+
+
+ friend class CursorNavigationAttached;
+};
+
+QML_DECLARE_TYPEINFO(CursorNavigation, QML_HAS_ATTACHED_PROPERTIES)
+
+#endif // CURSORNAVIGATION_H
diff --git a/plugin/cursornavigationalgorithm.cpp b/plugin/cursornavigationalgorithm.cpp
new file mode 100644
index 0000000..d21c81c
--- /dev/null
+++ b/plugin/cursornavigationalgorithm.cpp
@@ -0,0 +1,13 @@
+#include "cursornavigationalgorithm.h"
+#include "itemregister.h"
+
+CursorNavigationAlgorithm::CursorNavigationAlgorithm(ItemRegister *itemRegister)
+:m_itemRegister(itemRegister)
+{
+
+}
+
+CursorNavigationAlgorithm::~CursorNavigationAlgorithm()
+{
+
+}
diff --git a/plugin/cursornavigationalgorithm.h b/plugin/cursornavigationalgorithm.h
new file mode 100644
index 0000000..02f247a
--- /dev/null
+++ b/plugin/cursornavigationalgorithm.h
@@ -0,0 +1,26 @@
+#ifndef CURSORNAVIGATIONALGORITHM_H
+#define CURSORNAVIGATIONALGORITHM_H
+
+#include <QList>
+#include "inputtypes.h"
+
+class ItemRegister;
+class QQuickItem;
+
+class CursorNavigationAlgorithm
+{
+public:
+ CursorNavigationAlgorithm(ItemRegister *itemRegister);
+
+ virtual ~CursorNavigationAlgorithm();
+
+ virtual QQuickItem* getNextCandidate(const QList<QQuickItem*> &candidates,
+ const QQuickItem *currentItem,
+ const CursorNavigationCommand& cmd) = 0;
+
+private:
+ ItemRegister *m_itemRegister;
+
+};
+
+#endif // CURSORNAVIGATIONALGORITHM_H
diff --git a/plugin/cursornavigationattached.cpp b/plugin/cursornavigationattached.cpp
new file mode 100644
index 0000000..50b3686
--- /dev/null
+++ b/plugin/cursornavigationattached.cpp
@@ -0,0 +1,82 @@
+#include "cursornavigationattached.h"
+#include "cursornavigation.h"
+#include <QQuickItem>
+#include <QQuickWindow>
+
+CursorNavigationAttached::CursorNavigationAttached(QQuickItem *parent)
+:QObject(parent),
+m_acceptsCursor(true),
+m_cursorNavigation(nullptr),
+m_hasCursor(false)
+{
+ connect(parent, &QQuickItem::windowChanged, this, &CursorNavigationAttached::onWindowChanged);
+
+ if (item()->window())
+ {
+ qDebug() << "Item has a window already";
+ onWindowChanged(item()->window());
+ }
+}
+
+bool CursorNavigationAttached::acceptsCursor() const
+{
+ return m_acceptsCursor;
+}
+
+void CursorNavigationAttached::setAcceptsCursor(bool acceptsCursor)
+{
+ if (acceptsCursor != m_acceptsCursor) {
+ m_acceptsCursor=acceptsCursor;
+ emit acceptsCursorChanged(m_acceptsCursor);
+ }
+}
+
+bool CursorNavigationAttached::hasCursor() const
+{
+ return m_hasCursor;
+}
+
+bool CursorNavigationAttached::trapsCursor() const
+{
+ return m_trapsCursor;
+}
+
+void CursorNavigationAttached::setTrapsCursor(bool trapsCursor)
+{
+ if (trapsCursor != m_trapsCursor) {
+ m_trapsCursor=trapsCursor;
+ emit trapsCursorChanged(m_trapsCursor);
+ }
+}
+
+void CursorNavigationAttached::onWindowChanged(QQuickWindow *window)
+{
+ qDebug() << "window changed, window = " << window;
+ if (m_cursorNavigation)
+ m_cursorNavigation->m_itemRegister.unregisterItem(item());
+
+ if (window) {
+ m_cursorNavigation = CursorNavigation::cursorNavigationForWindow(window);
+ } else {
+ m_cursorNavigation = nullptr;
+ }
+
+ if (m_cursorNavigation)
+ m_cursorNavigation->m_itemRegister.registerItem(item());
+
+ //emit focusManagerChanged();
+}
+
+QQuickItem *CursorNavigationAttached::item() const
+{
+ Q_ASSERT(qobject_cast<QQuickItem *>(parent()));
+ return static_cast<QQuickItem *>(parent());
+}
+
+void CursorNavigationAttached::setHasCursor(bool hasCursor)
+{
+ if (hasCursor != m_hasCursor) {
+ m_hasCursor=hasCursor;
+ emit hasCursorChanged(m_hasCursor);
+ }
+}
diff --git a/plugin/cursornavigationattached.h b/plugin/cursornavigationattached.h
new file mode 100644
index 0000000..2be0f72
--- /dev/null
+++ b/plugin/cursornavigationattached.h
@@ -0,0 +1,54 @@
+#ifndef CURSORNAVIGATIONATTACHED_H
+#define CURSORNAVIGATIONATTACHED_H
+
+//#include <qqml.h>
+#include <QObject>
+
+class CursorNavigation;
+class QQuickItem;
+class QQuickWindow;
+
+class CursorNavigationAttached : public QObject
+{
+ Q_OBJECT
+ //is available for cursor navigation
+ Q_PROPERTY(bool acceptsCursor READ acceptsCursor WRITE setAcceptsCursor NOTIFY acceptsCursorChanged)
+ //is available for cursor navigation
+ Q_PROPERTY(bool hasCursor READ hasCursor NOTIFY hasCursorChanged)
+ //traps cursor. a trapped cursor can not be traversed outside of the item that traps it
+ Q_PROPERTY(bool trapsCursor READ trapsCursor WRITE setTrapsCursor NOTIFY trapsCursorChanged)
+ //proxy cursor to other items
+ //Q_PROPERTY(QQmlListProperty<QQuickItem> preferredCursorTargets READ preferredCursorTargetsQML)
+
+public:
+ CursorNavigationAttached(QQuickItem *parent);
+
+ bool acceptsCursor() const;
+ void setAcceptsCursor(bool acceptsCursor);
+
+ bool hasCursor() const;
+
+ bool trapsCursor() const;
+ void setTrapsCursor(bool trapsCursor);
+
+signals:
+ void acceptsCursorChanged(bool acceptsCursor);
+ void hasCursorChanged(bool hasCursor);
+ void trapsCursorChanged(bool trapsCursor);
+
+private slots:
+ void onWindowChanged(QQuickWindow *window);
+
+private:
+ QQuickItem *item() const;
+ void setHasCursor(bool hasCursor);
+
+ CursorNavigation *m_cursorNavigation;
+ bool m_acceptsCursor;
+ bool m_hasCursor;
+ bool m_trapsCursor;
+
+ friend class CursorNavigation;
+};
+
+#endif // CURSORNAVIGATIONATTACHED_H
diff --git a/plugin/inputadapter.cpp b/plugin/inputadapter.cpp
new file mode 100644
index 0000000..5814e5d
--- /dev/null
+++ b/plugin/inputadapter.cpp
@@ -0,0 +1,83 @@
+#include "inputadapter.h"
+#include "cursornavigation.h"
+#include <QEvent>
+#include <QKeyEvent>
+#include <QMouseEvent>
+#include <QWheelEvent>
+
+InputAdapter::InputAdapter(QObject *target, CursorNavigation *cursorNavigation)
+ : QObject(cursorNavigation)
+ ,m_target(target)
+ ,m_cursorNavigation(cursorNavigation)
+{
+ if (m_target)
+ m_target->installEventFilter(this);
+}
+
+bool InputAdapter::eventFilter(QObject *object, QEvent *event)
+{
+ if (object != m_target)
+ return false;
+ if (event->type() == QEvent::KeyPress) {
+ QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
+ return handleKeyEvent(keyEvent);
+ } else if (event->type() == QEvent::Wheel) {
+ QWheelEvent *wheelEvent = static_cast<QWheelEvent *>(event);
+ return handleWheelEvent(wheelEvent);
+ } else if (event->type() == QEvent::MouseMove) {
+ QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
+ return handleMouseEvent(mouseEvent);
+ }
+ return false;
+}
+
+bool InputAdapter::handleKeyEvent(QKeyEvent *event)
+{
+ CursorNavigationCommand cmd;
+ //detect arrow keys, tabs, enter and esc
+ switch (event->key())
+ {
+ case Qt::Key_Left:
+ cmd = CursorNavigationCommand::Left;
+ break;
+ case Qt::Key_Right:
+ cmd = CursorNavigationCommand::Right;
+ break;
+ case Qt::Key_Up:
+ cmd = CursorNavigationCommand::Up;
+ break;
+ case Qt::Key_Down:
+ cmd = CursorNavigationCommand::Down;
+ break;
+ case Qt::Key_Return:
+ case Qt::Key_Enter:
+ cmd.action = CursorNavigationCommand::Activate;
+ break;
+ case Qt::BackButton:
+ case Qt::Key_Escape:
+ cmd.action = CursorNavigationCommand::Escape;
+ break;
+ case Qt::Key_Tab:
+ cmd.action = CursorNavigationCommand::Escape;
+ break;
+ case Qt::Key_Backtab:
+ cmd.action = CursorNavigationCommand::Escape;
+ break;
+ default:
+ return false;
+ }
+
+ return m_cursorNavigation->inputCommand(cmd);
+}
+
+bool InputAdapter::handleMouseEvent(QMouseEvent *event)
+{
+ //interpret mouse movement as omnnidirectional joystick movements for testing purposes
+ return false;
+}
+
+bool InputAdapter::handleWheelEvent(QWheelEvent *event)
+{
+ //turn wheel events into tabs
+ return false;
+}
diff --git a/plugin/inputadapter.h b/plugin/inputadapter.h
new file mode 100644
index 0000000..0613751
--- /dev/null
+++ b/plugin/inputadapter.h
@@ -0,0 +1,40 @@
+#ifndef INPUTADAPTER_H
+#define INPUTADAPTER_H
+
+#include <QObject>
+#include "inputtypes.h"
+
+class CursorNavigation;
+class QQuickWindow;
+class QKeyEvent;
+class QMouseEvent;
+class QWheelEvent;
+
+/* filter various input events and translate them for the cursor navigation.
+ * it is possible to interpret mouse events as joystick/swipe events
+ * Set instance of this class as an input filter to the window or component that
+ * is being tracked.
+ * Events are passed forward to the CursorNavigation class, which should accept
+ * the event or reject it. When rejected, event is passed on.
+ */
+
+class InputAdapter : public QObject
+{
+ Q_OBJECT
+public:
+ InputAdapter(QObject *target, CursorNavigation *cursorNavigation);
+
+protected:
+ bool eventFilter(QObject *object, QEvent *event) override;
+
+private:
+ bool handleKeyEvent(QKeyEvent *ev);
+ bool handleMouseEvent(QMouseEvent *ev);
+ bool handleWheelEvent(QWheelEvent *ev);
+
+ QObject *const m_target;
+ CursorNavigation *m_cursorNavigation;
+
+};
+
+#endif // INPUTADAPTER_H
diff --git a/plugin/inputtypes.cpp b/plugin/inputtypes.cpp
new file mode 100644
index 0000000..770b03d
--- /dev/null
+++ b/plugin/inputtypes.cpp
@@ -0,0 +1,23 @@
+#include "inputtypes.h"
+
+const CursorNavigationCommand CursorNavigationCommand::Up(1.0, 270.0);
+const CursorNavigationCommand CursorNavigationCommand::Down(1.0, 90.0);
+const CursorNavigationCommand CursorNavigationCommand::Left(1.0, 180.0);
+const CursorNavigationCommand CursorNavigationCommand::Right(1.0, 0.0);
+
+CursorNavigationCommand::CursorNavigationCommand()
+ :magnitude(-1), angle(-1), action(NoAction)
+{}
+
+CursorNavigationCommand::CursorNavigationCommand(float magnitude, int angle)
+ :magnitude(magnitude), angle(angle), action(NoAction)
+{}
+
+//test if this commands angle is between given angles. clockwise from begin to end
+bool CursorNavigationCommand::angleIsBetween(int begin, int end) const
+{
+ if (begin > end)
+ return angle >= begin || angle <= end;
+ else
+ return angle >= begin && angle <= end;
+}
diff --git a/plugin/inputtypes.h b/plugin/inputtypes.h
new file mode 100644
index 0000000..bf7e82e
--- /dev/null
+++ b/plugin/inputtypes.h
@@ -0,0 +1,33 @@
+#ifndef INPUTTYPES_H
+#define INPUTTYPES_H
+
+struct CursorNavigationCommand
+{
+ enum Action
+ {
+ NoAction,
+ Forward, //tab
+ Back, //ctrl-tab
+ Activate, //enter/click on item
+ Escape //leave scope
+ };
+
+ CursorNavigationCommand();
+
+ CursorNavigationCommand(float magnitude, int angle);
+
+ //test if this commands angle is between given angles. clockwise from begin to end
+ bool angleIsBetween(int begin, int end) const;
+
+ float magnitude; //0.0 to 1.0
+ int angle; //0 to 359
+ Action action;
+
+ static const CursorNavigationCommand Up;
+ static const CursorNavigationCommand Down;
+ static const CursorNavigationCommand Left;
+ static const CursorNavigationCommand Right;
+
+};
+
+#endif // INPUTTYPES_H
diff --git a/plugin/itemregister.cpp b/plugin/itemregister.cpp
new file mode 100644
index 0000000..f4555f1
--- /dev/null
+++ b/plugin/itemregister.cpp
@@ -0,0 +1,36 @@
+#include "itemregister.h"
+#include <QQuickItem>
+
+ItemRegister::ItemRegister()
+{
+
+}
+
+void ItemRegister::registerItem(QQuickItem* item)
+{
+ if (!item)
+ return;
+
+ m_items.append(item);
+ connect(item, &QQuickItem::destroyed, this, &ItemRegister::onItemDestroyed);
+}
+
+void ItemRegister::unregisterItem(QQuickItem* item)
+{
+ if (!item)
+ return;
+
+ disconnect(item, &QQuickItem::destroyed, this, &ItemRegister::onItemDestroyed);
+ m_items.removeOne(item);
+}
+
+const QList<QQuickItem*> ItemRegister::items() const
+{
+ return m_items;
+}
+
+void ItemRegister::onItemDestroyed(QObject *obj)
+{
+ QQuickItem *item=static_cast<QQuickItem*>(obj);
+ m_items.removeOne(item);
+}
diff --git a/plugin/itemregister.h b/plugin/itemregister.h
new file mode 100644
index 0000000..479bca7
--- /dev/null
+++ b/plugin/itemregister.h
@@ -0,0 +1,30 @@
+#ifndef ITEMREGISTER_H
+#define ITEMREGISTER_H
+
+#include <QObject>
+
+class QQuickItem;
+
+//keeps track of items that are cursor navigable
+class ItemRegister : public QObject
+{
+ Q_OBJECT
+
+public:
+ ItemRegister();
+
+ void registerItem(QQuickItem* item);
+ void unregisterItem(QQuickItem* item);
+
+ const QList<QQuickItem*> items() const;
+
+private Q_SLOTS:
+ void onItemDestroyed(QObject *obj);
+
+private:
+ //for now the data structure is just a list. could be replaced with something more efficient for the final purpose
+ QList<QQuickItem*> m_items;
+
+};
+
+#endif // ITEMREGISTER_H
diff --git a/plugin/plugin.cpp b/plugin/plugin.cpp
new file mode 100644
index 0000000..26d156a
--- /dev/null
+++ b/plugin/plugin.cpp
@@ -0,0 +1,13 @@
+#include "plugin.h"
+#include "cursornavigation.h"
+#include "qqml.h"
+
+CursorNavigationPlugin::CursorNavigationPlugin()
+{
+}
+
+void CursorNavigationPlugin::registerTypes(const char *uri)
+{
+ qmlRegisterUncreatableType<CursorNavigation>(uri, 1, 0, "CursorNavigation",
+ QStringLiteral("CursorNavigation is not creatable, use the attached properties."));
+}
diff --git a/plugin/plugin.h b/plugin/plugin.h
new file mode 100644
index 0000000..237c7b2
--- /dev/null
+++ b/plugin/plugin.h
@@ -0,0 +1,16 @@
+#ifndef PLUGIN_H
+#define PLUGIN_H
+
+#include <QQmlExtensionPlugin>
+
+class CursorNavigationPlugin : public QQmlExtensionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
+
+public:
+ CursorNavigationPlugin();
+ void registerTypes(const char *uri);
+};
+
+#endif // PLUGIN_H
diff --git a/plugin/plugin.pro b/plugin/plugin.pro
new file mode 100644
index 0000000..72c6b96
--- /dev/null
+++ b/plugin/plugin.pro
@@ -0,0 +1,36 @@
+TARGET = cursornavigationplugin
+TEMPLATE = lib
+
+QT -= gui
+QT += qml quick
+
+CONFIG += plugin
+
+DEFINES += PLUGIN_LIBRARY
+
+SOURCES += \
+ plugin.cpp \
+ cursornavigation.cpp \
+ cursornavigationattached.cpp \
+ itemregister.cpp \
+ inputadapter.cpp \
+ cursornavigationalgorithm.cpp \
+ spatialnavigation4dir.cpp \
+ inputtypes.cpp
+
+HEADERS += \
+ plugin.h \
+ cursornavigation.h \
+ cursornavigationattached.h \
+ itemregister.h \
+ inputadapter.h \
+ inputtypes.h \
+ cursornavigationalgorithm.h \
+ spatialnavigation4dir.h
+
+pluginfiles.files += qmldir
+
+target.path = $$[QT_INSTALL_QML]/CursorNavigation
+pluginfiles.path = $$[QT_INSTALL_QML]/CursorNavigation
+
+INSTALLS += target pluginfiles
diff --git a/plugin/qmldir b/plugin/qmldir
new file mode 100644
index 0000000..02cff60
--- /dev/null
+++ b/plugin/qmldir
@@ -0,0 +1,2 @@
+module CursorNavigation
+plugin cursornavigationplugin
diff --git a/plugin/spatialnavigation4dir.cpp b/plugin/spatialnavigation4dir.cpp
new file mode 100644
index 0000000..fa1657e
--- /dev/null
+++ b/plugin/spatialnavigation4dir.cpp
@@ -0,0 +1,161 @@
+#include "spatialnavigation4dir.h"
+#include <QQuickItem>
+#include <QDebug>
+#include <algorithm>
+#include <functional>
+
+//we only compare distances to eachother so no need to calculate expensive
+//square roots. centerpoint comparison is just enough for now too
+float distanceSquared(const QRectF& item1, const QRectF& item2)
+{
+ QPointF p1=item1.center();
+ QPointF p2=item2.center();
+ float dx=p1.x()-p2.x();
+ float dy=p1.y()-p2.y();
+ return dx*dx+dy*dy;
+}
+
+SpatialNavigation4Dir::SpatialNavigation4Dir(ItemRegister *itemRegister)
+ :CursorNavigationAlgorithm (itemRegister)
+{
+
+}
+
+QQuickItem* SpatialNavigation4Dir::getNextCandidate(const QList<QQuickItem*> &candidates,
+ const QQuickItem *currentItem,
+ const CursorNavigationCommand &cmd)
+{
+ if (candidates.isEmpty())
+ return nullptr;
+
+ qDebug() << "spatial chooser called, no of candidates=" << candidates.count();
+
+ if (!currentItem && candidates.size()) {
+ qDebug() << "the spatial chooser falling back to first child" << candidates.first();
+ return candidates.first();
+ }
+
+ //picking the next item according to the current items location and the command:
+ //-check direction
+ //-choose candidates in that general direction currentItem our current item (up, down, left or right)
+ //-currentItem those pick ones inside of current items projection
+ // -currentItem those within the projection pick the closest one
+ //-if no hits within the projection, then take the closest with distance just in the general direction
+ //this algorithm uses the scene coordinates of the items
+
+ std::function<bool(const QRectF&)> isInDirection;
+ std::function<bool(const QRectF&)> isInProjection;
+
+ //scene coords of the current item
+ const QRectF currentItemSceneRect = currentItem->mapRectToScene(QRectF( 0, 0,
+ currentItem->width(), currentItem->height() ));
+
+ //NOTICE: overlapping candidates will be ignored for now (TODO, this needs to be changed)
+
+ if (cmd.angleIsBetween(315, 45) || cmd.angleIsBetween(135, 225) ) {
+ //if (cmd == CursorNavigationCommand::Right || cmd == CursorNavigationCommand::Left) {
+
+ isInProjection = [&currentItemSceneRect](const QRectF &itemRect) {
+ return !( currentItemSceneRect.y() > itemRect.y()+itemRect.height() ||
+ currentItemSceneRect.y()+currentItemSceneRect.height() < itemRect.y() );
+ };
+ if (cmd.angleIsBetween(315, 45)) {
+ //if (cmd == Command_Right) {
+ isInDirection = [&currentItemSceneRect](const QRectF &itemRect) {
+ return currentItemSceneRect.x()+currentItemSceneRect.width() <= itemRect.x();
+ };
+ } else {
+ isInDirection = [&currentItemSceneRect](const QRectF &itemRect) {
+ return currentItemSceneRect.x() >= itemRect.x()+itemRect.width();
+ };
+ }
+
+ } else if (cmd.angleIsBetween(225, 315) || cmd.angleIsBetween(45, 135)) {
+ //} else if (cmd == Command_Up || cmd == Command_Down) {
+ isInProjection = [&currentItemSceneRect](const QRectF &itemRect) {
+ return !( currentItemSceneRect.x() > itemRect.x()+itemRect.width() ||
+ currentItemSceneRect.x()+currentItemSceneRect.width() < itemRect.x() );
+ };
+ if (cmd.angleIsBetween(45, 135)) {
+ //if (cmd == Command_Down) {
+ isInDirection = [&currentItemSceneRect](const QRectF &itemRect) {
+ return currentItemSceneRect.y()+currentItemSceneRect.height() <= itemRect.y();
+ };
+ } else {
+ isInDirection = [currentItemSceneRect](const QRectF &itemRect) {
+ return currentItemSceneRect.y() >= itemRect.y()+itemRect.height();
+ };
+ }
+ } else {
+ return nullptr;
+ }
+
+ std::pair<QQuickItem*,int> closest(nullptr,0);
+
+ //qDebug() << "current: x=" << currentItemSceneRect.x() << " y=" << currentItemSceneRect.y();
+
+ for (auto candidate : candidates)
+ {
+ if (!candidate->isVisible() || !candidate->isEnabled()) {
+ //qDebug() << "skipping a invisible/disabled item";
+ continue;
+ }
+
+ //scene coords of the candidate
+ QRectF candidateSceneRect = candidate->mapRectToScene(
+ QRect( 0, 0,
+ candidate->width(), candidate->height() ));
+
+ //qDebug() << "x=" << candidateSceneRect.x() << " y=" << candidateSceneRect.y();
+
+ if (isInDirection(candidateSceneRect) && isInProjection(candidateSceneRect))
+ {
+ //qDebug() << " is in direction and projection...";
+ int dist = distanceSquared(currentItemSceneRect,candidateSceneRect);
+ if (closest.second > dist || !closest.first)
+ {
+ closest.second = dist;
+ closest.first = candidate;
+ }
+ }
+ }
+
+ if (closest.first)
+ {
+ qDebug() << "chosen one: " << closest.first->mapRectToScene(
+ QRect( 0, 0,
+ closest.first->width(), closest.first->height() ));
+ }
+
+ if (!closest.first) {
+ //qDebug() << Q_FUNC_INFO << " looking for a candidate in the general direction...";
+
+ for (auto candidate : candidates)
+ {
+ if (!candidate->isVisible() || !candidate->isEnabled()) {
+ //qDebug() << "skipping a invisible/disabled item";
+ continue;
+ }
+
+ //scene coords of the candidate
+ QRectF candidateSceneRect = candidate->mapRectToScene(
+ QRect( 0, 0,
+ candidate->width(), candidate->height() ));
+
+ if (isInDirection(candidateSceneRect))
+ {
+ int dist = distanceSquared(currentItemSceneRect,candidateSceneRect);
+ if (closest.second > dist || !closest.first)
+ {
+ closest.second = dist;
+ closest.first = candidate;
+ }
+ }
+ }
+ }
+
+ qDebug() << Q_FUNC_INFO << " selected candidate:" << closest.first;
+
+ return closest.first;
+
+}
diff --git a/plugin/spatialnavigation4dir.h b/plugin/spatialnavigation4dir.h
new file mode 100644
index 0000000..a91c505
--- /dev/null
+++ b/plugin/spatialnavigation4dir.h
@@ -0,0 +1,17 @@
+#ifndef SPATIALNAVIGATION_H
+#define SPATIALNAVIGATION_H
+
+#include "cursornavigationalgorithm.h"
+
+class SpatialNavigation4Dir : public CursorNavigationAlgorithm
+{
+public:
+ SpatialNavigation4Dir(ItemRegister *itemRegister);
+
+ virtual QQuickItem* getNextCandidate(const QList<QQuickItem*> &candidates,
+ const QQuickItem *currentItem,
+ const CursorNavigationCommand &cmd);
+
+};
+
+#endif // SPATIALNAVIGATION_H