All files / src/packlets/editor-core/slices box-selection-slice.ts

76.92% Statements 50/65
60% Branches 24/40
100% Functions 6/6
80.7% Lines 46/57

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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121              1x   51x                 15x       6x                   4x 4x       89x 89x 11x 11x 11x 11x 11x       188x 188x 9x                 3x 3x   3x 3x 3x 3x   3x 3x   3x 5x 5x 5x 5x   4x 4x 4x 4x                 4x 4x 4x 4x 12x 4x 4x         4x                       4x 4x       3x 3x 3x      
import { Slice } from "../slice";
import { SelectionSlice } from "./selection-slice";
import { ColumnsSlice } from "./columns-slice";
import { EVENT, BPM_CHANGE, TIME_SIGNATURE, NOTE, LEVEL_REF } from "../components";
import type { Entity } from "../../entity-manager";
 
export class BoxSelectionSlice extends Slice {
  static readonly sliceKey = "box-selection";
 
  private boxSelection = {
    active: false,
    startCol: 0,
    endCol: 0,
    startPulse: 0,
    endPulse: 0,
  };
 
  isActive(): boolean {
    return this.boxSelection.active;
  }
 
  start(colIndex: number, pulse: number): void {
    this.boxSelection = {
      active: true,
      startCol: colIndex,
      endCol: colIndex,
      startPulse: pulse,
      endPulse: pulse,
    };
  }
 
  update(colIndex: number, pulse: number): void {
    this.boxSelection.endCol = colIndex;
    this.boxSelection.endPulse = pulse;
  }
 
  isInBox(pulse: number, colIndex: number): boolean {
    const box = this.boxSelection;
    if (!box.active) return false;
    const minCol = Math.min(box.startCol, box.endCol);
    const maxCol = Math.max(box.startCol, box.endCol);
    const minPulse = Math.min(box.startPulse, box.endPulse);
    const maxPulse = Math.max(box.startPulse, box.endPulse);
    return pulse >= minPulse && pulse <= maxPulse && colIndex >= minCol && colIndex <= maxCol;
  }
 
  getBoxRect(): { minCol: number; maxCol: number; minPulse: number; maxPulse: number } | null {
    const box = this.boxSelection;
    if (!box.active) return null;
    return {
      minCol: Math.min(box.startCol, box.endCol),
      maxCol: Math.max(box.startCol, box.endCol),
      minPulse: Math.min(box.startPulse, box.endPulse),
      maxPulse: Math.max(box.startPulse, box.endPulse),
    };
  }
 
  finalize(entities: Entity[]): Set<string> {
    const box = this.boxSelection;
    Iif (!box.active) return new Set();
 
    const minCol = Math.min(box.startCol, box.endCol);
    const maxCol = Math.max(box.startCol, box.endCol);
    const minPulse = Math.min(box.startPulse, box.endPulse);
    const maxPulse = Math.max(box.startPulse, box.endPulse);
 
    const columns = this.ctx.get(ColumnsSlice).$columns.get();
    const next = new Set(this.ctx.get(SelectionSlice).$selection.get());
 
    for (const entity of entities) {
      const event = entity.components[EVENT.key];
      Iif (!event) continue;
      const pulse = event.y;
      if (pulse < minPulse || pulse > maxPulse) continue;
 
      let colIndex = -1;
      const note = entity.components[NOTE.key];
      const levelRef = entity.components[LEVEL_REF.key];
      Iif (note && levelRef) {
        for (let i = 0; i < columns.length; i++) {
          const col = columns[i]!;
          if (col.levelId === levelRef.levelId && col.laneIndex === note.lane) {
            colIndex = i;
            break;
          }
        }
      }
      Eif (colIndex === -1) {
        const bpm = entity.components[BPM_CHANGE.key];
        Eif (bpm) {
          for (let i = 0; i < columns.length; i++) {
            if (columns[i]!.id === "bpm") {
              colIndex = i;
              break;
            }
          }
        }
      }
      Iif (colIndex === -1) {
        const ts = entity.components[TIME_SIGNATURE.key];
        if (ts) {
          for (let i = 0; i < columns.length; i++) {
            if (columns[i]!.id === "time-sig") {
              colIndex = i;
              break;
            }
          }
        }
      }
 
      Eif (colIndex >= minCol && colIndex <= maxCol) {
        next.add(entity.id);
      }
    }
 
    this.boxSelection = { active: false, startCol: 0, endCol: 0, startPulse: 0, endPulse: 0 };
    this.ctx.get(SelectionSlice).$selection.set(next);
    return next;
  }
}