edit method

  1. @override
Reel edit(
  1. Take take
)
override

Implementation

@override
Reel edit(Take take) {
  var screen = take.screen;
  var camera = FrameNode(name: 'camera')
    ..x = 0
    ..y = 0
    ..width = screen.width
    ..height = screen.height
    ..clip = true
    ..children.addAll([
      ExternalNode(const ScreenArgs(), name: 'screen')
        ..width = screen.width
        ..height = screen.height,
      ExternalNode(const PointerArgs(), name: 'pointer')
        ..width = screen.width
        ..height = screen.height,
    ]);
  var root = FrameNode(name: 'root')
    ..width = screen.width
    ..height = screen.height
    ..fill = ground
    ..children.add(camera);
  // The document is made *after* the walk below has added its captions:
  // a document adopts the nodes it is given at construction, and a node
  // added later has no document to tell when its fx move.
  var motion = MotionDocument(sceneClassName: 'StockReel');
  var placed = <TimelineExpr>[];

  var b = ReelBuilder(take);
  var captions = 0;
  var held = false;
  for (var beat in take.beats) {
    switch (beat) {
      case Said(cue: ScenarioTitle(:var text)):
        // A lower third: a dark band and the words on it, because a stage
        // that knows nothing about the app cannot know what colour the app
        // is under the caption, and white on a white screen is no caption.
        // Both nodes are authored at full opacity and hidden by the track:
        // opacity fx *multiplies*, so a node authored at 0 stays at 0
        // whatever a track says; the track's first key is 0 and `At` clamps
        // to it before the window opens, which keeps the caption unseen
        // until its cue.
        var n = captions++;
        var band = ShapeNode(name: 'captionBand$n', corner: 10)
          ..x = 16
          ..y = screen.height - 108
          ..width = screen.width - 32
          ..height = 64
          ..fill = const SceneColor(0xCC15161A);
        var caption = TextNode(text, name: 'caption$n')
          ..x = 32
          ..y = screen.height - 94
          ..width = screen.width - 64
          ..fontSize = 28
          ..weight = SceneFontWeight.w700
          ..color = const SceneColor(0xFFFFFFFF);
        root.children.addAll([band, caption]);
        List<MotionKey> fadeKeys() => [
          MotionKey(at: Duration.zero, value: 0.0),
          MotionKey(at: const Duration(milliseconds: 350), value: 1.0),
          MotionKey(
            at: titleFor - const Duration(milliseconds: 350),
            value: 1.0,
          ),
          MotionKey(at: titleFor, value: 0.0),
        ];
        var fadeBand = AnimateGroup(band, name: band.name)
          ..tracks['opacity'] = MotionTrack(fadeKeys());
        var fade = AnimateGroup(caption, name: caption.name)
          ..tracks['opacity'] = MotionTrack(fadeKeys());
        // Said partway through a beat that has already been played — the
        // title over the opening hold — so it lands where it was said, not
        // where the walk has got to.
        var at = b.reelTimeOf(beat.at) ?? b.reel;
        motion.groups.addAll([fadeBand, fade]);
        placed.addAll([AtExpr(at, fadeBand), AtExpr(at, fade)]);
        b.say(ScenarioTitle(text), over: titleFor, at: at);

      case Tapped(:var target?):
        // Start moving before the finger presses: the camera knows what is
        // about to happen, which is the whole reason the edit reads a take
        // rather than watching a run.
        var press = beat.phase(PhaseKind.press) ?? beat.phases.first;
        var start = b.reel + (press.at - beat.at) - pushIn;
        if (start < Duration.zero) start = Duration.zero;
        var off = cameraOffset(
          screen: screen,
          toward: target.center,
          zoom: zoom,
        );
        var end = beat.duration;
        var back = end - pushIn;
        if (back < pushIn) back = pushIn;
        var push = AnimateGroup(camera, name: 'push${beat.at.inMilliseconds}')
          ..tracks['scale'] = MotionTrack([
            MotionKey(
              at: Duration.zero,
              value: 1.0,
              curve: SceneCurves.easeOut,
            ),
            MotionKey(at: pushIn, value: zoom),
            MotionKey(at: back, value: zoom, curve: SceneCurves.easeInOut),
            MotionKey(at: end, value: 1.0),
          ])
          ..tracks['translateX'] = MotionTrack([
            MotionKey(
              at: Duration.zero,
              value: 0.0,
              curve: SceneCurves.easeOut,
            ),
            MotionKey(at: pushIn, value: off.dx),
            MotionKey(at: back, value: off.dx, curve: SceneCurves.easeInOut),
            MotionKey(at: end, value: 0.0),
          ])
          ..tracks['translateY'] = MotionTrack([
            MotionKey(
              at: Duration.zero,
              value: 0.0,
              curve: SceneCurves.easeOut,
            ),
            MotionKey(at: pushIn, value: off.dy),
            MotionKey(at: back, value: off.dy, curve: SceneCurves.easeInOut),
            MotionKey(at: end, value: 0.0),
          ]);
        motion.groups.add(push);
        placed.add(AtExpr(start, push));
        b.playBeat(beat);
        if (!held) {
          held = true;
          b.hold(holdAfterFirstTap, after: beat);
        }

      case _:
        b.playBeat(beat);
    }
  }

  // The end: the last frame held, under the scenario's name.
  var closing = TextNode(take.scenario, name: 'closing')
    ..x = 24
    ..y = screen.height / 2 - 20
    ..width = screen.width - 48
    ..fontSize = 32
    ..weight = SceneFontWeight.w700
    ..color = const SceneColor(0xFFFFFFFF);
  root.children.add(closing);
  var dim = AnimateGroup(camera, name: 'dim')
    ..tracks['opacity'] = MotionTrack([
      MotionKey(at: Duration.zero, value: 1.0),
      MotionKey(at: const Duration(milliseconds: 500), value: 0.35),
    ]);
  var reveal = AnimateGroup(closing, name: 'reveal')
    ..tracks['opacity'] = MotionTrack([
      MotionKey(at: Duration.zero, value: 0.0),
      MotionKey(at: const Duration(milliseconds: 500), value: 1.0),
    ]);
  motion.groups.addAll([dim, reveal]);
  placed.add(AtExpr(b.reel, dim));
  placed.add(AtExpr(b.reel, reveal));
  b.freeze(tail);

  motion.timeline = ParExpr(placed);
  var scene = SceneDocument(root);
  var bound = BoundMotion.bind(motion, scene);
  return b.build(stage: SceneStage(scene, motion: bound));
}