add focus navigation to qml

This commit is contained in:
Cyril Anisimov 2024-09-19 20:27:48 +02:00
parent cecee3769e
commit 01e31b4b4d
23 changed files with 103 additions and 196 deletions

View file

@ -41,6 +41,11 @@ bool isLess(QObject* item1, QObject* 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 isListView(QObject* item)
{
return item->inherits("QQuickListView");
@ -129,7 +134,7 @@ void printItems(const T& items, QObject* current_item)
QQuickItem* i = qobject_cast<QQuickItem*>(item);
QPointF coords {getItemCenterPointOnScene(i)};
QString prefix = current_item == i ? "==>" : " ";
// qDebug() << prefix << " Item: " << i << " with coords: " << coords; // Uncomment to visualize tab transitions
qDebug() << prefix << " Item: " << i << " with coords: " << coords;
}
}
@ -236,7 +241,7 @@ QQuickItem* ListViewFocusController::focusedItem()
void ListViewFocusController::focusNextItem()
{
if (m_focusChain.empty()) {
qWarning() << "Empty focusChain with current delegate: " << currentDelegate();
qDebug() << "Empty focusChain with current delegate: " << currentDelegate();
m_focusChain = getSubChain(currentDelegate());
}
m_focusedItemIndex++;
@ -287,6 +292,7 @@ void FocusController::resetFocus()
qWarning() << "There is no focusable elements";
return;
}
if(m_focusedItemIndex == -1) {
m_focusedItemIndex = 0;
m_focusedItem = qobject_cast<QQuickItem*>(m_focusChain.at(m_focusedItemIndex));
@ -370,7 +376,7 @@ void FocusController::previousKeyTabItem()
m_focusedItem = qobject_cast<QQuickItem*>(m_focusChain.at(m_focusedItemIndex));
m_focusedItem->forceActiveFocus(Qt::TabFocusReason);
qDebug() << "--> Current focus was changed to " << m_focusedItem;
qDebug() << "===>> Current focus was changed to " << m_focusedItem;
}
void FocusController::nextKeyUpItem()
@ -439,7 +445,8 @@ void FocusController::reload()
return;
}
m_focusedItemIndex = m_focusChain.indexOf(window->activeFocusItem());
m_focusedItemIndex = m_focusChain.indexOf(m_focusedItem);
// m_focusedItemIndex = m_focusChain.indexOf(window->activeFocusItem());
if(m_focusedItemIndex == -1) {
qInfo() << "No focus item in chain. Moving focus to begin...";
@ -447,9 +454,9 @@ void FocusController::reload()
return;
}
m_focusedItem = qobject_cast<QQuickItem*>(m_focusChain.at(m_focusedItemIndex));
// m_focusedItem = qobject_cast<QQuickItem*>(m_focusChain.at(m_focusedItemIndex));
m_focusedItem->forceActiveFocus();
// m_focusedItem->forceActiveFocus();
}
void FocusController::setRootItem(QQuickItem* item)

View file

@ -37,11 +37,11 @@ DrawerType2 {
target: root
enabled: !GC.isMobile()
function onOpened() {
FocusController.setRoot(root)
FocusController.setRootItem(root)
}
function onClosed() {
FocusController.setRoot(null)
FocusController.setRootItem(null)
}
}

View file

@ -23,14 +23,14 @@ ListView {
interactive: false
activeFocusOnTab: true
Keys.onTabPressed: {
if (currentIndex < this.count - 1) {
this.incrementCurrentIndex()
} else {
currentIndex = 0
lastItemTabClickedSignal()
}
}
// Keys.onTabPressed: {
// if (currentIndex < this.count - 1) {
// this.incrementCurrentIndex()
// } else {
// currentIndex = 0
// lastItemTabClickedSignal()
// }
// }
onCurrentIndexChanged: {
if (visible) {

View file

@ -10,16 +10,6 @@ FocusScope {
property string backButtonImage: "qrc:/images/controls/arrow-left.svg"
property var backButtonFunction
// property bool isFocusable: true
// Keys.onTabPressed: {
// FocusController.nextKeyTabItem()
// }
// Keys.onBacktabPressed: {
// FocusController.previousKeyTabItem()
// }
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
@ -39,8 +29,6 @@ FocusScope {
implicitWidth: 40
implicitHeight: 40
// focus: true
onClicked: {
if (backButtonFunction && typeof backButtonFunction === "function") {
backButtonFunction()

View file

@ -67,7 +67,6 @@ Button {
focusPolicy: Qt.TabFocus
onFocusChanged: {
console.debug("===>> BUTTON: active.focus: ", root.activeFocus, " parentFlickable: ", root.parentFlickable )
if (root.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(this)

View file

@ -27,6 +27,8 @@ Button {
property alias focusItem: rightImage
property FlickableType parentFlickable
hoverEnabled: true
background: Rectangle {
@ -42,6 +44,22 @@ Button {
}
}
function ensureVisible(item) {
if (item.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(root)
}
}
}
onFocusChanged: {
ensureVisible(root)
}
focusItem.onFocusChanged: {
root.ensureVisible(focusItem)
}
contentItem: Item {
anchors.left: parent.left
anchors.right: parent.right

View file

@ -31,9 +31,8 @@ Item {
// Set a timer to set focus after a short delay
Timer {
id: timer
interval: 1000 // Milliseconds // TODO: return to 500
interval: 500 // Milliseconds
onTriggered: {
console.debug("===>> Page creation completed")
FocusController.resetFocus()
}
repeat: false // Stop the timer after one trigger

View file

@ -28,11 +28,11 @@ Popup {
}
onOpened: {
FocusController.setRoot(root)
FocusController.setRootItem(root)
}
onClosed: {
FocusController.setRoot(null)
FocusController.setRootItem(null)
}
background: Rectangle {

View file

@ -40,6 +40,7 @@ Item {
implicitHeight: content.implicitHeight
property FlickableType parentFlickable
Connections {
target: textField
function onFocusChanged() {
@ -84,7 +85,7 @@ Item {
TextField {
id: textField
// activeFocusOnTab: false
property bool isFocusable: true
Keys.onTabPressed: {

View file

@ -174,7 +174,6 @@ PageType {
checkEmptyText: true
Keys.onTabPressed: saveButton.forceActiveFocus()
}
Header2TextType {
@ -283,7 +282,6 @@ PageType {
text: qsTr("Save")
Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunc: function() {
forceActiveFocus()

View file

@ -171,7 +171,6 @@ PageType {
Layout.bottomMargin: 24
text: qsTr("Save")
Keys.onTabPressed: lastItemTabClicked(focusItem)
clickedFunc: function() {
forceActiveFocus()

View file

@ -14,7 +14,7 @@ import "../Config"
PageType {
id: root
FlickableType { // TODO: refactor either replace with ListView or Repeater
FlickableType {
id: fl
anchors.top: parent.top
anchors.bottom: parent.bottom
@ -142,7 +142,6 @@ PageType {
text: qsTr("Close application")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
leftImageSource: "qrc:/images/controls/x-circle.svg"
// isLeftImageHoverEnabled: false
clickedFunction: function() {
PageController.closeApplication()

View file

@ -14,17 +14,6 @@ import "../Components"
PageType {
id: root
// Item {
// id: focusItem
// KeyNavigation.tab: backButton
// onFocusChanged: {
// if (focusItem.activeFocus) {
// fl.contentY = 0
// }
// }
// }
BackButtonType {
id: backButton
@ -223,7 +212,6 @@ PageType {
text: qsTr("Privacy Policy")
Keys.onTabPressed: lastItemTabClicked()
parentFlickable: fl
clickedFunc: function() {

View file

@ -14,16 +14,6 @@ import "../Components"
PageType {
id: root
// Item {
// id: focusItem
// onFocusChanged: {
// if (focusItem.activeFocus) {
// fl.contentY = 0
// }
// }
// }
BackButtonType {
id: backButton
@ -31,8 +21,6 @@ PageType {
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20
// KeyNavigation.tab: GC.isMobile() ? switcher : switcherAutoStart
}
FlickableType {
@ -92,7 +80,6 @@ PageType {
descriptionText: qsTr("Enable notifications to show the VPN state in the status bar")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
// KeyNavigation.tab: labelWithButtonLanguage.rightButton
parentFlickable: fl
clickedFunction: function() {

View file

@ -17,8 +17,6 @@ import "../Controls2/TextTypes"
PageType {
id: root
// defaultActiveFocusItem: focusItem
Connections {
target: SettingsController
@ -86,6 +84,8 @@ PageType {
text: qsTr("Make a backup")
parentFlickable: fl
clickedFunc: function() {
var fileName = ""
if (GC.isMobile()) {
@ -120,6 +120,8 @@ PageType {
text: qsTr("Restore from backup")
parentFlickable: fl
clickedFunc: function() {
var filePath = SystemController.getFileName(qsTr("Open backup file"),
qsTr("Backup files (*.backup)"))
@ -127,8 +129,6 @@ PageType {
restoreBackup(filePath)
}
}
Keys.onTabPressed: lastItemTabClicked()
}
}
}

View file

@ -70,6 +70,8 @@ PageType {
descriptionText: qsTr("When AmneziaDNS is not used or installed")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
parentFlickable: fl
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsDns)
}
@ -85,19 +87,11 @@ PageType {
descriptionText: qsTr("Allows you to select which sites you want to access through the VPN")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
parentFlickable: fl
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
}
Keys.onTabPressed: {
if (splitTunnelingButton2.visible) {
return splitTunnelingButton2.rightButton.forceActiveFocus()
} else if (killSwitchSwitcher.visible) {
return killSwitchSwitcher.forceActiveFocus()
} else {
lastItemTabClicked()
}
}
}
DividerType {
@ -114,17 +108,11 @@ PageType {
descriptionText: qsTr("Allows you to use the VPN only for certain Apps")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
parentFlickable: fl
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling)
}
Keys.onTabPressed: {
if (killSwitchSwitcher.visible) {
return killSwitchSwitcher.forceActiveFocus()
} else {
lastItemTabClicked()
}
}
}
DividerType {
@ -141,6 +129,8 @@ PageType {
text: qsTr("KillSwitch")
descriptionText: qsTr("Disables your internet if your encrypted VPN connection drops out for any reason.")
parentFlickable: fl
checked: SettingsController.isKillSwitchEnabled()
checkable: !ConnectionController.isConnected
onCheckedChanged: {
@ -153,8 +143,6 @@ PageType {
PageController.showNotificationMessage(qsTr("Cannot change killSwitch settings during active connection"))
}
}
Keys.onTabPressed: lastItemTabClicked()
}
DividerType {

View file

@ -23,10 +23,20 @@ PageType {
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20
onFocusChanged: {
console.debug("MOVE THIS LOGIC TO CPP!")
if (activeFocus) {
if (fl) {
fl.ensureVisible(this)
}
}
}
}
FlickableType {
id: fl
anchors.top: backButton.bottom
anchors.bottom: parent.bottom
contentHeight: content.height
@ -51,6 +61,7 @@ PageType {
SwitcherType {
id: switcher
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
@ -66,14 +77,7 @@ PageType {
}
}
onFocusChanged: {
console.debug("MOVE THIS LOGIC TO CPP!")
if (activeFocus) {
if (fl) {
fl.ensureVisible(this)
}
}
}
parentFlickable: fl
}
DividerType {}

View file

@ -52,25 +52,13 @@ PageType {
height: 500 // servers.contentItem.height // TODO: calculate height
property bool isFocusable: true
model: ServersModel
clip: true
interactive: false
// activeFocusOnTab: true
// focus: true
// Keys.onTabPressed: {
// if (currentIndex < servers.count - 1) {
// servers.incrementCurrentIndex()
// } else {
// servers.currentIndex = 0
// focusItem.forceActiveFocus()
// root.lastItemTabClicked()
// }
// fl.ensureVisible(this.currentItem)
// }
onVisibleChanged: {
if (visible) {
currentIndex = 0
@ -81,12 +69,6 @@ PageType {
implicitWidth: servers.width
implicitHeight: delegateContent.implicitHeight
// onFocusChanged: {
// if (focus) {
// server.rightButton.forceActiveFocus()
// }
// }
ColumnLayout {
id: delegateContent
@ -99,7 +81,7 @@ PageType {
Layout.fillWidth: true
text: name
// parentFlickable: fl
descriptionText: {
var servicesNameString = ""
var servicesName = ServersModel.getAllInstalledServicesName(index)

View file

@ -51,6 +51,8 @@ PageType {
height: containers.contentItem.height
spacing: 16
property bool isFocusable: true
currentIndex: 1
interactive: false
model: ApiServicesModel
@ -85,6 +87,9 @@ PageType {
PageController.goToPage(PageEnum.PageSetupWizardApiServiceInfo)
}
}
Keys.onEnterPressed: clicked()
Keys.onReturnPressed: clicked()
}
}
}

View file

@ -25,8 +25,6 @@ PageType {
}
}
// defaultActiveFocusItem: focusItem
FlickableType {
id: fl
anchors.top: parent.top
@ -136,6 +134,8 @@ PageType {
}
ParagraphTextType {
objectName: "insertKeyLabel"
Layout.fillWidth: true
Layout.topMargin: 32
Layout.rightMargin: 16
@ -155,6 +155,8 @@ PageType {
headerText: qsTr("Insert key")
buttonText: qsTr("Insert")
parentFlickable: fl
clickedFunc: function() {
textField.text = ""
textField.paste()
@ -169,14 +171,7 @@ PageType {
Layout.rightMargin: 16
Layout.leftMargin: 16
onFocusChanged: {
console.debug("MOVE THIS LOGIC TO CPP!")
if (activeFocus) {
if (fl) {
fl.ensureVisible(this)
}
}
}
parentFlickable: fl
visible: textKey.textFieldText !== ""
@ -214,14 +209,7 @@ PageType {
rightImageSource: "qrc:/images/controls/chevron-right.svg"
leftImageSource: "qrc:/images/controls/amnezia.svg"
focusItem.onFocusChanged: {
console.debug("MOVE THIS LOGIC TO CPP!")
if (focusItem.activeFocus) {
if (fl) {
fl.ensureVisible(apiInstalling)
}
}
}
parentFlickable: fl
onClicked: function() {
PageController.showBusyIndicator(true)
@ -247,14 +235,7 @@ PageType {
rightImageSource: "qrc:/images/controls/chevron-right.svg"
leftImageSource: "qrc:/images/controls/server.svg"
focusItem.onFocusChanged: {
console.debug("MOVE THIS LOGIC TO CPP!")
if (focusItem.activeFocus) {
if (fl) {
fl.ensureVisible(manualInstalling)
}
}
}
parentFlickable: fl
onClicked: {
PageController.goToPage(PageEnum.PageSetupWizardCredentials)
@ -276,14 +257,7 @@ PageType {
rightImageSource: "qrc:/images/controls/chevron-right.svg"
leftImageSource: "qrc:/images/controls/archive-restore.svg"
focusItem.onFocusChanged: {
console.debug("MOVE THIS LOGIC TO CPP!")
if (focusItem.activeFocus) {
if (fl) {
fl.ensureVisible(backupRestore)
}
}
}
parentFlickable: fl
onClicked: {
var filePath = SystemController.getFileName(qsTr("Open backup file"),
@ -309,12 +283,7 @@ PageType {
rightImageSource: "qrc:/images/controls/chevron-right.svg"
leftImageSource: "qrc:/images/controls/folder-search-2.svg"
focusItem.onFocusChanged: {
console.debug("MOVE THIS LOGIC TO CPP!")
if (fl) {
fl.ensureVisible(openFile)
}
}
parentFlickable: fl
onClicked: {
var nameFilter = !ServersModel.getServersCount() ? "Config or backup files (*.vpn *.ovpn *.conf *.json *.backup)" :
@ -343,14 +312,7 @@ PageType {
rightImageSource: "qrc:/images/controls/chevron-right.svg"
leftImageSource: "qrc:/images/controls/scan-line.svg"
focusItem.onFocusChanged: {
console.debug("MOVE THIS LOGIC TO CPP!")
if (focusItem.activeFocus) {
if (fl) {
fl.ensureVisible(scanQr)
}
}
}
parentFlickable: fl
onClicked: {
ImportController.startDecodingQr()
@ -375,14 +337,7 @@ PageType {
rightImageSource: "qrc:/images/controls/chevron-right.svg"
leftImageSource: "qrc:/images/controls/help-circle.svg"
focusItem.onFocusChanged: {
console.debug("MOVE THIS LOGIC TO CPP!")
if (focusItem.activeFocus) {
if (fl) {
fl.ensureVisible(siteLink)
}
}
}
parentFlickable: fl
onClicked: {
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl())

View file

@ -13,8 +13,6 @@ import "../Controls2/TextTypes"
PageType {
id: root
// defaultActiveFocusItem: hostname.textField
BackButtonType {
id: backButton
@ -54,6 +52,8 @@ PageType {
headerText: qsTr("Server IP address [:port]")
textFieldPlaceholderText: qsTr("255.255.255.255:22")
parentFlickable: fl
textField.onFocusChanged: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
}
@ -66,6 +66,8 @@ PageType {
headerText: qsTr("SSH Username")
textFieldPlaceholderText: "root"
parentFlickable: fl
textField.onFocusChanged: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
}
@ -82,6 +84,8 @@ PageType {
buttonImageSource: textFieldText !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg")
: ""
parentFlickable: fl
clickedFunc: function() {
hidePassword = !hidePassword
}
@ -99,6 +103,8 @@ PageType {
text: qsTr("Continue")
parentFlickable: fl
clickedFunc: function() {
forceActiveFocus()
if (!isCredentialsFilled()) {
@ -138,6 +144,8 @@ PageType {
rightImageSource: "qrc:/images/controls/chevron-right.svg"
leftImageSource: "qrc:/images/controls/help-circle.svg"
parentFlickable: fl
onClicked: {
Qt.openUrlExternally(LanguageModel.getCurrentSiteUrl() + "/starter-guide")
}

View file

@ -99,6 +99,8 @@ PageType {
text: showContent ? qsTr("Collapse content") : qsTr("Show content")
parentFlickable: fl
clickedFunc: function() {
showContent = !showContent
}

View file

@ -90,16 +90,6 @@ PageType {
PageController.closePage()
}
}
// function onForceTabBarActiveFocus() {
// homeTabButton.focus = true
// tabBar.forceActiveFocus()
// }
// function onForceStackActiveFocus() {
// homeTabButton.focus = true
// tabBarStackView.forceActiveFocus()
// }
}
Connections {
@ -311,10 +301,6 @@ PageType {
tabBar.currentIndex = 0
FocusController.setRootItem(null) // TODO: move to do it automaticaly
}
// KeyNavigation.tab: shareTabButton
// Keys.onEnterPressed: this.clicked()
// Keys.onReturnPressed: this.clicked()
}
TabImageButtonType {
@ -340,8 +326,6 @@ PageType {
tabBarStackView.goToTabBarPage(PageEnum.PageShare)
tabBar.currentIndex = 1
}
// KeyNavigation.tab: settingsTabButton
}
TabImageButtonType {
@ -354,8 +338,6 @@ PageType {
tabBarStackView.goToTabBarPage(PageEnum.PageSettings)
tabBar.currentIndex = 2
}
// KeyNavigation.tab: plusTabButton
}
TabImageButtonType {
@ -368,8 +350,6 @@ PageType {
tabBarStackView.goToTabBarPage(PageEnum.PageSetupWizardConfigSource)
tabBar.currentIndex = 3
}
// Keys.onTabPressed: PageController.forceStackActiveFocus()
}
}
}