Cursor.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import {MusicPartManagerIterator} from "../MusicalScore/MusicParts/MusicPartManagerIterator";
  2. import {MusicPartManager} from "../MusicalScore/MusicParts/MusicPartManager";
  3. import {VoiceEntry} from "../MusicalScore/VoiceData/VoiceEntry";
  4. import {VexFlowStaffEntry} from "../MusicalScore/Graphical/VexFlow/VexFlowStaffEntry";
  5. import {MusicSystem} from "../MusicalScore/Graphical/MusicSystem";
  6. import {OpenSheetMusicDisplay} from "./OpenSheetMusicDisplay";
  7. import {GraphicalMusicSheet} from "../MusicalScore/Graphical/GraphicalMusicSheet";
  8. import {Instrument} from "../MusicalScore/Instrument";
  9. import {Note} from "../MusicalScore/VoiceData/Note";
  10. /**
  11. * A cursor which can iterate through the music sheet.
  12. */
  13. export class Cursor {
  14. constructor(container: HTMLElement, openSheetMusicDisplay: OpenSheetMusicDisplay) {
  15. this.container = container;
  16. this.openSheetMusicDisplay = openSheetMusicDisplay;
  17. const curs: HTMLElement = document.createElement("img");
  18. curs.style.position = "absolute";
  19. curs.style.zIndex = "-1";
  20. this.cursorElement = <HTMLImageElement>curs;
  21. this.container.appendChild(curs);
  22. }
  23. private container: HTMLElement;
  24. private openSheetMusicDisplay: OpenSheetMusicDisplay;
  25. private manager: MusicPartManager;
  26. protected iterator: MusicPartManagerIterator;
  27. private graphic: GraphicalMusicSheet;
  28. private hidden: boolean = true;
  29. private cursorElement: HTMLImageElement;
  30. /** Initialize the cursor. Necessary before using functions like show() and next(). */
  31. public init(manager: MusicPartManager, graphic: GraphicalMusicSheet): void {
  32. this.manager = manager;
  33. this.reset();
  34. this.graphic = graphic;
  35. this.hidden = true;
  36. this.hide();
  37. }
  38. /**
  39. * Make the cursor visible
  40. */
  41. public show(): void {
  42. this.hidden = false;
  43. this.update();
  44. }
  45. private getStaffEntriesFromVoiceEntry(voiceEntry: VoiceEntry): VexFlowStaffEntry {
  46. const measureIndex: number = voiceEntry.ParentSourceStaffEntry.VerticalContainerParent.ParentMeasure.measureListIndex;
  47. const staffIndex: number = voiceEntry.ParentSourceStaffEntry.ParentStaff.idInMusicSheet;
  48. return <VexFlowStaffEntry>this.graphic.findGraphicalStaffEntryFromMeasureList(staffIndex, measureIndex, voiceEntry.ParentSourceStaffEntry);
  49. }
  50. public update(): void {
  51. // Warning! This should NEVER call this.openSheetMusicDisplay.render()
  52. if (this.hidden) {
  53. return;
  54. }
  55. this.graphic.Cursors.length = 0;
  56. const iterator: MusicPartManagerIterator = this.iterator;
  57. const voiceEntries: VoiceEntry[] = iterator.CurrentVisibleVoiceEntries();
  58. if (iterator.EndReached || iterator.CurrentVoiceEntries === undefined || voiceEntries.length === 0) {
  59. return;
  60. }
  61. let x: number = 0, y: number = 0, height: number = 0;
  62. // get all staff entries inside the current voice entry
  63. const gseArr: VexFlowStaffEntry[] = voiceEntries.map(ve => this.getStaffEntriesFromVoiceEntry(ve));
  64. // sort them by x position and take the leftmost entry
  65. const gse: VexFlowStaffEntry =
  66. gseArr.sort((a, b) => a.PositionAndShape.AbsolutePosition.x <= b.PositionAndShape.AbsolutePosition.x ? -1 : 1 )[0];
  67. x = gse.PositionAndShape.AbsolutePosition.x;
  68. const musicSystem: MusicSystem = gse.parentMeasure.parentMusicSystem;
  69. y = musicSystem.PositionAndShape.AbsolutePosition.y + musicSystem.StaffLines[0].PositionAndShape.RelativePosition.y;
  70. const endY: number = musicSystem.PositionAndShape.AbsolutePosition.y +
  71. musicSystem.StaffLines[musicSystem.StaffLines.length - 1].PositionAndShape.RelativePosition.y + 4.0;
  72. height = endY - y;
  73. // The following code is not necessary (for now, but it could come useful later):
  74. // it highlights the notes under the cursor.
  75. //let vfNotes: { [voiceID: number]: Vex.Flow.StaveNote; } = gse.vfNotes;
  76. //for (let voiceId in vfNotes) {
  77. // if (vfNotes.hasOwnProperty(voiceId)) {
  78. // vfNotes[voiceId].setStyle({
  79. // fillStyle: "red",
  80. // strokeStyle: "red",
  81. // });
  82. // }
  83. //}
  84. // Update the graphical cursor
  85. // The following is the legacy cursor rendered on the canvas:
  86. // // let cursor: GraphicalLine = new GraphicalLine(new PointF2D(x, y), new PointF2D(x, y + height), 3, OutlineAndFillStyleEnum.PlaybackCursor);
  87. // This the current HTML Cursor:
  88. const cursorElement: HTMLImageElement = this.cursorElement;
  89. cursorElement.style.top = (y * 10.0 * this.openSheetMusicDisplay.zoom) + "px";
  90. cursorElement.style.left = ((x - 1.5) * 10.0 * this.openSheetMusicDisplay.zoom) + "px";
  91. cursorElement.height = (height * 10.0 * this.openSheetMusicDisplay.zoom);
  92. const newWidth: number = 3 * 10.0 * this.openSheetMusicDisplay.zoom;
  93. if (newWidth !== cursorElement.width) {
  94. cursorElement.width = newWidth;
  95. this.updateStyle(newWidth);
  96. }
  97. if (this.openSheetMusicDisplay.FollowCursor) {
  98. this.cursorElement.scrollIntoView({behavior: "smooth", block: "center"});
  99. }
  100. // Show cursor
  101. // // Old cursor: this.graphic.Cursors.push(cursor);
  102. this.cursorElement.style.display = "";
  103. }
  104. /**
  105. * Hide the cursor
  106. */
  107. public hide(): void {
  108. // Hide the actual cursor element
  109. this.cursorElement.style.display = "none";
  110. //this.graphic.Cursors.length = 0;
  111. // Forcing the sheet to re-render is not necessary anymore
  112. //if (!this.hidden) {
  113. // this.openSheetMusicDisplay.render();
  114. //}
  115. this.hidden = true;
  116. }
  117. /**
  118. * Go to next entry
  119. */
  120. public next(): void {
  121. this.iterator.moveToNext();
  122. if (!this.hidden) {
  123. this.show();
  124. }
  125. }
  126. /**
  127. * reset cursor to start
  128. */
  129. public reset(): void {
  130. this.iterator = this.manager.getIterator();
  131. this.iterator.moveToNext();
  132. this.update();
  133. }
  134. private updateStyle(width: number, color: string = "#33e02f"): void {
  135. // Create a dummy canvas to generate the gradient for the cursor
  136. // FIXME This approach needs to be improved
  137. const c: HTMLCanvasElement = document.createElement("canvas");
  138. c.width = this.cursorElement.width;
  139. c.height = 1;
  140. const ctx: CanvasRenderingContext2D = c.getContext("2d");
  141. ctx.globalAlpha = 0.5;
  142. // Generate the gradient
  143. const gradient: CanvasGradient = ctx.createLinearGradient(0, 0, this.cursorElement.width, 0);
  144. gradient.addColorStop(0, "white"); // it was: "transparent"
  145. gradient.addColorStop(0.2, color);
  146. gradient.addColorStop(0.8, color);
  147. gradient.addColorStop(1, "white"); // it was: "transparent"
  148. ctx.fillStyle = gradient;
  149. ctx.fillRect(0, 0, width, 1);
  150. // Set the actual image
  151. this.cursorElement.src = c.toDataURL("image/png");
  152. }
  153. public get Iterator(): MusicPartManagerIterator {
  154. return this.iterator;
  155. }
  156. public get Hidden(): boolean {
  157. return this.hidden;
  158. }
  159. /** returns voices under the current Cursor position. Without instrument argument, all voices are returned. */
  160. public VoicesUnderCursor(instrument?: Instrument): VoiceEntry[] {
  161. return this.iterator.CurrentVisibleVoiceEntries(instrument);
  162. }
  163. public NotesUnderCursor(instrument?: Instrument): Note[] {
  164. const voiceEntries: VoiceEntry[] = this.VoicesUnderCursor(instrument);
  165. const notes: Note[] = [];
  166. voiceEntries.forEach(voiceEntry => {
  167. notes.push.apply(notes, voiceEntry.Notes);
  168. });
  169. return notes;
  170. }
  171. }