All files / src/packlets/editor-core/slices view-command-slice.ts

100% Statements 50/50
58.33% Branches 7/12
100% Functions 9/9
100% Lines 45/45

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82                      1x     51x       6x 6x 6x 6x 6x 6x       1x 5x 1x       1x 6x 1x       4x 4x 4x 4x 4x 4x     4x 3x 6x 3x   1x 1x 1x     4x 4x 4x 4x 4x   4x 4x 4x       6x 6x 6x 6x 6x 6x 6x 6x 6x 6x      
import { Slice } from "../slice";
import type { EditorContext } from "../editor-context";
import { ZoomSlice } from "./zoom-slice";
import { ViewportSlice } from "./viewport-slice";
import { CursorSlice } from "./cursor-slice";
import { TimingSlice } from "./timing-slice";
import { ChartSlice } from "./chart-slice";
import { SnapSlice } from "./snap-slice";
import { BASE_SCALE_Y, ZOOM_PRESETS } from "../types";
 
export class ViewCommandSlice extends Slice {
  static readonly sliceKey = "viewCommand";
 
  constructor(ctx: EditorContext) {
    super(ctx);
  }
 
  setZoom(zoom: number): void {
    const zoomSlice = this.ctx.get(ZoomSlice);
    const viewport = this.ctx.get(ViewportSlice);
    const oldZoom = zoomSlice.$zoom.get();
    zoomSlice.$zoom.set(zoom);
    const newScrollTop = this.computeZoomScrollOffset(oldZoom, zoom);
    viewport.requestScroll({ x: viewport.$scroll.get().x, y: newScrollTop });
  }
 
  zoomIn(): void {
    const current = this.ctx.get(ZoomSlice).$zoom.get();
    const next = ZOOM_PRESETS.find((z) => z > current);
    Eif (next) this.setZoom(next);
  }
 
  zoomOut(): void {
    const current = this.ctx.get(ZoomSlice).$zoom.get();
    const prev = [...ZOOM_PRESETS].reverse().find((z) => z < current);
    Eif (prev) this.setZoom(prev);
  }
 
  navigateSnap(direction: "up" | "down"): void {
    const cursor = this.ctx.get(CursorSlice);
    const viewport = this.ctx.get(ViewportSlice);
    const currentPulse = cursor.$cursorPulse.get();
    const engine = this.ctx.get(TimingSlice).getTimingEngine();
    const snap = this.ctx.get(SnapSlice).$snap.get();
    const size = this.ctx.get(ChartSlice).getChartSize();
 
    let targetPulse: number;
    if (direction === "up") {
      const points = engine.getSnapPoints(snap, { start: currentPulse, end: size });
      const next = points.find((p) => p > currentPulse);
      targetPulse = next !== undefined ? next : currentPulse;
    } else {
      const points = engine.getSnapPoints(snap, { start: 0, end: currentPulse });
      const prev = points.length > 0 ? points[points.length - 1] : undefined;
      targetPulse = prev !== undefined ? prev : currentPulse;
    }
 
    const scaleY = viewport.getScaleY();
    const trackHeight = viewport.getTrackHeight();
    const currentY = trackHeight - currentPulse * scaleY;
    const targetY = trackHeight - targetPulse * scaleY;
    const deltaY = targetY - currentY;
 
    cursor.$cursorPulse.set(targetPulse);
    const currentScroll = viewport.$scroll.get();
    viewport.requestScroll({ x: currentScroll.x, y: currentScroll.y + deltaY });
  }
 
  computeZoomScrollOffset(oldZoom: number, newZoom: number): number {
    const size = this.ctx.get(ChartSlice).getChartSize();
    const oldScaleY = BASE_SCALE_Y * oldZoom;
    const newScaleY = BASE_SCALE_Y * newZoom;
    const cursorPulse = this.ctx.get(CursorSlice).$cursorPulse.get();
    const oldScrollTop = this.ctx.get(ViewportSlice).$scroll.get().y;
    const oldTrackHeight = size * oldScaleY;
    const newTrackHeight = size * newScaleY;
    const oldPlayheadY = oldTrackHeight - cursorPulse * oldScaleY - 1;
    const newPlayheadY = newTrackHeight - cursorPulse * newScaleY - 1;
    return oldScrollTop + newPlayheadY - oldPlayheadY;
  }
}