aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/companion/Figure.qml
diff options
context:
space:
mode:
Diffstat (limited to 'desktop/companion/Figure.qml')
-rw-r--r--desktop/companion/Figure.qml173
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 }
- }
-}