[ui] Controls: add SearchBar component + better keyboard focus handling

* use it in MetadataListView and GraphEditor 'add Node' menu
* GraphEditor: forward MenuItem key events to searchBar to be able to continue editing the filter even if it lost active focus
This commit is contained in:
Yann Lanthony 2019-01-07 16:48:17 +01:00
parent b5c985b3fb
commit b46a2dbba1
4 changed files with 62 additions and 28 deletions

View file

@ -0,0 +1,41 @@
import QtQuick 2.9
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import MaterialIcons 2.2
/**
* Basic SearchBar component with an appropriate icon and a TextField.
*/
FocusScope {
property alias textField: field
property alias text: field.text
implicitHeight: childrenRect.height
Keys.forwardTo: [field]
function forceActiveFocus() {
field.forceActiveFocus()
}
function clear() {
field.clear()
}
RowLayout {
width: parent.width
MaterialLabel {
text: MaterialIcons.search
}
TextField {
id: field
focus: true
Layout.fillWidth: true
selectByMouse: true
}
}
}

View file

@ -4,3 +4,4 @@ FloatingPane 1.0 FloatingPane.qml
Group 1.0 Group.qml Group 1.0 Group.qml
MessageDialog 1.0 MessageDialog.qml MessageDialog 1.0 MessageDialog.qml
Panel 1.0 Panel.qml Panel 1.0 Panel.qml
SearchBar 1.0 SearchBar.qml

View file

@ -142,18 +142,14 @@ Item {
if(visible) { if(visible) {
// when menu is shown, // when menu is shown,
// clear and give focus to the TextField filter // clear and give focus to the TextField filter
filterTextField.clear() searchBar.clear()
filterTextField.forceActiveFocus() searchBar.forceActiveFocus()
} }
} }
TextField { SearchBar {
id: filterTextField id: searchBar
selectByMouse: true
width: parent.width width: parent.width
// ensure down arrow give focus to the first MenuItem
// (without this, we have to pressed the down key twice to do so)
Keys.onDownPressed: nextItemInFocusChain().forceActiveFocus()
} }
Repeater { Repeater {
@ -164,24 +160,28 @@ Item {
id: menuItemDelegate id: menuItemDelegate
font.pointSize: 8 font.pointSize: 8
padding: 3 padding: 3
// Hide items that does not match the filter text // Hide items that does not match the filter text
visible: modelData.toLowerCase().indexOf(filterTextField.text.toLocaleLowerCase()) > -1 visible: modelData.toLowerCase().indexOf(searchBar.text.toLowerCase()) > -1
// Reset menu currentIndex if highlighted items gets filtered out
onVisibleChanged: if(highlighted) newNodeMenu.currentIndex = 0
text: modelData text: modelData
// Forward key events to the search bar to continue typing seamlessly
// even if this delegate took the activeFocus due to mouse hovering
Keys.forwardTo: [searchBar.textField]
Keys.onPressed: { Keys.onPressed: {
event.accepted = false;
switch(event.key) switch(event.key)
{ {
case Qt.Key_Return: case Qt.Key_Return:
case Qt.Key_Enter: case Qt.Key_Enter:
// create node on validation (Enter/Return keys) // create node on validation (Enter/Return keys)
newNodeMenu.createNode(modelData) newNodeMenu.createNode(modelData);
newNodeMenu.dismiss() newNodeMenu.close();
break; event.accepted = true;
case Qt.Key_Home:
// give focus back to filter
filterTextField.forceActiveFocus()
break; break;
default: default:
break; searchBar.textField.forceActiveFocus();
} }
} }
// Create node on mouse click // Create node on mouse click

View file

@ -121,17 +121,9 @@ FloatingPane {
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
// Search toolbar SearchBar {
RowLayout { id: searchBar
Label { Layout.fillWidth: true
text: MaterialIcons.search
font.family: MaterialIcons.fontFamily
}
TextField {
id: filter
Layout.fillWidth: true
z: 2
}
} }
// Metadata ListView // Metadata ListView
@ -148,7 +140,7 @@ FloatingPane {
model: metadataModel model: metadataModel
sortRole: "raw" sortRole: "raw"
filterRole: "raw" filterRole: "raw"
filterValue: filter.text filterValue: searchBar.text
delegate: RowLayout { delegate: RowLayout {
width: parent.width width: parent.width
Label { Label {