SheetRenderingManager.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. import { IZoomView } from "../Common/Interfaces/IZoomView";
  2. import { MusicSheetDrawer, GraphicalMusicSheet, BoundingBox, DrawingParameters,
  3. MusicSystem, GraphicalVoiceEntry } from "../MusicalScore/Graphical";
  4. import { ScreenViewingRegion } from "./ScreenViewingRegion";
  5. import { PointF2D, Fraction, SizeF2D } from "../Common/DataObjects";
  6. import { AbstractZoomView } from "./AbstractZoomView";
  7. import { InteractionType } from "../Common/Enums/InteractionType";
  8. import { AbstractDisplayInteractionManager } from "./AbstractDisplayInteractionManager";
  9. import { IUserDisplayInteractionListener } from "../Common/Interfaces/IUserDisplayInteractionListener";
  10. import { PlaybackManager } from "../Playback";
  11. export class SheetRenderingManager extends AbstractZoomView implements IZoomView {
  12. protected musicSheetDrawer: MusicSheetDrawer;
  13. protected graphicalMusicSheet: GraphicalMusicSheet;
  14. protected mainViewingRegion: ScreenViewingRegion = ScreenViewingRegion.createWithDefaults();
  15. protected tryAgainToRenderCount: number = 0;
  16. private yOffsetMouseDown: number = Number.MIN_VALUE;
  17. private unlockCursorDistancePixel: number = 50.0;
  18. private relativeTopPosition: number = 0.06;
  19. /* The preview images are supersampled (because of our drawing mechanism with more than 1 rasterization stage,
  20. * this increases the output image quality significantly)
  21. **/
  22. protected internalPreviewImageScale: number = 3.0;
  23. protected listeners: IUserDisplayInteractionListener[] = [];
  24. public PlaybackManager: PlaybackManager;
  25. constructor(displayInteractionManager: AbstractDisplayInteractionManager, playbackManager?: PlaybackManager) {
  26. super(displayInteractionManager);
  27. this.addZoomView(this);
  28. this.lockRanges = true;
  29. this.TopBarHeightInPixel = 70;
  30. this.BottomBarHeightInPixel = 0;
  31. }
  32. public addListener(listener: IUserDisplayInteractionListener): void {
  33. this.listeners.push(listener);
  34. }
  35. public SingleTouchDisabled: boolean;
  36. public DoubleTouchDisabled: boolean;
  37. public LockDisplayToCursor: boolean = true;
  38. public ZoomActive: boolean = false;
  39. protected convertToUnitsReady(): boolean {
  40. return this.graphicalMusicSheet !== undefined;
  41. }
  42. protected unitPosTouched(PosInUnits: PointF2D, relPosX: number, relPosY: number): void {
  43. // Pass mouse click to click listener
  44. if (!this.SingleTouchDisabled) {
  45. const relPos: PointF2D = new PointF2D(relPosX, relPosY);
  46. this.handleUserDisplayInteraction(relPos, PosInUnits, InteractionType.SingleTouch);
  47. for (const listener of this.listeners) {
  48. listener.userDisplayInteraction(relPos, PosInUnits, InteractionType.SingleTouch);
  49. }
  50. }
  51. }
  52. protected unitPosDoubleTouched(PosInUnits: PointF2D, relPosX: number, relPosY: number): void {
  53. if (!this.DoubleTouchDisabled) {
  54. const relPos: PointF2D = new PointF2D(relPosX, relPosY);
  55. this.handleUserDisplayInteraction(relPos, PosInUnits, InteractionType.DoubleTouch);
  56. for (const listener of this.listeners) {
  57. listener.userDisplayInteraction(relPos, PosInUnits, InteractionType.DoubleTouch);
  58. }
  59. }
  60. }
  61. protected unitPosTouchDown(PosInUnits: PointF2D, relPosX: number, relPosY: number): void {
  62. const relPos: PointF2D = new PointF2D(relPosX, relPosY);
  63. this.handleUserDisplayInteraction(relPos, PosInUnits, InteractionType.TouchDown);
  64. for (const listener of this.listeners) {
  65. listener.userDisplayInteraction(new PointF2D(relPosX, relPosY), PosInUnits, InteractionType.TouchDown);
  66. }
  67. this.yOffsetMouseDown = PosInUnits.y;
  68. }
  69. protected unitPosTouchUp(PosInUnits: PointF2D, relPosX: number, relPosY: number): void {
  70. const relPos: PointF2D = new PointF2D(relPosX, relPosY);
  71. this.handleUserDisplayInteraction(relPos, PosInUnits, InteractionType.TouchUp);
  72. for (const listener of this.listeners) {
  73. listener.userDisplayInteraction(new PointF2D(relPosX, relPosY), PosInUnits, InteractionType.TouchUp);
  74. }
  75. if (this.displayInteractionManager.WasZoomGestureActive === false) {
  76. this.unlockFromCursorIfNecessary(PosInUnits);
  77. }
  78. this.yOffsetMouseDown = Number.MIN_VALUE;
  79. }
  80. protected unitPosMove(PosInUnits: PointF2D, relPosX: number, relPosY: number): void {
  81. const relPos: PointF2D = new PointF2D(relPosX, relPosY);
  82. this.handleUserDisplayInteraction(relPos, PosInUnits, InteractionType.Move);
  83. for (const listener of this.listeners) {
  84. listener.userDisplayInteraction(new PointF2D(relPosX, relPosY), PosInUnits, InteractionType.Move);
  85. }
  86. this.unlockFromCursorIfNecessary(PosInUnits);
  87. }
  88. public get MainViewingRegion(): ScreenViewingRegion {
  89. return this.mainViewingRegion;
  90. }
  91. public TopBarHeightInPixel: number;
  92. public BottomBarHeightInPixel: number;
  93. public setMusicSheet(musicSheet: GraphicalMusicSheet): void {
  94. this.graphicalMusicSheet = musicSheet;
  95. this.adaptDisplayLimitsToSheet();
  96. this.setYOffset(0, true);
  97. }
  98. public viewportXChanged(offsetX: number, rangeX: number): void {
  99. if (this.graphicalMusicSheet === undefined) {
  100. return;
  101. }
  102. this.horizontalViewportChanged(offsetX, rangeX);
  103. }
  104. /**
  105. * Sets the vertical position and viewing height of the displayed area on the music score.
  106. */
  107. public viewportYChanged(offsetY: number, rangeY: number): void {
  108. if (this.graphicalMusicSheet === undefined) {
  109. return;
  110. }
  111. if (this.yOffsetMouseDown <= Number.MIN_VALUE + 0.5) {
  112. this.yOffsetMouseDown = offsetY;
  113. }
  114. this.verticalViewportChanged(offsetY, rangeY);
  115. }
  116. public displaySizeChanged(width: number, height: number): void {
  117. super.viewSizeChanged(width, height);
  118. if (Math.abs(width - 0) < 0.0000001 || Math.abs(height - 0) < 0.0000001) {
  119. return;
  120. }
  121. if (this.graphicalMusicSheet !== undefined) {
  122. this.graphicalMusicSheet.EnforceRedrawOfMusicSystems(); // probably not necessary, already handled by OSMD
  123. }
  124. this.mainViewingRegion.DisplaySizeInPixel = new SizeF2D (width, height);
  125. this.adaptDisplayLimitsToSheet();
  126. }
  127. public calcDisplayYPosition(system: MusicSystem): number {
  128. return system.PositionAndShape.AbsolutePosition.y + system.PositionAndShape.BorderMarginTop -
  129. this.topBarHeightInUnits() - this.relativeTopPosition * this.heightWithoutTopBottomBarsInUnits();
  130. }
  131. /**
  132. * The display scroll y-position to show the given system completely on the bottom of the screen
  133. */
  134. public yPositionForLastSystem(lastSystem: MusicSystem): number {
  135. return lastSystem.PositionAndShape.AbsolutePosition.y + lastSystem.PositionAndShape.BorderMarginBottom -
  136. this.topBarHeightInUnits() - (1 - this.relativeTopPosition) * this.heightWithoutTopBottomBarsInUnits();
  137. }
  138. // TODO MB: What is up with the unused variables here? Also: formatting of parameters.
  139. public scorePositionChanged(upperCursorPoint: PointF2D, enrolledTimeStamp: Fraction, sheetTimeStamp: Fraction,
  140. system: MusicSystem, resetOccurred: boolean, smoothAnimation: boolean): void {
  141. const positionY: number = this.calcDisplayYPosition(system);
  142. this.setYPosition(positionY, smoothAnimation);
  143. }
  144. public setXPosition(positionXInUnits: number, animated: boolean): void {
  145. if (this.LockDisplayToCursor) {
  146. this.setXOffset(positionXInUnits, animated);
  147. }
  148. }
  149. public setYPosition(positionYInUnits: number, animated: boolean): void {
  150. if (this.LockDisplayToCursor) {
  151. this.setYOffset(positionYInUnits, animated);
  152. }
  153. }
  154. public get DrawingParameters(): DrawingParameters {
  155. return this.musicSheetDrawer.drawingParameters;
  156. }
  157. public topBarHeightInUnits(): number {
  158. return this.mainViewingRegion.transformLengthYToUnitCoordinates(this.TopBarHeightInPixel);
  159. }
  160. public bottomBarHeightInUnits(): number {
  161. return this.mainViewingRegion.transformLengthYToUnitCoordinates(this.BottomBarHeightInPixel);
  162. }
  163. public heightWithoutTopBottomBarsInUnits(): number {
  164. return this.mainViewingRegion.ViewRegionInUnits.height - this.topBarHeightInUnits() - this.bottomBarHeightInUnits();
  165. }
  166. public activePositionToBottomBarHeight(): number {
  167. return (this.mainViewingRegion.ViewRegionInUnits.height - this.topBarHeightInUnits() - this.bottomBarHeightInUnits()) *
  168. (1 - 2 * this.relativeTopPosition);
  169. }
  170. public getClickPosition(relativePositionX: number, relativePositionY: number): PointF2D {
  171. return this.mainViewingRegion.transformToUnitCoordinates(new PointF2D(relativePositionX, relativePositionY));
  172. }
  173. public graphicalObjectIsVisible(boundingBox: BoundingBox): boolean {
  174. const isCompletelyInside: boolean = false;
  175. return this.mainViewingRegion.isVisible(boundingBox, isCompletelyInside);
  176. }
  177. /**
  178. * sets the size of the maximal musicpage seen including the extensions on top resp. bottom
  179. * !Caution!: settings/offsets have been changed for ScrollIndicator.. won't work anymore if changed again
  180. */
  181. public adaptDisplayLimitsToSheet(): void {
  182. if ( this.graphicalMusicSheet === undefined
  183. || this.graphicalMusicSheet.MusicPages.length === 0
  184. || this.graphicalMusicSheet.MusicPages[0].MusicSystems.length === 0) {
  185. return;
  186. }
  187. // set the new limits for viewing:
  188. this.offsetXMin = 0;
  189. this.rangeXMin = this.graphicalMusicSheet.MinAllowedSystemWidth;
  190. this.rangeXMax = 300;
  191. this.offsetYMin = -0.3 * this.RangeY;
  192. const lastPagePsi: BoundingBox = this.graphicalMusicSheet.MusicPages.last().PositionAndShape;
  193. this.offsetYMax = Math.max(0, lastPagePsi.BorderMarginBottom - 0.7 * this.RangeY);
  194. if (this.OffsetY > this.offsetYMax) {
  195. this.setYOffset(this.offsetYMax, true);
  196. }
  197. }
  198. protected horizontalViewportChanged(offsetX: number, rangeX: number): void {
  199. if (this.mainViewingRegion.WidthInUnits !== rangeX) {
  200. this.mainViewingRegion.WidthInUnits = rangeX;
  201. }
  202. }
  203. protected verticalViewportChanged(offsetY: number, rangeY: number): void {
  204. this.mainViewingRegion.UpperLeftPositionInUnits = new PointF2D(this.mainViewingRegion.UpperLeftPositionInUnits.x, offsetY);
  205. }
  206. private unlockFromCursorIfNecessary(PosInUnits: PointF2D): void {
  207. if (this.LockDisplayToCursor === false || this.ZoomActive) {
  208. return;
  209. }
  210. if (this.displayInteractionManager.ZoomGestureActive || this.displayInteractionManager.WasZoomGestureActive) {
  211. return;
  212. }
  213. // finally check for the movement distance, to not unlock already at little finger pressure changes...
  214. // TODO MB: Fix formatting.
  215. const ydiff: number = Math.abs((PosInUnits.y - this.yOffsetMouseDown) *
  216. this.mainViewingRegion.RegionSizeInPixel.height /
  217. this.mainViewingRegion.ViewRegionInUnits.height);
  218. if (ydiff > this.unlockCursorDistancePixel) {
  219. this.LockDisplayToCursor = false;
  220. }
  221. }
  222. protected getPositionInUnits(relativePositionX: number, relativePositionY: number): PointF2D {
  223. return this.mainViewingRegion.transformToUnitCoordinates(new PointF2D(relativePositionX, relativePositionY));
  224. }
  225. protected handleUserDisplayInteraction( relativePositionOnDisplay: PointF2D, positionOnMusicSheet: PointF2D,
  226. type: InteractionType): void {
  227. switch (type) {
  228. case InteractionType.TouchDown:
  229. case InteractionType.SingleTouch:
  230. case InteractionType.DoubleTouch: {
  231. const clickVe: GraphicalVoiceEntry = this.graphicalMusicSheet.GetNearestVoiceEntry(positionOnMusicSheet);
  232. // set cursor and/or start/end marker position
  233. if (clickVe) {
  234. if (clickVe.parentStaffEntry.parentVerticalContainer !== undefined) {
  235. const clickedTimeStamp: Fraction = clickVe.parentStaffEntry.parentVerticalContainer.AbsoluteTimestamp;
  236. this.setStartPosition(clickedTimeStamp);
  237. // playback clicked note
  238. if (clickVe.notes[0]?.sourceNote.Pitch !== undefined) {
  239. this.PlaybackManager?.playVoiceEntry(clickVe.parentVoiceEntry);
  240. }
  241. }
  242. }
  243. break;
  244. }
  245. case InteractionType.TouchUp: {
  246. break;
  247. }
  248. case InteractionType.TouchDown: {
  249. break;
  250. }
  251. case InteractionType.Move: {
  252. break;
  253. }
  254. default:
  255. throw new Error("type");
  256. }
  257. }
  258. protected setStartPosition(newStartPosition: Fraction): void {
  259. if (this.graphicalMusicSheet === undefined) {
  260. return;
  261. }
  262. this.graphicalMusicSheet.ParentMusicSheet.SelectionStart = newStartPosition;
  263. this.PlaybackManager?.reset();
  264. }
  265. }