Merge branch 'improve_navigation_cpp' into feature/android-tv
This commit is contained in:
commit
c72d76aec7
88 changed files with 3939 additions and 3369 deletions
324
client/ui/controllers/focusController.cpp
Normal file
324
client/ui/controllers/focusController.cpp
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
62
client/ui/controllers/focusController.h
Normal file
62
client/ui/controllers/focusController.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#ifndef FOCUSCONTROLLER_H
|
||||
#define FOCUSCONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QStack>
|
||||
#include <QSharedPointer>
|
||||
|
||||
|
||||
class QQuickItem;
|
||||
class QQmlApplicationEngine;
|
||||
class ListViewFocusController;
|
||||
|
||||
/*!
|
||||
* \brief The FocusController class makes focus control more straightforward
|
||||
* \details Focus is handled only for visible and enabled items which have
|
||||
* `isFocused` property from top left to bottom right.
|
||||
* \note There are items handled differently (e.g. ListView)
|
||||
*/
|
||||
class FocusController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FocusController(QQmlApplicationEngine* engine, QObject *parent = nullptr);
|
||||
~FocusController() override = default;
|
||||
|
||||
Q_INVOKABLE void nextKeyTabItem();
|
||||
Q_INVOKABLE void previousKeyTabItem();
|
||||
Q_INVOKABLE void nextKeyUpItem();
|
||||
Q_INVOKABLE void nextKeyDownItem();
|
||||
Q_INVOKABLE void nextKeyLeftItem();
|
||||
Q_INVOKABLE void nextKeyRightItem();
|
||||
Q_INVOKABLE void setFocusItem(QQuickItem* item);
|
||||
Q_INVOKABLE void setFocusOnDefaultItem();
|
||||
Q_INVOKABLE void pushRootObject(QObject* object);
|
||||
Q_INVOKABLE void dropRootObject(QObject* object);
|
||||
Q_INVOKABLE void resetRootObject();
|
||||
|
||||
private:
|
||||
enum class Direction {
|
||||
Forward,
|
||||
Backward,
|
||||
};
|
||||
|
||||
void reload(Direction direction);
|
||||
void nextItem(Direction direction);
|
||||
void focusNextListViewItem();
|
||||
void focusPreviousListViewItem();
|
||||
void dropListView();
|
||||
|
||||
QSharedPointer<QQmlApplicationEngine> m_engine; // Pointer to engine to get root object
|
||||
QList<QObject*> m_focusChain; // List of current objects to be focused
|
||||
QQuickItem* m_focusedItem; // Pointer to the active focus item
|
||||
QStack<QObject*> m_rootObjects;
|
||||
QSharedPointer<QQuickItem> m_defaultFocusItem;
|
||||
|
||||
ListViewFocusController* m_lvfc; // ListView focus manager
|
||||
|
||||
signals:
|
||||
void focusedItemChanged();
|
||||
};
|
||||
|
||||
#endif // FOCUSCONTROLLER_H
|
||||
393
client/ui/controllers/listViewFocusController.cpp
Normal file
393
client/ui/controllers/listViewFocusController.cpp
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
#include "listViewFocusController.h"
|
||||
|
||||
#include <QQuickItem>
|
||||
#include <QQueue>
|
||||
#include <QPointF>
|
||||
#include <QRectF>
|
||||
#include <QQuickWindow>
|
||||
|
||||
|
||||
bool isVisible(QObject* item)
|
||||
{
|
||||
const auto res = item->property("visible").toBool();
|
||||
return res;
|
||||
}
|
||||
|
||||
bool isFocusable(QObject* item)
|
||||
{
|
||||
const auto res = item->property("isFocusable").toBool();
|
||||
return res;
|
||||
}
|
||||
|
||||
QPointF getItemCenterPointOnScene(QQuickItem* item)
|
||||
{
|
||||
const auto x0 = item->x() + (item->width() / 2);
|
||||
const auto y0 = item->y() + (item->height() / 2);
|
||||
return item->parentItem()->mapToScene(QPointF{x0, y0});
|
||||
}
|
||||
|
||||
bool isLess(QObject* item1, QObject* item2)
|
||||
{
|
||||
const auto p1 = getItemCenterPointOnScene(qobject_cast<QQuickItem*>(item1));
|
||||
const auto p2 = getItemCenterPointOnScene(qobject_cast<QQuickItem*>(item2));
|
||||
return (p1.y() == p2.y()) ? (p1.x() < p2.x()) : (p1.y() < p2.y());
|
||||
}
|
||||
|
||||
bool isMore(QObject* item1, QObject* item2)
|
||||
{
|
||||
return !isLess(item1, item2);
|
||||
}
|
||||
|
||||
bool isEnabled(QObject* obj)
|
||||
{
|
||||
const auto item = qobject_cast<QQuickItem*>(obj);
|
||||
return item && item->isEnabled();
|
||||
}
|
||||
|
||||
QList<QObject*> getItemsChain(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)
|
||||
&& isEnabled(child)
|
||||
&& isVisible(child)
|
||||
) {
|
||||
res.append(child);
|
||||
} else {
|
||||
res.append(getItemsChain(child));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void printItems(const QList<QObject*>& items, QObject* current_item)
|
||||
{
|
||||
for(const auto& item : items) {
|
||||
QQuickItem* i = qobject_cast<QQuickItem*>(item);
|
||||
QPointF coords {getItemCenterPointOnScene(i)};
|
||||
QString prefix = current_item == i ? "==>" : " ";
|
||||
qDebug() << prefix << " Item: " << i << " with coords: " << coords;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ListViewFocusController::ListViewFocusController(QQuickItem* listView, QObject* parent)
|
||||
: QObject{parent}
|
||||
, m_listView{listView}
|
||||
, m_focusChain{}
|
||||
, m_currentSection{Section::Default}
|
||||
, m_header{nullptr}
|
||||
, m_footer{nullptr}
|
||||
, m_focusedItem{nullptr}
|
||||
, m_focusedItemIndex{-1}
|
||||
, m_delegateIndex{0}
|
||||
, m_isReturnNeeded{false}
|
||||
, m_currentSectionString {"Default", "Header", "Delegate", "Footer"}
|
||||
{
|
||||
QVariant headerItemProperty = m_listView->property("headerItem");
|
||||
m_header = headerItemProperty.canConvert<QQuickItem*>() ? headerItemProperty.value<QQuickItem*>() : nullptr;
|
||||
|
||||
QVariant footerItemProperty = m_listView->property("footerItem");
|
||||
m_footer = footerItemProperty.canConvert<QQuickItem*>() ? footerItemProperty.value<QQuickItem*>() : nullptr;
|
||||
}
|
||||
|
||||
ListViewFocusController::~ListViewFocusController()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ListViewFocusController::viewAtCurrentIndex() const
|
||||
{
|
||||
switch(m_currentSection) {
|
||||
case Section::Default:
|
||||
[[fallthrough]];
|
||||
case Section::Header: {
|
||||
qDebug() << "===>> [FOCUS ON BEGINNING...]";
|
||||
QMetaObject::invokeMethod(m_listView, "positionViewAtBeginning");
|
||||
break;
|
||||
}
|
||||
case Section::Delegate: {
|
||||
qDebug() << "===>> [FOCUS ON INDEX...]";
|
||||
QMetaObject::invokeMethod(m_listView, "positionViewAtIndex",
|
||||
Q_ARG(int, m_delegateIndex), // Index
|
||||
Q_ARG(int, 2)); // PositionMode (0 = Visible)
|
||||
break;
|
||||
}
|
||||
case Section::Footer: {
|
||||
qDebug() << "===>> [FOCUS ON END...]";
|
||||
QMetaObject::invokeMethod(m_listView, "positionViewAtEnd");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int ListViewFocusController::size() const
|
||||
{
|
||||
return m_listView->property("count").toInt();
|
||||
}
|
||||
|
||||
int ListViewFocusController::currentIndex() const
|
||||
{
|
||||
return m_delegateIndex;
|
||||
}
|
||||
|
||||
void ListViewFocusController::nextDelegate()
|
||||
{
|
||||
const auto sectionName = m_currentSectionString[static_cast<int>(m_currentSection)];
|
||||
qDebug() << "===>> [nextDelegate... current section: " << sectionName << " ]";
|
||||
switch(m_currentSection) {
|
||||
case Section::Default: {
|
||||
if(hasHeader()) {
|
||||
m_currentSection = Section::Header;
|
||||
viewAtCurrentIndex();
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
}
|
||||
case Section::Header: {
|
||||
if (size() > 0) {
|
||||
m_currentSection = Section::Delegate;
|
||||
viewAtCurrentIndex();
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
}
|
||||
case Section::Delegate:
|
||||
if (m_delegateIndex < (size() - 1)) {
|
||||
m_delegateIndex++;
|
||||
viewAtCurrentIndex();
|
||||
break;
|
||||
} else if (hasFooter()) {
|
||||
m_currentSection = Section::Footer;
|
||||
viewAtCurrentIndex();
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case Section::Footer: {
|
||||
m_isReturnNeeded = true;
|
||||
m_currentSection = Section::Default;
|
||||
viewAtCurrentIndex();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
qCritical() << "Current section is invalid!";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ListViewFocusController::previousDelegate()
|
||||
{
|
||||
switch(m_currentSection) {
|
||||
case Section::Default: {
|
||||
if(hasFooter()) {
|
||||
m_currentSection = Section::Footer;
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
}
|
||||
case Section::Footer: {
|
||||
if (size() > 0) {
|
||||
m_currentSection = Section::Delegate;
|
||||
m_delegateIndex = size() - 1;
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
}
|
||||
case Section::Delegate: {
|
||||
if (m_delegateIndex > 0) {
|
||||
m_delegateIndex--;
|
||||
break;
|
||||
} else if (hasHeader()) {
|
||||
m_currentSection = Section::Header;
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
}
|
||||
case Section::Header: {
|
||||
m_isReturnNeeded = true;
|
||||
m_currentSection = Section::Default;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
qCritical() << "Current section is invalid!";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ListViewFocusController::decrementIndex()
|
||||
{
|
||||
m_delegateIndex--;
|
||||
}
|
||||
|
||||
QQuickItem* ListViewFocusController::itemAtIndex(const int index) const
|
||||
{
|
||||
QQuickItem* item{nullptr};
|
||||
|
||||
QMetaObject::invokeMethod(m_listView, "itemAtIndex",
|
||||
Q_RETURN_ARG(QQuickItem*, item),
|
||||
Q_ARG(int, index));
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
QQuickItem* ListViewFocusController::currentDelegate() const
|
||||
{
|
||||
QQuickItem* result{nullptr};
|
||||
|
||||
switch(m_currentSection) {
|
||||
case Section::Default: {
|
||||
qWarning() << "No elements...";
|
||||
break;
|
||||
}
|
||||
case Section::Header: {
|
||||
result = m_header;
|
||||
break;
|
||||
}
|
||||
case Section::Delegate: {
|
||||
result = itemAtIndex(m_delegateIndex);
|
||||
break;
|
||||
}
|
||||
case Section::Footer: {
|
||||
result = m_footer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QQuickItem* ListViewFocusController::focusedItem() const
|
||||
{
|
||||
return m_focusedItem;
|
||||
}
|
||||
|
||||
void ListViewFocusController::focusNextItem()
|
||||
{
|
||||
if (m_isReturnNeeded) {
|
||||
qDebug() << "===>> [ RETURN IS NEEDED... ]";
|
||||
return;
|
||||
}
|
||||
|
||||
m_focusChain = getItemsChain(currentDelegate());
|
||||
|
||||
if (m_focusChain.empty()) {
|
||||
qWarning() << "No elements found in the delegate. Going to next delegate...";
|
||||
nextDelegate();
|
||||
focusNextItem();
|
||||
return;
|
||||
}
|
||||
m_focusedItemIndex++;
|
||||
m_focusedItem = qobject_cast<QQuickItem*>(m_focusChain.at(m_focusedItemIndex));
|
||||
qDebug() << "==>> [ Focused Item: " << m_focusedItem << " with Index: " << m_focusedItemIndex << " ]";
|
||||
m_focusedItem->forceActiveFocus(Qt::TabFocusReason);
|
||||
}
|
||||
|
||||
void ListViewFocusController::focusPreviousItem()
|
||||
{
|
||||
if (m_isReturnNeeded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_focusChain.empty()) {
|
||||
qDebug() << "Empty focusChain with current delegate: " << currentDelegate() << "Scanning for elements...";
|
||||
m_focusChain = getItemsChain(currentDelegate());
|
||||
}
|
||||
if (m_focusChain.empty()) {
|
||||
qWarning() << "No elements found in the delegate. Going to next delegate...";
|
||||
previousDelegate();
|
||||
focusPreviousItem();
|
||||
return;
|
||||
}
|
||||
if (m_focusedItemIndex == -1) {
|
||||
m_focusedItemIndex = m_focusChain.size();
|
||||
}
|
||||
m_focusedItemIndex--;
|
||||
m_focusedItem = qobject_cast<QQuickItem*>(m_focusChain.at(m_focusedItemIndex));
|
||||
qDebug() << "==>> [ Focused Item: " << m_focusedItem << " with Index: " << m_focusedItemIndex << " ]";
|
||||
m_focusedItem->forceActiveFocus(Qt::TabFocusReason);
|
||||
}
|
||||
|
||||
void ListViewFocusController::resetFocusChain()
|
||||
{
|
||||
m_focusChain.clear();
|
||||
m_focusedItem = nullptr;
|
||||
m_focusedItemIndex = -1;
|
||||
}
|
||||
|
||||
bool ListViewFocusController::isFirstFocusItemInDelegate() const
|
||||
{
|
||||
return m_focusedItem && (m_focusedItem == m_focusChain.first());
|
||||
}
|
||||
|
||||
bool ListViewFocusController::isLastFocusItemInDelegate() const
|
||||
{
|
||||
return m_focusedItem && (m_focusedItem == m_focusChain.last());
|
||||
}
|
||||
|
||||
bool ListViewFocusController::hasHeader() const
|
||||
{
|
||||
return m_header && !getItemsChain(m_header).isEmpty();
|
||||
}
|
||||
|
||||
bool ListViewFocusController::hasFooter() const
|
||||
{
|
||||
return m_footer && !getItemsChain(m_footer).isEmpty();
|
||||
}
|
||||
|
||||
bool ListViewFocusController::isFirstFocusItemInListView() const
|
||||
{
|
||||
switch (m_currentSection) {
|
||||
case Section::Footer: {
|
||||
return isFirstFocusItemInDelegate() && !hasHeader() && (size() == 0);
|
||||
}
|
||||
case Section::Delegate: {
|
||||
return isFirstFocusItemInDelegate() && (m_delegateIndex == 0) && !hasHeader();
|
||||
}
|
||||
case Section::Header: {
|
||||
isFirstFocusItemInDelegate();
|
||||
}
|
||||
case Section::Default: {
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
qWarning() << "Wrong section";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool ListViewFocusController::isLastFocusItemInListView() const
|
||||
{
|
||||
switch (m_currentSection) {
|
||||
case Section::Default: {
|
||||
return !hasHeader() && (size() == 0) && !hasFooter();
|
||||
}
|
||||
case Section::Header: {
|
||||
return isLastFocusItemInDelegate() && (size() == 0) && !hasFooter();
|
||||
}
|
||||
case Section::Delegate: {
|
||||
return isLastFocusItemInDelegate() && (m_delegateIndex == size() - 1) && !hasFooter();
|
||||
}
|
||||
case Section::Footer: {
|
||||
return isLastFocusItemInDelegate();
|
||||
}
|
||||
default:
|
||||
qWarning() << "Wrong section";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool ListViewFocusController::isReturnNeeded() const
|
||||
{
|
||||
return m_isReturnNeeded;
|
||||
}
|
||||
76
client/ui/controllers/listViewFocusController.h
Normal file
76
client/ui/controllers/listViewFocusController.h
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#ifndef LISTVIEWFOCUSCONTROLLER_H
|
||||
#define LISTVIEWFOCUSCONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QStack>
|
||||
#include <QSharedPointer>
|
||||
#include <QQuickItem>
|
||||
|
||||
|
||||
bool isEnabled(QObject* item);
|
||||
bool isFocusable(QObject* item);
|
||||
bool isMore(QObject* item1, QObject* item2);
|
||||
bool isLess(QObject* item1, QObject* item2);
|
||||
QList<QObject*> getSubChain(QObject* object);
|
||||
|
||||
void printItems(const QList<QObject*>& items, QObject* current_item);
|
||||
|
||||
/*!
|
||||
* \brief The ListViewFocusController class manages the focus of elements in ListView
|
||||
* \details This class object moving focus to ListView's controls since ListView stores
|
||||
* it's data implicitly and it could be got one by one.
|
||||
*
|
||||
* This class was made to store as less as possible data getting it from QML
|
||||
* when it's needed.
|
||||
*/
|
||||
class ListViewFocusController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ListViewFocusController(QQuickItem* listView, QObject* parent = nullptr);
|
||||
~ListViewFocusController();
|
||||
|
||||
void nextDelegate();
|
||||
void previousDelegate();
|
||||
void decrementIndex();
|
||||
void focusNextItem();
|
||||
void focusPreviousItem();
|
||||
void resetFocusChain();
|
||||
bool isFirstFocusItemInListView() const;
|
||||
bool isFirstFocusItemInDelegate() const;
|
||||
bool isLastFocusItemInListView() const;
|
||||
bool isLastFocusItemInDelegate() const;
|
||||
bool isReturnNeeded() const;
|
||||
|
||||
private:
|
||||
enum class Section {
|
||||
Default,
|
||||
Header,
|
||||
Delegate,
|
||||
Footer,
|
||||
};
|
||||
|
||||
int size() const;
|
||||
int currentIndex() const;
|
||||
void viewAtCurrentIndex() const;
|
||||
QQuickItem* itemAtIndex(const int index) const;
|
||||
QQuickItem* currentDelegate() const;
|
||||
QQuickItem* focusedItem() const;
|
||||
|
||||
bool hasHeader() const;
|
||||
bool hasFooter() const;
|
||||
|
||||
QQuickItem* m_listView;
|
||||
QList<QObject*> m_focusChain;
|
||||
Section m_currentSection;
|
||||
QQuickItem* m_header;
|
||||
QQuickItem* m_footer;
|
||||
QQuickItem* m_focusedItem; // Pointer to focused item on Delegate
|
||||
qsizetype m_focusedItemIndex;
|
||||
qsizetype m_delegateIndex;
|
||||
bool m_isReturnNeeded;
|
||||
|
||||
QList<QString> m_currentSectionString;
|
||||
};
|
||||
|
||||
#endif // LISTVIEWFOCUSCONTROLLER_H
|
||||
|
|
@ -81,7 +81,7 @@ void PageController::keyPressEvent(Qt::Key key)
|
|||
case Qt::Key_Escape: {
|
||||
if (m_drawerDepth) {
|
||||
emit closeTopDrawer();
|
||||
setDrawerDepth(getDrawerDepth() - 1);
|
||||
decrementDrawerDepth();
|
||||
} else {
|
||||
emit escapePressed();
|
||||
}
|
||||
|
|
@ -142,11 +142,25 @@ void PageController::setDrawerDepth(const int depth)
|
|||
}
|
||||
}
|
||||
|
||||
int PageController::getDrawerDepth()
|
||||
int PageController::getDrawerDepth() const
|
||||
{
|
||||
return m_drawerDepth;
|
||||
}
|
||||
|
||||
int PageController::incrementDrawerDepth()
|
||||
{
|
||||
return ++m_drawerDepth;
|
||||
}
|
||||
|
||||
int PageController::decrementDrawerDepth()
|
||||
{
|
||||
if (m_drawerDepth == 0) {
|
||||
return m_drawerDepth;
|
||||
} else {
|
||||
return --m_drawerDepth;
|
||||
}
|
||||
}
|
||||
|
||||
void PageController::onShowErrorMessage(ErrorCode errorCode)
|
||||
{
|
||||
const auto fullErrorMessage = errorString(errorCode);
|
||||
|
|
|
|||
|
|
@ -100,7 +100,9 @@ public slots:
|
|||
void closeApplication();
|
||||
|
||||
void setDrawerDepth(const int depth);
|
||||
int getDrawerDepth();
|
||||
int getDrawerDepth() const;
|
||||
int incrementDrawerDepth();
|
||||
int decrementDrawerDepth();
|
||||
|
||||
private slots:
|
||||
void onShowErrorMessage(amnezia::ErrorCode errorCode);
|
||||
|
|
@ -135,9 +137,6 @@ signals:
|
|||
void escapePressed();
|
||||
void closeTopDrawer();
|
||||
|
||||
void forceTabBarActiveFocus();
|
||||
void forceStackActiveFocus();
|
||||
|
||||
private:
|
||||
QSharedPointer<ServersModel> m_serversModel;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue