diff options
Diffstat (limited to 'desktop/companion/Figure.qml')
| -rw-r--r-- | desktop/companion/Figure.qml | 173 |
1 files changed, 0 insertions, 173 deletions
diff --git a/desktop/companion/Figure.qml b/desktop/companion/Figure.qml deleted file mode 100644 index 285e2cb..0000000 --- a/desktop/companion/Figure.qml +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (C) 2026 Danilo M. <danix@danix.xyz> -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License version 2 as -// published by the Free Software Foundation. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -import QtQuick -import ".." - -// One frame of the companion, drawn at half its pixel size and standing on -// its bottom edge: a crossfade between frames, a slow bob, a tremble, the -// drop out of view and back, and the self-destruct flash. Which frame is Mood.js's -// business; this only draws it. -Item { - id: s - - // The frame, and what to show when it is missing: the same outfit's - // base, never the other outfit's, so a gap in the art cannot put the - // skirt back mid-scene. - property string source - property string fallback - // Dropped below the bottom edge and faded out. - property bool out: false - signal settled(bool out) - - // Two images: the front one stays until the back one has loaded, then - // they swap, so a frame change never flashes empty. - property bool flip: false - readonly property Image front: s.flip ? b : a - width: s.front.implicitWidth / 2 - height: s.front.implicitHeight / 2 - - function load(url) { - const front = s.flip ? b : a, back = s.flip ? a : b; - if (String(front.source) === url) { back.source = ""; return; } - if (String(back.source) === url && back.status === Image.Ready) { s.flip = !s.flip; return; } - back.source = url; - } - onSourceChanged: s.load(s.source) - Component.onCompleted: s.load(s.source) - - // The incoming frame fades in on top while the outgoing one stays - // opaque under it. Fading both at once left each near half opacity - // mid-swap, and she blinked see-through on every turn of the dj loop. - property real fade: 1 - NumberAnimation { id: fadeIn; target: s; property: "fade"; from: 0; to: 1; duration: 120 } - onFlipChanged: fadeIn.restart() - - function ready(img) { - if (img.status === Image.Error && String(img.source) !== s.fallback) { - img.source = s.fallback; - return; - } - if (img.status === Image.Ready && img !== s.front) s.flip = !s.flip; - } - - property real drop: 0 - property real bob: 0 - NumberAnimation { - id: dropAnim - target: s; property: "drop"; duration: 320 - onFinished: s.settled(s.out) - } - // Out from creation (a shell started with her hidden) never ran the drop, - // since a starting value fires no onOutChanged, and she was drawn in place - // with her clock stopped, frozen on whatever frame came next. The body - // below hides her until she is shown, and the first entrance starts from - // below like any other. - onOutChanged: { - dropAnim.stop(); - if (!s.out && s.drop === 0) s.drop = s.height + 24; - dropAnim.to = s.out ? s.height + 24 : 0; - dropAnim.easing.type = s.out ? Easing.InCubic : Easing.OutBack; - dropAnim.start(); - } - // A quick side to side shiver, for the wallpaper blush. Stopping can - // leave it mid-swing, so it is put back to rest. - property bool trembling: false - property real shake: 0 - onTremblingChanged: if (!s.trembling) s.shake = 0 - SequentialAnimation on shake { - running: s.trembling && !s.out - loops: Animation.Infinite - NumberAnimation { to: 1.5; duration: 40 } - NumberAnimation { to: -1.5; duration: 80 } - NumberAnimation { to: 0; duration: 40 } - } - SequentialAnimation on bob { - running: !s.out - loops: Animation.Infinite - NumberAnimation { to: 2; duration: 1900; easing.type: Easing.InOutSine } - NumberAnimation { to: 0; duration: 2300; easing.type: Easing.InOutSine } - } - - Item { - anchors.fill: parent - visible: !s.out || dropAnim.running - opacity: 1 - s.drop / (s.height + 24) - transform: Translate { x: s.shake; y: s.drop - s.bob } - - Image { - id: a - width: implicitWidth / 2; height: implicitHeight / 2 - anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter } - smooth: true; mipmap: true; asynchronous: true - z: s.front === a ? 1 : 0 - opacity: s.front === a ? s.fade : s.fade < 1 ? 1 : 0 - onStatusChanged: s.ready(a) - } - Image { - id: b - width: implicitWidth / 2; height: implicitHeight / 2 - anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter } - smooth: true; mipmap: true; asynchronous: true - z: s.front === b ? 1 : 0 - opacity: s.front === b ? s.fade : s.fade < 1 ? 1 : 0 - onStatusChanged: s.ready(b) - } - } - - // --- self-destruct: the glow swells into a flash, then a ring and - // sparks burst out. About 1.1 s, matching Mood.T.destruct, during which - // the frame swaps to the leotard under the flash. - - function explode() { boom.restart(); } - - property real burst: 0 - Rectangle { - id: flash - anchors.centerIn: parent - width: s.height * 1.6; height: width; radius: width / 2 - color: "white"; opacity: 0; scale: 0 - } - Rectangle { - id: ring - anchors.centerIn: parent - width: s.height * 2; height: width; radius: width / 2 - color: "transparent" - border { width: 3; color: Theme.peach } - opacity: 0; scale: 0 - } - Repeater { - model: 8 - Rectangle { - required property int index - readonly property real angle: index * Math.PI / 4 - width: 6; height: 6; radius: 3 - color: index % 2 ? Theme.peach : Theme.yellow - x: s.width / 2 - 3 + Math.cos(angle) * s.burst * s.height - y: s.height / 2 - 3 + Math.sin(angle) * s.burst * s.height - opacity: s.burst > 0 && s.burst < 1 ? 1 - s.burst : 0 - } - } - SequentialAnimation { - id: boom - ParallelAnimation { - NumberAnimation { target: flash; property: "scale"; from: 0; to: 1; duration: 600; easing.type: Easing.InQuad } - NumberAnimation { target: flash; property: "opacity"; from: 0; to: 0.95; duration: 600 } - } - ParallelAnimation { - NumberAnimation { target: flash; property: "opacity"; to: 0; duration: 400 } - NumberAnimation { target: ring; property: "scale"; from: 0.2; to: 1.4; duration: 400; easing.type: Easing.OutCubic } - NumberAnimation { target: ring; property: "opacity"; from: 1; to: 0; duration: 400 } - NumberAnimation { target: s; property: "burst"; from: 0; to: 1; duration: 500; easing.type: Easing.OutCubic } - } - PropertyAction { target: s; property: "burst"; value: 0 } - } -} |
