viewLayout.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. import { Emitter } from '../../../base/common/event.js';
  6. import { Disposable } from '../../../base/common/lifecycle.js';
  7. import { Scrollable } from '../../../base/common/scrollable.js';
  8. import { LinesLayout } from './linesLayout.js';
  9. import { Viewport } from '../viewModel/viewModel.js';
  10. import { ContentSizeChangedEvent } from '../viewModel/viewModelEventDispatcher.js';
  11. const SMOOTH_SCROLLING_TIME = 125;
  12. class EditorScrollDimensions {
  13. constructor(width, contentWidth, height, contentHeight) {
  14. width = width | 0;
  15. contentWidth = contentWidth | 0;
  16. height = height | 0;
  17. contentHeight = contentHeight | 0;
  18. if (width < 0) {
  19. width = 0;
  20. }
  21. if (contentWidth < 0) {
  22. contentWidth = 0;
  23. }
  24. if (height < 0) {
  25. height = 0;
  26. }
  27. if (contentHeight < 0) {
  28. contentHeight = 0;
  29. }
  30. this.width = width;
  31. this.contentWidth = contentWidth;
  32. this.scrollWidth = Math.max(width, contentWidth);
  33. this.height = height;
  34. this.contentHeight = contentHeight;
  35. this.scrollHeight = Math.max(height, contentHeight);
  36. }
  37. equals(other) {
  38. return (this.width === other.width
  39. && this.contentWidth === other.contentWidth
  40. && this.height === other.height
  41. && this.contentHeight === other.contentHeight);
  42. }
  43. }
  44. class EditorScrollable extends Disposable {
  45. constructor(smoothScrollDuration, scheduleAtNextAnimationFrame) {
  46. super();
  47. this._onDidContentSizeChange = this._register(new Emitter());
  48. this.onDidContentSizeChange = this._onDidContentSizeChange.event;
  49. this._dimensions = new EditorScrollDimensions(0, 0, 0, 0);
  50. this._scrollable = this._register(new Scrollable(smoothScrollDuration, scheduleAtNextAnimationFrame));
  51. this.onDidScroll = this._scrollable.onScroll;
  52. }
  53. getScrollable() {
  54. return this._scrollable;
  55. }
  56. setSmoothScrollDuration(smoothScrollDuration) {
  57. this._scrollable.setSmoothScrollDuration(smoothScrollDuration);
  58. }
  59. validateScrollPosition(scrollPosition) {
  60. return this._scrollable.validateScrollPosition(scrollPosition);
  61. }
  62. getScrollDimensions() {
  63. return this._dimensions;
  64. }
  65. setScrollDimensions(dimensions) {
  66. if (this._dimensions.equals(dimensions)) {
  67. return;
  68. }
  69. const oldDimensions = this._dimensions;
  70. this._dimensions = dimensions;
  71. this._scrollable.setScrollDimensions({
  72. width: dimensions.width,
  73. scrollWidth: dimensions.scrollWidth,
  74. height: dimensions.height,
  75. scrollHeight: dimensions.scrollHeight
  76. }, true);
  77. const contentWidthChanged = (oldDimensions.contentWidth !== dimensions.contentWidth);
  78. const contentHeightChanged = (oldDimensions.contentHeight !== dimensions.contentHeight);
  79. if (contentWidthChanged || contentHeightChanged) {
  80. this._onDidContentSizeChange.fire(new ContentSizeChangedEvent(oldDimensions.contentWidth, oldDimensions.contentHeight, dimensions.contentWidth, dimensions.contentHeight));
  81. }
  82. }
  83. getFutureScrollPosition() {
  84. return this._scrollable.getFutureScrollPosition();
  85. }
  86. getCurrentScrollPosition() {
  87. return this._scrollable.getCurrentScrollPosition();
  88. }
  89. setScrollPositionNow(update) {
  90. this._scrollable.setScrollPositionNow(update);
  91. }
  92. setScrollPositionSmooth(update) {
  93. this._scrollable.setScrollPositionSmooth(update);
  94. }
  95. }
  96. export class ViewLayout extends Disposable {
  97. constructor(configuration, lineCount, scheduleAtNextAnimationFrame) {
  98. super();
  99. this._configuration = configuration;
  100. const options = this._configuration.options;
  101. const layoutInfo = options.get(130 /* layoutInfo */);
  102. const padding = options.get(74 /* padding */);
  103. this._linesLayout = new LinesLayout(lineCount, options.get(58 /* lineHeight */), padding.top, padding.bottom);
  104. this._scrollable = this._register(new EditorScrollable(0, scheduleAtNextAnimationFrame));
  105. this._configureSmoothScrollDuration();
  106. this._scrollable.setScrollDimensions(new EditorScrollDimensions(layoutInfo.contentWidth, 0, layoutInfo.height, 0));
  107. this.onDidScroll = this._scrollable.onDidScroll;
  108. this.onDidContentSizeChange = this._scrollable.onDidContentSizeChange;
  109. this._updateHeight();
  110. }
  111. dispose() {
  112. super.dispose();
  113. }
  114. getScrollable() {
  115. return this._scrollable.getScrollable();
  116. }
  117. onHeightMaybeChanged() {
  118. this._updateHeight();
  119. }
  120. _configureSmoothScrollDuration() {
  121. this._scrollable.setSmoothScrollDuration(this._configuration.options.get(102 /* smoothScrolling */) ? SMOOTH_SCROLLING_TIME : 0);
  122. }
  123. // ---- begin view event handlers
  124. onConfigurationChanged(e) {
  125. const options = this._configuration.options;
  126. if (e.hasChanged(58 /* lineHeight */)) {
  127. this._linesLayout.setLineHeight(options.get(58 /* lineHeight */));
  128. }
  129. if (e.hasChanged(74 /* padding */)) {
  130. const padding = options.get(74 /* padding */);
  131. this._linesLayout.setPadding(padding.top, padding.bottom);
  132. }
  133. if (e.hasChanged(130 /* layoutInfo */)) {
  134. const layoutInfo = options.get(130 /* layoutInfo */);
  135. const width = layoutInfo.contentWidth;
  136. const height = layoutInfo.height;
  137. const scrollDimensions = this._scrollable.getScrollDimensions();
  138. const contentWidth = scrollDimensions.contentWidth;
  139. this._scrollable.setScrollDimensions(new EditorScrollDimensions(width, scrollDimensions.contentWidth, height, this._getContentHeight(width, height, contentWidth)));
  140. }
  141. else {
  142. this._updateHeight();
  143. }
  144. if (e.hasChanged(102 /* smoothScrolling */)) {
  145. this._configureSmoothScrollDuration();
  146. }
  147. }
  148. onFlushed(lineCount) {
  149. this._linesLayout.onFlushed(lineCount);
  150. }
  151. onLinesDeleted(fromLineNumber, toLineNumber) {
  152. this._linesLayout.onLinesDeleted(fromLineNumber, toLineNumber);
  153. }
  154. onLinesInserted(fromLineNumber, toLineNumber) {
  155. this._linesLayout.onLinesInserted(fromLineNumber, toLineNumber);
  156. }
  157. // ---- end view event handlers
  158. _getHorizontalScrollbarHeight(width, scrollWidth) {
  159. const options = this._configuration.options;
  160. const scrollbar = options.get(91 /* scrollbar */);
  161. if (scrollbar.horizontal === 2 /* Hidden */) {
  162. // horizontal scrollbar not visible
  163. return 0;
  164. }
  165. if (width >= scrollWidth) {
  166. // horizontal scrollbar not visible
  167. return 0;
  168. }
  169. return scrollbar.horizontalScrollbarSize;
  170. }
  171. _getContentHeight(width, height, contentWidth) {
  172. const options = this._configuration.options;
  173. let result = this._linesLayout.getLinesTotalHeight();
  174. if (options.get(93 /* scrollBeyondLastLine */)) {
  175. result += Math.max(0, height - options.get(58 /* lineHeight */) - options.get(74 /* padding */).bottom);
  176. }
  177. else {
  178. result += this._getHorizontalScrollbarHeight(width, contentWidth);
  179. }
  180. return result;
  181. }
  182. _updateHeight() {
  183. const scrollDimensions = this._scrollable.getScrollDimensions();
  184. const width = scrollDimensions.width;
  185. const height = scrollDimensions.height;
  186. const contentWidth = scrollDimensions.contentWidth;
  187. this._scrollable.setScrollDimensions(new EditorScrollDimensions(width, scrollDimensions.contentWidth, height, this._getContentHeight(width, height, contentWidth)));
  188. }
  189. // ---- Layouting logic
  190. getCurrentViewport() {
  191. const scrollDimensions = this._scrollable.getScrollDimensions();
  192. const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
  193. return new Viewport(currentScrollPosition.scrollTop, currentScrollPosition.scrollLeft, scrollDimensions.width, scrollDimensions.height);
  194. }
  195. getFutureViewport() {
  196. const scrollDimensions = this._scrollable.getScrollDimensions();
  197. const currentScrollPosition = this._scrollable.getFutureScrollPosition();
  198. return new Viewport(currentScrollPosition.scrollTop, currentScrollPosition.scrollLeft, scrollDimensions.width, scrollDimensions.height);
  199. }
  200. _computeContentWidth(maxLineWidth) {
  201. const options = this._configuration.options;
  202. const wrappingInfo = options.get(131 /* wrappingInfo */);
  203. const fontInfo = options.get(43 /* fontInfo */);
  204. if (wrappingInfo.isViewportWrapping) {
  205. const layoutInfo = options.get(130 /* layoutInfo */);
  206. const minimap = options.get(64 /* minimap */);
  207. if (maxLineWidth > layoutInfo.contentWidth + fontInfo.typicalHalfwidthCharacterWidth) {
  208. // This is a case where viewport wrapping is on, but the line extends above the viewport
  209. if (minimap.enabled && minimap.side === 'right') {
  210. // We need to accomodate the scrollbar width
  211. return maxLineWidth + layoutInfo.verticalScrollbarWidth;
  212. }
  213. }
  214. return maxLineWidth;
  215. }
  216. else {
  217. const extraHorizontalSpace = options.get(92 /* scrollBeyondLastColumn */) * fontInfo.typicalHalfwidthCharacterWidth;
  218. const whitespaceMinWidth = this._linesLayout.getWhitespaceMinWidth();
  219. return Math.max(maxLineWidth + extraHorizontalSpace, whitespaceMinWidth);
  220. }
  221. }
  222. setMaxLineWidth(maxLineWidth) {
  223. const scrollDimensions = this._scrollable.getScrollDimensions();
  224. // const newScrollWidth = ;
  225. this._scrollable.setScrollDimensions(new EditorScrollDimensions(scrollDimensions.width, this._computeContentWidth(maxLineWidth), scrollDimensions.height, scrollDimensions.contentHeight));
  226. // The height might depend on the fact that there is a horizontal scrollbar or not
  227. this._updateHeight();
  228. }
  229. // ---- view state
  230. saveState() {
  231. const currentScrollPosition = this._scrollable.getFutureScrollPosition();
  232. let scrollTop = currentScrollPosition.scrollTop;
  233. let firstLineNumberInViewport = this._linesLayout.getLineNumberAtOrAfterVerticalOffset(scrollTop);
  234. let whitespaceAboveFirstLine = this._linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(firstLineNumberInViewport);
  235. return {
  236. scrollTop: scrollTop,
  237. scrollTopWithoutViewZones: scrollTop - whitespaceAboveFirstLine,
  238. scrollLeft: currentScrollPosition.scrollLeft
  239. };
  240. }
  241. // ---- IVerticalLayoutProvider
  242. changeWhitespace(callback) {
  243. const hadAChange = this._linesLayout.changeWhitespace(callback);
  244. if (hadAChange) {
  245. this.onHeightMaybeChanged();
  246. }
  247. return hadAChange;
  248. }
  249. getVerticalOffsetForLineNumber(lineNumber) {
  250. return this._linesLayout.getVerticalOffsetForLineNumber(lineNumber);
  251. }
  252. isAfterLines(verticalOffset) {
  253. return this._linesLayout.isAfterLines(verticalOffset);
  254. }
  255. isInTopPadding(verticalOffset) {
  256. return this._linesLayout.isInTopPadding(verticalOffset);
  257. }
  258. isInBottomPadding(verticalOffset) {
  259. return this._linesLayout.isInBottomPadding(verticalOffset);
  260. }
  261. getLineNumberAtVerticalOffset(verticalOffset) {
  262. return this._linesLayout.getLineNumberAtOrAfterVerticalOffset(verticalOffset);
  263. }
  264. getWhitespaceAtVerticalOffset(verticalOffset) {
  265. return this._linesLayout.getWhitespaceAtVerticalOffset(verticalOffset);
  266. }
  267. getLinesViewportData() {
  268. const visibleBox = this.getCurrentViewport();
  269. return this._linesLayout.getLinesViewportData(visibleBox.top, visibleBox.top + visibleBox.height);
  270. }
  271. getLinesViewportDataAtScrollTop(scrollTop) {
  272. // do some minimal validations on scrollTop
  273. const scrollDimensions = this._scrollable.getScrollDimensions();
  274. if (scrollTop + scrollDimensions.height > scrollDimensions.scrollHeight) {
  275. scrollTop = scrollDimensions.scrollHeight - scrollDimensions.height;
  276. }
  277. if (scrollTop < 0) {
  278. scrollTop = 0;
  279. }
  280. return this._linesLayout.getLinesViewportData(scrollTop, scrollTop + scrollDimensions.height);
  281. }
  282. getWhitespaceViewportData() {
  283. const visibleBox = this.getCurrentViewport();
  284. return this._linesLayout.getWhitespaceViewportData(visibleBox.top, visibleBox.top + visibleBox.height);
  285. }
  286. getWhitespaces() {
  287. return this._linesLayout.getWhitespaces();
  288. }
  289. // ---- IScrollingProvider
  290. getContentWidth() {
  291. const scrollDimensions = this._scrollable.getScrollDimensions();
  292. return scrollDimensions.contentWidth;
  293. }
  294. getScrollWidth() {
  295. const scrollDimensions = this._scrollable.getScrollDimensions();
  296. return scrollDimensions.scrollWidth;
  297. }
  298. getContentHeight() {
  299. const scrollDimensions = this._scrollable.getScrollDimensions();
  300. return scrollDimensions.contentHeight;
  301. }
  302. getScrollHeight() {
  303. const scrollDimensions = this._scrollable.getScrollDimensions();
  304. return scrollDimensions.scrollHeight;
  305. }
  306. getCurrentScrollLeft() {
  307. const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
  308. return currentScrollPosition.scrollLeft;
  309. }
  310. getCurrentScrollTop() {
  311. const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
  312. return currentScrollPosition.scrollTop;
  313. }
  314. validateScrollPosition(scrollPosition) {
  315. return this._scrollable.validateScrollPosition(scrollPosition);
  316. }
  317. setScrollPosition(position, type) {
  318. if (type === 1 /* Immediate */) {
  319. this._scrollable.setScrollPositionNow(position);
  320. }
  321. else {
  322. this._scrollable.setScrollPositionSmooth(position);
  323. }
  324. }
  325. deltaScrollNow(deltaScrollLeft, deltaScrollTop) {
  326. const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
  327. this._scrollable.setScrollPositionNow({
  328. scrollLeft: currentScrollPosition.scrollLeft + deltaScrollLeft,
  329. scrollTop: currentScrollPosition.scrollTop + deltaScrollTop
  330. });
  331. }
  332. }