homescreen: Add search widget

This commit is contained in:
Devin Lin 2022-01-03 00:25:33 -05:00
parent f849a5fea5
commit f22a1e0b8c
6 changed files with 322 additions and 18 deletions

View file

@ -12,6 +12,8 @@ import org.kde.plasma.private.mobileshell 1.0 as MobileShell
/**
* State object for the homescreen.
*
* We expose the data necessary to make custom "swipe-down" gestures from the page view.
*/
QtObject {
id: root
@ -87,7 +89,7 @@ QtObject {
SwipingPages, // horizontal movement between pages
SwipingAppDrawerVisibility, // opening/closing app drawer
SwipingAppDrawerList, // scrolling app drawer
SwipingActionPanel, // pulling down action panel
SwipingPagesDown, // custom gesture can be implemented for swiping down on the page view
DeterminingType
}
@ -132,6 +134,11 @@ QtObject {
xAnim.stop();
}
// expose signals necessary to implement any behaviour for the "swipe-down" action on the page view
signal swipeDownGestureBegin
signal swipeDownGestureEnd
signal swipeDownGestureOffset(real value)
// be very careful when resetting the swipe state
// ensure that we aren't in the middle of a gesture
function resetSwipeState() {
@ -191,8 +198,8 @@ QtObject {
xDetermineSwipePosition = 0;
yDetermineSwipePosition = 0;
} else if (yDetermineSwipePosition >= verticalSwipeStateDetermineThreshold) {
currentSwipeState = HomeScreenState.SwipingActionPanel;
MobileShell.TopPanelControls.startSwipe();
currentSwipeState = HomeScreenState.SwipingPagesDown;
root.swipeDownGestureBegin();
xDetermineSwipePosition = 0;
yDetermineSwipePosition = 0;
} else if (-yDetermineSwipePosition >= verticalSwipeStateDetermineThreshold) {
@ -206,10 +213,10 @@ QtObject {
xPosition += x;
break;
case HomeScreenState.SwipingActionPanel:
case HomeScreenState.SwipingPagesDown:
yPosition = pagesYPosition;
if (y !== 0) {
MobileShell.TopPanelControls.requestRelativeScroll(y);
root.swipeDownGestureOffset(y);
}
break;
@ -277,8 +284,8 @@ QtObject {
break;
}
case HomeScreenState.SwipingActionPanel: {
MobileShell.TopPanelControls.endSwipe();
case HomeScreenState.SwipingPagesDown: {
root.swipeDownGestureEnd();
root.resetSwipeState();
break;
}

View file

@ -34,8 +34,9 @@ StatusBar 1.0 statusbar/StatusBar.qml
TaskSwitcher 1.0 taskswitcher/TaskSwitcher.qml
# /widgets
MediaControlsWidget 1.0 widgets/MediaControlsWidget.qml
NotificationsWidget 1.0 widgets/NotificationsWidget.qml
KRunnerWidget 1.0 widgets/krunner/KRunnerWidget.qml
MediaControlsWidget 1.0 widgets/mediacontrols/MediaControlsWidget.qml
NotificationsWidget 1.0 widgets/notifications/NotificationsWidget.qml
# /
singleton HomeScreenControls 1.0 HomeScreenControls.qml

View file

@ -0,0 +1,271 @@
/*
* SPDX-FileCopyrightText: 2014 Aaron Seigo <aseigo@kde.org>
* SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.15
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.extras 2.0 as PlasmaExtras
import org.kde.plasma.private.mobileshell 1.0 as MobileShell
import org.kde.milou 0.1 as Milou
import org.kde.kirigami 2.19 as Kirigami
import "../../components" as Components
/**
* Search widget that is embedded into the homescreen. The dimensions of
* the root item is assumed to be the available screen area for applications.
*/
Item {
id: root
function startGesture() {
queryField.text = "";
flickable.contentY = closedContentY;
}
function updateGestureOffset(yOffset) {
flickable.contentY = Math.max(0, Math.min(closedContentY, flickable.contentY + yOffset));
}
// call when the touch gesture has let go
function endGesture() {
flickable.opening ? open() : close();
}
// open the search widget (animated)
function open() {
anim.to = openedContentY;
anim.restart();
}
// close the search widget (animated)
function close() {
anim.to = closedContentY;
anim.restart();
}
readonly property real closedContentY: PlasmaCore.Units.gridUnit * 5
readonly property real openedContentY: 0
readonly property real openFactor: Math.max(0, Math.min(1, 1 - flickable.contentY / closedContentY))
Rectangle {
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.3)
opacity: root.openFactor
}
Flickable {
id: flickable
anchors.fill: parent
anchors.topMargin: MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0
anchors.rightMargin: MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth
anchors.bottomMargin: MobileShell.TaskPanelControls.panelHeight
contentHeight: flickable.height + root.closedContentY + 999999
contentY: root.closedContentY
property real oldContentY: contentY
property bool opening: false
onContentYChanged: {
opening = contentY < oldContentY;
oldContentY = contentY;
}
onMovementEnded: root.endGesture()
onDraggingChanged: {
if (!dragging) {
root.endGesture();
}
}
NumberAnimation on contentY {
id: anim
duration: PlasmaCore.Units.longDuration * 2
easing.type: Easing.OutQuad
onFinished: {
if (root.openedContentY) {
queryField.forceActiveFocus();
}
}
}
ColumnLayout {
id: column
height: flickable.height
width: flickable.width
Controls.Control {
opacity: root.openFactor
Layout.fillWidth: true
Layout.leftMargin: PlasmaCore.Units.gridUnit
Layout.rightMargin: PlasmaCore.Units.gridUnit
leftPadding: PlasmaCore.Units.smallSpacing / 2
rightPadding: PlasmaCore.Units.smallSpacing / 2
topPadding: PlasmaCore.Units.smallSpacing / 2
bottomPadding: PlasmaCore.Units.smallSpacing / 2
background: Item {
// shadow for search window
RectangularGlow {
anchors.topMargin: 1
anchors.fill: parent
cached: true
glowRadius: 4
spread: 0.2
color: Qt.rgba(0, 0, 0, 0.15)
}
Rectangle {
anchors.fill: parent
color: PlasmaCore.Theme.backgroundColor
radius: PlasmaCore.Units.smallSpacing
}
}
contentItem: RowLayout {
Item {
implicitHeight: queryField.height
implicitWidth: height
Kirigami.Icon {
anchors.fill: parent
anchors.margins: Math.round(Kirigami.Units.smallSpacing * 1.5)
source: "start-here-symbolic"
}
}
Kirigami.SearchField {
id: queryField
focus: true
Layout.fillWidth: true
}
}
}
Controls.ScrollView {
opacity: root.openFactor === 1 ? 1 : 0
Behavior on opacity {
NumberAnimation { duration: PlasmaCore.Units.shortDuration }
}
Layout.fillHeight: true
Layout.fillWidth: true
Milou.ResultsListView {
id: listView
queryString: queryField.text
highlight: null
PlasmaCore.ColorScope.colorGroup: PlasmaCore.Theme.NormalColorGroup
onActivated: queryField.text = "";
onUpdateQueryString: {
queryField.text = text
queryField.cursorPosition = cursorPosition
}
delegate: MouseArea {
id: delegate
height: rowLayout.height
width: listView.width
onClicked: {
listView.currentIndex = model.index;
listView.runCurrentIndex();
}
hoverEnabled: true
Rectangle {
anchors.fill: parent
color: delegate.pressed ? Qt.rgba(255, 255, 255, 0.2) : (delegate.containsMouse ? Qt.rgba(255, 255, 255, 0.05) : "transparent")
Behavior on color {
ColorAnimation { duration: PlasmaCore.Units.shortDuration }
}
}
RowLayout {
id: rowLayout
height: PlasmaCore.Units.gridUnit * 3
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: PlasmaCore.Units.largeSpacing
anchors.rightMargin: PlasmaCore.Units.largeSpacing
Kirigami.Icon {
Layout.alignment: Qt.AlignVCenter
source: model.decoration
implicitWidth: PlasmaCore.Units.iconSizes.medium
implicitHeight: PlasmaCore.Units.iconSizes.medium
}
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: PlasmaCore.Units.smallSpacing
PlasmaComponents.Label {
id: title
Layout.fillWidth: true
Layout.leftMargin: PlasmaCore.Units.smallSpacing * 2
Layout.rightMargin: PlasmaCore.Units.largeSpacing
maximumLineCount: 1
elide: Text.ElideRight
text: typeof modelData !== "undefined" ? modelData : model.display
color: "white"
font.pointSize: PlasmaCore.Theme.defaultFont.pointSize
}
PlasmaComponents.Label {
id: subtitle
Layout.fillWidth: true
Layout.leftMargin: PlasmaCore.Units.smallSpacing * 2
Layout.rightMargin: PlasmaCore.Units.largeSpacing
maximumLineCount: 1
elide: Text.ElideRight
text: model.subtext || ""
color: "white"
opacity: 0.8
font.pointSize: Math.round(PlasmaCore.Theme.defaultFont.pointSize * 0.8)
}
}
Repeater {
id: actionsRepeater
model: typeof actions !== "undefined" ? actions : []
Controls.ToolButton {
icon.name: modelData.icon || ""
visible: modelData.visible || true
enabled: modelData.enabled || true
Accessible.role: Accessible.Button
Accessible.name: modelData.text
checkable: checked
checked: delegate.activeAction === index
focus: delegate.activeAction === index
onClicked: delegate.ListView.view.runAction(index)
}
}
}
}
}
}
}
}
}

View file

@ -16,7 +16,6 @@ import org.kde.plasma.components 3.0 as PlasmaComponents3
import org.kde.plasma.extras 2.0 as PlasmaExtras
import "../components" as Components
import "mediacontrols"
Components.BaseItem {
id: root

View file

@ -19,8 +19,6 @@ import org.kde.plasma.components 3.0 as PlasmaComponents3
import org.kde.notificationmanager 1.0 as NotificationManager
import "notifications"
/**
* Embeddable notification list widget optimized for mobile and touch.
* Used on the lockscreen and action drawer.

View file

@ -32,9 +32,10 @@ FocusScope {
}
function triggerHomeScreen() {
taskSwitcher.minimizeAll();
MobileShell.HomeScreenControls.resetHomeScreenPosition();
taskSwitcher.visible = false; // will trigger homescreen open
searchWidget.close();
taskSwitcher.minimizeAll();
}
//END functions
@ -126,18 +127,45 @@ FocusScope {
}
}
// control the opacity of both the search and homescreen components
property real homeScreenOpacity: 1
NumberAnimation on homeScreenOpacity {
id: opacityAnimation
duration: PlasmaCore.Units.longDuration
}
// homescreen component
HomeScreenComponents.HomeScreen {
id: homescreen
anchors.fill: parent
opacity: root.homeScreenOpacity * (1 - searchWidget.openFactor)
// make the homescreen not interactable when task switcher or startup feedback is on
interactive: !taskSwitcher.visible && !startupFeedback.visible
opacity: 1
NumberAnimation on opacity {
id: opacityAnimation
duration: PlasmaCore.Units.longDuration
}
// search component
MobileShell.KRunnerWidget {
id: searchWidget
anchors.fill: parent
opacity: root.homeScreenOpacity
visible: openFactor > 0
onOpenFactorChanged: homescreen.opacity = 1 - openFactor;
}
Connections {
target: homescreen.homeScreenState
function onSwipeDownGestureBegin() {
searchWidget.startGesture();
}
function onSwipeDownGestureEnd() {
searchWidget.endGesture();
}
function onSwipeDownGestureOffset(offset) {
searchWidget.updateGestureOffset(-offset);
}
}
@ -177,7 +205,7 @@ FocusScope {
opacityAnimation.to = 0;
opacityAnimation.restart();
} else {
homescreen.opacity = 0;
root.homeScreenOpacity = 0;
}
} else {