mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-28 22:53:09 +00:00
homescreens/halcyon: Unify search widget with folio
This commit drops halcyon's search widget in favour of using the same widget as folio to share code. This also brings over its keyboard navigation features.
This commit is contained in:
parent
0bcab0ae3a
commit
d0b1df97a6
4 changed files with 149 additions and 330 deletions
|
|
@ -1,324 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Controls as Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.plasma.core as PlasmaCore
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
import org.kde.plasma.extras 2.0 as PlasmaExtras
|
||||
|
||||
import org.kde.milou as Milou
|
||||
import org.kde.kirigami 2.19 as Kirigami
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
// content margins (background ignores this)
|
||||
property real topMargin: 0
|
||||
property real bottomMargin: 0
|
||||
property real leftMargin: 0
|
||||
property real rightMargin: 0
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// emitted when an item on the ListView is triggered
|
||||
signal actionTriggered()
|
||||
|
||||
readonly property real closedContentY: Kirigami.Units.gridUnit * 5
|
||||
readonly property real openedContentY: 0
|
||||
readonly property real openFactor: Math.max(0, Math.min(1, 1 - flickable.contentY / closedContentY))
|
||||
readonly property bool isOpen: openFactor != 0
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(0, 0, 0, 0.3)
|
||||
opacity: root.openFactor
|
||||
}
|
||||
|
||||
onOpacityChanged: {
|
||||
if (opacity === 0) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Down) {
|
||||
listView.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
Flickable {
|
||||
id: flickable
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: root.topMargin
|
||||
anchors.bottomMargin: root.bottomMargin
|
||||
anchors.leftMargin: root.leftMargin
|
||||
anchors.rightMargin: root.rightMargin
|
||||
|
||||
contentHeight: flickable.height + root.closedContentY
|
||||
contentY: root.closedContentY
|
||||
property real oldContentY: contentY
|
||||
property bool opening: false
|
||||
|
||||
onContentYChanged: {
|
||||
opening = contentY < oldContentY;
|
||||
oldContentY = contentY;
|
||||
|
||||
if (contentY !== root.openedContentY) {
|
||||
queryField.focus = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMovementEnded: root.endGesture()
|
||||
|
||||
onDraggingChanged: {
|
||||
if (!dragging) {
|
||||
root.endGesture();
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation on contentY {
|
||||
id: anim
|
||||
duration: Kirigami.Units.longDuration * 2
|
||||
easing.type: Easing.OutQuad
|
||||
running: false
|
||||
onFinished: {
|
||||
if (anim.to === root.openedContentY) {
|
||||
queryField.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: column
|
||||
height: flickable.height
|
||||
width: flickable.width
|
||||
|
||||
Controls.Control {
|
||||
opacity: root.openFactor
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 30
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Kirigami.Units.gridUnit
|
||||
Layout.leftMargin: Kirigami.Units.gridUnit
|
||||
Layout.rightMargin: Kirigami.Units.gridUnit
|
||||
|
||||
leftPadding: Kirigami.Units.smallSpacing
|
||||
rightPadding: Kirigami.Units.smallSpacing
|
||||
topPadding: Kirigami.Units.smallSpacing
|
||||
bottomPadding: Kirigami.Units.smallSpacing
|
||||
|
||||
background: Item {
|
||||
|
||||
// shadow for search window
|
||||
MultiEffect {
|
||||
anchors.fill: parent
|
||||
source: rectBackground
|
||||
blurMax: 16
|
||||
shadowEnabled: true
|
||||
shadowVerticalOffset: 1
|
||||
shadowOpacity: 0.15
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: rectBackground
|
||||
anchors.fill: parent
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
radius: Kirigami.Units.cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
Item {
|
||||
implicitHeight: queryField.height
|
||||
implicitWidth: height
|
||||
Kirigami.Icon {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Math.round(Kirigami.Units.smallSpacing)
|
||||
source: "start-here-symbolic"
|
||||
}
|
||||
}
|
||||
PlasmaComponents.TextField {
|
||||
id: queryField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: i18n("Search…")
|
||||
inputMethodHints: Qt.ImhNoPredictiveText // don't need to press "enter" to update text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Controls.ScrollView {
|
||||
opacity: root.openFactor === 1 ? 1 : 0
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: Kirigami.Units.shortDuration }
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: listView.contentHeight > availableHeight
|
||||
|
||||
Milou.ResultsListView {
|
||||
id: listView
|
||||
queryString: queryField.text
|
||||
clip: true
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||
|
||||
highlight: activeFocus ? highlightComponent : null
|
||||
Component {
|
||||
id: highlightComponent
|
||||
|
||||
PlasmaExtras.Highlight {}
|
||||
}
|
||||
|
||||
onActivated: {
|
||||
root.close();
|
||||
}
|
||||
onUpdateQueryString: {
|
||||
queryField.text = text
|
||||
queryField.cursorPosition = cursorPosition
|
||||
}
|
||||
|
||||
delegate: MouseArea {
|
||||
id: delegate
|
||||
height: rowLayout.height
|
||||
width: listView.width
|
||||
|
||||
onClicked: {
|
||||
listView.currentIndex = model.index;
|
||||
listView.runCurrentIndex();
|
||||
|
||||
root.actionTriggered();
|
||||
}
|
||||
hoverEnabled: true
|
||||
|
||||
function activateNextAction() {
|
||||
queryField.forceActiveFocus();
|
||||
queryField.selectAll();
|
||||
listView.currentIndex = -1;
|
||||
}
|
||||
|
||||
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: Kirigami.Units.shortDuration }
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
height: Kirigami.Units.gridUnit * 3
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: Kirigami.Units.gridUnit
|
||||
anchors.rightMargin: Kirigami.Units.gridUnit
|
||||
|
||||
Kirigami.Icon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
source: model.decoration
|
||||
implicitWidth: Kirigami.Units.iconSizes.medium
|
||||
implicitHeight: Kirigami.Units.iconSizes.medium
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
PlasmaComponents.Label {
|
||||
id: title
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.smallSpacing * 2
|
||||
Layout.rightMargin: Kirigami.Units.gridUnit
|
||||
|
||||
maximumLineCount: 1
|
||||
elide: Text.ElideRight
|
||||
text: typeof modelData !== "undefined" ? modelData : model.display
|
||||
color: "white"
|
||||
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize
|
||||
}
|
||||
PlasmaComponents.Label {
|
||||
id: subtitle
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.smallSpacing * 2
|
||||
Layout.rightMargin: Kirigami.Units.gridUnit
|
||||
|
||||
maximumLineCount: 1
|
||||
elide: Text.ElideRight
|
||||
text: model.subtext || ""
|
||||
color: "white"
|
||||
opacity: 0.8
|
||||
|
||||
font.pointSize: Math.round(Kirigami.Theme.defaultFont.pointSize * 0.8)
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: actionsRepeater
|
||||
model: typeof actions !== "undefined" ? actions : []
|
||||
|
||||
Controls.ToolButton {
|
||||
icon: 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
onClicked: close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,6 +57,13 @@ Item {
|
|||
Plasmoid.editMode = false;
|
||||
}
|
||||
|
||||
// Pass focus to swipe view child
|
||||
onFocusChanged: {
|
||||
if (focus) {
|
||||
swipeView.focusChild();
|
||||
}
|
||||
}
|
||||
|
||||
WindowPlugin.WindowMaximizedTracker {
|
||||
id: windowMaximizedTracker
|
||||
screenGeometry: Plasmoid.containment.screenGeometry
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2014 Aaron Seigo <aseigo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2015 Marco Martin <notmart@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2021-2025 Devin Lin <devin@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as Controls
|
||||
|
||||
import org.kde.plasma.private.mobileshell as MobileShell
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Content margins
|
||||
property real topPadding: 0
|
||||
property real bottomPadding: 0
|
||||
property real leftPadding: 0
|
||||
property real rightPadding: 0
|
||||
|
||||
// Call when the gesture has started
|
||||
function startGesture() {
|
||||
krunnerScreen.clearField();
|
||||
flickable.contentY = flickable.closedContentY;
|
||||
}
|
||||
|
||||
// Call when the touch gesture has been updated
|
||||
function updateGestureOffset(yOffset) {
|
||||
flickable.contentY = Math.max(0, Math.min(flickable.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 = flickable.openedContentY;
|
||||
anim.restart();
|
||||
}
|
||||
|
||||
// Close the search widget (animated)
|
||||
function close() {
|
||||
anim.to = flickable.closedContentY;
|
||||
anim.restart();
|
||||
}
|
||||
|
||||
// Emitted when it is requested to force active focus on the parent and release focus on the widget
|
||||
signal releaseFocusRequested()
|
||||
|
||||
readonly property real openFactor: Math.max(0, Math.min(1, 1 - flickable.contentY / flickable.closedContentY))
|
||||
readonly property bool isOpen: openFactor != 0
|
||||
|
||||
// Pass focus to search screen
|
||||
onFocusChanged: {
|
||||
if (focus) {
|
||||
krunnerScreen.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(0, 0, 0, 0.3)
|
||||
opacity: root.openFactor
|
||||
}
|
||||
|
||||
Flickable {
|
||||
id: flickable
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: root.topPadding
|
||||
anchors.bottomMargin: root.bottomPadding
|
||||
anchors.leftMargin: root.leftPadding
|
||||
anchors.rightMargin: root.rightPadding
|
||||
|
||||
opacity: root.openFactor
|
||||
|
||||
contentHeight: flickable.height + flickable.closedContentY
|
||||
contentY: flickable.closedContentY
|
||||
|
||||
property real oldContentY: contentY
|
||||
property bool opening: false
|
||||
|
||||
// The y at which the flickable is fully open
|
||||
readonly property real closedContentY: Kirigami.Units.gridUnit * 5
|
||||
|
||||
// The y at which the flickable is fully closed
|
||||
readonly property real openedContentY: 0
|
||||
|
||||
onContentYChanged: {
|
||||
opening = contentY < oldContentY;
|
||||
oldContentY = contentY;
|
||||
|
||||
if (krunnerScreen.focus) {
|
||||
// Unfocus from search
|
||||
root.releaseFocusRequested();
|
||||
}
|
||||
}
|
||||
|
||||
onMovementEnded: root.endGesture()
|
||||
onDraggingChanged: {
|
||||
if (!dragging) {
|
||||
root.endGesture();
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation on contentY {
|
||||
id: anim
|
||||
duration: Kirigami.Units.longDuration * 2
|
||||
easing.type: Easing.OutQuad
|
||||
running: false
|
||||
onFinished: {
|
||||
if (anim.to === flickable.openedContentY) {
|
||||
krunnerScreen.requestFocus();
|
||||
} else {
|
||||
// Unfocus from search
|
||||
root.releaseFocusRequested();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MobileShell.KRunnerScreen {
|
||||
id: krunnerScreen
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
onRequestedClose: root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -136,17 +136,17 @@ ContainmentItem {
|
|||
}
|
||||
|
||||
// search component
|
||||
MobileShell.KRunnerWidget {
|
||||
SearchWidget {
|
||||
id: search
|
||||
anchors.fill: parent
|
||||
visible: openFactor > 0
|
||||
|
||||
onActionTriggered: search.close()
|
||||
topPadding: homeScreen.topMargin
|
||||
bottomPadding: homeScreen.bottomMargin
|
||||
leftPadding: homeScreen.leftMargin
|
||||
rightPadding: homeScreen.rightMargin
|
||||
|
||||
topMargin: homeScreen.topMargin
|
||||
bottomMargin: homeScreen.bottomMargin
|
||||
leftMargin: homeScreen.leftMargin
|
||||
rightMargin: homeScreen.rightMargin
|
||||
onReleaseFocusRequested: halcyonHomeScreen.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue