Slight refactor of ExpoLayout

ExpoCell is a QQuickItem which manages the geometry of window
thumbnails, movig a bit of the logic in the c++ part.

The partialActivationFactor property switches between the geometry of the window and the
geometry it will have in the overview grid

Get rid of the complicated, 2-stage state machine that the delegate had
This commit is contained in:
Marco Martin 2024-08-12 13:09:41 +00:00
parent e48b7b77ec
commit 01d7ddff68
7 changed files with 509 additions and 503 deletions

View file

@ -177,7 +177,9 @@ Item {
DropArea {
id: dropArea
anchors.fill: parent
onEntered: (drag) => {
drag.accepted = true;
}
onDropped: drop => {
drop.accepted = true;
// dragging a KWin::Window

View file

@ -453,6 +453,9 @@ FocusScope {
property real deltaColumn: column - allDesktopHeaps.currentBackgroundItem.column - deltaX
property real deltaRow: row - allDesktopHeaps.currentBackgroundItem.row - deltaY
onDeltaColumnChanged: heap.layout.updateCellsMapping()
onDeltaRowChanged: heap.layout.updateCellsMapping()
Behavior on deltaColumn {
enabled: overviewVal > 0 && !container.desktopJustCreated
NumberAnimation {
@ -499,6 +502,7 @@ FocusScope {
// Initially places transition desktops in a grid around the current one,
// and moves them slighly to avoid overlapping the UI
Translate {
id: desktopTranslation
x: minX * 0.5 * overviewVal + deltaColumn * width * (1 - gridVal)
y: minY * 0.5 * overviewVal + deltaRow * height * (1 - gridVal)
}
@ -645,6 +649,12 @@ FocusScope {
}
delegate: WindowHeapDelegate {
windowHeap: heap
offsetX: mainBackground.deltaColumn * container.width * (1 - gridVal) + (dragHandler.active ? (dragHandler.centroid.pressPosition.x - dragHandler.centroid.position.x) : 0)
offsetY: mainBackground.deltaRow * container.height * (1 - gridVal) + (dragHandler.active ? (dragHandler.centroid.pressPosition.y - dragHandler.centroid.position.y) : 0)
partialActivationFactor: container.overviewVal + container.gridVal * effect.organizedGrid
// Parent switch needed for the option "organize windows in gridview" to work correctly
contentItemParent: container.gridVal > 0 ? mainBackground : container
// This is preferable over using gestureInProgress values since gridVal and
// overviewVal are animated even after the gesture ends, and since the partial
@ -652,8 +662,6 @@ FocusScope {
// fluent animation.
gestureInProgress: !Number.isInteger(gridVal) || !Number.isInteger(overviewVal)
partialActivationFactor: container.overviewVal + container.gridVal * effect.organizedGrid
targetScale: {
if (!container.anyDesktopBar) return targetScale;
if (overviewVal != 1) return targetScale;

View file

@ -11,9 +11,26 @@
#include <deque>
#include <tuple>
ExpoCell::ExpoCell(QObject *parent)
: QObject(parent)
ExpoCell::ExpoCell(QQuickItem *parent)
: QQuickItem(parent)
{
connect(this, &ExpoCell::visibleChanged, this, [this]() {
if (m_contentItem) {
m_contentItem->setVisible(isVisible());
}
});
// This only works for a static visual tree hierarchy.
// TODO: Make it work with reparenting or warn if any parent in the tree changes?
QQuickItem *ancestor = this;
while (ancestor) {
connect(ancestor, &QQuickItem::xChanged, this, &ExpoCell::polish);
connect(ancestor, &QQuickItem::yChanged, this, &ExpoCell::polish);
connect(ancestor, &QQuickItem::widthChanged, this, &ExpoCell::polish);
connect(ancestor, &QQuickItem::heightChanged, this, &ExpoCell::polish);
ancestor = ancestor->parentItem();
}
}
ExpoCell::~ExpoCell()
@ -21,6 +38,12 @@ ExpoCell::~ExpoCell()
setLayout(nullptr);
}
void ExpoCell::componentComplete()
{
QQuickItem::componentComplete();
updateContentItemGeometry();
}
ExpoLayout *ExpoCell::layout() const
{
return m_layout;
@ -35,159 +58,177 @@ void ExpoCell::setLayout(ExpoLayout *layout)
m_layout->removeCell(this);
}
m_layout = layout;
if (m_layout && m_enabled) {
if (m_layout && m_shouldLayout) {
m_layout->addCell(this);
}
updateContentItemGeometry();
Q_EMIT layoutChanged();
}
bool ExpoCell::isEnabled() const
bool ExpoCell::shouldLayout() const
{
return m_enabled;
return m_shouldLayout;
}
void ExpoCell::setEnabled(bool enabled)
void ExpoCell::setShouldLayout(bool shouldLayout)
{
if (m_enabled != enabled) {
m_enabled = enabled;
if (enabled) {
if (m_layout) {
m_layout->addCell(this);
}
} else {
if (m_layout) {
m_layout->removeCell(this);
}
}
Q_EMIT enabledChanged();
if (shouldLayout == m_shouldLayout) {
return;
}
m_shouldLayout = shouldLayout;
if (m_layout) {
if (m_shouldLayout) {
m_layout->addCell(this);
} else {
m_layout->removeCell(this);
}
}
Q_EMIT shouldLayoutChanged();
}
void ExpoCell::update()
QQuickItem *ExpoCell::contentItem() const
{
return m_contentItem;
}
void ExpoCell::setContentItem(QQuickItem *item)
{
if (m_contentItem == item) {
return;
}
m_contentItem = item;
if (m_contentItem) {
m_contentItem->setVisible(isVisible());
}
updateContentItemGeometry();
Q_EMIT contentItemChanged();
}
qreal ExpoCell::partialActivationFactor() const
{
return m_partialActivationFactor;
}
void ExpoCell::setPartialActivationFactor(qreal factor)
{
if (m_partialActivationFactor == factor) {
return;
}
m_partialActivationFactor = factor;
// Since this is an animation controller we want it to have immediate effect
updateContentItemGeometry();
Q_EMIT partialActivationFactorChanged();
}
void ExpoCell::updateLayout()
{
if (m_layout) {
m_layout->polish();
}
}
int ExpoCell::naturalX() const
qreal ExpoCell::offsetX() const
{
return m_offsetX;
}
void ExpoCell::setOffsetX(qreal x)
{
if (m_offsetX != x) {
m_offsetX = x;
updateContentItemGeometry();
Q_EMIT offsetXChanged();
}
}
qreal ExpoCell::offsetY() const
{
return m_offsetY;
}
void ExpoCell::setOffsetY(qreal y)
{
if (m_offsetY != y) {
m_offsetY = y;
updateContentItemGeometry();
Q_EMIT offsetYChanged();
}
}
qreal ExpoCell::naturalX() const
{
return m_naturalX;
}
void ExpoCell::setNaturalX(int x)
void ExpoCell::setNaturalX(qreal x)
{
if (m_naturalX != x) {
m_naturalX = x;
update();
updateLayout();
Q_EMIT naturalXChanged();
}
}
int ExpoCell::naturalY() const
qreal ExpoCell::naturalY() const
{
return m_naturalY;
}
void ExpoCell::setNaturalY(int y)
void ExpoCell::setNaturalY(qreal y)
{
if (m_naturalY != y) {
m_naturalY = y;
update();
updateLayout();
Q_EMIT naturalYChanged();
}
}
int ExpoCell::naturalWidth() const
qreal ExpoCell::naturalWidth() const
{
return m_naturalWidth;
}
void ExpoCell::setNaturalWidth(int width)
void ExpoCell::setNaturalWidth(qreal width)
{
if (m_naturalWidth != width) {
m_naturalWidth = width;
update();
updateLayout();
Q_EMIT naturalWidthChanged();
}
}
int ExpoCell::naturalHeight() const
qreal ExpoCell::naturalHeight() const
{
return m_naturalHeight;
}
void ExpoCell::setNaturalHeight(int height)
void ExpoCell::setNaturalHeight(qreal height)
{
if (m_naturalHeight != height) {
m_naturalHeight = height;
update();
updateLayout();
Q_EMIT naturalHeightChanged();
}
}
QRect ExpoCell::naturalRect() const
QRectF ExpoCell::naturalRect() const
{
return QRect(naturalX(), naturalY(), naturalWidth(), naturalHeight());
return QRectF(m_naturalX, m_naturalY, m_naturalWidth, m_naturalHeight);
}
QMargins ExpoCell::margins() const
QMarginsF ExpoCell::margins() const
{
return m_margins;
}
int ExpoCell::x() const
{
return m_x.value_or(0);
}
void ExpoCell::setX(int x)
{
if (m_x != x) {
m_x = x;
Q_EMIT xChanged();
}
}
int ExpoCell::y() const
{
return m_y.value_or(0);
}
void ExpoCell::setY(int y)
{
if (m_y != y) {
m_y = y;
Q_EMIT yChanged();
}
}
int ExpoCell::width() const
{
return m_width.value_or(0);
}
void ExpoCell::setWidth(int width)
{
if (m_width != width) {
m_width = width;
Q_EMIT widthChanged();
}
}
int ExpoCell::height() const
{
return m_height.value_or(0);
}
void ExpoCell::setHeight(int height)
{
if (m_height != height) {
m_height = height;
Q_EMIT heightChanged();
}
}
QString ExpoCell::persistentKey() const
{
return m_persistentKey;
@ -197,17 +238,17 @@ void ExpoCell::setPersistentKey(const QString &key)
{
if (m_persistentKey != key) {
m_persistentKey = key;
update();
updateLayout();
Q_EMIT persistentKeyChanged();
}
}
int ExpoCell::bottomMargin() const
qreal ExpoCell::bottomMargin() const
{
return m_margins.bottom();
}
void ExpoCell::setBottomMargin(int margin)
void ExpoCell::setBottomMargin(qreal margin)
{
if (m_margins.bottom() != margin) {
m_margins.setBottom(margin);
@ -216,6 +257,31 @@ void ExpoCell::setBottomMargin(int margin)
}
}
void ExpoCell::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
{
updateContentItemGeometry();
QQuickItem::geometryChange(newGeometry, oldGeometry);
}
void ExpoCell::updateContentItemGeometry()
{
if (!m_contentItem) {
return;
}
QRectF rect = mapRectToItem(m_contentItem->parentItem(), boundingRect());
rect = {
rect.x() * m_partialActivationFactor + (m_naturalX + m_offsetX) * (1.0 - m_partialActivationFactor),
rect.y() * m_partialActivationFactor + (m_naturalY + m_offsetY) * (1.0 - m_partialActivationFactor),
rect.width() * m_partialActivationFactor + m_naturalWidth * (1.0 - m_partialActivationFactor),
rect.height() * m_partialActivationFactor + m_naturalHeight * (1.0 - m_partialActivationFactor)};
m_contentItem->setX(rect.x());
m_contentItem->setY(rect.y());
m_contentItem->setSize(rect.size());
}
ExpoLayout::ExpoLayout(QQuickItem *parent)
: QQuickItem(parent)
{
@ -253,6 +319,13 @@ void ExpoLayout::forceLayout()
updatePolish();
}
void ExpoLayout::updateCellsMapping()
{
for (ExpoCell *cell : m_cells) {
cell->polish();
}
}
void ExpoLayout::addCell(ExpoCell *cell)
{
Q_ASSERT(!m_cells.contains(cell));
@ -308,9 +381,9 @@ void ExpoLayout::updatePolish()
QList<QRectF> windowSizes;
for (ExpoCell *cell : std::as_const(m_cells)) {
const QMargins &margins = cell->margins();
const QMarginsF &margins = cell->margins();
const QMarginsF scaledMargins(margins.left() / scale, margins.top() / scale, margins.right() / scale, margins.bottom() / scale);
windowSizes.emplace_back(cell->naturalRect().toRectF().marginsAdded(scaledMargins));
windowSizes.emplace_back(cell->naturalRect().marginsAdded(scaledMargins));
}
auto windowLayouts = ExpoLayout::layout(area, windowSizes);
for (int i = 0; i < windowLayouts.size(); ++i) {
@ -324,10 +397,18 @@ void ExpoLayout::updatePolish()
QRectF rect = cell->naturalRect();
moveToFit(rect, target);
cell->setX(rect.x());
cell->setY(rect.y());
cell->setWidth(rect.width());
cell->setHeight(rect.height());
if (m_ready) {
// Use setProperty so the QML side can animate with Behavior
cell->setProperty("x", rect.x());
cell->setProperty("y", rect.y());
cell->setProperty("width", rect.width());
cell->setProperty("height", rect.height());
} else {
cell->setX(rect.x());
cell->setY(rect.y());
cell->setWidth(rect.width());
cell->setHeight(rect.height());
}
}
setReady();
}

View file

@ -105,6 +105,7 @@ public:
void setReady();
Q_INVOKABLE void forceLayout();
Q_INVOKABLE void updateCellsMapping();
protected:
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
@ -185,95 +186,100 @@ private:
qreal m_maxScale = 1.0;
};
class ExpoCell : public QObject
class ExpoCell : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(ExpoLayout *layout READ layout WRITE setLayout NOTIFY layoutChanged)
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged)
Q_PROPERTY(int naturalX READ naturalX WRITE setNaturalX NOTIFY naturalXChanged)
Q_PROPERTY(int naturalY READ naturalY WRITE setNaturalY NOTIFY naturalYChanged)
Q_PROPERTY(int naturalWidth READ naturalWidth WRITE setNaturalWidth NOTIFY naturalWidthChanged)
Q_PROPERTY(int naturalHeight READ naturalHeight WRITE setNaturalHeight NOTIFY naturalHeightChanged)
Q_PROPERTY(int x READ x NOTIFY xChanged)
Q_PROPERTY(int y READ y NOTIFY yChanged)
Q_PROPERTY(int width READ width NOTIFY widthChanged)
Q_PROPERTY(int height READ height NOTIFY heightChanged)
Q_PROPERTY(QQuickItem *contentItem READ contentItem WRITE setContentItem NOTIFY contentItemChanged)
Q_PROPERTY(qreal partialActivationFactor READ partialActivationFactor WRITE setPartialActivationFactor NOTIFY partialActivationFactorChanged)
Q_PROPERTY(bool shouldLayout READ shouldLayout WRITE setShouldLayout NOTIFY shouldLayoutChanged)
Q_PROPERTY(qreal offsetX READ offsetX WRITE setOffsetX NOTIFY offsetXChanged)
Q_PROPERTY(qreal offsetY READ offsetY WRITE setOffsetY NOTIFY offsetYChanged)
Q_PROPERTY(qreal naturalX READ naturalX WRITE setNaturalX NOTIFY naturalXChanged)
Q_PROPERTY(qreal naturalY READ naturalY WRITE setNaturalY NOTIFY naturalYChanged)
Q_PROPERTY(qreal naturalWidth READ naturalWidth WRITE setNaturalWidth NOTIFY naturalWidthChanged)
Q_PROPERTY(qreal naturalHeight READ naturalHeight WRITE setNaturalHeight NOTIFY naturalHeightChanged)
Q_PROPERTY(QString persistentKey READ persistentKey WRITE setPersistentKey NOTIFY persistentKeyChanged)
Q_PROPERTY(int bottomMargin READ bottomMargin WRITE setBottomMargin NOTIFY bottomMarginChanged)
Q_PROPERTY(qreal bottomMargin READ bottomMargin WRITE setBottomMargin NOTIFY bottomMarginChanged)
public:
explicit ExpoCell(QObject *parent = nullptr);
explicit ExpoCell(QQuickItem *parent = nullptr);
~ExpoCell() override;
bool isEnabled() const;
void setEnabled(bool enabled);
void componentComplete() override;
ExpoLayout *layout() const;
void setLayout(ExpoLayout *layout);
int naturalX() const;
void setNaturalX(int x);
bool shouldLayout() const;
void setShouldLayout(bool layout);
int naturalY() const;
void setNaturalY(int y);
QQuickItem *contentItem() const;
void setContentItem(QQuickItem *item);
int naturalWidth() const;
void setNaturalWidth(int width);
qreal partialActivationFactor() const;
void setPartialActivationFactor(qreal factor);
int naturalHeight() const;
void setNaturalHeight(int height);
qreal offsetX() const;
void setOffsetX(qreal x);
QRect naturalRect() const;
QMargins margins() const;
qreal offsetY() const;
void setOffsetY(qreal y);
int x() const;
void setX(int x);
qreal naturalX() const;
void setNaturalX(qreal x);
int y() const;
void setY(int y);
qreal naturalY() const;
void setNaturalY(qreal y);
int width() const;
void setWidth(int width);
qreal naturalWidth() const;
void setNaturalWidth(qreal width);
int height() const;
void setHeight(int height);
qreal naturalHeight() const;
void setNaturalHeight(qreal height);
QRectF naturalRect() const;
QMarginsF margins() const;
QString persistentKey() const;
void setPersistentKey(const QString &key);
int bottomMargin() const;
void setBottomMargin(int margin);
qreal bottomMargin() const;
void setBottomMargin(qreal margin);
public Q_SLOTS:
void update();
protected:
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
Q_SIGNALS:
void layoutChanged();
void enabledChanged();
void shouldLayoutChanged();
void contentItemChanged();
void partialActivationFactorChanged();
void offsetXChanged();
void offsetYChanged();
void naturalXChanged();
void naturalYChanged();
void naturalWidthChanged();
void naturalHeightChanged();
void xChanged();
void yChanged();
void widthChanged();
void heightChanged();
void persistentKeyChanged();
void bottomMarginChanged();
private:
void updateContentItemGeometry();
void updateLayout();
QString m_persistentKey;
bool m_enabled = true;
int m_naturalX = 0;
int m_naturalY = 0;
int m_naturalWidth = 0;
int m_naturalHeight = 0;
QMargins m_margins;
std::optional<int> m_x;
std::optional<int> m_y;
std::optional<int> m_width;
std::optional<int> m_height;
qreal m_offsetX = 0;
qreal m_offsetY = 0;
qreal m_naturalX = 0;
qreal m_naturalY = 0;
qreal m_naturalWidth = 0;
qreal m_naturalHeight = 0;
QMarginsF m_margins;
QPointer<ExpoLayout> m_layout;
QPointer<QQuickItem> m_contentItem;
qreal m_partialActivationFactor = 1.0;
bool m_shouldLayout = true;
};
/**

View file

@ -136,6 +136,7 @@ FocusScope {
delegate: WindowHeapDelegate {
windowHeap: heap
layout: expoLayout
}
onObjectRemoved: (index, object) => {

View file

@ -1,6 +1,7 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
SPDX-FileCopyrightText: 2024 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
@ -15,7 +16,7 @@ import org.kde.plasma.components 3.0 as PC3
import org.kde.plasma.extras as PlasmaExtras
import org.kde.ksvg 1.0 as KSvg
Item {
ExpoCell {
id: thumb
required property QtObject window
@ -23,8 +24,9 @@ Item {
required property Item windowHeap
readonly property bool selected: windowHeap.selectedIndex === index
property real partialActivationFactor: effect.partialActivationFactor
property bool gestureInProgress: effect.gestureInProgress
// Where the internal contentItem will be parented to
property Item contentItemParent: this
// no desktops is a special value which means "All Desktops"
readonly property bool presentOnCurrentDesktop: !window.desktops.length || window.desktops.indexOf(KWinComponents.Workspace.currentDesktop) !== -1
@ -61,125 +63,161 @@ Item {
readonly property alias downGestureProgress: touchDragHandler.downGestureProgress
signal downGestureTriggered()
// "normal" | "pressed" | "drag" | "reparenting"
property string substate: "normal"
state: {
if (thumb.gestureInProgress) {
return "partial";
}
if (thumb.partialActivationFactor > 0.5 && (cell.isReady || activeHidden)) {
return activeHidden ? "active-hidden" : `active-${substate}`;
}
return initialHidden ? "initial-hidden" : "initial";
}
visible: opacity > 0
z: (activeDragHandler.active || returning.running) ? 1000
: window.stackingOrder * (presentOnCurrentDesktop ? 1 : 0.001)
property bool isReady: width !== 0 && height !== 0
function restoreDND(oldGlobalRect: rect) {
thumbSource.restoreDND(oldGlobalRect);
}
component TweenBehavior : Behavior {
enabled: thumb.state === "active-normal" && thumb.windowHeap.animationEnabled && thumb.animationEnabled && !thumb.activeDragHandler.active
layout: windowHeap.layout
shouldLayout: !thumb.activeHidden
partialActivationFactor: effect.partialActivationFactor
naturalX: thumb.window.x - thumb.window.output.geometry.x
naturalY: thumb.window.y - thumb.window.output.geometry.y
naturalWidth: thumb.window.width
naturalHeight: thumb.window.height
persistentKey: thumb.window.internalId
bottomMargin: icon.height / 4 + (thumb.windowTitleVisible ? caption.height : 0)
Behavior on x {
enabled: thumb.isReady
NumberAnimation {
duration: thumb.windowHeap.animationDuration
easing.type: Easing.InOutCubic
}
}
Behavior on y {
enabled: thumb.isReady
NumberAnimation {
duration: thumb.windowHeap.animationDuration
easing.type: Easing.InOutCubic
}
}
Behavior on width {
enabled: thumb.isReady
NumberAnimation {
duration: thumb.windowHeap.animationDuration
easing.type: Easing.InOutCubic
}
}
Behavior on height {
enabled: thumb.isReady
NumberAnimation {
duration: thumb.windowHeap.animationDuration
easing.type: Easing.InOutCubic
}
}
TweenBehavior on x {}
TweenBehavior on y {}
TweenBehavior on width {}
TweenBehavior on height {}
contentItem: Item {
id: mainContent
parent: contentItemParent
visible: opacity > 0 && (!activeHidden || !initialHidden)
opacity: (1 - downGestureProgress) * (initialHidden ? partialActivationFactor : 1)
z: (activeDragHandler.active || returnAnimation.running) ? 1000
: thumb.window.stackingOrder * (presentOnCurrentDesktop ? 1 : 0.001)
KWinComponents.WindowThumbnail {
id: thumbSource
wId: thumb.window.internalId
KWinComponents.WindowThumbnail {
id: thumbSource
wId: thumb.window.internalId
scale: targetScale
width: mainContent.width
height: mainContent.height
Drag.proposedAction: Qt.MoveAction
Drag.supportedActions: Qt.MoveAction
Drag.source: thumb.window
Drag.hotSpot: Qt.point(
thumb.activeDragHandler.centroid.pressPosition.x * thumb.targetScale,
thumb.activeDragHandler.centroid.pressPosition.y * thumb.targetScale)
Drag.keys: ["kwin-window"]
Binding on width {
value: mainContent.width
when: !returnAnimation.active
}
Binding on height {
value: mainContent.height
when: !returnAnimation.active
}
onXChanged: effect.checkItemDraggedOutOfScreen(thumbSource)
onYChanged: effect.checkItemDraggedOutOfScreen(thumbSource)
Drag.proposedAction: Qt.MoveAction
Drag.supportedActions: Qt.MoveAction
Drag.source: thumb.window
Drag.hotSpot: Qt.point(
thumb.activeDragHandler.centroid.pressPosition.x * thumb.targetScale,
thumb.activeDragHandler.centroid.pressPosition.y * thumb.targetScale)
Drag.keys: ["kwin-window"]
function saveDND() {
const oldGlobalRect = mapToItem(null, 0, 0, width, height);
thumb.windowHeap.saveDND(thumb.window.internalId, oldGlobalRect);
}
function restoreDND(oldGlobalRect: rect) {
thumb.substate = "reparenting";
onXChanged: effect.checkItemDraggedOutOfScreen(thumbSource)
onYChanged: effect.checkItemDraggedOutOfScreen(thumbSource)
const newGlobalRect = mapFromItem(null, oldGlobalRect);
function saveDND() {
const oldGlobalRect = mapToItem(null, 0, 0, width, height);
thumb.windowHeap.saveDND(thumb.window.internalId, oldGlobalRect);
}
function restoreDND(oldGlobalRect: rect) {
const newGlobalRect = mapFromItem(null, oldGlobalRect);
// Disable bindings
returnAnimation.active = true;
x = newGlobalRect.x;
y = newGlobalRect.y;
width = newGlobalRect.width;
height = newGlobalRect.height;
returnAnimation.restart();
}
function deleteDND() {
thumb.windowHeap.deleteDND(thumb.window.internalId);
}
x = newGlobalRect.x;
y = newGlobalRect.y;
width = newGlobalRect.width;
height = newGlobalRect.height;
// Not using FrameSvg hover element intentionally for stylistic reasons
Rectangle {
border.width: Kirigami.Units.largeSpacing
border.color: Kirigami.Theme.highlightColor
anchors.fill: parent
anchors.margins: -border.width
radius: Kirigami.Units.cornerRadius
color: "transparent"
visible: !thumb.windowHeap.dragActive && (hoverHandler.hovered || (thumb.selected && Window.window.activeFocusItem)) && windowHeap.effectiveOrganized
}
thumb.substate = "normal";
}
function deleteDND() {
thumb.windowHeap.deleteDND(thumb.window.internalId);
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: thumb.activeDragHandler.active ? Qt.ClosedHandCursor : Qt.ArrowCursor
}
ParallelAnimation {
id: returnAnimation
property bool active: false
onRunningChanged: active = running
NumberAnimation {
target: thumbSource
properties: "x,y"
to: 0
duration: thumb.windowHeap.animationDuration
easing.type: Easing.InOutCubic
}
NumberAnimation {
target: thumbSource
property: "width"
to: mainContent.width
duration: thumb.windowHeap.animationDuration
easing.type: Easing.InOutCubic
}
NumberAnimation {
target: thumbSource
property: "height"
to: mainContent.height
duration: thumb.windowHeap.animationDuration
easing.type: Easing.InOutCubic
}
NumberAnimation {
target: thumbSource
property: "scale"
to: 1
duration: thumb.windowHeap.animationDuration
easing.type: Easing.InOutCubic
}
}
}
// Not using FrameSvg hover element intentionally for stylistic reasons
Rectangle {
border.width: Kirigami.Units.largeSpacing
border.color: Kirigami.Theme.highlightColor
anchors.fill: parent
anchors.margins: -border.width
radius: Kirigami.Units.cornerRadius
color: "transparent"
visible: !thumb.windowHeap.dragActive && (hoverHandler.hovered || (thumb.selected && Window.window.activeFocusItem)) && windowHeap.effectiveOrganized
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: thumb.activeDragHandler.active ? Qt.ClosedHandCursor : Qt.ArrowCursor
}
}
PC3.Label {
anchors.fill: thumbSource
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: i18nd("kwin", "Drag Down To Close")
opacity: thumbSource.opacity
visible: !thumb.activeHidden && touchDragHandler.active
}
Kirigami.Icon {
id: icon
width: Kirigami.Units.iconSizes.large
height: Kirigami.Units.iconSizes.large
source: thumb.window.icon
anchors.horizontalCenter: thumbSource.horizontalCenter
anchors.bottom: thumbSource.bottom
anchors.bottomMargin: -Math.round(height / 4)
visible: !thumb.activeHidden && !activeDragHandler.active
PC3.Label {
id: caption
visible: thumb.windowTitleVisible
width: cell.width
maximumLineCount: 1
anchors.top: parent.bottom
anchors.topMargin: Kirigami.Units.smallSpacing
anchors.horizontalCenter: parent.horizontalCenter
elide: Text.ElideRight
text: thumb.window.caption
color: Kirigami.Theme.textColor
textFormat: Text.PlainText
anchors.fill: thumbSource
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: i18nd("kwin", "Drag Down To Close")
visible: !thumb.activeHidden && touchDragHandler.active
background: Rectangle {
anchors.centerIn: parent
height: parent.contentHeight + Kirigami.Units.smallSpacing
@ -188,288 +226,157 @@ Item {
radius: Kirigami.Units.cornerRadius
}
}
}
ExpoCell {
id: cell
layout: windowHeap.layout
enabled: !thumb.activeHidden
naturalX: thumb.window.x
naturalY: thumb.window.y
naturalWidth: thumb.window.width
naturalHeight: thumb.window.height
persistentKey: thumb.window.internalId
bottomMargin: icon.height / 4 + (thumb.windowTitleVisible ? caption.height : 0)
property bool isReady: width !== 0 && height !== 0
}
states: [
State {
name: "initial"
PropertyChanges {
target: thumb
x: thumb.window.x - targetScreen.geometry.x - (thumb.windowHeap.absolutePositioning ? windowHeap.layout.Kirigami.ScenePosition.x : 0)
y: thumb.window.y - targetScreen.geometry.y - (thumb.windowHeap.absolutePositioning ? windowHeap.layout.Kirigami.ScenePosition.y : 0)
width: thumb.window.width
height: thumb.window.height
}
PropertyChanges {
target: thumbSource
x: 0
y: 0
width: thumb.window.width
height: thumb.window.height
}
PropertyChanges {
target: icon
opacity: 0
}
PropertyChanges {
target: closeButton
opacity: 0
}
},
State {
name: "partial"
PropertyChanges {
target: thumb
x: (thumb.window.x - targetScreen.geometry.x - (thumb.windowHeap.absolutePositioning ? windowHeap.layout.Kirigami.ScenePosition.x : 0)) * (thumb.activeHidden ? 1 : 1 - thumb.partialActivationFactor) + (thumb.activeHidden ? 0 : cell.x * thumb.partialActivationFactor)
y: (thumb.window.y - targetScreen.geometry.y - (thumb.windowHeap.absolutePositioning ? windowHeap.layout.Kirigami.ScenePosition.y : 0)) * (thumb.activeHidden ? 1 : 1 - thumb.partialActivationFactor) + (thumb.activeHidden ? 0 : cell.y * thumb.partialActivationFactor)
width: thumb.window.width * (thumb.activeHidden ? 1 : 1 - thumb.partialActivationFactor) + cell.width * (thumb.activeHidden ? 0 : thumb.partialActivationFactor)
height: thumb.window.height * (thumb.activeHidden ? 1 : 1 - thumb.partialActivationFactor) + cell.height * (thumb.activeHidden ? 0 : thumb.partialActivationFactor)
opacity: thumb.initialHidden
? (thumb.activeHidden ? 0 : thumb.partialActivationFactor)
: (thumb.activeHidden ? 1 - thumb.partialActivationFactor : 1)
}
PropertyChanges {
target: thumbSource
x: 0
y: 0
Kirigami.Icon {
id: icon
width: Kirigami.Units.iconSizes.large
height: Kirigami.Units.iconSizes.large
opacity: partialActivationFactor
scale: Math.min(1.0, mainContent.width / Math.max(0.01, thumb.width))
source: thumb.window.icon
anchors.horizontalCenter: thumbSource.horizontalCenter
anchors.verticalCenter: thumbSource.bottom
anchors.verticalCenterOffset: -Math.round(height / 4) * scale
visible: !thumb.activeHidden && !activeDragHandler.active && !returnAnimation.running
PC3.Label {
id: caption
visible: thumb.windowTitleVisible
width: thumb.width
height: thumb.height
}
PropertyChanges {
target: icon
opacity: thumb.partialActivationFactor
}
PropertyChanges {
target: closeButton
opacity: thumb.partialActivationFactor
}
},
State {
name: "initial-hidden"
extend: "initial"
PropertyChanges {
target: thumb
opacity: 0
}
PropertyChanges {
target: icon
opacity: 0
}
PropertyChanges {
target: closeButton
opacity: 0
}
},
State {
name: "active-hidden"
extend: "initial-hidden"
},
State {
// this state is never directly used without a substate
name: "active"
PropertyChanges {
target: thumb
x: cell.x
y: cell.y
width: cell.width
height: cell.height
}
PropertyChanges {
target: icon
opacity: 1
}
PropertyChanges {
target: closeButton
opacity: 1
}
},
State {
name: "active-normal"
extend: "active"
PropertyChanges {
target: thumbSource
x: 0
y: 0
width: thumb.width
height: thumb.height
}
},
State {
name: "active-pressed"
extend: "active"
PropertyChanges {
target: thumbSource
width: thumb.width
height: thumb.height
}
},
State {
name: "active-drag"
extend: "active"
PropertyChanges {
target: thumbSource
x: -thumb.activeDragHandler.centroid.pressPosition.x * thumb.targetScale +
thumb.activeDragHandler.centroid.position.x
y: -thumb.activeDragHandler.centroid.pressPosition.y * thumb.targetScale +
thumb.activeDragHandler.centroid.position.y
width: thumb.width * thumb.targetScale
height: thumb.height * thumb.targetScale
}
},
State {
name: "active-reparenting"
extend: "active"
}
]
transitions: [
Transition {
id: returning
from: "active-drag, active-reparenting"
to: "active-normal"
enabled: thumb.windowHeap.animationEnabled
NumberAnimation {
duration: thumb.windowHeap.animationDuration
properties: "x, y, width, height"
easing.type: Easing.InOutCubic
maximumLineCount: 1
anchors.top: parent.bottom
anchors.topMargin: Kirigami.Units.smallSpacing
anchors.horizontalCenter: parent.horizontalCenter
elide: Text.ElideRight
text: thumb.window.caption
color: Kirigami.Theme.textColor
textFormat: Text.PlainText
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
background: Rectangle {
anchors.centerIn: parent
height: parent.contentHeight + Kirigami.Units.smallSpacing
width: parent.contentWidth + Kirigami.Units.smallSpacing
color: Kirigami.Theme.backgroundColor
radius: Kirigami.Units.cornerRadius
}
}
}
]
HoverHandler {
id: hoverHandler
onHoveredChanged: if (hovered !== selected) {
thumb.windowHeap.resetSelected();
}
}
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: {
KWinComponents.Workspace.activeWindow = thumb.window;
thumb.windowHeap.activated();
}
onPressedChanged: {
if (pressed) {
var saved = Qt.point(thumbSource.x, thumbSource.y);
thumbSource.Drag.active = true;
thumb.substate = "pressed";
thumbSource.x = saved.x;
thumbSource.y = saved.y;
} else if (!thumb.activeDragHandler.active) {
thumbSource.Drag.active = false;
thumb.substate = "normal";
HoverHandler {
id: hoverHandler
onHoveredChanged: if (hovered !== selected) {
thumb.windowHeap.resetSelected();
}
}
}
component DragManager : DragHandler {
target: null
grabPermissions: PointerHandler.CanTakeOverFromAnything
// This does not work when moving pointer fast and pressing along the way
// See also QTBUG-105903, QTBUG-105904
// enabled: thumb.state !== "active-normal"
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: {
KWinComponents.Workspace.activeWindow = thumb.window;
thumb.windowHeap.activated();
}
onPressedChanged: {
if (pressed) {
thumbSource.Drag.active = true;
} else if (!thumb.activeDragHandler.active) {
thumbSource.Drag.active = false;
}
}
}
onActiveChanged: {
thumb.windowHeap.dragActive = active;
if (active) {
thumb.activeDragHandler = this;
thumb.substate = "drag";
} else {
thumbSource.saveDND();
component DragManager : DragHandler {
target: thumbSource
grabPermissions: PointerHandler.CanTakeOverFromAnything
// This does not work when moving pointer fast and pressing along the way
// See also QTBUG-105903, QTBUG-105904
// enabled: thumb.state !== "active-normal"
var action = thumbSource.Drag.drop();
if (action === Qt.MoveAction) {
// This whole component is in the process of being destroyed due to drop onto
// another virtual desktop (not another screen).
if (typeof thumbSource !== "undefined") {
// Except the case when it was dropped on the same desktop which it's already on, so let's return to normal state anyway.
thumbSource.deleteDND();
thumb.substate = "normal";
onActiveChanged: {
thumb.windowHeap.dragActive = active;
if (active) {
thumb.activeDragHandler = this;
} else {
thumbSource.saveDND();
returnAnimation.restart();
var action = thumbSource.Drag.drop();
if (action === Qt.MoveAction) {
// This whole component is in the process of being destroyed due to drop onto
// another virtual desktop (not another screen).
if (typeof thumbSource !== "undefined") {
// Except the case when it was dropped on the same desktop which it's already on, so let's return to normal state anyway.
thumbSource.deleteDND();
}
return;
}
return;
}
var globalPos = targetScreen.mapToGlobal(centroid.scenePosition);
effect.checkItemDroppedOutOfScreen(globalPos, thumbSource);
var globalPos = targetScreen.mapToGlobal(centroid.scenePosition);
effect.checkItemDroppedOutOfScreen(globalPos, thumbSource);
if (typeof thumbSource !== "undefined") {
// else, return to normal without reparenting
thumbSource.deleteDND();
thumb.substate = "normal";
if (typeof thumbSource !== "undefined") {
// else, return to normal without reparenting
thumbSource.deleteDND();
}
}
}
}
}
DragManager {
id: dragHandler
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad | PointerDevice.Stylus
}
DragManager {
id: touchDragHandler
acceptedDevices: PointerDevice.TouchScreen
readonly property double downGestureProgress: {
if (!active) {
return 0.0;
}
const startDistance = thumb.windowHeap.Kirigami.ScenePosition.y + thumb.windowHeap.height - centroid.scenePressPosition.y;
const localPosition = thumb.windowHeap.Kirigami.ScenePosition.y + thumb.windowHeap.height - centroid.scenePosition.y;
return 1 - Math.min(localPosition/startDistance, 1);
DragManager {
id: dragHandler
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad | PointerDevice.Stylus
}
onActiveChanged: {
if (!active) {
if (downGestureProgress > 0.6) {
thumb.downGestureTriggered();
DragManager {
id: touchDragHandler
acceptedDevices: PointerDevice.TouchScreen
readonly property double downGestureProgress: {
if (!active) {
return 0.0;
}
const startDistance = thumb.windowHeap.Kirigami.ScenePosition.y + thumb.windowHeap.height - centroid.scenePressPosition.y;
const localPosition = thumb.windowHeap.Kirigami.ScenePosition.y + thumb.windowHeap.height - centroid.scenePosition.y;
return 1 - Math.min(localPosition/startDistance, 1);
}
onActiveChanged: {
if (!active) {
if (downGestureProgress > 0.6) {
thumb.downGestureTriggered();
}
}
}
}
}
Loader {
id: closeButton
LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
Loader {
id: closeButton
LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
anchors {
right: thumbSource.right
top: thumbSource.top
margins: Kirigami.Units.smallSpacing
anchors {
right: thumbSource.right
top: thumbSource.top
margins: Kirigami.Units.smallSpacing
}
active: thumb.closeButtonVisible && (hoverHandler.hovered || Kirigami.Settings.tabletMode || Kirigami.Settings.hasTransientTouchInput) && thumb.window.closeable && !thumb.activeDragHandler.active && !returnAnimation.running
sourceComponent: PC3.Button {
text: i18ndc("kwin", "@info:tooltip as in: 'close this window'", "Close window")
icon.name: "window-close"
display: PC3.AbstractButton.IconOnly
PC3.ToolTip.text: text
PC3.ToolTip.visible: hovered && display === PC3.AbstractButton.IconOnly
PC3.ToolTip.delay: Kirigami.Units.toolTipDelay
Accessible.name: text
onClicked: thumb.window.closeWindow();
}
}
active: thumb.closeButtonVisible && (hoverHandler.hovered || Kirigami.Settings.tabletMode || Kirigami.Settings.hasTransientTouchInput) && thumb.window.closeable && !thumb.activeDragHandler.active
sourceComponent: PC3.Button {
text: i18ndc("kwin", "@info:tooltip as in: 'close this window'", "Close window")
icon.name: "window-close"
display: PC3.AbstractButton.IconOnly
PC3.ToolTip.text: text
PC3.ToolTip.visible: hovered && display === PC3.AbstractButton.IconOnly
PC3.ToolTip.delay: Kirigami.Units.toolTipDelay
Accessible.name: text
onClicked: thumb.window.closeWindow();
}
}
Component.onDestruction: {
if (selected) {
windowHeap.resetSelected();
Component.onDestruction: {
if (selected) {
windowHeap.resetSelected();
}
}
}
}

View file

@ -202,6 +202,7 @@ Item {
id: delegate
windowHeap: heap
partialActivationFactor: container.organized ? 1 : 0
contentItemParent: container
Behavior on partialActivationFactor {
SequentialAnimation {
PropertyAction {