motion: call the curve bank the shell has always carried
Appearance.animationCurves is Material 3 Expressive and every spatial curve in it overshoots. Twelve souveraine surfaces, 31 hand-typed linear animations, three calls into that bank from two files. The card's entry rise is gated on inFlight; unguarded it is TASK-60's second writer over the carry.
This commit is contained in:
parent
f6ede33494
commit
787075f47f
3 changed files with 76 additions and 13 deletions
|
|
@ -309,16 +309,20 @@ PanelWindow {
|
||||||
NumberAnimation { target: handle; property: "scale"; to: 1.0; duration: 220; easing.type: Easing.InOutQuad }
|
NumberAnimation { target: handle; property: "scale"; to: 1.0; duration: 220; easing.type: Easing.InOutQuad }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All three off the house bank rather than hand-typed linear ramps.
|
||||||
|
// Still gated on `!dragging` — while the thumb is on the pill the
|
||||||
|
// handle tracks it directly and a Behavior would put a curve between
|
||||||
|
// the finger and the thing it is holding.
|
||||||
Behavior on width {
|
Behavior on width {
|
||||||
enabled: !rail.dragging
|
enabled: !rail.dragging
|
||||||
NumberAnimation { duration: 120 }
|
animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)
|
||||||
}
|
}
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
ColorAnimation { duration: 140 }
|
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||||
}
|
}
|
||||||
Behavior on y {
|
Behavior on y {
|
||||||
enabled: !rail.dragging
|
enabled: !rail.dragging
|
||||||
NumberAnimation { duration: 160; easing.type: Easing.OutCubic }
|
animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -134,10 +134,13 @@ Item {
|
||||||
// and must not be smoothed, or the cards lag the fingers that are
|
// and must not be smoothed, or the cards lag the fingers that are
|
||||||
// moving them.
|
// moving them.
|
||||||
enabled: root.open || ZoneTransition.travel === 0
|
enabled: root.open || ZoneTransition.travel === 0
|
||||||
NumberAnimation {
|
// `elementMoveEnter` rather than a hand-typed OutCubic. The house
|
||||||
duration: 260
|
// curve bank is Material 3 Expressive and every one of its spatial
|
||||||
easing.type: Easing.OutCubic
|
// curves overshoots — the shell has carried it since ii and only
|
||||||
}
|
// `SubconsciousTicker` and `AgentMessage` had ever called it, so every
|
||||||
|
// navigation surface animated on a flat cubic. That is the missing
|
||||||
|
// whoosh, and it was a two-line import away the whole time.
|
||||||
|
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask the moment this becomes visible. Cheap, and the whole answer to why
|
// Ask the moment this becomes visible. Cheap, and the whole answer to why
|
||||||
|
|
@ -318,7 +321,27 @@ Item {
|
||||||
scale: 1 - 0.07 * zoneCard.depth
|
scale: 1 - 0.07 * zoneCard.depth
|
||||||
opacity: zoneCard.share * (1 - 0.4 * Math.min(1, zoneCard.depth))
|
opacity: zoneCard.share * (1 - 0.4 * Math.min(1, zoneCard.depth))
|
||||||
Behavior on scale {
|
Behavior on scale {
|
||||||
NumberAnimation { duration: 180; easing.type: Easing.OutCubic }
|
animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The card arrives rather than fading in place.
|
||||||
|
//
|
||||||
|
// A separate transform, and deliberately with **no** Behavior of its
|
||||||
|
// own: `share` is continuous — it is `progress` remapped — so it is
|
||||||
|
// already smoothed by the Behavior on `progress` above. Folding it
|
||||||
|
// into `scale` would put a 350 ms animation under a value that
|
||||||
|
// changes every frame, which restarts the animation every frame and
|
||||||
|
// reads as lag rather than as motion. `depth` is step-like and is
|
||||||
|
// the one that wants a Behavior.
|
||||||
|
//
|
||||||
|
// Gated on `inFlight` for the same reason `depth` is: while the
|
||||||
|
// compositor carries a real window onto this rect, a card that also
|
||||||
|
// scales is TASK-60's two-writer fault exactly.
|
||||||
|
transform: Scale {
|
||||||
|
origin.x: zoneCard.width / 2
|
||||||
|
origin.y: zoneCard.height / 2
|
||||||
|
xScale: ZoneTransition.inFlight ? 1 : 0.90 + 0.10 * zoneCard.share
|
||||||
|
yScale: xScale
|
||||||
}
|
}
|
||||||
|
|
||||||
// No `scale` tied to the *gesture*, and that absence is the point of
|
// No `scale` tied to the *gesture*, and that absence is the point of
|
||||||
|
|
@ -540,7 +563,9 @@ Item {
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.margins: 12
|
anchors.margins: 12
|
||||||
height: ZoneTransition.trayHeight - 12
|
height: ZoneTransition.trayHeight - 12
|
||||||
opacity: root.progress
|
// The verbs carry their own staggered fade now, so this band no longer
|
||||||
|
// multiplies one over the top of them — squaring the ramp made the row
|
||||||
|
// arrive late and all at once, which is the opposite of the stagger.
|
||||||
transform: Translate { y: (1 - root.progress) * 24 }
|
transform: Translate { y: (1 - root.progress) * 24 }
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
|
|
@ -555,6 +580,7 @@ Item {
|
||||||
// where you left it — which makes multitasking the only place it is
|
// where you left it — which makes multitasking the only place it is
|
||||||
// findable, and therefore the only honest place to turn it off.
|
// findable, and therefore the only honest place to turn it off.
|
||||||
TrayVerb {
|
TrayVerb {
|
||||||
|
order: 0
|
||||||
symbol: root.subject && root.subject.floating === true
|
symbol: root.subject && root.subject.floating === true
|
||||||
? "picture_in_picture_off" : "picture_in_picture"
|
? "picture_in_picture_off" : "picture_in_picture"
|
||||||
caption: root.subject && root.subject.floating === true
|
caption: root.subject && root.subject.floating === true
|
||||||
|
|
@ -576,6 +602,7 @@ Item {
|
||||||
// `Some(w) if w >= count => self.workspaces.push()`, so asking for
|
// `Some(w) if w >= count => self.workspaces.push()`, so asking for
|
||||||
// the zone past the end *is* the mint. One button, no new verb.
|
// the zone past the end *is* the mint. One button, no new verb.
|
||||||
TrayVerb {
|
TrayVerb {
|
||||||
|
order: 1
|
||||||
symbol: "add_to_queue"
|
symbol: "add_to_queue"
|
||||||
caption: qsTr("New zone")
|
caption: qsTr("New zone")
|
||||||
active: root.subject !== null
|
active: root.subject !== null
|
||||||
|
|
@ -592,6 +619,7 @@ Item {
|
||||||
// a row of cards is the wrong place to take that choice away.
|
// a row of cards is the wrong place to take that choice away.
|
||||||
// Force lives on the sheet, behind a hold, where it is deliberate.
|
// Force lives on the sheet, behind a hold, where it is deliberate.
|
||||||
TrayVerb {
|
TrayVerb {
|
||||||
|
order: 2
|
||||||
symbol: "clear_all"
|
symbol: "clear_all"
|
||||||
caption: qsTr("Close all")
|
caption: qsTr("Close all")
|
||||||
active: root.restingZone !== null
|
active: root.restingZone !== null
|
||||||
|
|
@ -610,7 +638,8 @@ Item {
|
||||||
anchors.bottomMargin: 4
|
anchors.bottomMargin: 4
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
opacity: root.spoken.length > 0 ? 0.95 : 0.55
|
// Times `progress`, which the band used to apply for it.
|
||||||
|
opacity: root.progress * (root.spoken.length > 0 ? 0.95 : 0.55)
|
||||||
text: root.note
|
text: root.note
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -623,12 +652,27 @@ Item {
|
||||||
id: verb
|
id: verb
|
||||||
required property string symbol
|
required property string symbol
|
||||||
required property string caption
|
required property string caption
|
||||||
|
// Where in the row this one sits, so the band arrives as a sentence
|
||||||
|
// rather than as a block. Named at the call site rather than taken from
|
||||||
|
// a Repeater index because the three verbs are three deliberate things,
|
||||||
|
// not a model.
|
||||||
|
required property int order
|
||||||
property bool active: true
|
property bool active: true
|
||||||
signal triggered
|
signal triggered
|
||||||
|
|
||||||
width: 84
|
width: 84
|
||||||
height: 78
|
height: 78
|
||||||
opacity: verb.active ? 1 : 0.35
|
|
||||||
|
// Each verb starts 6% of the climb after the one before it, so the row
|
||||||
|
// lands left to right. Capped by the same `min(1, …)` shape the cards
|
||||||
|
// use, so a fully open tray is fully opaque and not 94% of one.
|
||||||
|
readonly property real share: {
|
||||||
|
const start = verb.order * 0.06;
|
||||||
|
return Math.max(0, Math.min(1, (root.progress - start) / (1 - start)));
|
||||||
|
}
|
||||||
|
|
||||||
|
opacity: verb.share * (verb.active ? 1 : 0.35)
|
||||||
|
transform: Translate { y: (1 - verb.share) * 14 }
|
||||||
|
|
||||||
RippleButton {
|
RippleButton {
|
||||||
id: hit
|
id: hit
|
||||||
|
|
@ -639,6 +683,15 @@ Item {
|
||||||
buttonRadius: 26
|
buttonRadius: 26
|
||||||
enabled: verb.active
|
enabled: verb.active
|
||||||
onClicked: verb.triggered()
|
onClicked: verb.triggered()
|
||||||
|
// The bank's own bounce, which nothing outside the subconscious
|
||||||
|
// ticker had ever used. `expressiveDefaultSpatial` overshoots to
|
||||||
|
// 1.21, so the button comes back past its resting size and settles
|
||||||
|
// — the difference between a control that acknowledges a thumb and
|
||||||
|
// one that merely changes colour under it.
|
||||||
|
scale: hit.down ? 0.90 : 1
|
||||||
|
Behavior on scale {
|
||||||
|
animation: Appearance.animation.clickBounce.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
contentItem: MaterialSymbol {
|
contentItem: MaterialSymbol {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: verb.symbol
|
text: verb.symbol
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,14 @@ Item {
|
||||||
border.color: Appearance.colors.colLayer1Active
|
border.color: Appearance.colors.colLayer1Active
|
||||||
|
|
||||||
scale: area.pressed ? 0.94 : 1.0
|
scale: area.pressed ? 0.94 : 1.0
|
||||||
Behavior on scale { NumberAnimation { duration: 90 } }
|
// The house bounce, which overshoots on the way back — a flat 90 ms
|
||||||
Behavior on color { ColorAnimation { duration: 90 } }
|
// ramp is a size change, not an acknowledgement.
|
||||||
|
Behavior on scale {
|
||||||
|
animation: Appearance.animation.clickBounce.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
Behavior on color {
|
||||||
|
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
|
||||||
MaterialSymbol {
|
MaterialSymbol {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue