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 }
|
||||
}
|
||||
|
||||
// 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 {
|
||||
enabled: !rail.dragging
|
||||
NumberAnimation { duration: 120 }
|
||||
animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)
|
||||
}
|
||||
Behavior on color {
|
||||
ColorAnimation { duration: 140 }
|
||||
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||
}
|
||||
Behavior on y {
|
||||
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
|
||||
// moving them.
|
||||
enabled: root.open || ZoneTransition.travel === 0
|
||||
NumberAnimation {
|
||||
duration: 260
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
// `elementMoveEnter` rather than a hand-typed OutCubic. The house
|
||||
// curve bank is Material 3 Expressive and every one of its spatial
|
||||
// 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
|
||||
|
|
@ -318,7 +321,27 @@ Item {
|
|||
scale: 1 - 0.07 * zoneCard.depth
|
||||
opacity: zoneCard.share * (1 - 0.4 * Math.min(1, zoneCard.depth))
|
||||
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
|
||||
|
|
@ -540,7 +563,9 @@ Item {
|
|||
anchors.bottom: parent.bottom
|
||||
anchors.margins: 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 }
|
||||
|
||||
Row {
|
||||
|
|
@ -555,6 +580,7 @@ Item {
|
|||
// where you left it — which makes multitasking the only place it is
|
||||
// findable, and therefore the only honest place to turn it off.
|
||||
TrayVerb {
|
||||
order: 0
|
||||
symbol: root.subject && root.subject.floating === true
|
||||
? "picture_in_picture_off" : "picture_in_picture"
|
||||
caption: root.subject && root.subject.floating === true
|
||||
|
|
@ -576,6 +602,7 @@ Item {
|
|||
// `Some(w) if w >= count => self.workspaces.push()`, so asking for
|
||||
// the zone past the end *is* the mint. One button, no new verb.
|
||||
TrayVerb {
|
||||
order: 1
|
||||
symbol: "add_to_queue"
|
||||
caption: qsTr("New zone")
|
||||
active: root.subject !== null
|
||||
|
|
@ -592,6 +619,7 @@ Item {
|
|||
// 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.
|
||||
TrayVerb {
|
||||
order: 2
|
||||
symbol: "clear_all"
|
||||
caption: qsTr("Close all")
|
||||
active: root.restingZone !== null
|
||||
|
|
@ -610,7 +638,8 @@ Item {
|
|||
anchors.bottomMargin: 4
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -623,12 +652,27 @@ Item {
|
|||
id: verb
|
||||
required property string symbol
|
||||
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
|
||||
signal triggered
|
||||
|
||||
width: 84
|
||||
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 {
|
||||
id: hit
|
||||
|
|
@ -639,6 +683,15 @@ Item {
|
|||
buttonRadius: 26
|
||||
enabled: verb.active
|
||||
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 {
|
||||
anchors.centerIn: parent
|
||||
text: verb.symbol
|
||||
|
|
|
|||
|
|
@ -51,8 +51,14 @@ Item {
|
|||
border.color: Appearance.colors.colLayer1Active
|
||||
|
||||
scale: area.pressed ? 0.94 : 1.0
|
||||
Behavior on scale { NumberAnimation { duration: 90 } }
|
||||
Behavior on color { ColorAnimation { duration: 90 } }
|
||||
// The house bounce, which overshoots on the way back — a flat 90 ms
|
||||
// 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 {
|
||||
anchors.centerIn: parent
|
||||
|
|
|
|||
Loading…
Reference in a new issue