effects/private: Merge two state machines into one with substates

Having one state machine instead of two somehow simplifies the code and
makes it less fragile.

The new property `substate`, which replaces `thumbSource.state` still
supports imperative assignments that are so important for Drag&Drop,
and still triggers state updates. However, now a substate may not
become an active state: that behavior didn't make sense in previous
iterations, and has led to a glitch where a window won't resize during
return-to-initial animation. The glitch happened because two state
transitions were starting almost-simultaneously (and by Murphy's law in
the worst possible order, of course), and both animations were playing,
but one animation was supposed to to a property that is managed by the
other. But since QtQuick animations cache their from/to values before
they start, the dependent transition essentially didn't animate at all.
This commit is contained in:
ivan tkachenko 2022-09-16 00:53:52 +03:00
parent 74af27dc55
commit 9897afa55f
No known key found for this signature in database
GPG key ID: AF72731B7C654CB3

View file

@ -56,12 +56,15 @@ Item {
readonly property alias downGestureProgress: touchDragHandler.downGestureProgress
signal downGestureTriggered()
// "normal" | "pressed" | "drag" | "reparenting"
property string substate: "normal"
state: {
if (effect.gestureInProgress) {
return "partial";
}
if (windowHeap.effectiveOrganized) {
return activeHidden ? "active-hidden" : "active";
return activeHidden ? "active-hidden" : `active-${substate}`;
}
return initialHidden ? "initial-hidden" : "initial";
}
@ -101,13 +104,12 @@ Item {
onXChanged: effect.checkItemDraggedOutOfScreen(thumbSource)
onYChanged: effect.checkItemDraggedOutOfScreen(thumbSource)
state: "normal"
function saveDND() {
const oldGlobalRect = mapToItem(null, 0, 0, width, height);
thumb.windowHeap.saveDND(thumb.client.internalId, oldGlobalRect);
}
function restoreDND(oldGlobalRect: rect) {
state = "reparenting";
thumb.substate = "reparenting";
const newGlobalRect = mapFromItem(null, oldGlobalRect);
@ -116,56 +118,7 @@ Item {
width = newGlobalRect.width;
height = newGlobalRect.height;
state = "normal";
}
states: [
State {
name: "normal"
PropertyChanges {
target: thumbSource
x: 0
y: 0
width: thumb.width
height: thumb.height
}
},
State {
name: "pressed"
PropertyChanges {
target: thumbSource
width: thumb.width
height: thumb.height
}
},
State {
name: "drag"
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: "reparenting"
PropertyChanges {
target: thumbSource
}
}
]
transitions: Transition {
id: returning
from: "drag,reparenting"
to: "normal"
enabled: thumb.windowHeap.animationEnabled
NumberAnimation {
duration: thumb.windowHeap.animationDuration
properties: "x, y, width, height"
easing.type: Easing.OutCubic
}
thumb.substate = "normal";
}
PlasmaCore.FrameSvgItem {
@ -244,6 +197,13 @@ Item {
width: thumb.client.width
height: thumb.client.height
}
PropertyChanges {
target: thumbSource
x: 0
y: 0
width: thumb.client.width
height: thumb.client.height
}
PropertyChanges {
target: icon
opacity: 0
@ -291,6 +251,11 @@ Item {
}
},
State {
name: "active-hidden"
extend: "initial-hidden"
},
State {
// this state is never directly used without a substate
name: "active"
PropertyChanges {
target: thumb
@ -309,20 +274,66 @@ Item {
}
},
State {
name: "active-hidden"
extend: "initial-hidden"
name: "active-normal"
extend: "active"
PropertyChanges {
target: thumbSource
x: 0
y: 0
width: cell.width
height: cell.height
}
},
State {
name: "active-pressed"
extend: "active"
PropertyChanges {
target: thumbSource
width: cell.width
height: cell.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: cell.width * thumb.targetScale
height: cell.height * thumb.targetScale
}
},
State {
name: "active-reparenting"
extend: "active"
}
]
transitions: Transition {
to: "initial, initial-hidden, active, active-hidden"
enabled: thumb.windowHeap.animationEnabled
NumberAnimation {
duration: thumb.windowHeap.animationDuration
properties: "x, y, width, height, opacity"
easing.type: Easing.OutCubic
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.OutCubic
}
},
Transition {
to: "initial, initial-hidden, active-normal, active-hidden"
enabled: thumb.windowHeap.animationEnabled
NumberAnimation {
duration: thumb.windowHeap.animationDuration
properties: "x, y, width, height, opacity"
easing.type: Easing.OutCubic
}
}
}
]
HoverHandler {
id: hoverHandler
@ -341,12 +352,12 @@ Item {
if (pressed) {
var saved = Qt.point(thumbSource.x, thumbSource.y);
thumbSource.Drag.active = true;
thumbSource.state = "pressed";
thumb.substate = "pressed";
thumbSource.x = saved.x;
thumbSource.y = saved.y;
} else if (!thumb.activeDragHandler.active) {
thumbSource.Drag.active = false;
thumbSource.state = "normal";
thumb.substate = "normal";
}
}
}
@ -365,13 +376,13 @@ Item {
grabPermissions: PointerHandler.CanTakeOverFromAnything
// This does not work when moving pointer fast and pressing along the way
// See also QTBUG-105903, QTBUG-105904
// enabled: thumbSource.state !== "normal"
// enabled: thumb.state !== "active-normal"
onActiveChanged: {
thumb.windowHeap.dragActive = active;
if (active) {
thumb.activeDragHandler = this;
thumbSource.state = "drag";
thumb.substate = "drag";
} else {
thumbSource.saveDND();
@ -381,7 +392,7 @@ Item {
// 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.state = "normal";
thumb.substate = "normal";
}
return;
}
@ -390,7 +401,7 @@ Item {
effect.checkItemDroppedOutOfScreen(globalPos, thumbSource);
// else, return to normal without reparenting
thumbSource.state = "normal";
thumb.substate = "normal";
}
}
}