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 { DropArea {
id: dropArea id: dropArea
anchors.fill: parent anchors.fill: parent
onEntered: (drag) => {
drag.accepted = true;
}
onDropped: drop => { onDropped: drop => {
drop.accepted = true; drop.accepted = true;
// dragging a KWin::Window // dragging a KWin::Window

View file

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

View file

@ -11,9 +11,26 @@
#include <deque> #include <deque>
#include <tuple> #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() ExpoCell::~ExpoCell()
@ -21,6 +38,12 @@ ExpoCell::~ExpoCell()
setLayout(nullptr); setLayout(nullptr);
} }
void ExpoCell::componentComplete()
{
QQuickItem::componentComplete();
updateContentItemGeometry();
}
ExpoLayout *ExpoCell::layout() const ExpoLayout *ExpoCell::layout() const
{ {
return m_layout; return m_layout;
@ -35,159 +58,177 @@ void ExpoCell::setLayout(ExpoLayout *layout)
m_layout->removeCell(this); m_layout->removeCell(this);
} }
m_layout = layout; m_layout = layout;
if (m_layout && m_enabled) { if (m_layout && m_shouldLayout) {
m_layout->addCell(this); m_layout->addCell(this);
} }
updateContentItemGeometry();
Q_EMIT layoutChanged(); 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) { if (shouldLayout == m_shouldLayout) {
m_enabled = enabled; return;
if (enabled) {
if (m_layout) {
m_layout->addCell(this);
}
} else {
if (m_layout) {
m_layout->removeCell(this);
}
}
Q_EMIT enabledChanged();
} }
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) { if (m_layout) {
m_layout->polish(); 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; return m_naturalX;
} }
void ExpoCell::setNaturalX(int x) void ExpoCell::setNaturalX(qreal x)
{ {
if (m_naturalX != x) { if (m_naturalX != x) {
m_naturalX = x; m_naturalX = x;
update(); updateLayout();
Q_EMIT naturalXChanged(); Q_EMIT naturalXChanged();
} }
} }
int ExpoCell::naturalY() const qreal ExpoCell::naturalY() const
{ {
return m_naturalY; return m_naturalY;
} }
void ExpoCell::setNaturalY(int y) void ExpoCell::setNaturalY(qreal y)
{ {
if (m_naturalY != y) { if (m_naturalY != y) {
m_naturalY = y; m_naturalY = y;
update(); updateLayout();
Q_EMIT naturalYChanged(); Q_EMIT naturalYChanged();
} }
} }
int ExpoCell::naturalWidth() const qreal ExpoCell::naturalWidth() const
{ {
return m_naturalWidth; return m_naturalWidth;
} }
void ExpoCell::setNaturalWidth(int width) void ExpoCell::setNaturalWidth(qreal width)
{ {
if (m_naturalWidth != width) { if (m_naturalWidth != width) {
m_naturalWidth = width; m_naturalWidth = width;
update(); updateLayout();
Q_EMIT naturalWidthChanged(); Q_EMIT naturalWidthChanged();
} }
} }
int ExpoCell::naturalHeight() const qreal ExpoCell::naturalHeight() const
{ {
return m_naturalHeight; return m_naturalHeight;
} }
void ExpoCell::setNaturalHeight(int height) void ExpoCell::setNaturalHeight(qreal height)
{ {
if (m_naturalHeight != height) { if (m_naturalHeight != height) {
m_naturalHeight = height; m_naturalHeight = height;
update(); updateLayout();
Q_EMIT naturalHeightChanged(); 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; 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 QString ExpoCell::persistentKey() const
{ {
return m_persistentKey; return m_persistentKey;
@ -197,17 +238,17 @@ void ExpoCell::setPersistentKey(const QString &key)
{ {
if (m_persistentKey != key) { if (m_persistentKey != key) {
m_persistentKey = key; m_persistentKey = key;
update(); updateLayout();
Q_EMIT persistentKeyChanged(); Q_EMIT persistentKeyChanged();
} }
} }
int ExpoCell::bottomMargin() const qreal ExpoCell::bottomMargin() const
{ {
return m_margins.bottom(); return m_margins.bottom();
} }
void ExpoCell::setBottomMargin(int margin) void ExpoCell::setBottomMargin(qreal margin)
{ {
if (m_margins.bottom() != margin) { if (m_margins.bottom() != margin) {
m_margins.setBottom(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) ExpoLayout::ExpoLayout(QQuickItem *parent)
: QQuickItem(parent) : QQuickItem(parent)
{ {
@ -253,6 +319,13 @@ void ExpoLayout::forceLayout()
updatePolish(); updatePolish();
} }
void ExpoLayout::updateCellsMapping()
{
for (ExpoCell *cell : m_cells) {
cell->polish();
}
}
void ExpoLayout::addCell(ExpoCell *cell) void ExpoLayout::addCell(ExpoCell *cell)
{ {
Q_ASSERT(!m_cells.contains(cell)); Q_ASSERT(!m_cells.contains(cell));
@ -308,9 +381,9 @@ void ExpoLayout::updatePolish()
QList<QRectF> windowSizes; QList<QRectF> windowSizes;
for (ExpoCell *cell : std::as_const(m_cells)) { 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); 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); auto windowLayouts = ExpoLayout::layout(area, windowSizes);
for (int i = 0; i < windowLayouts.size(); ++i) { for (int i = 0; i < windowLayouts.size(); ++i) {
@ -324,10 +397,18 @@ void ExpoLayout::updatePolish()
QRectF rect = cell->naturalRect(); QRectF rect = cell->naturalRect();
moveToFit(rect, target); moveToFit(rect, target);
cell->setX(rect.x()); if (m_ready) {
cell->setY(rect.y()); // Use setProperty so the QML side can animate with Behavior
cell->setWidth(rect.width()); cell->setProperty("x", rect.x());
cell->setHeight(rect.height()); 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(); setReady();
} }

View file

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

View file

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

View file

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