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