Improve navigation cpp (#1061)

* add focusController class

* add more key handlers

* add focus navigation to qml

* fixed language selector

* add reverse focus change to FocusController

* add default focus item

* update transitions

* update pages

* add ListViewFocusController

* fix ListView navigation

* update CardType for using with focus navigation

* remove useless key navigation

* remove useless slots, logs, Drawer open and close

* fix reverse focus move on listView

* fix drawer radio buttons selection

* fix drawer layout and focus move

* fix PageSetupWizardProtocolSettings focus move

* fix back navigation on default focus item

* fix crashes after ListView navigation

* fix protocol settings focus move

* fix focus on users on page share

* clean up page share

* fix server rename

* fix page share default server selection

* refactor about page for correct focus move

* fix focus move on list views with header and-or footer

* minor fixes

* fix server list back button handler

* fix spawn signals on switch

* fix share details drawer

* fix drawer open close usage

* refactor listViewFocusController

* refactor focusController to make the logic more
straightforward

* fix focus on notification

* update config page for scrolling with tab

* fix crash on return with esc key

* fix focus navigation in dynamic delegate of list view

* fix focus move on qr code on share page

* refactor page logging settings for focus navigation

* update popup

* Bump version

* Add mandatory requirement for android.software.leanback.

* Fix importing files on TVs

* fix: add separate method for reading files to fix file reading on Android TV

* fix(android): add CHANGE_NETWORK_STATE permission for all Android versions

* Fix connection check for AWG/WG

* chore: minor fixes (#1235)

* fix: add a workaround to open files on Android TV due to lack of SAF

* fix: change the banner format for TV

* refactor: make TvFilePicker activity more sustainable

* fix: add the touch emulation method for Android TV

* fix: null uri processing

* fix: add the touch emulation method for Android TV

* fix: hide UI elements that use file saving

* chore: bump version code

* add `ScrollBarType`

* update initial config page

* refactor credentials setup page to handle the focus navigation

* add `setDelegateIndex` method to `listViewFocusController`

* fix focus behavior on new page/popup

* make minor fixes and clean up

* fix: get rid of the assign function call

* Scrollbar is on if the content is larger than a screen

* Fix selection in language change list

* Update select language list

* update logging settings page

* fix checked item in lists

* fix split tunneling settings

* make unchangable properties readonly

* refactor SwitcherType

* fix hide/unhide password

* `PageShare` readonly properties

* Fix list view focus moving on `PageShare`

* remove manual focus control on `PageShare`

* format `ListViewFocusController`

* format `FocusController`

* add `focusControl` with utility functions for
focus control

* refactor `listViewFocusController` acoording to `focusControl`

* refactor `focusConroller` according to `focusControl`

* add `printSectionName` method to `listViewController`

* remove arrow from `Close application` item

* fix focus movement in `ServersListView`

* `Restore from backup` is visible only on start screen

* `I have nothing` is visible only on start screen

* fix back button on `SelectLanguageDrawer`

* rename `focusControl` to `qmlUtils`

* fix `CMakeLists.txt`

* fix `ScrollBarType`

* fix `PageSetupWizardApiServicesList`

* fix focus movement on dynamic delegates in listView

* refactor `PageSetupWizardProtocols`

* remove comments and clean up

* fix `ListViewWithLabelsType`

* fix `PageProtocolCloakSettings`

* fix `PageSettingsAppSplitTunneling`

* fix `PageDevMenu`

* remove debug output from `FocusController`

* remove debug output from `ListViewFocusController`

* remove debug output from `focusControl`

* `focusControl` => `FocusControl`

---------

Co-authored-by: albexk <albexk@proton.me>
Co-authored-by: Nethius <nethiuswork@gmail.com>
This commit is contained in:
Cyril Anisimov 2024-12-31 04:16:52 +01:00 committed by GitHub
parent 212e9b3a91
commit 6acaab0ffa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
109 changed files with 4036 additions and 3700 deletions

View file

@ -4,7 +4,7 @@ import Qt5Compat.GraphicalEffects
import Style 1.0
Item {
FocusScope {
id: root
property string backButtonImage: "qrc:/images/controls/arrow-left.svg"
@ -15,12 +15,6 @@ Item {
visible: backButtonImage !== ""
onActiveFocusChanged: {
if (activeFocus) {
backButton.forceActiveFocus()
}
}
RowLayout {
id: content

View file

@ -35,10 +35,35 @@ Button {
property alias buttonTextLabel: buttonText
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
implicitHeight: 56
hoverEnabled: true
focusPolicy: Qt.TabFocus
onFocusChanged: {
if (root.activeFocus) {
@ -150,7 +175,7 @@ Button {
ButtonTextType {
id: buttonText
color: textColor
color: root.textColor
text: root.text
visible: root.text === "" ? false : true

View file

@ -22,6 +22,7 @@ RadioButton {
property string pressedBorderColor: AmneziaStyle.color.softGoldenApricot
property string selectedBorderColor: AmneziaStyle.color.goldenApricot
property string defaultBodredColor: AmneziaStyle.color.transparent
property string focusBorderColor: AmneziaStyle.color.paleGray
property int borderWidth: 0
implicitWidth: content.implicitWidth
@ -29,6 +30,32 @@ RadioButton {
hoverEnabled: true
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
indicator: Rectangle {
anchors.fill: parent
radius: 16
@ -52,6 +79,8 @@ RadioButton {
return pressedBorderColor
} else if (root.checked) {
return selectedBorderColor
} else if (root.activeFocus) {
return focusBorderColor
}
}
return defaultBodredColor
@ -59,7 +88,7 @@ RadioButton {
border.width: {
if (root.enabled) {
if(root.checked) {
if(root.checked || root.activeFocus) {
return 1
}
return root.pressed ? 1 : 0

View file

@ -25,10 +25,15 @@ Button {
property real textOpacity: 1.0
property alias focusItem: rightImage
property FlickableType parentFlickable
hoverEnabled: true
background: Rectangle {
id: backgroundRect
anchors.fill: parent
radius: 16
@ -39,13 +44,31 @@ 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
implicitHeight: content.implicitHeight
RowLayout {
id: content
anchors.fill: parent
Image {
@ -61,6 +84,7 @@ Button {
}
ColumnLayout {
ListItemTitleType {
text: root.headerText
visible: text !== ""
@ -123,6 +147,7 @@ Button {
Rectangle {
id: rightImageBackground
anchors.fill: parent
radius: 12
color: "transparent"
@ -131,10 +156,9 @@ Button {
PropertyAnimation { duration: 200 }
}
}
onClicked: {
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
root.clicked()
}
}
}

View file

@ -9,17 +9,14 @@ import "TextTypes"
Item {
id: root
readonly property string drawerExpanded: "expanded"
readonly property string drawerCollapsed: "collapsed"
readonly property string drawerExpandedStateName: "expanded"
readonly property string drawerCollapsedStateName: "collapsed"
readonly property bool isOpened: drawerContent.state === root.drawerExpanded || (drawerContent.state === root.drawerCollapsed && dragArea.drag.active === true)
readonly property bool isClosed: drawerContent.state === root.drawerCollapsed && dragArea.drag.active === false
readonly property bool isOpened: isExpandedStateActive() || (isCollapsedStateActive && (dragArea.drag.active === true))
readonly property bool isClosed: isCollapsedStateActive() && (dragArea.drag.active === false)
readonly property bool isExpanded: drawerContent.state === root.drawerExpanded
readonly property bool isCollapsed: drawerContent.state === root.drawerCollapsed
property Component collapsedContent
property Component expandedContent
property Component collapsedStateContent
property Component expandedStateContent
property string defaultColor: AmneziaStyle.color.onyxBlack
property string borderColor: AmneziaStyle.color.slateGray
@ -29,29 +26,41 @@ Item {
property int depthIndex: 0
signal entered
signal exited
signal cursorEntered
signal cursorExited
signal pressed(bool pressed, bool entered)
signal aboutToHide
signal aboutToShow
signal close
signal open
signal closeTriggered
signal openTriggered
signal closed
signal opened
function isExpandedStateActive() {
return isStateActive(drawerExpandedStateName)
}
function isCollapsedStateActive() {
return isStateActive(drawerCollapsedStateName)
}
function isStateActive(stateName) {
return drawerContent.state === stateName
}
Connections {
target: PageController
function onCloseTopDrawer() {
if (depthIndex === PageController.getDrawerDepth()) {
if (isCollapsed) {
if (isCollapsedStateActive()) {
return
}
aboutToHide()
drawerContent.state = root.drawerCollapsed
drawerContent.state = root.drawerCollapsedStateName
depthIndex = 0
closed()
}
@ -61,30 +70,52 @@ Item {
Connections {
target: root
function onClose() {
if (isCollapsed) {
function onCloseTriggered() {
if (isCollapsedStateActive()) {
return
}
aboutToHide()
drawerContent.state = root.drawerCollapsed
depthIndex = 0
PageController.setDrawerDepth(PageController.getDrawerDepth() - 1)
closed()
}
function onOpen() {
if (isExpanded) {
function onClosed() {
drawerContent.state = root.drawerCollapsedStateName
if (root.isCollapsedStateActive()) {
var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor()
if (initialPageNavigationBarColor !== 0xFF1C1D21) {
PageController.updateNavigationBarColor(initialPageNavigationBarColor)
}
}
depthIndex = 0
PageController.decrementDrawerDepth()
FocusController.dropRootObject(root)
}
function onOpenTriggered() {
if (root.isExpandedStateActive()) {
return
}
aboutToShow()
root.aboutToShow()
drawerContent.state = root.drawerExpanded
depthIndex = PageController.getDrawerDepth() + 1
PageController.setDrawerDepth(depthIndex)
opened()
root.opened()
}
function onOpened() {
drawerContent.state = root.drawerExpandedStateName
if (isExpandedStateActive()) {
if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) {
PageController.updateNavigationBarColor(0xFF1C1D21)
}
}
depthIndex = PageController.incrementDrawerDepth()
FocusController.pushRootObject(root)
}
}
@ -102,18 +133,17 @@ Item {
MouseArea {
id: emptyArea
anchors.fill: parent
enabled: root.isExpanded
visible: enabled
onClicked: {
root.close()
root.closeTriggered()
}
}
MouseArea {
id: dragArea
objectName: "dragArea"
anchors.fill: drawerContentBackground
cursorShape: root.isCollapsed ? Qt.PointingHandCursor : Qt.ArrowCursor
hoverEnabled: true
enabled: drawerContent.implicitHeight > 0
@ -125,35 +155,36 @@ Item {
/** If drag area is released at any point other than min or max y, transition to the other state */
onReleased: {
if (root.isCollapsed && drawerContent.y < dragArea.drag.maximumY) {
root.open()
if (isCollapsedStateActive() && drawerContent.y < dragArea.drag.maximumY) {
root.openTriggered()
return
}
if (root.isExpanded && drawerContent.y > dragArea.drag.minimumY) {
root.close()
if (isExpandedStateActive() && drawerContent.y > dragArea.drag.minimumY) {
root.closeTriggered()
return
}
}
onEntered: {
root.entered()
root.cursorEntered()
}
onExited: {
root.exited()
root.cursorExited()
}
onPressedChanged: {
root.pressed(pressed, entered)
}
onClicked: {
if (root.isCollapsed) {
root.open()
if (isCollapsedStateActive()) {
root.openTriggered()
}
}
}
Rectangle {
id: drawerContentBackground
objectName: "drawerContentBackground"
anchors { left: drawerContent.left; right: drawerContent.right; top: drawerContent.top }
height: root.height
@ -174,53 +205,80 @@ Item {
Item {
id: drawerContent
objectName: "drawerContent"
Drag.active: dragArea.drag.active
anchors.right: root.right
anchors.left: root.left
y: root.height - drawerContent.height
state: root.drawerCollapsed
implicitHeight: root.isCollapsed ? collapsedHeight : expandedHeight
onStateChanged: {
if (root.isCollapsed) {
var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor()
if (initialPageNavigationBarColor !== 0xFF1C1D21) {
PageController.updateNavigationBarColor(initialPageNavigationBarColor)
}
return
}
if (root.isExpanded) {
if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) {
PageController.updateNavigationBarColor(0xFF1C1D21)
}
return
}
}
state: root.drawerCollapsedStateName
states: [
State {
name: root.drawerCollapsed
name: root.drawerCollapsedStateName
PropertyChanges {
target: drawerContent
implicitHeight: collapsedHeight
y: root.height - root.collapsedHeight
}
PropertyChanges {
target: background
color: AmneziaStyle.color.transparent
}
PropertyChanges {
target: dragArea
cursorShape: Qt.PointingHandCursor
}
PropertyChanges {
target: emptyArea
enabled: false
visible: false
}
PropertyChanges {
target: collapsedLoader
// visible: true
}
PropertyChanges {
target: expandedLoader
visible: false
}
},
State {
name: root.drawerExpanded
name: root.drawerExpandedStateName
PropertyChanges {
target: drawerContent
implicitHeight: expandedHeight
y: dragArea.drag.minimumY
}
PropertyChanges {
target: background
color: Qt.rgba(14/255, 14/255, 17/255, 0.8)
}
PropertyChanges {
target: dragArea
cursorShape: Qt.ArrowCursor
}
PropertyChanges {
target: emptyArea
enabled: true
visible: true
}
PropertyChanges {
target: collapsedLoader
// visible: false
}
PropertyChanges {
target: expandedLoader
visible: true
}
}
]
transitions: [
Transition {
from: root.drawerCollapsed
to: root.drawerExpanded
from: root.drawerCollapsedStateName
to: root.drawerExpandedStateName
PropertyAnimation {
target: drawerContent
properties: "y"
@ -228,8 +286,8 @@ Item {
}
},
Transition {
from: root.drawerExpanded
to: root.drawerCollapsed
from: root.drawerExpandedStateName
to: root.drawerCollapsedStateName
PropertyAnimation {
target: drawerContent
properties: "y"
@ -241,7 +299,7 @@ Item {
Loader {
id: collapsedLoader
sourceComponent: root.collapsedContent
sourceComponent: root.collapsedStateContent
anchors.right: parent.right
anchors.left: parent.left
@ -250,8 +308,7 @@ Item {
Loader {
id: expandedLoader
visible: root.isExpanded
sourceComponent: root.expandedContent
sourceComponent: root.expandedStateContent
anchors.right: parent.right
anchors.left: parent.left

View file

@ -45,33 +45,56 @@ Item {
property Item drawerParent
property Component listView
signal open
signal close
signal openTriggered
signal closeTriggered
function popupClosedFunc() {
if (!GC.isMobile()) {
this.forceActiveFocus()
}
readonly property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
property var parentFlickable
onFocusChanged: {
if (root.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(root)
}
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
implicitWidth: rootButtonContent.implicitWidth
implicitHeight: rootButtonContent.implicitHeight
onOpen: {
menu.open()
onOpenTriggered: {
menu.openTriggered()
}
onClose: {
menu.close()
onCloseTriggered: {
menu.closeTriggered()
}
Keys.onEnterPressed: {
if (menu.isClosed) {
menu.openTriggered()
}
}
Keys.onReturnPressed: {
if (menu.isClosed) {
menu.openTriggered()
}
}
Rectangle {
@ -173,7 +196,7 @@ Item {
if (rootButtonClickedFunction && typeof rootButtonClickedFunction === "function") {
rootButtonClickedFunction()
} else {
menu.open()
menu.openTriggered()
}
}
}
@ -186,27 +209,10 @@ Item {
anchors.fill: parent
expandedHeight: drawerParent.height * drawerHeight
onClosed: {
root.popupClosedFunc()
}
expandedContent: Item {
expandedStateContent: 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
@ -218,61 +224,35 @@ Item {
BackButtonType {
id: backButton
backButtonImage: root.headerBackButtonImage
backButtonFunction: function() { menu.close() }
KeyNavigation.tab: listViewLoader.item
backButtonFunction: function() { menu.closeTriggered() }
}
}
FlickableType {
id: flickable
Column {
id: col
anchors.top: header.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 16
contentHeight: col.implicitHeight
Column {
id: col
anchors.top: parent.top
spacing: 16
Header2Type {
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 16
headerText: root.headerText
Header2Type {
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 16
anchors.rightMargin: 16
width: parent.width
}
headerText: root.headerText
width: parent.width
}
Loader {
id: listViewLoader
sourceComponent: root.listView
onLoaded: {
listViewLoader.item.parentFlickable = flickable
listViewLoader.item.lastItemTabClicked = function() {
focusItem.forceActiveFocus()
}
}
}
Loader {
id: listViewLoader
sourceComponent: root.listView
}
}
}
}
Keys.onEnterPressed: {
if (menu.isClosed) {
menu.open()
}
}
Keys.onReturnPressed: {
if (menu.isClosed) {
menu.open()
}
}
}

View file

@ -7,10 +7,11 @@ Flickable {
function ensureVisible(item) {
if (item.y < fl.contentY) {
fl.contentY = item.y
fl.contentY = item.y - 40 // 40 is a top margin
} else if (item.y + item.height > fl.contentY + fl.height) {
fl.contentY = item.y + item.height - fl.height + 40 // 40 is a bottom margin
}
fl.returnToBounds()
}
clip: true
@ -24,7 +25,7 @@ Flickable {
Keys.onUpPressed: scrollBar.decrease()
Keys.onDownPressed: scrollBar.increase()
ScrollBar.vertical: ScrollBar {
ScrollBar.vertical: ScrollBarType {
id: scrollBar
policy: fl.height >= fl.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn
}

View file

@ -19,8 +19,6 @@ Item {
property string descriptionText
focus: true
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight

View file

@ -27,6 +27,32 @@ RadioButton {
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
indicator: Rectangle {
anchors.fill: parent
radius: 16

View file

@ -24,22 +24,39 @@ Button {
property int borderFocusedWidth: 1
hoverEnabled: true
focus: true
focusPolicy: Qt.TabFocus
icon.source: image
icon.color: root.enabled ? imageColor : disableImageColor
property Flickable parentFlickable
property bool isFocusable: true
onFocusChanged: {
if (root.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(this)
}
}
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
Keys.onEnterPressed: root.clicked()
Keys.onReturnPressed: root.clicked()
Behavior on icon.color {
PropertyAnimation { duration: 200 }
}

View file

@ -41,6 +41,32 @@ Item {
property bool descriptionOnTop: false
property bool hideDescription: true
property bool isFocusable: !(eyeImage.visible || rightImage.visible) // TODO: this component already has focusable items
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin
implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin

View file

@ -17,7 +17,7 @@ ListView {
property bool dividerVisible: false
currentIndex: 0
property int selectedIndex: 0
width: rootWidth
height: menuContent.contentItem.height
@ -45,7 +45,7 @@ ListView {
rightImageSource: imageSource
clickedFunction: function() {
menuContent.currentIndex = index
menuContent.selectedIndex = index
menuContent.selectedText = name
if (menuContent.clickedFunction && typeof menuContent.clickedFunction === "function") {
menuContent.clickedFunction()
@ -62,7 +62,7 @@ ListView {
}
Component.onCompleted: {
if (menuContent.currentIndex === index) {
if (menuContent.selectedIndex === index) {
menuContent.selectedText = name
}
}

View file

@ -20,155 +20,132 @@ ListView {
property var clickedFunction
currentIndex: 0
property int selectedIndex: 0
width: rootWidth
height: root.contentItem.height
clip: true
interactive: false
reuseItems: true
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))
}
}
property bool isFocusable: true
ButtonGroup {
id: buttonGroup
}
function triggerCurrentItem() {
var item = root.itemAtIndex(currentIndex)
var radioButton = item.children[0].children[0]
radioButton.clicked()
var item = root.itemAtIndex(selectedIndex)
item.selectable.clicked()
}
delegate: Item {
delegate: ColumnLayout {
id: content
property alias selectable: radioButton
implicitWidth: rootWidth
implicitHeight: content.implicitHeight
onActiveFocusChanged: {
if (activeFocus) {
radioButton.forceActiveFocus()
RadioButton {
id: radioButton
implicitWidth: parent.width
implicitHeight: radioButtonContent.implicitHeight
hoverEnabled: true
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
}
ColumnLayout {
id: content
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
anchors.fill: parent
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
RadioButton {
id: radioButton
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
implicitWidth: parent.width
implicitHeight: radioButtonContent.implicitHeight
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
hoverEnabled: true
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
indicator: Rectangle {
width: parent.width - 1
height: parent.height
color: radioButton.hovered ? AmneziaStyle.color.slateGray : AmneziaStyle.color.onyxBlack
border.color: radioButton.focus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.transparent
border.width: radioButton.focus ? 1 : 0
indicator: Rectangle {
width: parent.width - 1
height: parent.height
color: radioButton.hovered ? AmneziaStyle.color.slateGray : AmneziaStyle.color.onyxBlack
border.color: radioButton.focus ? AmneziaStyle.color.paleGray : AmneziaStyle.color.transparent
border.width: radioButton.focus ? 1 : 0
Behavior on color {
PropertyAnimation { duration: 200 }
}
Behavior on border.color {
PropertyAnimation { duration: 200 }
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
}
Behavior on color {
PropertyAnimation { duration: 200 }
}
Behavior on border.color {
PropertyAnimation { duration: 200 }
}
RowLayout {
id: radioButtonContent
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
}
}
anchors.rightMargin: 16
anchors.leftMargin: 16
RowLayout {
id: radioButtonContent
anchors.fill: parent
z: 1
anchors.rightMargin: 16
anchors.leftMargin: 16
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 20
Layout.bottomMargin: 20
z: 1
text: name
maximumLineCount: root.textMaximumLineCount
elide: root.textElide
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 20
Layout.bottomMargin: 20
}
text: name
maximumLineCount: root.textMaximumLineCount
elide: root.textElide
Image {
source: imageSource
visible: radioButton.checked
width: 24
height: 24
Layout.rightMargin: 8
}
}
ButtonGroup.group: buttonGroup
checked: root.currentIndex === index
Image {
source: imageSource
visible: radioButton.checked
onClicked: {
root.currentIndex = index
root.selectedText = name
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
width: 24
height: 24
Layout.rightMargin: 8
}
}
ButtonGroup.group: buttonGroup
checked: root.selectedIndex === index
onClicked: {
root.selectedIndex = index
root.selectedText = name
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
}
Component.onCompleted: {
if (root.currentIndex === index) {
if (root.selectedIndex === index) {
root.selectedText = name
}
}

View file

@ -9,53 +9,22 @@ Item {
property StackView stackView: StackView.view
property var defaultActiveFocusItem: null
onVisibleChanged: {
if (visible && !GC.isMobile()) {
if (visible) {
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
// anchors.fill: parent
// enabled: true
// onPressed: function(mouse) {
// forceActiveFocus()
// mouse.accepted = false
// }
// }
// Set a timer to set focus after a short delay
Timer {
id: timer
interval: 100 // Milliseconds
interval: 200 // Milliseconds
onTriggered: {
if (defaultActiveFocusItem) {
defaultActiveFocusItem.forceActiveFocus()
}
console.debug(">>> PageType timer triggered")
FocusController.resetRootObject()
FocusController.setFocusOnDefaultItem()
}
repeat: false // Stop the timer after one trigger
running: !GC.isMobile() // Start the timer
running: true // Start the timer
}
}

View file

@ -5,6 +5,7 @@ import QtQuick.Layouts
import Style 1.0
import "TextTypes"
import "../Config"
Popup {
id: root
@ -28,11 +29,11 @@ Popup {
}
onOpened: {
focusItem.forceActiveFocus()
timer.start()
}
onClosed: {
PageController.forceStackActiveFocus()
FocusController.dropRootObject(root)
}
background: Rectangle {
@ -42,6 +43,17 @@ Popup {
radius: 4
}
Timer {
id: timer
interval: 200 // Milliseconds
onTriggered: {
FocusController.pushRootObject(root)
FocusController.setFocusItem(closeButton)
}
repeat: false // Stop the timer after one trigger
running: true // Start the timer
}
contentItem: Item {
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
@ -72,11 +84,6 @@ Popup {
}
}
Item {
id: focusItem
KeyNavigation.tab: closeButton
}
BasicButtonType {
id: closeButton
visible: closeButtonVisible
@ -92,7 +99,6 @@ Popup {
borderWidth: 0
text: qsTr("Close")
KeyNavigation.tab: focusItem
clickedFunc: function() {
root.close()

View file

@ -0,0 +1,11 @@
import QtQuick
import QtQuick.Controls
import "./"
import "../Controls2"
ScrollBar {
id: root
policy: parent.height >= parent.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn
}

View file

@ -35,10 +35,37 @@ Switch {
property string hoveredIndicatorBackgroundColor: AmneziaStyle.color.translucentWhite
property string defaultIndicatorBackgroundColor: AmneziaStyle.color.transparent
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
hoverEnabled: enabled ? true : false
focusPolicy: Qt.TabFocus
property FlickableType parentFlickable: null
onFocusChanged: {
if (root.activeFocus) {
if (root.parentFlickable) {
@ -131,13 +158,15 @@ Switch {
enabled: false
}
Keys.onEnterPressed: {
root.checked = !root.checked
root.checkedChanged()
}
Keys.onEnterPressed: event => handleSwitch(event)
Keys.onReturnPressed: event => handleSwitch(event)
Keys.onSpacePressed: event => handleSwitch(event)
Keys.onReturnPressed: {
root.checked = !root.checked
root.checkedChanged()
function handleSwitch(event) {
if (!event.isAutoRepeat) {
root.checked = !root.checked
root.checkedChanged()
}
event.accepted = true
}
}

View file

@ -17,10 +17,35 @@ TabButton {
property bool isSelected: false
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
implicitHeight: 48
hoverEnabled: true
focusPolicy: Qt.TabFocus
background: Rectangle {
id: background

View file

@ -14,13 +14,38 @@ TabButton {
property bool isSelected: false
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
property string borderFocusedColor: AmneziaStyle.color.paleGray
property int borderFocusedWidth: 1
property var clickedFunc
hoverEnabled: true
focusPolicy: Qt.TabFocus
icon.source: image
icon.color: isSelected ? selectedColor : defaultColor
@ -41,7 +66,7 @@ TabButton {
cursorShape: Qt.PointingHandCursor
enabled: false
}
Keys.onEnterPressed: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()

View file

@ -78,9 +78,6 @@ Rectangle {
placeholderText: root.placeholderText
text: root.text
KeyNavigation.tab: firstButton
onCursorVisibleChanged: {
if (textArea.cursorVisible) {
fl.interactive = true

View file

@ -40,6 +40,7 @@ Item {
implicitHeight: content.implicitHeight
property FlickableType parentFlickable
Connections {
target: textField
function onFocusChanged() {
@ -84,7 +85,16 @@ Item {
TextField {
id: textField
activeFocusOnTab: false
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
enabled: root.textFieldEditable
color: root.enabled ? root.textFieldTextColor : root.textFieldTextDisabledColor
@ -209,9 +219,9 @@ Item {
clickedFunc()
}
if (KeyNavigation.tab) {
KeyNavigation.tab.forceActiveFocus();
}
// if (KeyNavigation.tab) {
// KeyNavigation.tab.forceActiveFocus();
// }
}
Keys.onReturnPressed: {
@ -219,8 +229,8 @@ Item {
clickedFunc()
}
if (KeyNavigation.tab) {
KeyNavigation.tab.forceActiveFocus();
}
// if (KeyNavigation.tab) {
// KeyNavigation.tab.forceActiveFocus();
// }
}
}

View file

@ -28,8 +28,33 @@ RadioButton {
property string imageSource
property bool showImage
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
hoverEnabled: true
focusPolicy: Qt.TabFocus
indicator: Rectangle {
id: background