scrollViewer.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. import { Nullable } from "babylonjs/types";
  2. import { Observer } from "babylonjs/Misc/observable";
  3. import { PointerInfo, PointerEventTypes } from "babylonjs/Events/pointerEvents";
  4. import { Rectangle } from "../rectangle";
  5. import { Grid } from "../grid";
  6. import { Control } from "../control";
  7. import { Container } from "../container";
  8. import { Measure } from "../../measure";
  9. import { AdvancedDynamicTexture } from "../../advancedDynamicTexture";
  10. import { _ScrollViewerWindow } from "./scrollViewerWindow";
  11. import { ScrollBar } from "../sliders/scrollBar";
  12. /**
  13. * Class used to hold a viewer window and sliders in a grid
  14. */
  15. export class ScrollViewer extends Rectangle {
  16. private _grid: Grid;
  17. private _horizontalBarSpace: Rectangle;
  18. private _verticalBarSpace: Rectangle;
  19. private _dragSpace: Rectangle;
  20. private _horizontalBar: ScrollBar;
  21. private _verticalBar: ScrollBar;
  22. private _barColor: string;
  23. private _barBackground: string;
  24. private _barSize: number = 20;
  25. private _endLeft: number;
  26. private _endTop: number;
  27. private _window: _ScrollViewerWindow;
  28. private _pointerIsOver: Boolean = false;
  29. private _wheelPrecision: number = 0.05;
  30. private _onPointerObserver: Nullable<Observer<PointerInfo>>;
  31. private _clientWidth: number;
  32. private _clientHeight: number;
  33. /**
  34. * Gets the horizontal scrollbar
  35. */
  36. public get horizontalBar(): ScrollBar {
  37. return this._horizontalBar;
  38. }
  39. /**
  40. * Gets the vertical scrollbar
  41. */
  42. public get verticalBar(): ScrollBar {
  43. return this._verticalBar;
  44. }
  45. /**
  46. * Adds a new control to the current container
  47. * @param control defines the control to add
  48. * @returns the current container
  49. */
  50. public addControl(control: Nullable<Control>): Container {
  51. if (!control) {
  52. return this;
  53. }
  54. this._window.addControl(control);
  55. return this;
  56. }
  57. /**
  58. * Removes a control from the current container
  59. * @param control defines the control to remove
  60. * @returns the current container
  61. */
  62. public removeControl(control: Control): Container {
  63. this._window.removeControl(control);
  64. return this;
  65. }
  66. /** Gets the list of children */
  67. public get children(): Control[] {
  68. return this._window.children;
  69. }
  70. public _flagDescendantsAsMatrixDirty(): void {
  71. for (var child of this._children) {
  72. child._markMatrixAsDirty();
  73. }
  74. }
  75. /**
  76. * Creates a new ScrollViewer
  77. * @param name of ScrollViewer
  78. */
  79. constructor(name?: string) {
  80. super(name);
  81. this.onDirtyObservable.add(() => {
  82. this._horizontalBarSpace.color = this.color;
  83. this._verticalBarSpace.color = this.color;
  84. this._dragSpace.color = this.color;
  85. });
  86. this.onPointerEnterObservable.add(() => {
  87. this._pointerIsOver = true;
  88. });
  89. this.onPointerOutObservable.add(() => {
  90. this._pointerIsOver = false;
  91. });
  92. this._grid = new Grid();
  93. this._horizontalBar = new ScrollBar();
  94. this._verticalBar = new ScrollBar();
  95. this._window = new _ScrollViewerWindow();
  96. this._window.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
  97. this._window.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
  98. this._grid.addColumnDefinition(1);
  99. this._grid.addColumnDefinition(0, true);
  100. this._grid.addRowDefinition(1);
  101. this._grid.addRowDefinition(0, true);
  102. super.addControl(this._grid);
  103. this._grid.addControl(this._window, 0, 0);
  104. this._verticalBar.paddingLeft = 0;
  105. this._verticalBar.width = "100%";
  106. this._verticalBar.height = "100%";
  107. this._verticalBar.barOffset = 0;
  108. this._verticalBar.value = 0;
  109. this._verticalBar.maximum = 1;
  110. this._verticalBar.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
  111. this._verticalBar.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
  112. this._verticalBar.isVertical = true;
  113. this._verticalBar.rotation = Math.PI;
  114. this._verticalBar.isVisible = false;
  115. this._verticalBarSpace = new Rectangle();
  116. this._verticalBarSpace.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
  117. this._verticalBarSpace.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
  118. this._verticalBarSpace.thickness = 1;
  119. this._grid.addControl(this._verticalBarSpace, 0, 1);
  120. this._verticalBarSpace.addControl(this._verticalBar);
  121. this._verticalBar.onValueChangedObservable.add((value) => {
  122. this._window.top = value * this._endTop + "px";
  123. });
  124. this._horizontalBar.paddingLeft = 0;
  125. this._horizontalBar.width = "100%";
  126. this._horizontalBar.height = "100%";
  127. this._horizontalBar.barOffset = 0;
  128. this._horizontalBar.value = 0;
  129. this._horizontalBar.maximum = 1;
  130. this._horizontalBar.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
  131. this._horizontalBar.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
  132. this._horizontalBar.isVisible = false;
  133. this._horizontalBarSpace = new Rectangle();
  134. this._horizontalBarSpace.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
  135. this._horizontalBarSpace.verticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
  136. this._horizontalBarSpace.thickness = 1;
  137. this._grid.addControl(this._horizontalBarSpace, 1, 0);
  138. this._horizontalBarSpace.addControl(this._horizontalBar);
  139. this._horizontalBar.onValueChangedObservable.add((value) => {
  140. this._window.left = value * this._endLeft + "px";
  141. });
  142. this._dragSpace = new Rectangle();
  143. this._dragSpace.thickness = 1;
  144. this._grid.addControl(this._dragSpace, 1, 1);
  145. // Colors
  146. this.barColor = "grey";
  147. this.barBackground = "transparent";
  148. }
  149. /** Reset the scroll viewer window to initial size */
  150. public resetWindow() {
  151. this._window.width = "100%";
  152. this._window.height = "100%";
  153. }
  154. protected _getTypeName(): string {
  155. return "ScrollViewer";
  156. }
  157. private _buildClientSizes() {
  158. this._window.parentClientWidth = this._currentMeasure.width - (this._verticalBar.isVisible ? this._barSize : 0) - 2 * this.thickness;
  159. this._window.parentClientHeight = this._currentMeasure.height - (this._horizontalBar.isVisible ? this._barSize : 0) - 2 * this.thickness;
  160. this._clientWidth = this._window.parentClientWidth;
  161. this._clientHeight = this._window.parentClientHeight;
  162. }
  163. protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
  164. super._additionalProcessing(parentMeasure, context);
  165. this._buildClientSizes();
  166. }
  167. protected _postMeasure(): void {
  168. super._postMeasure();
  169. this._updateScroller();
  170. }
  171. /**
  172. * Gets or sets the mouse wheel precision
  173. * from 0 to 1 with a default value of 0.05
  174. * */
  175. public get wheelPrecision(): number {
  176. return this._wheelPrecision;
  177. }
  178. public set wheelPrecision(value: number) {
  179. if (this._wheelPrecision === value) {
  180. return;
  181. }
  182. if (value < 0) {
  183. value = 0;
  184. }
  185. if (value > 1) {
  186. value = 1;
  187. }
  188. this._wheelPrecision = value;
  189. }
  190. /** Gets or sets the bar color */
  191. public get barColor(): string {
  192. return this._barColor;
  193. }
  194. public set barColor(color: string) {
  195. if (this._barColor === color) {
  196. return;
  197. }
  198. this._barColor = color;
  199. this._horizontalBar.color = color;
  200. this._verticalBar.color = color;
  201. }
  202. /** Gets or sets the size of the bar */
  203. public get barSize(): number {
  204. return this._barSize;
  205. }
  206. public set barSize(value: number) {
  207. if (this._barSize === value) {
  208. return;
  209. }
  210. this._barSize = value;
  211. this._markAsDirty();
  212. if (this._horizontalBar.isVisible) {
  213. this._grid.setRowDefinition(1, this._barSize, true);
  214. }
  215. if (this._verticalBar.isVisible) {
  216. this._grid.setColumnDefinition(1, this._barSize, true);
  217. }
  218. }
  219. /** Gets or sets the bar background */
  220. public get barBackground(): string {
  221. return this._barBackground;
  222. }
  223. public set barBackground(color: string) {
  224. if (this._barBackground === color) {
  225. return;
  226. }
  227. this._barBackground = color;
  228. this._horizontalBar.background = color;
  229. this._verticalBar.background = color;
  230. this._dragSpace.background = color;
  231. }
  232. /** @hidden */
  233. private _updateScroller(): void {
  234. let windowContentsWidth = this._window._currentMeasure.width;
  235. let windowContentsHeight = this._window._currentMeasure.height;
  236. if (this._horizontalBar.isVisible && windowContentsWidth <= this._clientWidth) {
  237. this._grid.setRowDefinition(1, 0, true);
  238. this._horizontalBar.isVisible = false;
  239. this._horizontalBar.value = 0;
  240. this._rebuildLayout = true;
  241. }
  242. else if (!this._horizontalBar.isVisible && windowContentsWidth > this._clientWidth) {
  243. this._grid.setRowDefinition(1, this._barSize, true);
  244. this._horizontalBar.isVisible = true;
  245. this._rebuildLayout = true;
  246. }
  247. if (this._verticalBar.isVisible && windowContentsHeight <= this._clientHeight) {
  248. this._grid.setColumnDefinition(1, 0, true);
  249. this._verticalBar.isVisible = false;
  250. this._verticalBar.value = 0;
  251. this._rebuildLayout = true;
  252. }
  253. else if (!this._verticalBar.isVisible && windowContentsHeight > this._clientHeight) {
  254. this._grid.setColumnDefinition(1, this._barSize, true);
  255. this._verticalBar.isVisible = true;
  256. this._rebuildLayout = true;
  257. }
  258. this._buildClientSizes();
  259. this._endLeft = this._clientWidth - windowContentsWidth;
  260. this._endTop = this._clientHeight - windowContentsHeight;
  261. const newLeft = this._horizontalBar.value * this._endLeft + "px";
  262. const newTop = this._verticalBar.value * this._endTop + "px";
  263. if (newLeft !== this._window.left) {
  264. this._window.left = newLeft;
  265. this._rebuildLayout = true;
  266. }
  267. if (newTop !== this._window.top) {
  268. this._window.top = newTop;
  269. this._rebuildLayout = true;
  270. }
  271. let horizontalMultiplicator = this._clientWidth / windowContentsWidth;
  272. let verticalMultiplicator = this._clientHeight / windowContentsHeight;
  273. this._horizontalBar.thumbWidth = (this._clientWidth * horizontalMultiplicator) + "px";
  274. this._verticalBar.thumbWidth = (this._clientHeight * verticalMultiplicator) + "px";
  275. }
  276. public _link(host: AdvancedDynamicTexture): void {
  277. super._link(host);
  278. this._attachWheel();
  279. }
  280. /** @hidden */
  281. private _attachWheel() {
  282. if (!this._host || this._onPointerObserver) {
  283. return;
  284. }
  285. let scene = this._host.getScene();
  286. this._onPointerObserver = scene!.onPointerObservable.add((pi, state) => {
  287. if (!this._pointerIsOver || pi.type !== PointerEventTypes.POINTERWHEEL) {
  288. return;
  289. }
  290. if (this._verticalBar.isVisible == true) {
  291. if ((<MouseWheelEvent>pi.event).deltaY < 0 && this._verticalBar.value > 0) {
  292. this._verticalBar.value -= this._wheelPrecision;
  293. } else if ((<MouseWheelEvent>pi.event).deltaY > 0 && this._verticalBar.value < this._verticalBar.maximum) {
  294. this._verticalBar.value += this._wheelPrecision;
  295. }
  296. }
  297. if (this._horizontalBar.isVisible == true) {
  298. if ((<MouseWheelEvent>pi.event).deltaX < 0 && this._horizontalBar.value < this._horizontalBar.maximum) {
  299. this._horizontalBar.value += this._wheelPrecision;
  300. } else if ((<MouseWheelEvent>pi.event).deltaX > 0 && this._horizontalBar.value > 0) {
  301. this._horizontalBar.value -= this._wheelPrecision;
  302. }
  303. }
  304. });
  305. }
  306. public _renderHighlightSpecific(context: CanvasRenderingContext2D): void {
  307. if (!this.isHighlighted) {
  308. return;
  309. }
  310. super._renderHighlightSpecific(context);
  311. this._grid._renderHighlightSpecific(context);
  312. context.restore();
  313. }
  314. /** Releases associated resources */
  315. public dispose() {
  316. let scene = this._host.getScene();
  317. if (scene && this._onPointerObserver) {
  318. scene.onPointerObservable.remove(this._onPointerObserver);
  319. this._onPointerObserver = null;
  320. }
  321. super.dispose();
  322. }
  323. }