add focusController class

This commit is contained in:
Cyril Anisimov 2024-09-14 19:42:55 +02:00
parent 8547de82ea
commit 02bbcd3a31
76 changed files with 1906 additions and 1576 deletions

View file

@ -4,23 +4,27 @@ import Qt5Compat.GraphicalEffects
import Style 1.0
Item {
FocusScope {
id: root
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
visible: backButtonImage !== ""
onActiveFocusChanged: {
if (activeFocus) {
backButton.forceActiveFocus()
}
}
RowLayout {
id: content
@ -35,6 +39,8 @@ Item {
implicitWidth: 40
implicitHeight: 40
// focus: true
onClicked: {
if (backButtonFunction && typeof backButtonFunction === "function") {
backButtonFunction()

View file

@ -35,12 +35,25 @@ Button {
property alias buttonTextLabel: buttonText
property bool isFocusable: true
Keys.onTabPressed: {
console.debug("--> Tab is pressed on BasicButtonType: ", objectName)
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
console.debug("--> Shift+Tab is pressed on ", objectName)
FocusController.previousKeyTabItem()
}
implicitHeight: 56
hoverEnabled: true
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

@ -25,10 +25,13 @@ Button {
property real textOpacity: 1.0
property alias focusItem: rightImage
hoverEnabled: true
background: Rectangle {
id: backgroundRect
anchors.fill: parent
radius: 16
@ -44,8 +47,10 @@ Button {
anchors.right: parent.right
implicitHeight: content.implicitHeight
RowLayout {
id: content
anchors.fill: parent
Image {
@ -61,6 +66,7 @@ Button {
}
ColumnLayout {
ListItemTitleType {
text: root.headerText
visible: text !== ""
@ -123,6 +129,7 @@ Button {
Rectangle {
id: rightImageBackground
anchors.fill: parent
radius: 12
color: "transparent"
@ -131,10 +138,9 @@ Button {
PropertyAnimation { duration: 200 }
}
}
onClicked: {
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
root.clicked()
}
}
}

View file

@ -9,17 +9,17 @@ 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 isExpanded: isExpandedStateActive()
// readonly property bool isCollapsed: isCollapsedStateActive()
readonly property bool isExpanded: drawerContent.state === root.drawerExpanded
readonly property bool isCollapsed: drawerContent.state === root.drawerCollapsed
readonly property bool isOpened: isExpandedStateActive() || (isCollapsedStateActive && (dragArea.drag.active === true))
readonly property bool isClosed: isCollapsedStateActive() && (dragArea.drag.active === false)
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 +29,43 @@ 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() {
console.debug("===>> onCloseTopDrawer function")
if (depthIndex === PageController.getDrawerDepth()) {
if (isCollapsed) {
if (isCollapsedStateActive()) {
return
}
aboutToHide()
drawerContent.state = root.drawerCollapsed
drawerContent.state = root.drawerCollapsedStateName
depthIndex = 0
closed()
}
@ -61,30 +75,62 @@ Item {
Connections {
target: root
function onClose() {
if (isCollapsed) {
function onCloseTriggered() {
console.debug("***>> onClose root connection")
if (isCollapsedStateActive()) {
return
}
aboutToHide()
drawerContent.state = root.drawerCollapsed
depthIndex = 0
PageController.setDrawerDepth(PageController.getDrawerDepth() - 1)
closed()
}
function onOpen() {
if (isExpanded) {
function onClosed() {
console.debug("***>> onClosed root connection")
drawerContent.state = root.drawerCollapsedStateName
if (root.isCollapsedStateActive()) {
var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor()
if (initialPageNavigationBarColor !== 0xFF1C1D21) {
PageController.updateNavigationBarColor(initialPageNavigationBarColor)
}
}
depthIndex = 0
PageController.decrementDrawerDepth()
FocusController.setRootItem(null)
}
function onOpenTriggered() {
console.debug("===>> onOpen root connection")
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
console.debug("===>> onOpened root connection")
if (isExpandedStateActive()) {
console.error("new state - extended")
if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) {
PageController.updateNavigationBarColor(0xFF1C1D21)
}
}
depthIndex = PageController.incrementDrawerDepth()
FocusController.setRootItem(root)
console.debug("===>> Root item has changed to ", root)
}
}
@ -102,18 +148,18 @@ Item {
MouseArea {
id: emptyArea
anchors.fill: parent
enabled: root.isExpanded
visible: enabled
onClicked: {
root.close()
console.debug("===>> onClicked emptyArea")
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 +171,46 @@ 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()
console.debug("===>> onReleased dragArea")
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()
console.debug("===>> onEntered dragArea")
root.cursorEntered()
}
onExited: {
root.exited()
console.debug("===>> onExited dragArea")
root.cursorExited()
}
onPressedChanged: {
console.debug("===>> onPressedChanged dragArea")
root.pressed(pressed, entered)
}
onClicked: {
if (root.isCollapsed) {
root.open()
console.debug("===>> onClicked dragArea")
if (isCollapsedStateActive()) {
root.openTriggered()
}
}
}
Rectangle {
id: drawerContentBackground
objectName: "drawerContentBackground"
anchors { left: drawerContent.left; right: drawerContent.right; top: drawerContent.top }
height: root.height
@ -174,53 +231,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 +312,8 @@ Item {
}
},
Transition {
from: root.drawerExpanded
to: root.drawerCollapsed
from: root.drawerExpandedStateName
to: root.drawerCollapsedStateName
PropertyAnimation {
target: drawerContent
properties: "y"
@ -241,7 +325,7 @@ Item {
Loader {
id: collapsedLoader
sourceComponent: root.collapsedContent
sourceComponent: root.collapsedStateContent
anchors.right: parent.right
anchors.left: parent.left
@ -250,8 +334,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,51 @@ 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)
}
}
}
// 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()
onOpenTriggered: {
menu.openTriggered()
}
onClose: {
menu.close()
onCloseTriggered: {
menu.closeTriggered()
}
Keys.onEnterPressed: {
if (menu.isClosed) {
menu.openTriggered()
}
}
Keys.onReturnPressed: {
if (menu.isClosed) {
menu.openTriggered()
}
}
Rectangle {
@ -153,6 +171,8 @@ Item {
}
ImageButtonType {
// isFocusable: false
Layout.rightMargin: 16
implicitWidth: 40
@ -173,7 +193,7 @@ Item {
if (rootButtonClickedFunction && typeof rootButtonClickedFunction === "function") {
rootButtonClickedFunction()
} else {
menu.open()
menu.openTriggered()
}
}
}
@ -186,27 +206,14 @@ Item {
anchors.fill: parent
expandedHeight: drawerParent.height * drawerHeight
onClosed: {
root.popupClosedFunc()
}
// 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 +225,43 @@ Item {
BackButtonType {
id: backButton
backButtonImage: root.headerBackButtonImage
backButtonFunction: function() { menu.close() }
KeyNavigation.tab: listViewLoader.item
backButtonFunction: function() { menu.closeTriggered() }
// KeyNavigation.tab: listViewLoader.item
}
}
FlickableType {
id: flickable
anchors.top: header.bottom
anchors.topMargin: 16
contentHeight: col.implicitHeight
Column {
id: col
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
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
Loader {
id: listViewLoader
sourceComponent: root.listView
width: parent.width
}
Loader {
id: listViewLoader
sourceComponent: root.listView
onLoaded: {
listViewLoader.item.parentFlickable = flickable
listViewLoader.item.lastItemTabClicked = function() {
focusItem.forceActiveFocus()
}
}
onLoaded: {
// listViewLoader.item.parentFlickable = flickable
// FocusController.reload()
// listViewLoader.item.lastItemTabClicked = function() {
// focusItem.forceActiveFocus()
// }
}
}
}
}
}
Keys.onEnterPressed: {
if (menu.isClosed) {
menu.open()
}
}
Keys.onReturnPressed: {
if (menu.isClosed) {
menu.open()
}
}
}

View file

@ -7,7 +7,7 @@ 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
}

View file

@ -27,6 +27,18 @@ RadioButton {
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
property bool isFocusable: true
Keys.onTabPressed: {
console.debug("--> Tab is pressed on BasicButtonType: ", objectName)
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
console.debug("--> Shift+Tab is pressed on ", objectName)
FocusController.previousKeyTabItem()
}
indicator: Rectangle {
anchors.fill: parent
radius: 16

View file

@ -24,22 +24,23 @@ 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.onEnterPressed: root.clicked()
Keys.onReturnPressed: root.clicked()
Behavior on icon.color {
PropertyAnimation { duration: 200 }
}

View file

@ -41,27 +41,37 @@ 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()
}
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)
}
}
}
// 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)
}
}
}
}
// Connections {
// target: rightImage
// function onFocusChanged() {
// if (rightImage.activeFocus) {
// if (root.parentFlickable) {
// root.parentFlickable.ensureVisible(root)
// }
// }
// }
// }
MouseArea {
anchors.fill: parent

View file

@ -33,40 +33,14 @@ ListView {
property int currentFocusIndex: 0
activeFocusOnTab: true
onActiveFocusChanged: {
if (activeFocus) {
this.currentFocusIndex = 0
this.itemAtIndex(currentFocusIndex).forceActiveFocus()
}
}
property bool isFocusable: true
Keys.onTabPressed: {
if (currentFocusIndex < this.count - 1) {
currentFocusIndex += 1
} else {
currentFocusIndex = 0
}
this.itemAtIndex(currentFocusIndex).forceActiveFocus()
FocusController.nextKeyTabItem()
}
Item {
id: focusItem
Keys.onTabPressed: {
root.forceActiveFocus()
}
}
onVisibleChanged: {
if (visible) {
focusItem.forceActiveFocus()
}
}
onCurrentFocusIndexChanged: {
if (parentFlickable) {
parentFlickable.ensureVisible(this.itemAtIndex(currentFocusIndex))
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
ButtonGroup {
@ -83,12 +57,6 @@ ListView {
implicitWidth: rootWidth
implicitHeight: content.implicitHeight
onActiveFocusChanged: {
if (activeFocus) {
radioButton.forceActiveFocus()
}
}
ColumnLayout {
id: content

View file

@ -9,30 +9,12 @@ Item {
property StackView stackView: StackView.view
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
@ -49,11 +31,10 @@ Item {
// Set a timer to set focus after a short delay
Timer {
id: timer
interval: 100 // Milliseconds
interval: 1000 // Milliseconds // TODO: return to 500
onTriggered: {
if (defaultActiveFocusItem) {
defaultActiveFocusItem.forceActiveFocus()
}
console.debug("===>> Page creation completed")
FocusController.resetFocus()
}
repeat: false // Stop the timer after one trigger
running: !GC.isMobile() // Start the timer

View file

@ -28,11 +28,11 @@ Popup {
}
onOpened: {
focusItem.forceActiveFocus()
FocusController.setRoot(root)
}
onClosed: {
PageController.forceStackActiveFocus()
FocusController.setRoot(null)
}
background: Rectangle {
@ -72,11 +72,6 @@ Popup {
}
}
Item {
id: focusItem
KeyNavigation.tab: closeButton
}
BasicButtonType {
id: closeButton
visible: closeButtonVisible
@ -92,7 +87,6 @@ Popup {
borderWidth: 0
text: qsTr("Close")
KeyNavigation.tab: focusItem
clickedFunc: function() {
root.close()

View file

@ -35,6 +35,16 @@ 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()
}
hoverEnabled: enabled ? true : false
focusPolicy: Qt.TabFocus

View file

@ -17,10 +17,19 @@ TabButton {
property bool isSelected: false
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
implicitHeight: 48
hoverEnabled: true
focusPolicy: Qt.TabFocus
background: Rectangle {
id: background

View file

@ -14,13 +14,23 @@ TabButton {
property bool isSelected: false
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
property string borderFocusedColor: AmneziaStyle.color.paleGray
property int borderFocusedWidth: 1
property var clickedFunc
hoverEnabled: true
focusPolicy: Qt.TabFocus
// focusPolicy: Qt.TabFocus
icon.source: image
icon.color: isSelected ? selectedColor : defaultColor
@ -41,8 +51,9 @@ TabButton {
cursorShape: Qt.PointingHandCursor
enabled: false
}
Keys.onEnterPressed: {
console.log("$$$$$$$$$ ENTER PRESSED INSIDE TABIMAGEBUTTONTYPE")
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

@ -84,7 +84,16 @@ Item {
TextField {
id: textField
activeFocusOnTab: false
// 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 +218,9 @@ Item {
clickedFunc()
}
if (KeyNavigation.tab) {
KeyNavigation.tab.forceActiveFocus();
}
// if (KeyNavigation.tab) {
// KeyNavigation.tab.forceActiveFocus();
// }
}
Keys.onReturnPressed: {
@ -219,8 +228,8 @@ Item {
clickedFunc()
}
if (KeyNavigation.tab) {
KeyNavigation.tab.forceActiveFocus();
}
// if (KeyNavigation.tab) {
// KeyNavigation.tab.forceActiveFocus();
// }
}
}

View file

@ -28,8 +28,14 @@ RadioButton {
property string imageSource
property bool showImage
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
hoverEnabled: true
focusPolicy: Qt.TabFocus
// focusPolicy: Qt.TabFocus
indicator: Rectangle {
id: background