Cursor.ts 6.4 KB

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