-
Notifications
You must be signed in to change notification settings - Fork 406
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'improve_navigation_cpp' into feature/android-tv
- Loading branch information
Showing
88 changed files
with
3,797 additions
and
3,227 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,324 @@ | ||
#include "focusController.h" | ||
|
||
#include "listViewFocusController.h" | ||
|
||
#include <QQuickWindow> | ||
#include <QQmlApplicationEngine> | ||
|
||
|
||
bool isListView(QObject* item) | ||
{ | ||
return item->inherits("QQuickListView"); | ||
} | ||
|
||
bool isOnTheScene(QObject* object) | ||
{ | ||
QQuickItem* item = qobject_cast<QQuickItem*>(object); | ||
if (!item) { | ||
qWarning() << "Couldn't recognize object as item"; | ||
return false; | ||
} | ||
|
||
if (!item->isVisible()) { | ||
// qDebug() << "===>> The item is not visible: " << item; | ||
return false; | ||
} | ||
|
||
QRectF itemRect = item->mapRectToScene(item->childrenRect()); | ||
|
||
QQuickWindow* window = item->window(); | ||
if (!window) { | ||
qWarning() << "Couldn't get the window on the Scene check"; | ||
return false; | ||
} | ||
|
||
const auto contentItem = window->contentItem(); | ||
if (!contentItem) { | ||
qWarning() << "Couldn't get the content item on the Scene check"; | ||
return false; | ||
} | ||
QRectF windowRect = contentItem->childrenRect(); | ||
const auto res = (windowRect.contains(itemRect) || isListView(item)); | ||
// qDebug() << (res ? "===>> item is inside the Scene" : "===>> ITEM IS OUTSIDE THE SCENE") << " itemRect: " << itemRect << "; windowRect: " << windowRect; | ||
return res; | ||
} | ||
|
||
QList<QObject*> getSubChain(QObject* object) | ||
{ | ||
QList<QObject*> res; | ||
if (!object) { | ||
qDebug() << "The object is NULL"; | ||
return res; | ||
} | ||
|
||
const auto children = object->children(); | ||
|
||
for(const auto child : children) { | ||
if (child | ||
&& isFocusable(child) | ||
&& isOnTheScene(child) | ||
&& isEnabled(child) | ||
) { | ||
res.append(child); | ||
} else { | ||
res.append(getSubChain(child)); | ||
} | ||
} | ||
return res; | ||
} | ||
|
||
FocusController::FocusController(QQmlApplicationEngine* engine, QObject *parent) | ||
: QObject{parent} | ||
, m_engine{engine} | ||
, m_focusChain{} | ||
, m_focusedItem{nullptr} | ||
, m_rootObjects{} | ||
, m_defaultFocusItem{QSharedPointer<QQuickItem>()} | ||
, m_lvfc{nullptr} | ||
{ | ||
QObject::connect(m_engine.get(), &QQmlApplicationEngine::objectCreated, this, [this](QObject *object, const QUrl &url){ | ||
QQuickItem* newDefaultFocusItem = object->findChild<QQuickItem*>("defaultFocusItem"); | ||
if(newDefaultFocusItem && m_defaultFocusItem != newDefaultFocusItem) { | ||
m_defaultFocusItem.reset(newDefaultFocusItem); | ||
qDebug() << "===>> NEW DEFAULT FOCUS ITEM: " << m_defaultFocusItem; | ||
} | ||
}); | ||
|
||
QObject::connect(this, &FocusController::focusedItemChanged, this, [this]() { | ||
m_focusedItem->forceActiveFocus(Qt::TabFocusReason); | ||
}); | ||
} | ||
|
||
|
||
void FocusController::nextKeyTabItem() | ||
{ | ||
nextItem(Direction::Forward); | ||
} | ||
|
||
void FocusController::previousKeyTabItem() | ||
{ | ||
nextItem(Direction::Backward); | ||
} | ||
|
||
void FocusController::nextKeyUpItem() | ||
{ | ||
nextItem(Direction::Backward); | ||
} | ||
|
||
void FocusController::nextKeyDownItem() | ||
{ | ||
nextItem(Direction::Forward); | ||
} | ||
|
||
void FocusController::nextKeyLeftItem() | ||
{ | ||
nextItem(Direction::Backward); | ||
} | ||
|
||
void FocusController::nextKeyRightItem() | ||
{ | ||
nextItem(Direction::Forward); | ||
} | ||
|
||
void FocusController::setFocusItem(QQuickItem* item) | ||
{ | ||
if (m_focusedItem != item) { | ||
m_focusedItem = item; | ||
emit focusedItemChanged(); | ||
qDebug() << "===>> FocusItem is changed to " << item << "!"; | ||
} else { | ||
qDebug() << "===>> FocusItem is is the same: " << item << "!"; | ||
} | ||
} | ||
|
||
void FocusController::setFocusOnDefaultItem() | ||
{ | ||
qDebug() << "===>> Setting focus on DEFAULT FOCUS ITEM..."; | ||
setFocusItem(m_defaultFocusItem.get()); | ||
} | ||
|
||
void FocusController::pushRootObject(QObject* object) | ||
{ | ||
qDebug() << "===>> Calling < pushRootObject >..."; | ||
m_rootObjects.push(object); | ||
dropListView(); | ||
// setFocusOnDefaultItem(); | ||
qDebug() << "===>> ROOT OBJECT is changed to: " << m_rootObjects.top(); | ||
qDebug() << "===>> ROOT OBJECTS: " << m_rootObjects; | ||
} | ||
|
||
void FocusController::dropRootObject(QObject* object) | ||
{ | ||
qDebug() << "===>> Calling < dropRootObject >..."; | ||
if (m_rootObjects.empty()) { | ||
qDebug() << "ROOT OBJECT is already DEFAULT"; | ||
|
||
return; | ||
} | ||
|
||
if (m_rootObjects.top() == object) { | ||
m_rootObjects.pop(); | ||
dropListView(); | ||
setFocusOnDefaultItem(); | ||
if(m_rootObjects.size()) { | ||
qDebug() << "===>> ROOT OBJECT is changed to: " << m_rootObjects.top(); | ||
} else { | ||
qDebug() << "===>> ROOT OBJECT is changed to DEFAULT"; | ||
} | ||
} else { | ||
qWarning() << "===>> TRY TO DROP WRONG ROOT OBJECT: " << m_rootObjects.top() << " SHOULD BE: " << object; | ||
} | ||
} | ||
|
||
void FocusController::resetRootObject() | ||
{ | ||
qDebug() << "===>> Calling < resetRootObject >..."; | ||
m_rootObjects.clear(); | ||
qDebug() << "===>> ROOT OBJECT IS RESETED"; | ||
} | ||
|
||
void FocusController::reload(Direction direction) | ||
{ | ||
qDebug() << "===>> Calling < reload >..."; | ||
m_focusChain.clear(); | ||
|
||
QObject* rootObject = (m_rootObjects.empty() | ||
? m_engine->rootObjects().value(0) | ||
: m_rootObjects.top()); | ||
|
||
if(!rootObject) { | ||
qCritical() << "No ROOT OBJECT found!"; | ||
resetRootObject(); | ||
dropListView(); | ||
return; | ||
} | ||
|
||
qDebug() << "===>> ROOT OBJECTS: " << rootObject; | ||
|
||
m_focusChain.append(getSubChain(rootObject)); | ||
|
||
std::sort(m_focusChain.begin(), m_focusChain.end(), direction == Direction::Forward ? isLess : isMore); | ||
|
||
if (m_focusChain.empty()) { | ||
qWarning() << "Focus chain is empty!"; | ||
resetRootObject(); | ||
dropListView(); | ||
return; | ||
} | ||
} | ||
|
||
void FocusController::nextItem(Direction direction) | ||
{ | ||
qDebug() << "===>> Calling < nextItem >..."; | ||
|
||
reload(direction); | ||
|
||
if (m_lvfc && isListView(m_focusedItem)) { | ||
direction == Direction::Forward ? focusNextListViewItem() : focusPreviousListViewItem(); | ||
qDebug() << "===>> Handling the [ ListView ]..."; | ||
|
||
return; | ||
} | ||
|
||
if(m_focusChain.empty()) { | ||
qWarning() << "There are no items to navigate"; | ||
setFocusOnDefaultItem(); | ||
return; | ||
} | ||
|
||
auto focusedItemIndex = m_focusChain.indexOf(m_focusedItem); | ||
|
||
if (focusedItemIndex == -1) { | ||
qDebug() << "Current FocusItem is not in chain, switch to first in chain..."; | ||
focusedItemIndex = 0; | ||
} else if (focusedItemIndex == (m_focusChain.size() - 1)) { | ||
qDebug() << "Last focus index. Starting from the beginning..."; | ||
focusedItemIndex = 0; | ||
} else { | ||
qDebug() << "Incrementing focus index..."; | ||
focusedItemIndex++; | ||
} | ||
|
||
const auto focusedItem = qobject_cast<QQuickItem*>(m_focusChain.at(focusedItemIndex)); | ||
|
||
if(focusedItem == nullptr) { | ||
qWarning() << "Failed to get item to focus on. Setting focus on default"; | ||
setFocusOnDefaultItem(); | ||
return; | ||
} | ||
|
||
if(isListView(focusedItem)) { | ||
qDebug() << "===>> Found [ListView]"; | ||
m_lvfc = new ListViewFocusController(focusedItem, this); | ||
m_focusedItem = focusedItem; | ||
if(direction == Direction::Forward) { | ||
m_lvfc->nextDelegate(); | ||
focusNextListViewItem(); | ||
} else { | ||
m_lvfc->previousDelegate(); | ||
focusPreviousListViewItem(); | ||
} | ||
return; | ||
} | ||
|
||
setFocusItem(focusedItem); | ||
|
||
printItems(m_focusChain, focusedItem); | ||
|
||
/////////////////////////////////////////////////////////// | ||
|
||
const auto w = m_defaultFocusItem->window(); | ||
|
||
qDebug() << "===>> CURRENT ACTIVE ITEM: " << w->activeFocusItem(); | ||
qDebug() << "===>> CURRENT FOCUS OBJECT: " << w->focusObject(); | ||
if(m_rootObjects.empty()) { | ||
qDebug() << "===>> ROOT OBJECT IS DEFAULT"; | ||
} else { | ||
qDebug() << "===>> ROOT OBJECT: " << m_rootObjects.top(); | ||
} | ||
} | ||
|
||
void FocusController::focusNextListViewItem() | ||
{ | ||
qDebug() << "===>> Calling < focusNextListViewItem >..."; | ||
|
||
if (m_lvfc->isLastFocusItemInListView() || m_lvfc->isReturnNeeded()) { | ||
qDebug() << "===>> Last item in [ ListView ] was reached. Going to the NEXT element after [ ListView ]"; | ||
dropListView(); | ||
nextItem(Direction::Forward); | ||
return; | ||
} else if (m_lvfc->isLastFocusItemInDelegate()) { | ||
qDebug() << "===>> End of delegate elements was reached. Going to the next delegate"; | ||
m_lvfc->resetFocusChain(); | ||
m_lvfc->nextDelegate(); | ||
} | ||
|
||
m_lvfc->focusNextItem(); | ||
} | ||
|
||
void FocusController::focusPreviousListViewItem() | ||
{ | ||
qDebug() << "===>> Calling < focusPreviousListViewItem >..."; | ||
|
||
if (m_lvfc->isFirstFocusItemInListView() || m_lvfc->isReturnNeeded()) { | ||
qDebug() << "===>> First item in [ ListView ] was reached. Going to the PREVIOUS element after [ ListView ]"; | ||
dropListView(); | ||
nextItem(Direction::Backward); | ||
return; | ||
} else if (m_lvfc->isFirstFocusItemInDelegate()) { | ||
m_lvfc->resetFocusChain(); | ||
m_lvfc->previousDelegate(); | ||
} | ||
|
||
m_lvfc->focusPreviousItem(); | ||
} | ||
|
||
void FocusController::dropListView() | ||
{ | ||
qDebug() << "===>> Calling < dropListView >..."; | ||
|
||
if(m_lvfc) { | ||
delete m_lvfc; | ||
m_lvfc = nullptr; | ||
} | ||
} |
Oops, something went wrong.