Added tab navigation functional. (#721)

- Added tab navigation functional.
- In basic types added parentFlickable property, which will help to ensure, that the item is visible within flickable parent during tab navigation.
- Added focus state for some basic types.
- In PageType qml file added lastItemTabClicked function, which will help to focus tab bar buttons when the last tab on the current page clicked.
- Added Focus for back button for all pages and drawers.
- Added scroll on tab for Servers ListView on PageHome.
This commit is contained in:
Garegin Harutyunyan 2024-04-18 17:54:55 +04:00 committed by GitHub
parent d50e7dd3f4
commit 0e4ae26bae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
66 changed files with 2269 additions and 143 deletions

View file

@ -13,6 +13,12 @@ Item {
visible: backButtonImage !== ""
onActiveFocusChanged: {
if (activeFocus) {
backButton.forceActiveFocus()
}
}
RowLayout {
id: content
@ -20,6 +26,7 @@ Item {
anchors.leftMargin: 8
ImageButtonType {
id: backButton
image: backButtonImage
imageColor: "#D7D8DB"
@ -42,4 +49,7 @@ Item {
color: "transparent"
}
}
Keys.onEnterPressed: backButton.clicked()
Keys.onReturnPressed: backButton.clicked()
}

View file

@ -26,18 +26,29 @@ Button {
property bool squareLeftSide: false
property FlickableType parentFlickable
property var clickedFunc
implicitHeight: 56
hoverEnabled: true
focusPolicy: Qt.TabFocus
onFocusChanged: {
if (root.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(this)
}
}
}
background: Rectangle {
id: focusBorder
color: "transparent"
border.color: root.activeFocus ? root.borderFocusedColor : "transparent"
border.width: root.activeFocus ? root.borderFocusedWidth : "transparent"
border.width: root.activeFocus ? root.borderFocusedWidth : 0
anchors.fill: parent

View file

@ -23,6 +23,8 @@ CheckBox {
property string checkedBorderColor: "#FBB26A"
property string checkedBorderDisabledColor: "#402102"
property string borderFocusedColor: "#D7D8DB"
property string checkedImageColor: "#FBB26A"
property string pressedImageColor: "#A85809"
property string defaultImageColor: "transparent"
@ -30,7 +32,24 @@ CheckBox {
property string imageSource: "qrc:/images/controls/check.svg"
property var parentFlickable
onFocusChanged: {
if (root.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(root)
}
}
}
hoverEnabled: enabled ? true : false
focusPolicy: Qt.NoFocus
background: Rectangle {
color: "transparent"
border.color: root.focus ? borderFocusedColor : "transparent"
border.width: 1
radius: 16
}
indicator: Rectangle {
id: background
@ -59,7 +78,11 @@ CheckBox {
width: 24
height: 24
color: "transparent"
border.color: root.checked ? (root.enabled ? checkedBorderColor : checkedBorderDisabledColor) : defaultBorderColor
border.color: root.checked ?
(root.enabled ?
checkedBorderColor :
checkedBorderDisabledColor) :
defaultBorderColor
border.width: 1
radius: 4
@ -130,6 +153,16 @@ CheckBox {
cursorShape: Qt.PointingHandCursor
enabled: false
}
Keys.onEnterPressed: {
root.checked = !root.checked
}
Keys.onReturnPressed: {
root.checked = !root.checked
}
}

View file

@ -3,6 +3,7 @@ import QtQuick.Controls
import QtQuick.Layouts
import "TextTypes"
import "../Config"
Item {
id: root
@ -27,6 +28,9 @@ Item {
property string rootButtonBackgroundHoveredColor: "#1C1D21"
property string rootButtonBackgroundPressedColor: "#1C1D21"
property string borderFocusedColor: "#D7D8DB"
property int borderFocusedWidth: 1
property string rootButtonHoveredBorderColor: "#494B50"
property string rootButtonDefaultBorderColor: "#2C2D30"
property string rootButtonPressedBorderColor: "#D7D8DB"
@ -42,44 +46,70 @@ Item {
signal open
signal close
function popupClosedFunc() {
if (!GC.isMobile()) {
this.forceActiveFocus()
}
}
property var parentFlickable
onFocusChanged: {
if (root.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(root)
}
}
}
implicitWidth: rootButtonContent.implicitWidth
implicitHeight: rootButtonContent.implicitHeight
onOpen: {
menu.open()
rootButtonBackground.border.color = rootButtonPressedBorderColor
}
onClose: {
menu.close()
rootButtonBackground.border.color = rootButtonDefaultBorderColor
}
onEnabledChanged: {
if (enabled) {
rootButtonBackground.color = rootButtonBackgroundColor
rootButtonBackground.border.color = rootButtonDefaultBorderColor
} else {
rootButtonBackground.color = "transparent"
rootButtonBackground.border.color = rootButtonHoveredBorderColor
}
}
Rectangle {
id: rootButtonBackground
id: focusBorder
color: "transparent"
border.color: root.activeFocus ? root.borderFocusedColor : "transparent"
border.width: root.activeFocus ? root.borderFocusedWidth : 0
anchors.fill: rootButtonContent
radius: 16
color: root.enabled ? rootButtonBackgroundColor : "transparent"
border.color: root.enabled ? rootButtonDefaultBorderColor : rootButtonHoveredBorderColor
border.width: 1
Behavior on border.color {
PropertyAnimation { duration: 200 }
}
Behavior on color {
PropertyAnimation { duration: 200 }
Rectangle {
id: rootButtonBackground
anchors.fill: focusBorder
anchors.margins: root.activeFocus ? 2 : 0
radius: root.activeFocus ? 14 : 16
color: {
if (root.enabled) {
if (root.pressed) {
return root.rootButtonBackgroundPressedColor
}
return root.hovered ? root.rootButtonBackgroundHoveredColor : root.rootButtonBackgroundColor
} else {
return "transparent"
}
}
border.color: rootButtonDefaultBorderColor
border.width: 1
Behavior on border.color {
PropertyAnimation { duration: 200 }
}
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
}
@ -107,6 +137,7 @@ Item {
}
ButtonTextType {
id: buttonText
Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft
@ -136,26 +167,6 @@ Item {
cursorShape: Qt.PointingHandCursor
hoverEnabled: root.enabled ? true : false
onEntered: {
if (menu.isClosed) {
rootButtonBackground.border.color = rootButtonHoveredBorderColor
rootButtonBackground.color = rootButtonBackgroundHoveredColor
}
}
onExited: {
if (menu.isClosed) {
rootButtonBackground.border.color = rootButtonDefaultBorderColor
rootButtonBackground.color = rootButtonBackgroundColor
}
}
onPressed: {
if (menu.isClosed) {
rootButtonBackground.color = pressed ? rootButtonBackgroundPressedColor : entered ? rootButtonHoveredBorderColor : rootButtonDefaultBorderColor
}
}
onClicked: {
if (rootButtonClickedFunction && typeof rootButtonClickedFunction === "function") {
rootButtonClickedFunction()
@ -173,11 +184,27 @@ Item {
anchors.fill: parent
expandedHeight: drawerParent.height * drawerHeight
onClosed: {
root.popupClosedFunc()
}
expandedContent: Item {
id: container
implicitHeight: menu.expandedHeight
Connections {
target: menu
enabled: !GC.isMobile()
function onOpened() {
focusItem.forceActiveFocus()
}
}
Item {
id: focusItem
KeyNavigation.tab: backButton
}
ColumnLayout {
id: header
@ -187,14 +214,15 @@ Item {
anchors.topMargin: 16
BackButtonType {
id: backButton
backButtonImage: root.headerBackButtonImage
backButtonFunction: function() {
menu.close()
}
backButtonFunction: function() { menu.close() }
KeyNavigation.tab: listViewLoader.item
}
}
FlickableType {
id: flickable
anchors.top: header.bottom
anchors.topMargin: 16
contentHeight: col.implicitHeight
@ -221,9 +249,28 @@ Item {
Loader {
id: listViewLoader
sourceComponent: root.listView
onLoaded: {
listViewLoader.item.parentFlickable = flickable
listViewLoader.item.lastItemTabClicked = function() {
focusItem.forceActiveFocus()
}
}
}
}
}
}
}
Keys.onEnterPressed: {
if (menu.isClosed) {
menu.open()
}
}
Keys.onReturnPressed: {
if (menu.isClosed) {
menu.open()
}
}
}

View file

@ -5,6 +5,14 @@ import "../Config"
Flickable {
id: fl
function ensureVisible(item) {
if (item.y < fl.contentY) {
fl.contentY = item.y
} else if (item.y + item.height > fl.contentY + fl.height) {
fl.contentY = item.y + item.height - fl.height + 40 // 40 is a bottom margin
}
}
clip: true
width: parent.width

View file

@ -9,6 +9,8 @@ Item {
property string actionButtonImage
property var actionButtonFunction
property alias actionButton: headerActionButton
property string headerText
property string descriptionText
@ -60,4 +62,16 @@ Item {
visible: root.descriptionText !== ""
}
}
Keys.onEnterPressed: {
if (actionButtonFunction && typeof actionButtonFunction === "function") {
actionButtonFunction()
}
}
Keys.onReturnPressed: {
if (actionButtonFunction && typeof actionButtonFunction === "function") {
actionButtonFunction()
}
}
}

View file

@ -9,12 +9,16 @@ Item {
property string actionButtonImage
property var actionButtonFunction
property alias actionButton: headerActionButton
property string headerText
property int headerTextMaximumLineCount: 2
property int headerTextElide: Qt.ElideRight
property string descriptionText
focus: true
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
@ -67,4 +71,16 @@ Item {
visible: root.descriptionText !== ""
}
}
Keys.onEnterPressed: {
if (actionButtonFunction && typeof actionButtonFunction === "function") {
actionButtonFunction()
}
}
Keys.onReturnPressed: {
if (actionButtonFunction && typeof actionButtonFunction === "function") {
actionButtonFunction()
}
}
}

View file

@ -19,6 +19,7 @@ RadioButton {
property string checkedBorderColor: "#FBB26A"
property string defaultBodredColor: "transparent"
property string checkedDisabledBorderColor: "#84603D"
property string borderFocusedColor: "#D7D8DB"
property int borderWidth: 0
implicitWidth: content.implicitWidth
@ -47,6 +48,8 @@ RadioButton {
return root.pressedBorderColor
} else if (root.checked) {
return root.checkedBorderColor
} else if (root.activeFocus) {
return root.borderFocusedColor
}
return root.defaultBodredColor
} else {
@ -58,7 +61,7 @@ RadioButton {
}
border.width: {
if(root.checked) {
if(root.checked || root.activeFocus) {
return 1
}
return root.pressed ? 1 : 0
@ -97,4 +100,12 @@ RadioButton {
cursorShape: Qt.PointingHandCursor
enabled: false
}
Keys.onEnterPressed: {
this.clicked()
}
Keys.onReturnPressed: {
this.clicked()
}
}

View file

@ -18,11 +18,26 @@ Button {
property alias backgroundColor: background.color
property alias backgroundRadius: background.radius
property string borderFocusedColor: "#D7D8DB"
property int borderFocusedWidth: 1
hoverEnabled: true
focus: true
focusPolicy: Qt.TabFocus
icon.source: image
icon.color: root.enabled ? imageColor : disableImageColor
property Flickable parentFlickable
onFocusChanged: {
if (root.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(this)
}
}
}
Behavior on icon.color {
PropertyAnimation { duration: 200 }
}
@ -31,6 +46,9 @@ Button {
id: background
anchors.fill: parent
border.color: root.activeFocus ? root.borderFocusedColor : "transparent"
border.width: root.activeFocus ? root.borderFocusedWidth : 0
color: {
if (root.enabled) {
if (root.pressed) {
@ -44,6 +62,9 @@ Button {
Behavior on color {
PropertyAnimation { duration: 200 }
}
Behavior on border.color {
PropertyAnimation { duration: 200 }
}
}
MouseArea {

View file

@ -19,12 +19,18 @@ Item {
property string leftImageSource
property bool isLeftImageHoverEnabled: true //todo separete this qml file to 3
property alias rightButton: rightImage
property FlickableType parentFlickable
property string textColor: "#d7d8db"
property string textDisabledColor: "#878B91"
property string descriptionColor: "#878B91"
property string descriptionDisabledColor: "#494B50"
property real textOpacity: 1.0
property string borderFocusedColor: "#D7D8DB"
property int borderFocusedWidth: 1
property string rightImageColor: "#d7d8db"
property bool descriptionOnTop: false
@ -32,6 +38,25 @@ Item {
implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin
implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin
onFocusChanged: {
if (root.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(root)
}
}
}
Connections {
target: rightImage
function onFocusChanged() {
if (rightImage.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(root)
}
}
}
}
RowLayout {
id: content
anchors.fill: parent
@ -163,6 +188,9 @@ Item {
anchors.fill: root
color: "transparent"
border.color: root.activeFocus ? root.borderFocusedColor : "transparent"
border.width: root.activeFocus ? root.borderFocusedWidth : 0
Behavior on color {
PropertyAnimation { duration: 200 }
@ -207,4 +235,16 @@ Item {
}
}
}
Keys.onEnterPressed: {
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
Keys.onReturnPressed: {
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
}

View file

@ -26,6 +26,47 @@ ListView {
clip: true
interactive: false
property FlickableType parentFlickable
property var lastItemTabClicked
property int currentFocusIndex: 0
activeFocusOnTab: true
onActiveFocusChanged: {
if (activeFocus) {
this.currentFocusIndex = 0
this.itemAtIndex(currentFocusIndex).forceActiveFocus()
}
}
Keys.onTabPressed: {
if (currentFocusIndex < this.count - 1) {
currentFocusIndex += 1
} else {
currentFocusIndex = 0
}
this.itemAtIndex(currentFocusIndex).forceActiveFocus()
}
Item {
id: focusItem
Keys.onTabPressed: {
root.forceActiveFocus()
}
}
onVisibleChanged: {
if (visible) {
focusItem.forceActiveFocus()
}
}
onCurrentFocusIndexChanged: {
if (parentFlickable) {
parentFlickable.ensureVisible(this.itemAtIndex(currentFocusIndex))
}
}
ButtonGroup {
id: buttonGroup
}
@ -40,6 +81,12 @@ ListView {
implicitWidth: rootWidth
implicitHeight: content.implicitHeight
onActiveFocusChanged: {
if (activeFocus) {
radioButton.forceActiveFocus()
}
}
ColumnLayout {
id: content
@ -54,12 +101,18 @@ ListView {
hoverEnabled: true
indicator: Rectangle {
anchors.fill: parent
width: parent.width - 1
height: parent.height
color: radioButton.hovered ? "#2C2D30" : "#1C1D21"
border.color: radioButton.focus ? "#D7D8DB" : "transparent"
border.width: radioButton.focus ? 1 : 0
Behavior on color {
PropertyAnimation { duration: 200 }
}
Behavior on border.color {
PropertyAnimation { duration: 200 }
}
MouseArea {
anchors.fill: parent
@ -117,5 +170,13 @@ ListView {
root.selectedText = name
}
}
Keys.onReturnPressed: {
radioButton.clicked()
}
Keys.onEnterPressed: {
radioButton.clicked()
}
}
}

View file

@ -11,6 +11,28 @@ Item {
property var defaultActiveFocusItem: null
onVisibleChanged: {
if (visible && !GC.isMobile()) {
timer.start()
}
}
function lastItemTabClicked(focusItem) {
if (GC.isMobile()) {
return
}
if (focusItem) {
focusItem.forceActiveFocus()
PageController.forceTabBarActiveFocus()
} else {
if (defaultActiveFocusItem) {
defaultActiveFocusItem.forceActiveFocus()
}
PageController.forceTabBarActiveFocus()
}
}
// MouseArea {
// id: globalMouseArea
// z: 99

View file

@ -25,6 +25,14 @@ Popup {
color: Qt.rgba(14/255, 14/255, 17/255, 0.8)
}
onOpened: {
focusItem.forceActiveFocus()
}
onClosed: {
PageController.forceStackActiveFocus()
}
background: Rectangle {
anchors.fill: parent
@ -52,7 +60,13 @@ Popup {
text: root.text
}
Item {
id: focusItem
KeyNavigation.tab: closeButton
}
BasicButtonType {
id: closeButton
visible: closeButtonVisible
implicitHeight: 32
@ -66,6 +80,8 @@ Popup {
borderWidth: 0
text: qsTr("Close")
KeyNavigation.tab: focusItem
clickedFunc: function() {
root.close()
}

View file

@ -18,6 +18,9 @@ Switch {
property string defaultIndicatorColor: "transparent"
property string checkedDisabledIndicatorColor: "#402102"
property string borderFocusedColor: "#D7D8DB"
property int borderFocusedWidth: 1
property string checkedIndicatorBorderColor: "#633303"
property string defaultIndicatorBorderColor: "#494B50"
property string checkedDisabledIndicatorBorderColor: "#402102"
@ -31,6 +34,16 @@ Switch {
property string defaultIndicatorBackgroundColor: "transparent"
hoverEnabled: enabled ? true : false
focusPolicy: Qt.TabFocus
property FlickableType parentFlickable: null
onFocusChanged: {
if (root.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(root)
}
}
}
indicator: Rectangle {
id: switcher
@ -44,8 +57,9 @@ Switch {
radius: 16
color: root.checked ? (root.enabled ? root.checkedIndicatorColor : root.checkedDisabledIndicatorColor)
: root.defaultIndicatorColor
border.color: root.checked ? (root.enabled ? root.checkedIndicatorBorderColor : root.checkedDisabledIndicatorBorderColor)
: root.defaultIndicatorBorderColor
border.color: root.activeFocus ? root.borderFocusedColor : (root.checked ? (root.enabled ? root.checkedIndicatorBorderColor : root.checkedDisabledIndicatorBorderColor)
: root.defaultIndicatorBorderColor)
Behavior on color {
PropertyAnimation { duration: 200 }
@ -114,4 +128,14 @@ Switch {
cursorShape: Qt.PointingHandCursor
enabled: false
}
Keys.onEnterPressed: {
root.checked = !root.checked
root.checkedChanged()
}
Keys.onReturnPressed: {
root.checked = !root.checked
root.checkedChanged()
}
}

View file

@ -10,11 +10,15 @@ TabButton {
property string textColor: "#D7D8DB"
property string borderFocusedColor: "#D7D8DB"
property int borderFocusedWidth: 1
property bool isSelected: false
implicitHeight: 48
hoverEnabled: true
focusPolicy: Qt.TabFocus
background: Rectangle {
id: background
@ -22,6 +26,9 @@ TabButton {
anchors.fill: parent
color: "transparent"
border.color: root.activeFocus ? root.borderFocusedColor : "transparent"
border.width: root.activeFocus ? root.borderFocusedWidth : 0
Rectangle {
width: parent.width
height: 1

View file

@ -12,7 +12,13 @@ TabButton {
property bool isSelected: false
property string borderFocusedColor: "#D7D8DB"
property int borderFocusedWidth: 1
property var clickedFunc
hoverEnabled: true
focusPolicy: Qt.TabFocus
icon.source: image
icon.color: isSelected ? selectedColor : defaultColor
@ -21,6 +27,11 @@ TabButton {
id: background
anchors.fill: parent
color: "transparent"
radius: 10
border.color: root.activeFocus ? root.borderFocusedColor : "transparent"
border.width: root.activeFocus ? root.borderFocusedWidth : 0
}
MouseArea {
@ -28,4 +39,22 @@ TabButton {
cursorShape: Qt.PointingHandCursor
enabled: false
}
Keys.onEnterPressed: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
Keys.onReturnPressed: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
onClicked: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
}

View file

@ -19,6 +19,15 @@ Rectangle {
border.color: getBorderColor(borderNormalColor)
radius: 16
property FlickableType parentFlickable: null
onFocusChanged: {
if (root.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(root)
}
}
}
MouseArea {
id: parentMouse
anchors.fill: parent

View file

@ -13,6 +13,7 @@ Item {
property alias errorText: errorField.text
property bool checkEmptyText: false
property bool rightButtonClickedOnEnter: false
property string buttonText
property string buttonImageSource
@ -36,6 +37,18 @@ Item {
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
property FlickableType parentFlickable
Connections {
target: textField
function onFocusChanged() {
if (textField.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(root)
}
}
}
}
ColumnLayout {
id: content
anchors.fill: parent
@ -188,10 +201,22 @@ Item {
}
Keys.onEnterPressed: {
KeyNavigation.tab.forceActiveFocus();
if (root.rightButtonClickedOnEnter && root.clickedFunc && typeof root.clickedFunc === "function") {
clickedFunc()
}
if (KeyNavigation.tab) {
KeyNavigation.tab.forceActiveFocus();
}
}
Keys.onReturnPressed: {
KeyNavigation.tab.forceActiveFocus();
if (root.rightButtonClickedOnEnter &&root.clickedFunc && typeof root.clickedFunc === "function") {
clickedFunc()
}
if (KeyNavigation.tab) {
KeyNavigation.tab.forceActiveFocus();
}
}
}

View file

@ -20,16 +20,23 @@ RadioButton {
property string textColor: "#D7D8DB"
property string selectedTextColor: "#FBB26A"
property string borderFocusedColor: "#D7D8DB"
property int borderFocusedWidth: 1
property string imageSource
property bool showImage
hoverEnabled: true
focusPolicy: Qt.TabFocus
indicator: Rectangle {
id: background
anchors.verticalCenter: parent.verticalCenter
border.color: root.focus ? root.borderFocusedColor : "transparent"
border.width: root.focus ? root.borderFocusedWidth : 0
implicitWidth: 56
implicitHeight: 56
radius: 16
@ -51,6 +58,10 @@ RadioButton {
PropertyAnimation { duration: 200 }
}
Behavior on border.color {
PropertyAnimation { duration: 200 }
}
Image {
source: {
if (showImage) {