From de4be82bf141f1eb5f57189bf251123e57996f28 Mon Sep 17 00:00:00 2001 From: Christian Mollekopf Date: Wed, 16 Aug 2017 23:27:51 -0600 Subject: Non listview based conversationview The listview deals badly with non uniformly sized items. We use the buffer hack to ensure all items are loaded so it works at all, and setting the current index resulted in unpredictable scrolling. With this new approach we manage everything ourselves in a Flickable, and just always load all delegates (which we also did before, but with a hack). As an optimization it should be possible to avoid loading some delegates until they become visible. Note that ConversationView is thightly coupled to ConversationListView due to dependencies on some properties in the delegate. This could be handled more elegantly with attached properties. In any case, this seems to work much, much better. --- framework/qml/ConversationListView.qml | 138 +++++++++++++++++++++++++++++++++ framework/qml/ConversationView.qml | 82 ++++---------------- framework/qmldir | 1 + 3 files changed, 155 insertions(+), 66 deletions(-) create mode 100644 framework/qml/ConversationListView.qml (limited to 'framework') diff --git a/framework/qml/ConversationListView.qml b/framework/qml/ConversationListView.qml new file mode 100644 index 00000000..fb91e14d --- /dev/null +++ b/framework/qml/ConversationListView.qml @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2016 Michael Bohlender, + * Copyright (C) 2017 Christian Mollekopf, + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.7 +import QtQuick.Controls 2 +import QtQuick.Layouts 1.1 +import org.kube.framework 1.0 as Kube + +import QtQml 2.2 as QtQml + +Flickable { + id: root + focus: true + property alias model: repeater.model + property alias delegate: repeater.delegate + property int currentIndex: -1 + + property var currentItem: null + + function setCurrentItem() { + if (currentItem) { + currentItem.isCurrentItem = false + } + if (currentIndex >= 0 && activeFocus) { + var item = repeater.itemAt(currentIndex) + if (item) { + item.isCurrentItem = true + currentItem = item + } + } else { + currentItem = null + } + } + + onCurrentIndexChanged: { + setCurrentItem() + } + + onActiveFocusChanged: { + setCurrentItem() + } + + //Optimize for view quality + pixelAligned: true + + contentWidth: width + contentHeight: col.height + + function scrollToIndex(index) { + var item = repeater.itemAt(index) + var pos = item.y + var scrollToEndPos = (root.contentHeight - root.height) + //Avoid scrolling past the end + if (pos < scrollToEndPos) { + root.contentY = pos + } else { + root.contentY = scrollToEndPos + } + } + + onContentHeightChanged: { + if (repeater.count) { + //Scroll to the last item + currentIndex = repeater.count - 1 + scrollToIndex(repeater.count - 1) + } + } + + property real span : contentY + height + Column { + id: col + width: parent.width + spacing: 2 + Repeater { + id: repeater + onCountChanged: { + for (var i = 0; i < count; i++) { + itemAt(i).index = i + } + } + } + } + + function incrementCurrentIndex() { + if (currentIndex < repeater.count - 1) { + currentIndex = currentIndex + 1 + } + } + + function decrementCurrentIndex() { + if (currentIndex > 0) { + currentIndex = currentIndex - 1 + } + } + + Keys.onDownPressed: { + incrementCurrentIndex() + scrollToIndex(currentIndex) + } + + Keys.onUpPressed: { + decrementCurrentIndex() + scrollToIndex(currentIndex) + } + + Kube.ScrollHelper { + id: scrollHelper + flickable: root + anchors.fill: parent + } + + //Intercept all scroll events, + //necessary due to the webengineview + Kube.MouseProxy { + anchors.fill: parent + target: scrollHelper + forwardWheelEvents: true + } + + ScrollBar.vertical: ScrollBar {} + +} diff --git a/framework/qml/ConversationView.qml b/framework/qml/ConversationView.qml index 85707300..03ea3065 100644 --- a/framework/qml/ConversationView.qml +++ b/framework/qml/ConversationView.qml @@ -36,7 +36,6 @@ FocusScope { filter: Kube.Messages.mailSelection onMessageReceived: { root.mail = message.mail - listView.forceLayout() } } @@ -52,10 +51,9 @@ FocusScope { anchors.fill: parent color: Kube.Colors.backgroundColor - Kube.ListView { + Kube.ConversationListView { id: listView focus: true - currentIndex: -1 anchors { top: parent.top @@ -65,69 +63,33 @@ FocusScope { //Shrink the listview if the content doesn't fill the full height, so the email appears on top instead of on the bottom. height: Math.min(contentHeight, parent.height) - verticalLayoutDirection: ListView.BottomToTop - - onActiveFocusChanged: { - if (activeFocus) { - if (currentIndex < 0) { - currentIndex = 0 - } - } else { - currentIndex = -1 - } - } - - //Position view so the last email begins on top of the view - onContentHeightChanged: { - //FIXME This doesn't work quite correctly when we have headers and footers in the listview and the mail loads to slowly and only one item to show. - listView.positionViewAtIndex(currentIndex, ListView.End) - } - - Keys.onDownPressed: { - decrementCurrentIndex() - positionViewAtIndex(listView.currentIndex, ListView.End) - } - - Keys.onUpPressed: { - incrementCurrentIndex() - positionViewAtIndex(listView.currentIndex, ListView.End) - } - - onCurrentIndexChanged: { - markAsReadTimer.restart(); - } - model: Kube.MailListModel { mail: root.mail } - header: Item { - height: Kube.Units.gridUnit * 0.5 - width: parent.width - } - - footer: Item { - height: Kube.Units.gridUnit - width: parent.width - } - delegate: FocusScope { id: delegateRoot property var currentData: model + property bool isCurrentItem: false + property int index: -1 focus: false activeFocusOnTab: false height: sheet.height + Kube.Units.gridUnit - width: parent.width + width: listView.width visible: !((root.hideTrash && model.trash) || (root.hideNonTrash && !model.trash)) MouseArea { anchors.fill: parent hoverEnabled: true - onEntered: delegateRoot.ListView.view.currentIndex = index - onClicked: delegateRoot.ListView.view.currentIndex = index + onEntered: listView.currentIndex = delegateRoot.index + onClicked: { + listView.currentIndex = delegateRoot.index + listView.forceActiveFocus(Qt.MouseFocusReason) + } + z: 1.0 } MailViewer { @@ -147,19 +109,15 @@ FocusScope { draft: model.draft sent: model.sent incomplete: model.incomplete - current: delegateRoot.ListView.isCurrentItem + current: delegateRoot.isCurrentItem } } - - //Optimize for view quality - pixelAligned: true - - - //The cacheBuffer needs to be large enough to fit the whole thread. - //Otherwise the contentHeight will constantly increase and decrease, - //which will break lot's of things. - cacheBuffer: 100000 + onCurrentItemChanged: { + if (currentItem) { + markAsReadTimer.restart() + } + } Timer { id: markAsReadTimer @@ -172,14 +130,6 @@ FocusScope { } } } - - //Intercept all scroll events, - //necessary due to the webengineview - Kube.MouseProxy { - anchors.fill: parent - target: listView.mouseProxy - forwardWheelEvents: true - } } } } diff --git a/framework/qmldir b/framework/qmldir index 02a66bb6..7ce99e2a 100644 --- a/framework/qmldir +++ b/framework/qmldir @@ -1,6 +1,7 @@ module org.kube.framework ConversationView 1.0 ConversationView.qml +ConversationListView 1.0 ConversationListView.qml FolderListView 1.0 FolderListView.qml MailListView 1.0 MailListView.qml InlineAccountSwitcher 1.0 InlineAccountSwitcher.qml -- cgit v1.2.3