effects/overview: Add desktop bar

This provides a way to create, destroy, and rename virtual desktops in
the overview effect as well as switch between desktops.

The mechanics of switching between virtual desktops can be revisited
later though.
This commit is contained in:
Vlad Zahorodnii 2021-09-06 21:55:46 +03:00
parent d73610d6d6
commit ce2fc670c4
6 changed files with 392 additions and 19 deletions

View file

@ -261,6 +261,11 @@ void OverviewEffect::deactivate()
m_shutdownTimer->start(animationDuration());
}
void OverviewEffect::quickDeactivate()
{
m_shutdownTimer->start(0);
}
void OverviewEffect::realDeactivate()
{
qDeleteAll(m_screenViews);

View file

@ -70,6 +70,7 @@ Q_SIGNALS:
public Q_SLOTS:
void activate();
void deactivate();
void quickDeactivate();
void toggle();
private:

View file

@ -0,0 +1,257 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtGraphicalEffects 1.12
import org.kde.kirigami 2.12 as Kirigami
import org.kde.kwin 3.0 as KWinComponents
import org.kde.plasma.components 3.0 as PC3
import org.kde.plasma.core 2.0 as PlasmaCore
Item {
id: bar
readonly property real desktopHeight: PlasmaCore.Units.gridUnit * 4
readonly property real desktopWidth: desktopHeight * targetScreen.geometry.width / targetScreen.geometry.height
readonly property real columnHeight: desktopHeight + PlasmaCore.Units.gridUnit
property QtObject clientModel
property alias desktopModel: desktopRepeater.model
property QtObject selectedDesktop: null
implicitHeight: columnHeight + 2 * PlasmaCore.Units.smallSpacing
Flickable {
anchors.fill: parent
leftMargin: Math.max((width - contentWidth) / 2, 0)
topMargin: Math.max((height - contentHeight) / 2, 0)
contentWidth: contentItem.childrenRect.width
contentHeight: contentItem.childrenRect.height
clip: true
flickableDirection: Flickable.HorizontalFlick
Row {
spacing: PlasmaCore.Units.largeSpacing
Repeater {
id: desktopRepeater
Column {
id: delegate
width: bar.desktopWidth
height: bar.columnHeight
spacing: PlasmaCore.Units.smallSpacing
required property QtObject desktop
required property int index
function remove() {
bar.desktopModel.remove(delegate.index);
}
Item {
width: bar.desktopWidth
height: bar.desktopHeight
Rectangle {
id: mask
anchors.fill: parent
radius: 3
visible: false
}
DesktopView {
id: thumbnail
property bool scaled: state == "scaled"
width: targetScreen.geometry.width
height: targetScreen.geometry.height
visible: scaled
clientModel: bar.clientModel
desktop: delegate.desktop
scale: bar.desktopHeight / targetScreen.geometry.height
transformOrigin: Item.TopLeft
// Disable the item layer while being scaled.
layer.enabled: !scaled
layer.textureSize: Qt.size(bar.desktopWidth, bar.desktopHeight)
states: State {
name: "scaled"
ParentChange {
target: thumbnail
parent: container
x: 0
y: 0
scale: 1
}
}
transitions: Transition {
SequentialAnimation {
ParentAnimation {
NumberAnimation {
properties: "x,y,scale"
duration: effect.animationDuration
easing.type: Easing.OutCubic
}
}
ScriptAction {
script: {
KWinComponents.Workspace.currentVirtualDesktop = delegate.desktop;
effect.quickDeactivate();
}
}
}
}
}
OpacityMask {
anchors.fill: parent
cached: true
source: thumbnail
maskSource: mask
}
Rectangle {
anchors.fill: parent
radius: 3
color: "transparent"
border.width: 2
border.color: PlasmaCore.ColorScope.highlightColor
opacity: dropArea.containsDrag ? 0.5 : 1.0
visible: !thumbnail.scaled && (dropArea.containsDrag || bar.selectedDesktop == delegate.desktop)
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
onClicked: {
mouse.accepted = true;
switch (mouse.button) {
case Qt.LeftButton:
thumbnail.state = "scaled";
break;
case Qt.MiddleButton:
delegate.remove();
break;
}
}
}
Loader {
LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft
active: hoverHandler.hovered
anchors.right: parent.right
anchors.top: parent.top
sourceComponent: PC3.Button {
icon.name: "delete"
onClicked: delegate.remove()
}
}
DropArea {
id: dropArea
anchors.fill: parent
onEntered: {
drag.accepted = true;
}
onDropped: {
drag.source.desktop = delegate.desktop.x11DesktopNumber;
}
}
}
Item {
id: label
width: bar.desktopWidth
height: PlasmaCore.Units.gridUnit
state: "normal"
PC3.Label {
PlasmaCore.ColorScope.colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
anchors.fill: parent
elide: Text.ElideRight
text: delegate.desktop.name
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
visible: label.state == "normal"
}
MouseArea {
anchors.fill: parent
onPressed: {
mouse.accepted = true;
label.startEditing();
}
}
TextInput {
id: editor
anchors.fill: parent
visible: label.state == "editing"
color: PlasmaCore.ColorScope.textColor
text: delegate.desktop.name
onAccepted: {
delegate.desktop.name = text;
label.stopEditing();
}
Keys.onEscapePressed: label.stopEditing();
}
states: [
State {
name: "normal"
},
State {
name: "editing"
}
]
function startEditing() {
state = "editing";
editor.forceActiveFocus();
}
function stopEditing() {
state = "normal";
}
}
HoverHandler {
id: hoverHandler
}
}
}
PC3.Button {
width: bar.desktopWidth
height: bar.desktopHeight
icon.name: "list-add"
opacity: hovered ? 1 : 0.75
onClicked: desktopModel.create(desktopModel.rowCount())
ToolTip.text: i18n("Add Desktop")
ToolTip.visible: hovered
ToolTip.delay: Kirigami.Units.toolTipDelay
DropArea {
anchors.fill: parent
onEntered: {
drag.accepted = desktopModel.rowCount() < 20
}
onDropped: {
desktopModel.create(desktopModel.rowCount());
drag.source.desktop = desktopModel.rowCount() + 1;
}
}
}
}
}
}

View file

@ -0,0 +1,33 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.12
import org.kde.kwin 3.0 as KWinComponents
Item {
id: desktopView
required property QtObject clientModel
required property QtObject desktop
Repeater {
model: KWinComponents.ClientFilterModel {
activity: KWinComponents.Workspace.currentActivity
desktop: desktopView.desktop
screenName: targetScreen.name
clientModel: desktopView.clientModel
}
KWinComponents.WindowThumbnailItem {
wId: model.client.internalId
x: model.client.x - targetScreen.geometry.x
y: model.client.y - targetScreen.geometry.y
width: model.client.width
height: model.client.height
z: model.client.stackingOrder
}
}
}

View file

@ -89,34 +89,56 @@ FocusScope {
height: heapArea.height
Item {
id: searchBar
width: parent.width
height: searchField.height + 2 * PlasmaCore.Units.largeSpacing
id: topBar
state: container.organized ? "visible" : "hidden"
width: parent.width
height: searchBar.height + desktopBar.height
PC3.TextField {
id: searchField
anchors.centerIn: parent
width: Math.min(parent.width, 20 * PlasmaCore.Units.gridUnit)
focus: true
placeholderText: i18n("Search...")
clearButtonShown: true
Keys.priority: Keys.AfterItem
Keys.forwardTo: heap
Rectangle {
id: desktopBar
width: parent.width
implicitHeight: bar.implicitHeight + 2 * PlasmaCore.Units.smallSpacing
color: Qt.rgba(0, 0, 0, 0.25)
DesktopBar {
id: bar
anchors.fill: parent
clientModel: stackModel
desktopModel: desktopModel
selectedDesktop: KWinComponents.Workspace.currentVirtualDesktop
}
}
Item {
id: searchBar
anchors.top: desktopBar.bottom
width: parent.width
height: searchField.height + 2 * PlasmaCore.Units.largeSpacing
PC3.TextField {
id: searchField
anchors.centerIn: parent
width: Math.min(parent.width, 20 * PlasmaCore.Units.gridUnit)
focus: true
placeholderText: i18n("Search...")
clearButtonShown: true
Keys.priority: Keys.AfterItem
Keys.forwardTo: heap
}
}
states: [
State {
name: "hidden"
PropertyChanges {
target: searchBar
target: topBar
opacity: 0
}
},
State {
name: "visible"
PropertyChanges {
target: searchBar
target: topBar
opacity: 1
}
}
@ -141,7 +163,7 @@ FocusScope {
WindowHeap {
id: heap
width: parent.width
height: parent.height - searchBar.height
height: parent.height - topBar.height
padding: PlasmaCore.Units.largeSpacing
filter: searchField.text
animationEnabled: container.animationEnabled
@ -184,5 +206,9 @@ FocusScope {
id: stackModel
}
KWinComponents.VirtualDesktopModel {
id: desktopModel
}
Component.onCompleted: start();
}

View file

@ -65,12 +65,40 @@ FocusScope {
}
visible: opacity > 0
z: client.stackingOrder
z: dragHandler.active ? 100 : client.stackingOrder
KWinComponents.WindowThumbnailItem {
id: thumbSource
anchors.fill: parent
wId: thumb.client.internalId
state: dragHandler.active ? "drag" : "normal"
Drag.active: dragHandler.active
Drag.source: thumb.client
states: [
State {
name: "normal"
PropertyChanges {
target: thumbSource
x: 0
y: 0
width: thumb.width
height: thumb.height
}
},
State {
name: "drag"
PropertyChanges {
target: thumbSource
x: -dragHandler.centroid.pressPosition.x * dragHandler.targetScale +
dragHandler.centroid.position.x
y: -dragHandler.centroid.pressPosition.y * dragHandler.targetScale +
dragHandler.centroid.position.y
width: cell.width * dragHandler.targetScale
height: cell.height * dragHandler.targetScale
}
}
]
}
PlasmaCore.IconItem {
@ -81,6 +109,7 @@ FocusScope {
anchors.horizontalCenter: thumbSource.horizontalCenter
anchors.bottom: thumbSource.bottom
anchors.bottomMargin: -height / 4
visible: !dragHandler.active
}
PC3.Label {
@ -92,6 +121,7 @@ FocusScope {
text: thumb.client.caption
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
visible: !dragHandler.active
PlasmaCore.ColorScope.colorGroup: PlasmaCore.Theme.ComplementaryColorGroup
layer.enabled: true
@ -157,7 +187,7 @@ FocusScope {
]
component TweenBehavior : Behavior {
enabled: heap.animationEnabled
enabled: heap.animationEnabled && !dragHandler.active
NumberAnimation {
duration: effect.animationDuration
easing.type: Easing.InOutCubic
@ -199,6 +229,27 @@ FocusScope {
onTapped: thumb.client.closeWindow()
}
DragHandler {
id: dragHandler
target: null
readonly property double targetScale: {
const localPressPosition = centroid.scenePressPosition.y - expoLayout.Kirigami.ScenePosition.y;
if (localPressPosition == 0) {
return 0.1
} else {
const localPosition = centroid.scenePosition.y - expoLayout.Kirigami.ScenePosition.y;
return Math.max(0.1, Math.min(localPosition / localPressPosition, 1))
}
}
onActiveChanged: {
if (!active) {
thumbSource.Drag.drop();
}
}
}
PC3.Button {
LayoutMirroring.enabled: Qt.application.layoutDirection == Qt.RightToLeft
icon.name: "window-close"
@ -208,7 +259,7 @@ FocusScope {
anchors.topMargin: PlasmaCore.Units.largeSpacing
implicitWidth: PlasmaCore.Units.iconSizes.medium
implicitHeight: implicitWidth
visible: (hovered || hoverHandler.hovered) && thumb.client.closeable
visible: (hovered || hoverHandler.hovered) && thumb.client.closeable && !dragHandler.active
onClicked: thumb.client.closeWindow();
}