From c9a97de34ca56d4cab671beeeb6d27a66ac3da4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antti=20H=C3=B6ltt=C3=A4?= Date: Tue, 13 Nov 2018 15:40:13 +0100 Subject: Add 360 algorithm Add algorithm that allows navigation to any direction. Works somewhat well now. Example app has a page with a gamepad/stick support. --- plugin/cursornavigation.cpp | 121 +++++++++++++-------- plugin/cursornavigation.h | 15 ++- plugin/cursornavigationattached.cpp | 36 +++---- plugin/cursornavigationattached.h | 3 +- plugin/inputadapter.cpp | 18 ++-- plugin/inputtypes.cpp | 33 ++++-- plugin/inputtypes.h | 37 ++++--- plugin/plugin.pro | 6 +- plugin/spatialnavigation360.cpp | 210 ++++++++++++++++++++++++++++++++++++ plugin/spatialnavigation360.h | 29 +++++ plugin/spatialnavigation4dir.cpp | 17 ++- 11 files changed, 415 insertions(+), 110 deletions(-) create mode 100644 plugin/spatialnavigation360.cpp create mode 100644 plugin/spatialnavigation360.h (limited to 'plugin') diff --git a/plugin/cursornavigation.cpp b/plugin/cursornavigation.cpp index 0011cb2..4eaff64 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"; @@ -15,13 +16,71 @@ CursorNavigation::CursorNavigation(QQuickWindow *parent) { m_rootItem->setParent(m_window->contentItem()); - m_algorithms.push_back(new SpatialNavigation4Dir()); + //m_algorithms.push_back(new SpatialNavigation4Dir()); connect(m_window, &QQuickWindow::activeFocusItemChanged, this, &CursorNavigation::onActiveFocusItemChanged); onActiveFocusItemChanged(); } -bool CursorNavigation::inputCommand(const CursorNavigationCommand &cmd) +void CursorNavigation::move(qreal angle, qreal tolerance, bool discrete) +{ + qreal a = qDegreesToRadians(angle); + qreal t = qDegreesToRadians(qFabs(std::fmod(tolerance, 180))); + qWarning() << "move, angle = " << a << " tolerance = " << t << " discrete = " << discrete; + CursorNavigationCommand cmd(a, t); + handleMove(cmd, discrete); +} + +void CursorNavigation::move(const QVector2D& vector, qreal tolerance, bool discrete) +{ + qreal angle = qAtan2(vector.y(), vector.x()); + qreal t = qDegreesToRadians(qFabs(std::fmod(tolerance, 180))); + qWarning() << "move(vector2d), angle = " << angle << " tolerance = " << t << " discrete = " << discrete; + CursorNavigationCommand cmd(angle, tolerance); + handleMove(cmd, discrete); +} + +void CursorNavigation::action(Action action) +{ + qWarning() << "handleActionCommand, action= " << action; + switch (action) { + case Forward: + break; + case Back: + break; + case Activate: + break; + case Escape: { + /* if item has escapeTrgate defined, set focus to that. otherwise leave + * scope, ie. go back to parent's parent in the hierarchy and set focus + * (back) to it (setting the focus further to one of its children + * depends on the focus mechanism). + * if we are already at the root item's children, nothing happens + */ + if (!m_currentItem) + break; + + QQuickItem *escapeTarget = m_currentItem->m_parentNavigable->escapeTarget(); + if (!escapeTarget) { + if (m_currentItem->m_parentNavigable == m_rootItem) { + break; + } + escapeTarget = m_currentItem->m_parentNavigable->m_parentNavigable->item(); + } + qWarning() << "escaping, target = " << escapeTarget; + setCursorOnItem(nullptr); + escapeTarget->forceActiveFocus(); + onActiveFocusItemChanged(); + //escapeTarget->setFocus(true); + break; + } + + default: + break; + } +} + +/*bool CursorNavigation::inputCommand(const CursorNavigationCommand &cmd) { if (cmd.action == CursorNavigationCommand::NoAction) { @@ -29,7 +88,7 @@ bool CursorNavigation::inputCommand(const CursorNavigationCommand &cmd) } else { return handleActionCommand(cmd); } -} +}*/ CursorNavigationAttached *CursorNavigation::qmlAttachedProperties(QObject *object) { @@ -139,7 +198,7 @@ void CursorNavigation::unregisterItem(CursorNavigationAttached* item) item->m_parentNavigable->m_children.removeOne(item); } -bool CursorNavigation::handleDirectionCommand(const CursorNavigationCommand &cmd) +bool CursorNavigation::handleMove(const CursorNavigationCommand &cmd, bool discrete) { qWarning() << "handleDirectionCommand"; CursorNavigationAttached *nextItem = nullptr; @@ -148,54 +207,24 @@ bool CursorNavigation::handleDirectionCommand(const CursorNavigationCommand &cmd m_currentItem->m_parentNavigable->m_children : m_rootItem->m_children; - for (auto alg : m_algorithms) { + if (discrete) { + nextItem = m_navigation4Dir.getNextCandidate(candidates, m_currentItem, cmd); + } else { + nextItem = m_navigation360.getNextCandidate(candidates, m_currentItem, cmd); + } + + if (nextItem) { + setCursorOnItem(nextItem); + } + +/* for (auto alg : m_algorithms) { nextItem = alg->getNextCandidate(candidates, m_currentItem, cmd); if (nextItem) { setCursorOnItem(nextItem); break; } - } + }*/ return true; } -bool CursorNavigation::handleActionCommand(const CursorNavigationCommand &cmd) -{ - qWarning() << "handleActionCommand, cmd.action= " << cmd.action; - switch (cmd.action) { - case CursorNavigationCommand::Forward: - break; - case CursorNavigationCommand::Back: - break; - case CursorNavigationCommand::Activate: - break; - case CursorNavigationCommand::Escape: { - /* if item has escapeTrgate defined, set focus to that. otherwise leave - * scope, ie. go back to parent's parent in the hierarchy and set focus - * (back) to it (setting the focus further to one of its children - * depends on the focus mechanism). - * if we are already at the root item's children, nothing happens - */ - if (!m_currentItem) - break; - - QQuickItem *escapeTarget = m_currentItem->m_parentNavigable->escapeTarget(); - if (!escapeTarget) { - if (m_currentItem->m_parentNavigable == m_rootItem) { - break; - } - escapeTarget = m_currentItem->m_parentNavigable->m_parentNavigable->item(); - } - qWarning() << "escaping, target = " << escapeTarget; - setCursorOnItem(nullptr); - escapeTarget->forceActiveFocus(); - onActiveFocusItemChanged(); - //escapeTarget->setFocus(true); - break; - } - - default: - break; - } -} - diff --git a/plugin/cursornavigation.h b/plugin/cursornavigation.h index 96c5e67..956d900 100644 --- a/plugin/cursornavigation.h +++ b/plugin/cursornavigation.h @@ -4,13 +4,14 @@ #include "cursornavigationattached.h" #include "inputtypes.h" #include "inputadapter.h" +#include "spatialnavigation360.h" +#include "spatialnavigation4dir.h" #include #include #include class QQuickItem; -class CursorNavigationAlgorithm; class CursorNavigation : public QObject { @@ -19,7 +20,10 @@ class CursorNavigation : public QObject public: CursorNavigation(QQuickWindow *parent); - bool inputCommand(const CursorNavigationCommand &cmd); + //bool inputCommand(const CursorNavigationCommand &cmd); + void move(qreal angle, qreal tolerance, bool discrete); + void move(const QVector2D& vector, qreal tolerance, bool discrete); + void action(Action action); static CursorNavigationAttached *qmlAttachedProperties(QObject *object); @@ -33,15 +37,16 @@ private: void registerItem(CursorNavigationAttached* item); void unregisterItem(CursorNavigationAttached* item); - bool handleDirectionCommand(const CursorNavigationCommand &cmd); - bool handleActionCommand(const CursorNavigationCommand &cmd); + bool handleMove(const CursorNavigationCommand &cmd, bool discrete); private: static const char windowPropertyName[]; QQuickWindow *m_window; InputAdapter m_inputAdapter; CursorNavigationAttached *m_currentItem; //item that currently has the cursor - QList m_algorithms; + //QList m_algorithms; + SpatialNavigation360 m_navigation360; + SpatialNavigation4Dir m_navigation4Dir; //a root item that is not tied to any actual QQuickItem CursorNavigationAttached *m_rootItem; //QStack m_scopeStack; diff --git a/plugin/cursornavigationattached.cpp b/plugin/cursornavigationattached.cpp index c015cd3..0f7afdc 100644 --- a/plugin/cursornavigationattached.cpp +++ b/plugin/cursornavigationattached.cpp @@ -68,54 +68,56 @@ void CursorNavigationAttached::setEscapeTarget(QQuickItem *escapeTarget) emit escapeTargetChanged(m_escapeTarget); } -void CursorNavigationAttached::move(int angle, float magnitude) +void CursorNavigationAttached::move(qreal angle, qreal tolerance) { - CursorNavigationCommand cmd(magnitude, angle); - m_cursorNavigation->inputCommand(cmd); + qWarning() << "move"; + m_cursorNavigation->move(angle, tolerance, false); +} + +void CursorNavigationAttached::move(QVector2D vector, qreal tolerance) +{ + qWarning() << "move"; + m_cursorNavigation->move(vector, tolerance, false); } void CursorNavigationAttached::moveUp() { - m_cursorNavigation->inputCommand(CursorNavigationCommand::Up); + m_cursorNavigation->move(-90, 0, true); } void CursorNavigationAttached::moveDown() { - m_cursorNavigation->inputCommand(CursorNavigationCommand::Down); + m_cursorNavigation->move(90, 0, true); } void CursorNavigationAttached::moveRight() { - m_cursorNavigation->inputCommand(CursorNavigationCommand::Right); + m_cursorNavigation->move(0, 0, true); } void CursorNavigationAttached::moveLeft() { - m_cursorNavigation->inputCommand(CursorNavigationCommand::Left); + m_cursorNavigation->move(180, 0, true); } void CursorNavigationAttached::activate() { - CursorNavigationCommand cmd(CursorNavigationCommand::Activate); - m_cursorNavigation->inputCommand(cmd); + m_cursorNavigation->action(Activate); } void CursorNavigationAttached::forward() { - CursorNavigationCommand cmd(CursorNavigationCommand::Forward); - m_cursorNavigation->inputCommand(cmd); + m_cursorNavigation->action(Forward); } void CursorNavigationAttached::back() { - CursorNavigationCommand cmd(CursorNavigationCommand::Back); - m_cursorNavigation->inputCommand(cmd); + m_cursorNavigation->action(Back); } void CursorNavigationAttached::escape() { - CursorNavigationCommand cmd(CursorNavigationCommand::Escape); - m_cursorNavigation->inputCommand(cmd); + m_cursorNavigation->action(Escape); } void CursorNavigationAttached::onWindowChanged(QQuickWindow *window) @@ -155,7 +157,3 @@ void CursorNavigationAttached::setHasCursor(bool hasCursor) } } -/*QList &CursorNavigationAttached::siblings() -{ - return m_parentNavigable->m_children; -}*/ diff --git a/plugin/cursornavigationattached.h b/plugin/cursornavigationattached.h index 344b4fa..ee32f76 100644 --- a/plugin/cursornavigationattached.h +++ b/plugin/cursornavigationattached.h @@ -40,7 +40,8 @@ public slots: void setTrapsCursor(bool trapsCursor); void setEscapeTarget(QQuickItem * escapeTarget); - void move(int angle, float magnitude); + void move(qreal angle, qreal tolerance = 0); + void move(QVector2D vector, qreal tolerance = 0); void moveUp(); void moveDown(); void moveRight(); diff --git a/plugin/inputadapter.cpp b/plugin/inputadapter.cpp index 99eeb4f..89fa1a8 100644 --- a/plugin/inputadapter.cpp +++ b/plugin/inputadapter.cpp @@ -38,36 +38,36 @@ bool InputAdapter::handleKeyEvent(QKeyEvent *event) switch (event->key()) { case Qt::Key_Left: - cmd = CursorNavigationCommand::Left; + m_cursorNavigation->move(180, 0, true); break; case Qt::Key_Right: - cmd = CursorNavigationCommand::Right; + m_cursorNavigation->move(0, 0, true); break; case Qt::Key_Up: - cmd = CursorNavigationCommand::Up; + m_cursorNavigation->move(270, 0, true); break; case Qt::Key_Down: - cmd = CursorNavigationCommand::Down; + m_cursorNavigation->move(90, 0, true); break; case Qt::Key_Return: case Qt::Key_Enter: - cmd.action = CursorNavigationCommand::Activate; + m_cursorNavigation->action(Activate); break; case Qt::BackButton: case Qt::Key_Escape: - cmd.action = CursorNavigationCommand::Escape; + m_cursorNavigation->action(Escape); break; case Qt::Key_Tab: - cmd.action = CursorNavigationCommand::Forward; + m_cursorNavigation->action(Forward); break; case Qt::Key_Backtab: - cmd.action = CursorNavigationCommand::Back; + m_cursorNavigation->action(Back); break; default: return false; } - return m_cursorNavigation->inputCommand(cmd); + return true; } bool InputAdapter::handleMouseEvent(QMouseEvent *event) diff --git a/plugin/inputtypes.cpp b/plugin/inputtypes.cpp index 344254b..a1b2f8d 100644 --- a/plugin/inputtypes.cpp +++ b/plugin/inputtypes.cpp @@ -1,27 +1,42 @@ #include "inputtypes.h" +#include -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); +const CursorNavigationCommand CursorNavigationCommand::Up(-M_PI_2, 0); +const CursorNavigationCommand CursorNavigationCommand::Down(M_PI_2, 0); +const CursorNavigationCommand CursorNavigationCommand::Left(M_PI, 0); +const CursorNavigationCommand CursorNavigationCommand::Right(0, 0); CursorNavigationCommand::CursorNavigationCommand() - :magnitude(-1), angle(-1), action(NoAction) + :angle(-1), angleTolerance(-1), action(NoAction) {} -CursorNavigationCommand::CursorNavigationCommand(float magnitude, int angle) - :magnitude(magnitude), angle(angle), action(NoAction) +CursorNavigationCommand::CursorNavigationCommand(qreal a, qreal tolerance) + :angle(CursorNavigationCommand::fitAngle(a)), angleTolerance(tolerance), action(NoAction) {} CursorNavigationCommand::CursorNavigationCommand(Action a) - :magnitude(-1), angle(-1), action(a) + :angle(-1), angleTolerance(-1), action(a) {} //test if this commands angle is between given angles. clockwise from begin to end -bool CursorNavigationCommand::angleIsBetween(int begin, int end) const +//bool CursorNavigationCommand::angleIsBetween(qreal begin, qreal end) +//{ +// return CursorNavigationCommand::angleIsBetween(this->angle, begin, end); +//} + +bool CursorNavigationCommand::angleIsBetween(qreal angle, qreal begin, qreal end) { if (begin > end) return angle >= begin || angle <= end; else return angle >= begin && angle <= end; } + +qreal CursorNavigationCommand::fitAngle(qreal angle) +{ + if (angle > M_PI) + return -M_PI + std::fmod(angle ,M_PI); + else if (angle < -M_PI) + return M_PI + std::fmod(angle ,M_PI); + return angle; +} diff --git a/plugin/inputtypes.h b/plugin/inputtypes.h index c6cc532..9de40a4 100644 --- a/plugin/inputtypes.h +++ b/plugin/inputtypes.h @@ -1,31 +1,32 @@ #ifndef INPUTTYPES_H #define INPUTTYPES_H +#include + //TODO: make these into classes w accessors +enum Action +{ + NoAction, + Forward, //tab + Back, //ctrl-tab + Activate, //enter/click on item + Escape //leave scope +}; + //generic of way of describing the input that cursor management can handle struct CursorNavigationCommand { - enum Action - { - NoAction, - Forward, //tab - Back, //ctrl-tab - Activate, //enter/click on item - Escape //leave scope - }; - CursorNavigationCommand(); - CursorNavigationCommand(float magnitude, int angle); + CursorNavigationCommand(qreal angle, qreal tolerance); CursorNavigationCommand(Action a); - //test if this commands angle is between given angles. clockwise from begin to end - bool angleIsBetween(int begin, int end) const; + //bool angleIsBetween(qreal begin, qreal end); - float magnitude; //0.0 to 1.0 - int angle; //0 to 359 + qreal angle; //-pi to pi + qreal angleTolerance; //0 to pi Action action; static const CursorNavigationCommand Up; @@ -33,6 +34,12 @@ struct CursorNavigationCommand static const CursorNavigationCommand Left; static const CursorNavigationCommand Right; + //test if given angle is between given sector. sector defined clockwise from begin to end + static bool angleIsBetween(qreal angle, qreal begin, qreal end); + + //fit the given angle to be between -pi and pi + static qreal fitAngle(qreal angle); + }; /*feedback datatype returned for commands, describing command results @@ -59,7 +66,7 @@ class CursorNavigationFeedback { // Q_GADGET // Q_PROPERTY(CommandResult READ commandResult NOTIFY commandResultChanged ) -// Q_PROPERTY(CommandResult READ commandResult NOTIFY commandResultChanged ) +// Q_PROPERTY(Boundary READ boundary NOTIFY boundaryChanged ) //feedback cases; //-cmd succesfull diff --git a/plugin/plugin.pro b/plugin/plugin.pro index 6f1a4c3..4d7fed9 100644 --- a/plugin/plugin.pro +++ b/plugin/plugin.pro @@ -15,7 +15,8 @@ SOURCES += \ inputadapter.cpp \ cursornavigationalgorithm.cpp \ spatialnavigation4dir.cpp \ - inputtypes.cpp + inputtypes.cpp \ + spatialnavigation360.cpp HEADERS += \ plugin.h \ @@ -24,7 +25,8 @@ HEADERS += \ inputadapter.h \ inputtypes.h \ cursornavigationalgorithm.h \ - spatialnavigation4dir.h + spatialnavigation4dir.h \ + spatialnavigation360.h pluginfiles.files += qmldir diff --git a/plugin/spatialnavigation360.cpp b/plugin/spatialnavigation360.cpp new file mode 100644 index 0000000..9898b07 --- /dev/null +++ b/plugin/spatialnavigation360.cpp @@ -0,0 +1,210 @@ +#include "spatialnavigation360.h" +#include +#include "cursornavigationattached.h" +#include +#include + +SpatialNavigation360::SpatialNavigation360() +{ + +} + +//test if point is contained in at least one of the given quadrants +bool isPointIncluded(const std::vector &q, const QPointF &p, const QPointF &o) +{ + return (q[0] && (p.x()-o.x()) >= 0 && (p.y()-o.y()) >= 0) || + (q[1] && (p.x()-o.x()) >= 0 && (p.y()-o.y()) < 0) || + (q[2] && (p.x()-o.x()) < 0 && (p.y()-o.y()) < 0) || + (q[3] && (p.x()-o.x()) < 0 && (p.y()-o.y()) >= 0); +} + +//test if rect is contained in at least one of the given quadrants +bool isRectIncluded(const std::vector &q, const QRectF &rect, const QPointF &origin) +{ + return !rect.contains(origin) && + (isPointIncluded(q, rect.topLeft(), origin) || + isPointIncluded(q, rect.bottomRight(), origin)); +} + +//test if angle is between start and end angles +bool angleIsBetween(qreal angle, qreal begin, qreal end) +{ + if (begin > end) + return angle >= begin || angle <= end; + else + return angle >= begin && angle <= end; +} + +//test if 2 sectors overlap +bool sectorsOverlap(qreal begin1, qreal end1, + qreal begin2, qreal end2) +{ + return angleIsBetween(begin1, begin2, end2) || + angleIsBetween(end1, begin2, end2) || + angleIsBetween(begin2, begin1, end1) || + angleIsBetween(end2, begin1, end1); +} + +qreal pointDistance(const QPointF& p1, const QPointF& p2) +{ + qreal dx=p1.x()-p2.x(); + qreal dy=p1.y()-p2.y(); + return qSqrt(qPow(dx,2)+qPow(dy,2)); +} + +//minimum distance between 2 rects, calculated from their edges +qreal rectDistance(const QRectF& rect1, const QRectF& rect2) +{ + bool left = rect2.bottomRight().x() < rect1.topLeft().x(); + bool right = rect2.topLeft().x() > rect1.bottomRight().x(); + bool bottom = rect2.topLeft().y() > rect1.bottomRight().y(); + bool top = rect2.bottomRight().y() < rect1.topLeft().y(); + + if (top && left) + return pointDistance(rect1.topLeft(), rect2.bottomRight()); + else if (left && bottom) + return pointDistance(rect1.bottomLeft(), rect2.topRight()); + else if (bottom && right) + return pointDistance(rect1.bottomRight(), rect2.topLeft()); + else if (right && top) + return pointDistance(rect1.topRight(), rect2.bottomRight()); + else if (left) + return rect1.x() - rect2.bottomRight().x(); + else if (right) + return rect2.x() - rect1.bottomRight().x(); + else if (bottom) + return rect2.y() - rect1.bottomRight().y(); + else if (top) + return rect1.y() - rect2.bottomRight().y();//y2 - y1b + else //rectangles overlap + return 0; +} + +//get the widest arc that is less than 180 degrees, that this item covers, clockwise around the origin +std::pair getSector(const QRectF &rect, const QPointF &origin) +{ + std::pair angles; + angles = std::minmax({std::atan2(rect.topLeft().y()-origin.y(), rect.topLeft().x()-origin.x()), + std::atan2(rect.topRight().y()-origin.y(), rect.topRight().x()-origin.x()), + std::atan2(rect.bottomRight().y()-origin.y(), rect.bottomRight().x()-origin.x()), + std::atan2(rect.bottomLeft().y()-origin.y(), rect.bottomLeft().x()-origin.x()) + }); + + //if delta larger than 180, invert min and max + if (angles.second-angles.first > M_PI) { + qreal temp = angles.first; + angles.first = angles.second; + angles.second = temp; + } + + return angles; +} + +CursorNavigationAttached* SpatialNavigation360::getNextCandidate( + const QList &candidates, + const CursorNavigationAttached *currentItem, + const CursorNavigationCommand& cmd) +{ + /* -angle should be between -180 and 180 in relation to the x axis, clockwise around the origin + * -depending on the input angle, find min and max x and y values which items must have + * -calculate angles for items points that are within the limits. from those angles, pick min and max (widest sector the item covers) + * -items that overlap the current item center, should be ignored + * -items that are within seaqrch beams angle limits, are stored along the angle ranges they have + * -if there are more than 1 stored items, we select the item that cuts the exact selection vector. + * if no item overlaps the center, pick the one closest to the current item + * -remember to use current item's coord system as the reference!!! + */ + + qWarning() << "##### navigation360: start, angle = " << cmd.angle; + + if (candidates.isEmpty()) + return nullptr; + + if (!currentItem && candidates.size()) { + qDebug() << "the spatial chooser falling back to first child" << candidates.first(); + return candidates.first(); + } + + //booleans for quadrants + std::vector quadrants(4); + + //define selector beam sector +// int angle1_deg, angle2_deg; + qreal angle1, angle2; + + angle1 = CursorNavigationCommand::fitAngle(cmd.angle - cmd.angleTolerance); + angle2 = CursorNavigationCommand::fitAngle(cmd.angle + cmd.angleTolerance); + + +// angle1_deg = (cmd.angle - cmd.angleTolerance) % 360; +// if (angle1_deg < 0) +// angle1_deg = 360 + angle1_deg; +// angle2_deg = (cmd.angle + cmd.angleTolerance) % 360; + +// int a=angle2_deg-angle1_deg; +// while (a > 0) { +// quadrants[(angle1_deg/90+a/90) % 4] = true; +// a -= 90; +// } + + quadrants[0] = sectorsOverlap(angle1, angle2, 0, M_PI_2); + quadrants[1] = sectorsOverlap(angle1, angle2, M_PI_2, M_PI); + quadrants[2] = sectorsOverlap(angle1, angle2, -M_PI, -M_PI_2); + quadrants[3] = sectorsOverlap(angle1, angle2, -M_PI_2, 0); + + qWarning() << "navigation360: quadrants = " << quadrants; + + const QRectF currentItemSceneRect = currentItem->item()->mapRectToScene( + QRectF( 0, 0, currentItem->item()->width(), currentItem->item()->height() )); + + const QPointF origin = currentItemSceneRect.center(); + qWarning() << "origin = " << origin; + + //item that overlaps the center of the selector beam + CursorNavigationAttached* directHitItem = nullptr; + qreal directHitDistance = -1; + //item that overlaps selector beam does not overlap with the center + CursorNavigationAttached* withinToleranceItem = nullptr; + qreal withinToleranceDistance = -1; + + + for (auto iter: candidates) + { + const QRectF itemSceneRect = iter->item()->mapRectToScene( + QRectF( 0, 0, iter->item()->width(), iter->item()->height() )); + + if (iter == currentItem || !iter->item()->isVisible() || !iter->item()->isEnabled()) + continue; + + //if (isRectIncluded(quadrants, itemSceneRect, origin)) { + + std::pair sector = getSector(itemSceneRect, origin); + qWarning() << "item " << iter->item() << " rect = " << itemSceneRect << " sector " << sector; + + if (angleIsBetween(cmd.angle, sector.first, sector.second)) { + qWarning() << "is direct hit"; + qreal dist = rectDistance(itemSceneRect, currentItemSceneRect); + if (!directHitItem || dist < directHitDistance) { + directHitDistance = dist; + directHitItem = iter; + } + } else if (!directHitItem && sectorsOverlap(angle1, angle2, sector.first, sector.second)) { + qWarning() << "is within tolerances"; + qreal dist = rectDistance(itemSceneRect, currentItemSceneRect); + if (dist < withinToleranceDistance) { + withinToleranceDistance = dist; + withinToleranceItem = iter; + } + } + //} + } + + qWarning() << "##### end, directHit = " << + directHitItem << " withinTolerances = " << withinToleranceItem; + + if (directHitItem) + return directHitItem; + + return withinToleranceItem; + +} diff --git a/plugin/spatialnavigation360.h b/plugin/spatialnavigation360.h new file mode 100644 index 0000000..3b9cdb9 --- /dev/null +++ b/plugin/spatialnavigation360.h @@ -0,0 +1,29 @@ +#ifndef SPATIALNAVIGATION360_H +#define SPATIALNAVIGATION360_H + +#include "cursornavigationalgorithm.h" + +/* algorithm for stepless 360 cursor navigation + * + * idea: allow user customization by providing a set of variables to use for + * selecting a candidate. for these, the user may set tolerances and weights. + * tolerance affects the first step of finding candidates. weights are + * used when choosing between multiple candidates, that are fit within the tolerances + * + */ + +class SpatialNavigation360 : public CursorNavigationAlgorithm +{ +public: + SpatialNavigation360(); + + virtual CursorNavigationAttached* getNextCandidate( + const QList &candidates, + const CursorNavigationAttached *currentItem, + const CursorNavigationCommand& cmd); + +private: + +}; + +#endif // SPATIALNAVIGATION360_H diff --git a/plugin/spatialnavigation4dir.cpp b/plugin/spatialnavigation4dir.cpp index a6965b0..0ccf041 100644 --- a/plugin/spatialnavigation4dir.cpp +++ b/plugin/spatialnavigation4dir.cpp @@ -4,6 +4,7 @@ #include #include #include +#include //we only compare distances to eachother so no need to calculate expensive //square roots. centerpoint comparison is just enough for now too @@ -53,14 +54,21 @@ CursorNavigationAttached* SpatialNavigation4Dir::getNextCandidate( //NOTICE: overlapping candidates will be ignored for now (TODO, this needs to be changed) - if (cmd.angleIsBetween(315, 45) || cmd.angleIsBetween(135, 225) ) { + qreal right_start = -M_PI_4; + qreal right_end = M_PI_4; + qreal left_start = M_PI-M_PI_4; + qreal left_end = -M_PI+M_PI_4; + + + if (CursorNavigationCommand::angleIsBetween(cmd.angle, right_start, right_end) || + CursorNavigationCommand::angleIsBetween(cmd.angle, left_start, left_end) ) { //if (cmd == CursorNavigationCommand::Right || cmd == CursorNavigationCommand::Left) { isInProjection = [¤tItemSceneRect](const QRectF &itemRect) { return !( currentItemSceneRect.y() > itemRect.y()+itemRect.height() || currentItemSceneRect.y()+currentItemSceneRect.height() < itemRect.y() ); }; - if (cmd.angleIsBetween(315, 45)) { + if (CursorNavigationCommand::angleIsBetween(cmd.angle, right_start, right_end)) { //if (cmd == Command_Right) { isInDirection = [¤tItemSceneRect](const QRectF &itemRect) { return currentItemSceneRect.x()+currentItemSceneRect.width() <= itemRect.x(); @@ -71,13 +79,14 @@ CursorNavigationAttached* SpatialNavigation4Dir::getNextCandidate( }; } - } else if (cmd.angleIsBetween(225, 315) || cmd.angleIsBetween(45, 135)) { + } else if (CursorNavigationCommand::angleIsBetween(cmd.angle, left_end, right_start) || + CursorNavigationCommand::angleIsBetween(cmd.angle, right_end, left_start)) { //} else if (cmd == Command_Up || cmd == Command_Down) { isInProjection = [¤tItemSceneRect](const QRectF &itemRect) { return !( currentItemSceneRect.x() > itemRect.x()+itemRect.width() || currentItemSceneRect.x()+currentItemSceneRect.width() < itemRect.x() ); }; - if (cmd.angleIsBetween(45, 135)) { + if (CursorNavigationCommand::angleIsBetween(cmd.angle, right_end, left_start)) { //if (cmd == Command_Down) { isInDirection = [¤tItemSceneRect](const QRectF &itemRect) { return currentItemSceneRect.y()+currentItemSceneRect.height() <= itemRect.y(); -- cgit v1.2.3