Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

Commit 48b32259 authored by Eike Hein's avatar Eike Hein

Port Kickoff to shared view component using single-MouseArea pattern

Summary:
This:
* Moves MouseArea out of the item delegate and uses a single MouseArea
  per view.
* Ports all the pages to using a shared view component.

Aside from saving a lot of QObjects, using a single MouseArea also
has the advantage that MouseArea.positionChanged isn't fired when
QQuickWindow synthesizes a hover event per frame, unlike MouseArea
.containsMouse. This fixes a user-reported bug in the search page,
and the same pattern is used by various other UIs we have for
similar reasons.

BUG:397693

Reviewers: #plasma, ngraham, davidedmundson

Reviewed By: #plasma, davidedmundson

Subscribers: davidedmundson, ngraham, plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D15855
parent 74db6d8e
......@@ -2,7 +2,7 @@
Copyright (C) 2011 Martin Gräßlin <mgraesslin@kde.org>
Copyright (C) 2012 Gregor Taetzner <gregor@freenet.de>
Copyright 2014 Sebastian Kügler <sebas@kde.org>
Copyright (C) 2015 Eike Hein <hein@kde.org>
Copyright (C) 2015-2018 Eike Hein <hein@kde.org>
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
......@@ -30,7 +30,7 @@ Item {
objectName: "ApplicationsView"
property ListView listView: applicationsView
property ListView listView: applicationsView.listView
function decrementCurrentIndex() {
applicationsView.decrementCurrentIndex();
......@@ -46,7 +46,7 @@ Item {
return;
}
}
appViewScrollArea.state = "OutgoingLeft";
applicationsView.state = "OutgoingLeft";
}
function openContextMenu() {
......@@ -56,7 +56,7 @@ Item {
function deactivateCurrentIndex() {
if (crumbModel.count > 0) { // this is not the case when switching from the "Applications" to the "Favorites" tab using the "Left" key
breadcrumbsElement.children[crumbModel.count-1].clickCrumb();
appViewScrollArea.state = "OutgoingRight";
applicationsView.state = "OutgoingRight";
return true;
}
return false;
......@@ -64,7 +64,7 @@ Item {
function reset() {
applicationsView.model = rootModel;
applicationsView.positionViewAtBeginning();
applicationsView.listView.positionViewAtBeginning();
applicationsView.clearBreadcrumbs();
}
......@@ -145,10 +145,8 @@ Item {
} // Flickable
} // crumbContainer
PlasmaExtras.ScrollArea {
id: appViewScrollArea
property Item activatedItem: null
KickoffListView {
id: applicationsView
anchors {
top: crumbContainer.bottom
......@@ -157,60 +155,50 @@ Item {
leftMargin: -units.largeSpacing
}
width: parent.width
property Item activatedItem: null
property var newModel: null
Behavior on opacity { NumberAnimation { duration: units.longDuration } }
width: parent.width
focus: true
function moveRight() {
state = "";
activatedItem.activate()
applicationsView.positionViewAtBeginning()
}
appView: true
model: rootModel
function moveLeft() {
state = "";
// newModelIndex set by clicked breadcrumb
var oldModel = applicationsView.model;
applicationsView.model = applicationsView.newModel;
applicationsView.positionViewAtIndex(applicationsView.model.rowForModel(oldModel), ListView.Center)
listView.positionViewAtIndex(model.rowForModel(oldModel), ListView.Center)
}
ListView {
id: applicationsView
property variant newModel
focus: true
keyNavigationWraps: true
boundsBehavior: Flickable.StopAtBounds
highlight: KickoffHighlight {}
highlightMoveDuration : 0
highlightResizeDuration: 0
model: rootModel
delegate: KickoffItem {
id: kickoffItem
function moveRight() {
state = "";
activatedItem.activate()
applicationsView.listView.positionViewAtBeginning()
}
appView: true
}
function clearBreadcrumbs() {
crumbModel.clear();
crumbModel.models = [];
}
function addBreadcrumb(model, title) {
crumbModel.append({"text": title, "depth": crumbModel.count+1})
crumbModel.models.push(model);
}
onReset: appViewContainer.reset()
function clearBreadcrumbs() {
crumbModel.clear();
crumbModel.models = [];
}
} // applicationsView
onAddBreadcrumb: {
crumbModel.append({"text": title, "depth": crumbModel.count+1})
crumbModel.models.push(model);
}
states: [
State {
name: "OutgoingLeft"
PropertyChanges {
target: appViewScrollArea
target: applicationsView
x: -parent.width
opacity: 0.0
}
......@@ -218,7 +206,7 @@ Item {
State {
name: "OutgoingRight"
PropertyChanges {
target: appViewScrollArea
target: applicationsView
x: parent.width
opacity: 0.0
}
......@@ -232,24 +220,26 @@ Item {
// We need to cache the currentItem since the selection can move during animation,
// and we want the item that has been clicked on, not the one that is under the
// mouse once the animation is done
ScriptAction { script: appViewScrollArea.activatedItem = applicationsView.currentItem }
ScriptAction { script: applicationsView.activatedItem = applicationsView.currentItem }
NumberAnimation { properties: "x,opacity"; easing.type: Easing.InQuad; duration: units.longDuration }
ScriptAction { script: appViewScrollArea.moveRight() }
ScriptAction { script: applicationsView.moveRight() }
}
},
Transition {
to: "OutgoingRight"
SequentialAnimation {
NumberAnimation { properties: "x,opacity"; easing.type: Easing.InQuad; duration: units.longDuration }
ScriptAction { script: appViewScrollArea.moveLeft() }
ScriptAction { script: applicationsView.moveLeft() }
}
}
]
} // appViewScrollArea
}
MouseArea {
anchors.fill: appViewScrollArea
anchors.fill: parent
acceptedButtons: Qt.BackButton
onClicked: {
deactivateCurrentIndex()
}
......@@ -265,13 +255,13 @@ Item {
if (running) {
updatedLabel.opacity = 1;
crumbContainer.opacity = 0.3;
appViewScrollArea.opacity = 0.3;
applicationsView.scrollArea.opacity = 0.3;
}
}
onTriggered: {
updatedLabel.opacity = 0;
crumbContainer.opacity = 1;
appViewScrollArea.opacity = 1;
applicationsView.scrollArea.opacity = 1;
running = false;
}
}
......
/*
Copyright (C) 2011 Martin Gräßlin <mgraesslin@kde.org>
Copyright (C) 2012 Marco Martin <mart@kde.org>
Copyright (C) 2015 Eike Hein <hein@kde.org>
Copyright (C) 2015-2018 Eike Hein <hein@kde.org>
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
......@@ -25,56 +25,43 @@ import org.kde.draganddrop 2.0
Item {
property alias model: kickoffListView.model
property alias delegate: kickoffListView.delegate
property alias model: baseView.model
property alias delegate: baseView.delegate
property ListView listView: kickoffListView
property ListView listView: baseView.listView
function decrementCurrentIndex() {
kickoffListView.decrementCurrentIndex();
baseView.decrementCurrentIndex();
}
function incrementCurrentIndex() {
kickoffListView.incrementCurrentIndex();
baseView.incrementCurrentIndex();
}
function activateCurrentIndex() {
kickoffListView.currentItem.activate();
baseView.currentItem.activate();
}
function openContextMenu() {
kickoffListView.currentItem.openActionMenu();
baseView.currentItem.openActionMenu();
}
PlasmaExtras.ScrollArea {
anchors.fill: parent
Connections {
target: plasmoid
ListView {
id: kickoffListView
onExpandedChanged: {
if (!expanded) {
baseView.currentIndex = -1;
}
}
}
interactive: contentHeight > height
boundsBehavior: Flickable.StopAtBounds
currentIndex: -1
keyNavigationWraps: true
highlight: KickoffHighlight {}
highlightMoveDuration : 0
highlightResizeDuration: 0
KickoffListView {
id: baseView
delegate: KickoffItem {}
anchors.fill: parent
section {
property: "group"
criteria: ViewSection.FullString
delegate: SectionDelegate {}
}
Connections {
target: plasmoid
onExpandedChanged: {
if (!expanded) {
kickoffListView.currentIndex = -1;
}
}
}
}
currentIndex: -1
interactive: contentHeight > height
}
}
......@@ -53,7 +53,7 @@ Item {
} else {
applicationsView.newModel = crumbModel.models[index];
}
appViewScrollArea.state = "OutgoingRight";
applicationsView.state = "OutgoingRight";
}
}
......
......@@ -2,7 +2,7 @@
Copyright (C) 2011 Martin Gräßlin <mgraesslin@kde.org>
Copyright (C) 2012 Marco Martin <mart@kde.org>
Copyright 2014 Sebastian Kügler <sebas@kde.org>
Copyright (C) 2015 Eike Hein <hein@kde.org>
Copyright (C) 2015-2018 Eike Hein <hein@kde.org>
Copyright (C) 2016 Jonathan Liu <net147@gmail.com>
Copyright (C) 2016 Kai Uwe Broulik <kde@privat.broulik.de>
......@@ -35,22 +35,22 @@ Item {
objectName: "FavoritesView"
property ListView listView: kickoffListView
property ListView listView: favoritesView.listView
function decrementCurrentIndex() {
kickoffListView.decrementCurrentIndex();
favoritesView.decrementCurrentIndex();
}
function incrementCurrentIndex() {
kickoffListView.incrementCurrentIndex();
favoritesView.incrementCurrentIndex();
}
function activateCurrentIndex() {
kickoffListView.currentItem.activate();
favoritesView.currentItem.activate();
}
function openContextMenu() {
kickoffListView.currentItem.openActionMenu();
favoritesView.currentItem.openActionMenu();
}
// QQuickItem::isAncestorOf is not invokable...
......@@ -69,94 +69,77 @@ Item {
DropArea {
property int startRow: -1
anchors.fill: scrollArea
anchors.fill: parent
enabled: plasmoid.immutability !== PlasmaCore.Types.SystemImmutable
function syncTarget(event) {
if (kickoffListView.animating) {
if (favoritesView.animating) {
return;
}
var pos = mapToItem(kickoffListView.contentItem, event.x, event.y);
var above = kickoffListView.itemAt(pos.x, pos.y);
var pos = mapToItem(listView.contentItem, event.x, event.y);
var above = listView.itemAt(pos.x, pos.y);
var source = kickoff.dragSource;
if (above && above !== source && isChildOf(source, kickoffListView)) {
kickoffListView.model.moveRow(source.itemIndex, above.itemIndex);
if (above && above !== source && isChildOf(source, favoritesView)) {
favoritesView.model.moveRow(source.itemIndex, above.itemIndex);
// itemIndex changes directly after moving,
// we can just set the currentIndex to it then.
kickoffListView.currentIndex = source.itemIndex;
favoritesView.currentIndex = source.itemIndex;
}
}
onDragEnter: {
syncTarget(event);
startRow = kickoffListView.currentIndex;
startRow = favoritesView.currentIndex;
}
onDragMove: syncTarget(event);
onDragMove: syncTarget(event)
}
Transition {
id: moveTransition
SequentialAnimation {
PropertyAction { target: kickoffListView; property: "animating"; value: true }
PropertyAction { target: favoritesView; property: "animating"; value: true }
NumberAnimation {
duration: kickoffListView.animationDuration
duration: favoritesView.animationDuration
properties: "x, y"
easing.type: Easing.OutQuad
}
PropertyAction { target: kickoffListView; property: "animating"; value: false }
PropertyAction { target: favoritesView; property: "animating"; value: false }
}
}
PlasmaExtras.ScrollArea {
id: scrollArea
anchors.fill: parent
ListView {
id: kickoffListView
property bool animating: false
property int animationDuration: resetAnimationDurationTimer.interval
Connections {
target: plasmoid
onExpandedChanged: {
if (!expanded) {
favoritesView.currentIndex = -1;
}
}
}
currentIndex: -1
boundsBehavior: Flickable.StopAtBounds
keyNavigationWraps: true
interactive: contentHeight > height
KickoffListView {
id: favoritesView
delegate: KickoffItem {}
highlight: KickoffHighlight {}
highlightMoveDuration : 0
highlightResizeDuration: 0
anchors.fill: parent
model: globalFavorites
property bool animating: false
property int animationDuration: resetAnimationDurationTimer.interval
onCountChanged: {
animationDuration = 0;
resetAnimationDurationTimer.start();
}
interactive: contentHeight > height
section {
property: "group"
criteria: ViewSection.FullString
}
move: moveTransition
moveDisplaced: moveTransition
move: moveTransition
moveDisplaced: moveTransition
model: globalFavorites
Connections {
target: plasmoid
onExpandedChanged: {
if (!expanded) {
kickoffListView.currentIndex = -1;
}
}
}
onCountChanged: {
animationDuration = 0;
resetAnimationDurationTimer.start();
}
}
......@@ -165,7 +148,7 @@ Item {
interval: 150
onTriggered: kickoffListView.animationDuration = interval - 20
onTriggered: favoritesView.animationDuration = interval - 20
}
}
......@@ -2,7 +2,7 @@
Copyright (C) 2011 Martin Gräßlin <mgraesslin@kde.org>
Copyright (C) 2012 Gregor Taetzner <gregor@freenet.de>
Copyright 2014 Sebastian Kügler <sebas@kde.org>
Copyright (C) 2015 Eike Hein <hein@kde.org>
Copyright (C) 2015-2018 Eike Hein <hein@kde.org>
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
......@@ -31,16 +31,19 @@ Item {
width: ListView.view.width
height: (units.smallSpacing * 2) + Math.max(elementIcon.height, titleElement.height + subTitleElement.height)
signal reset
signal actionTriggered(string actionId, variant actionArgument)
signal aboutToShowActionMenu(variant actionMenu)
signal addBreadcrumb(var model, string title)
readonly property int itemIndex: model.index
readonly property string url: model.url || ""
readonly property var decoration: model.decoration || ""
property bool dropEnabled: false
property bool appView: false
property bool modelChildren: model.hasChildren || false
property bool isCurrent: listItem.ListView.view.currentIndex === index;
property string url: model.url || ""
property bool showAppsByName: plasmoid.configuration.showAppsByName
property bool hasActionList: ((model.favoriteId != null)
......@@ -68,21 +71,18 @@ Item {
if (model.hasChildren) {
var childModel = view.model.modelForRow(index);
view.addBreadcrumb(childModel, display);
listItem.addBreadcrumb(childModel, display);
view.model = childModel;
} else {
view.model.trigger(index, "", null);
plasmoid.expanded = false;
if (view.reset) {
view.reset();
}
listItem.reset();
}
}
function openActionMenu(visualParent, x, y) {
function openActionMenu(x, y) {
aboutToShowActionMenu(actionMenu);
actionMenu.visualParent = visualParent != undefined ? visualParent : mouseArea;
actionMenu.visualParent = listItem;
actionMenu.open(x, y);
}
......@@ -94,151 +94,82 @@ Item {
}
}
MouseArea {
id: mouseArea
PlasmaCore.IconItem {
id: elementIcon
anchors {
left: parent.left
right: parent.right
top: parent.top
bottom: parent.bottom
}
property bool pressed: false
property int pressX: -1
property int pressY: -1
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
onEntered: {
listItem.ListView.view.currentIndex = index;
leftMargin: (units.gridUnit * 4) - units.iconSizes.medium
verticalCenter: parent.verticalCenter
}
width: units.iconSizes.medium
height: width
onExited: {
listItem.ListView.view.currentIndex = -1;
}
animated: false
usesPlasmaTheme: false
onPressed: {
if (mouse.buttons & Qt.RightButton) {
if (hasActionList) {
openActionMenu(mouseArea, mouse.x, mouse.y);
}
} else {
pressed = true;
pressX = mouse.x;
pressY = mouse.y;
}
}
onReleased: {
if (pressed) {
if (appView) {
appViewScrollArea.state = "OutgoingLeft";
} else {
listItem.activate();
}
listItem.ListView.view.currentIndex = -1;
}
pressed = false;
pressX = -1;
pressY = -1;
}
onPositionChanged: {
if (pressX != -1 && model.url && dragHelper.isDrag(pressX, pressY, mouse.x, mouse.y)) {
kickoff.dragSource = listItem;
dragHelper.startDrag(root, model.url, model.decoration);
pressed = false;
pressX = -1;
pressY = -1;
}
}
source: model.decoration
}
onContainsMouseChanged: {
if (!containsMouse) {
pressed = false;
pressX = -1;
pressY = -1;
}
}
PlasmaComponents.Label {
id: titleElement
PlasmaCore.IconItem {
id: elementIcon
y: Math.round((parent.height - titleElement.height - ( (subTitleElement.text != "") ? subTitleElement.paintedHeight : 0) ) / 2)
anchors {
//bottom: elementIcon.verticalCenter
left: elementIcon.right
right: arrow.left
leftMargin: units.gridUnit
rightMargin: units.gridUnit * 2
}
height: paintedHeight
// TODO: games should always show the by name!
text: model.display
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
}
anchors {
left: parent.left
leftMargin: (units.gridUnit * 4) - units.iconSizes.medium
verticalCenter: parent.verticalCenter
}
width: units.iconSizes.medium
height: width
PlasmaComponents.Label {
id: subTitleElement
animated: false
usesPlasmaTheme: false
anchors {
left: titleElement.left
right: arrow.left
rightMargin: units.gridUnit * 2
top: titleElement.bottom
}
height: paintedHeight
text: model.description
opacity: isCurrent ? 0.8 : 0.6
font.pointSize: theme.smallestFont.pointSize
elide: Text.ElideMiddle
horizontalAlignment: Text.AlignLeft
}
source: model.decoration
}
PlasmaComponents.Label {
id: titleElement
y: Math.round((parent.height - titleElement.height - ( (subTitleElement.text != "") ? subTitleElement.paintedHeight : 0) ) / 2)
anchors {
//bottom: elementIcon.verticalCenter
left: elementIcon.right
right: arrow.left
leftMargin: units.gridUnit
rightMargin: units.gridUnit * 2
}
height: paintedHeight
// TODO: games should always show the by name!