9f259b6654be41cf9e024de6f331333d2f8fae2dee9ce00c56284d718ee1689e555f987d832529c58c179603baf9dcaaee10add733049d9d2d3b6c14bb6125 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  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. var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  6. var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  7. if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  8. else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  9. return c > 3 && r && Object.defineProperty(target, key, r), r;
  10. };
  11. import { $, append, createStyleSheet, EventHelper, getElementsByTagName } from '../../dom.js';
  12. import { DomEmitter } from '../../event.js';
  13. import { EventType, Gesture } from '../../touch.js';
  14. import { Delayer } from '../../../common/async.js';
  15. import { memoize } from '../../../common/decorators.js';
  16. import { Emitter, Event } from '../../../common/event.js';
  17. import { Disposable, DisposableStore, toDisposable } from '../../../common/lifecycle.js';
  18. import { isMacintosh } from '../../../common/platform.js';
  19. import './sash.css';
  20. /**
  21. * Allow the sashes to be visible at runtime.
  22. * @remark Use for development purposes only.
  23. */
  24. const DEBUG = false;
  25. export var OrthogonalEdge;
  26. (function (OrthogonalEdge) {
  27. OrthogonalEdge["North"] = "north";
  28. OrthogonalEdge["South"] = "south";
  29. OrthogonalEdge["East"] = "east";
  30. OrthogonalEdge["West"] = "west";
  31. })(OrthogonalEdge || (OrthogonalEdge = {}));
  32. let globalSize = 4;
  33. const onDidChangeGlobalSize = new Emitter();
  34. let globalHoverDelay = 300;
  35. const onDidChangeHoverDelay = new Emitter();
  36. class MouseEventFactory {
  37. constructor() {
  38. this.disposables = new DisposableStore();
  39. }
  40. get onPointerMove() {
  41. return this.disposables.add(new DomEmitter(window, 'mousemove')).event;
  42. }
  43. get onPointerUp() {
  44. return this.disposables.add(new DomEmitter(window, 'mouseup')).event;
  45. }
  46. dispose() {
  47. this.disposables.dispose();
  48. }
  49. }
  50. __decorate([
  51. memoize
  52. ], MouseEventFactory.prototype, "onPointerMove", null);
  53. __decorate([
  54. memoize
  55. ], MouseEventFactory.prototype, "onPointerUp", null);
  56. class GestureEventFactory {
  57. constructor(el) {
  58. this.el = el;
  59. this.disposables = new DisposableStore();
  60. }
  61. get onPointerMove() {
  62. return this.disposables.add(new DomEmitter(this.el, EventType.Change)).event;
  63. }
  64. get onPointerUp() {
  65. return this.disposables.add(new DomEmitter(this.el, EventType.End)).event;
  66. }
  67. dispose() {
  68. this.disposables.dispose();
  69. }
  70. }
  71. __decorate([
  72. memoize
  73. ], GestureEventFactory.prototype, "onPointerMove", null);
  74. __decorate([
  75. memoize
  76. ], GestureEventFactory.prototype, "onPointerUp", null);
  77. class OrthogonalPointerEventFactory {
  78. constructor(factory) {
  79. this.factory = factory;
  80. }
  81. get onPointerMove() {
  82. return this.factory.onPointerMove;
  83. }
  84. get onPointerUp() {
  85. return this.factory.onPointerUp;
  86. }
  87. dispose() {
  88. // noop
  89. }
  90. }
  91. __decorate([
  92. memoize
  93. ], OrthogonalPointerEventFactory.prototype, "onPointerMove", null);
  94. __decorate([
  95. memoize
  96. ], OrthogonalPointerEventFactory.prototype, "onPointerUp", null);
  97. const PointerEventsDisabledCssClass = 'pointer-events-disabled';
  98. /**
  99. * The {@link Sash} is the UI component which allows the user to resize other
  100. * components. It's usually an invisible horizontal or vertical line which, when
  101. * hovered, becomes highlighted and can be dragged along the perpendicular dimension
  102. * to its direction.
  103. *
  104. * Features:
  105. * - Touch event handling
  106. * - Corner sash support
  107. * - Hover with different mouse cursor support
  108. * - Configurable hover size
  109. * - Linked sash support, for 2x2 corner sashes
  110. */
  111. export class Sash extends Disposable {
  112. constructor(container, layoutProvider, options) {
  113. super();
  114. this.hoverDelay = globalHoverDelay;
  115. this.hoverDelayer = this._register(new Delayer(this.hoverDelay));
  116. this._state = 3 /* SashState.Enabled */;
  117. this.onDidEnablementChange = this._register(new Emitter());
  118. this._onDidStart = this._register(new Emitter());
  119. this._onDidChange = this._register(new Emitter());
  120. this._onDidReset = this._register(new Emitter());
  121. this._onDidEnd = this._register(new Emitter());
  122. this.orthogonalStartSashDisposables = this._register(new DisposableStore());
  123. this.orthogonalStartDragHandleDisposables = this._register(new DisposableStore());
  124. this.orthogonalEndSashDisposables = this._register(new DisposableStore());
  125. this.orthogonalEndDragHandleDisposables = this._register(new DisposableStore());
  126. /**
  127. * An event which fires whenever the user starts dragging this sash.
  128. */
  129. this.onDidStart = this._onDidStart.event;
  130. /**
  131. * An event which fires whenever the user moves the mouse while
  132. * dragging this sash.
  133. */
  134. this.onDidChange = this._onDidChange.event;
  135. /**
  136. * An event which fires whenever the user double clicks this sash.
  137. */
  138. this.onDidReset = this._onDidReset.event;
  139. /**
  140. * An event which fires whenever the user stops dragging this sash.
  141. */
  142. this.onDidEnd = this._onDidEnd.event;
  143. /**
  144. * A linked sash will be forwarded the same user interactions and events
  145. * so it moves exactly the same way as this sash.
  146. *
  147. * Useful in 2x2 grids. Not meant for widespread usage.
  148. */
  149. this.linkedSash = undefined;
  150. this.el = append(container, $('.monaco-sash'));
  151. if (options.orthogonalEdge) {
  152. this.el.classList.add(`orthogonal-edge-${options.orthogonalEdge}`);
  153. }
  154. if (isMacintosh) {
  155. this.el.classList.add('mac');
  156. }
  157. const onMouseDown = this._register(new DomEmitter(this.el, 'mousedown')).event;
  158. this._register(onMouseDown(e => this.onPointerStart(e, new MouseEventFactory()), this));
  159. const onMouseDoubleClick = this._register(new DomEmitter(this.el, 'dblclick')).event;
  160. this._register(onMouseDoubleClick(this.onPointerDoublePress, this));
  161. const onMouseEnter = this._register(new DomEmitter(this.el, 'mouseenter')).event;
  162. this._register(onMouseEnter(() => Sash.onMouseEnter(this)));
  163. const onMouseLeave = this._register(new DomEmitter(this.el, 'mouseleave')).event;
  164. this._register(onMouseLeave(() => Sash.onMouseLeave(this)));
  165. this._register(Gesture.addTarget(this.el));
  166. const onTouchStart = Event.map(this._register(new DomEmitter(this.el, EventType.Start)).event, e => { var _a; return (Object.assign(Object.assign({}, e), { target: (_a = e.initialTarget) !== null && _a !== void 0 ? _a : null })); });
  167. this._register(onTouchStart(e => this.onPointerStart(e, new GestureEventFactory(this.el)), this));
  168. const onTap = this._register(new DomEmitter(this.el, EventType.Tap)).event;
  169. const onDoubleTap = Event.map(Event.filter(Event.debounce(onTap, (res, event) => { var _a; return ({ event, count: ((_a = res === null || res === void 0 ? void 0 : res.count) !== null && _a !== void 0 ? _a : 0) + 1 }); }, 250), ({ count }) => count === 2), ({ event }) => { var _a; return (Object.assign(Object.assign({}, event), { target: (_a = event.initialTarget) !== null && _a !== void 0 ? _a : null })); });
  170. this._register(onDoubleTap(this.onPointerDoublePress, this));
  171. if (typeof options.size === 'number') {
  172. this.size = options.size;
  173. if (options.orientation === 0 /* Orientation.VERTICAL */) {
  174. this.el.style.width = `${this.size}px`;
  175. }
  176. else {
  177. this.el.style.height = `${this.size}px`;
  178. }
  179. }
  180. else {
  181. this.size = globalSize;
  182. this._register(onDidChangeGlobalSize.event(size => {
  183. this.size = size;
  184. this.layout();
  185. }));
  186. }
  187. this._register(onDidChangeHoverDelay.event(delay => this.hoverDelay = delay));
  188. this.layoutProvider = layoutProvider;
  189. this.orthogonalStartSash = options.orthogonalStartSash;
  190. this.orthogonalEndSash = options.orthogonalEndSash;
  191. this.orientation = options.orientation || 0 /* Orientation.VERTICAL */;
  192. if (this.orientation === 1 /* Orientation.HORIZONTAL */) {
  193. this.el.classList.add('horizontal');
  194. this.el.classList.remove('vertical');
  195. }
  196. else {
  197. this.el.classList.remove('horizontal');
  198. this.el.classList.add('vertical');
  199. }
  200. this.el.classList.toggle('debug', DEBUG);
  201. this.layout();
  202. }
  203. get state() { return this._state; }
  204. get orthogonalStartSash() { return this._orthogonalStartSash; }
  205. get orthogonalEndSash() { return this._orthogonalEndSash; }
  206. /**
  207. * The state of a sash defines whether it can be interacted with by the user
  208. * as well as what mouse cursor to use, when hovered.
  209. */
  210. set state(state) {
  211. if (this._state === state) {
  212. return;
  213. }
  214. this.el.classList.toggle('disabled', state === 0 /* SashState.Disabled */);
  215. this.el.classList.toggle('minimum', state === 1 /* SashState.AtMinimum */);
  216. this.el.classList.toggle('maximum', state === 2 /* SashState.AtMaximum */);
  217. this._state = state;
  218. this.onDidEnablementChange.fire(state);
  219. }
  220. /**
  221. * A reference to another sash, perpendicular to this one, which
  222. * aligns at the start of this one. A corner sash will be created
  223. * automatically at that location.
  224. *
  225. * The start of a horizontal sash is its left-most position.
  226. * The start of a vertical sash is its top-most position.
  227. */
  228. set orthogonalStartSash(sash) {
  229. this.orthogonalStartDragHandleDisposables.clear();
  230. this.orthogonalStartSashDisposables.clear();
  231. if (sash) {
  232. const onChange = (state) => {
  233. this.orthogonalStartDragHandleDisposables.clear();
  234. if (state !== 0 /* SashState.Disabled */) {
  235. this._orthogonalStartDragHandle = append(this.el, $('.orthogonal-drag-handle.start'));
  236. this.orthogonalStartDragHandleDisposables.add(toDisposable(() => this._orthogonalStartDragHandle.remove()));
  237. this.orthogonalStartDragHandleDisposables.add(new DomEmitter(this._orthogonalStartDragHandle, 'mouseenter')).event(() => Sash.onMouseEnter(sash), undefined, this.orthogonalStartDragHandleDisposables);
  238. this.orthogonalStartDragHandleDisposables.add(new DomEmitter(this._orthogonalStartDragHandle, 'mouseleave')).event(() => Sash.onMouseLeave(sash), undefined, this.orthogonalStartDragHandleDisposables);
  239. }
  240. };
  241. this.orthogonalStartSashDisposables.add(sash.onDidEnablementChange.event(onChange, this));
  242. onChange(sash.state);
  243. }
  244. this._orthogonalStartSash = sash;
  245. }
  246. /**
  247. * A reference to another sash, perpendicular to this one, which
  248. * aligns at the end of this one. A corner sash will be created
  249. * automatically at that location.
  250. *
  251. * The end of a horizontal sash is its right-most position.
  252. * The end of a vertical sash is its bottom-most position.
  253. */
  254. set orthogonalEndSash(sash) {
  255. this.orthogonalEndDragHandleDisposables.clear();
  256. this.orthogonalEndSashDisposables.clear();
  257. if (sash) {
  258. const onChange = (state) => {
  259. this.orthogonalEndDragHandleDisposables.clear();
  260. if (state !== 0 /* SashState.Disabled */) {
  261. this._orthogonalEndDragHandle = append(this.el, $('.orthogonal-drag-handle.end'));
  262. this.orthogonalEndDragHandleDisposables.add(toDisposable(() => this._orthogonalEndDragHandle.remove()));
  263. this.orthogonalEndDragHandleDisposables.add(new DomEmitter(this._orthogonalEndDragHandle, 'mouseenter')).event(() => Sash.onMouseEnter(sash), undefined, this.orthogonalEndDragHandleDisposables);
  264. this.orthogonalEndDragHandleDisposables.add(new DomEmitter(this._orthogonalEndDragHandle, 'mouseleave')).event(() => Sash.onMouseLeave(sash), undefined, this.orthogonalEndDragHandleDisposables);
  265. }
  266. };
  267. this.orthogonalEndSashDisposables.add(sash.onDidEnablementChange.event(onChange, this));
  268. onChange(sash.state);
  269. }
  270. this._orthogonalEndSash = sash;
  271. }
  272. onPointerStart(event, pointerEventFactory) {
  273. EventHelper.stop(event);
  274. let isMultisashResize = false;
  275. if (!event.__orthogonalSashEvent) {
  276. const orthogonalSash = this.getOrthogonalSash(event);
  277. if (orthogonalSash) {
  278. isMultisashResize = true;
  279. event.__orthogonalSashEvent = true;
  280. orthogonalSash.onPointerStart(event, new OrthogonalPointerEventFactory(pointerEventFactory));
  281. }
  282. }
  283. if (this.linkedSash && !event.__linkedSashEvent) {
  284. event.__linkedSashEvent = true;
  285. this.linkedSash.onPointerStart(event, new OrthogonalPointerEventFactory(pointerEventFactory));
  286. }
  287. if (!this.state) {
  288. return;
  289. }
  290. const iframes = getElementsByTagName('iframe');
  291. for (const iframe of iframes) {
  292. iframe.classList.add(PointerEventsDisabledCssClass); // disable mouse events on iframes as long as we drag the sash
  293. }
  294. const startX = event.pageX;
  295. const startY = event.pageY;
  296. const altKey = event.altKey;
  297. const startEvent = { startX, currentX: startX, startY, currentY: startY, altKey };
  298. this.el.classList.add('active');
  299. this._onDidStart.fire(startEvent);
  300. // fix https://github.com/microsoft/vscode/issues/21675
  301. const style = createStyleSheet(this.el);
  302. const updateStyle = () => {
  303. let cursor = '';
  304. if (isMultisashResize) {
  305. cursor = 'all-scroll';
  306. }
  307. else if (this.orientation === 1 /* Orientation.HORIZONTAL */) {
  308. if (this.state === 1 /* SashState.AtMinimum */) {
  309. cursor = 's-resize';
  310. }
  311. else if (this.state === 2 /* SashState.AtMaximum */) {
  312. cursor = 'n-resize';
  313. }
  314. else {
  315. cursor = isMacintosh ? 'row-resize' : 'ns-resize';
  316. }
  317. }
  318. else {
  319. if (this.state === 1 /* SashState.AtMinimum */) {
  320. cursor = 'e-resize';
  321. }
  322. else if (this.state === 2 /* SashState.AtMaximum */) {
  323. cursor = 'w-resize';
  324. }
  325. else {
  326. cursor = isMacintosh ? 'col-resize' : 'ew-resize';
  327. }
  328. }
  329. style.textContent = `* { cursor: ${cursor} !important; }`;
  330. };
  331. const disposables = new DisposableStore();
  332. updateStyle();
  333. if (!isMultisashResize) {
  334. this.onDidEnablementChange.event(updateStyle, null, disposables);
  335. }
  336. const onPointerMove = (e) => {
  337. EventHelper.stop(e, false);
  338. const event = { startX, currentX: e.pageX, startY, currentY: e.pageY, altKey };
  339. this._onDidChange.fire(event);
  340. };
  341. const onPointerUp = (e) => {
  342. EventHelper.stop(e, false);
  343. this.el.removeChild(style);
  344. this.el.classList.remove('active');
  345. this._onDidEnd.fire();
  346. disposables.dispose();
  347. for (const iframe of iframes) {
  348. iframe.classList.remove(PointerEventsDisabledCssClass);
  349. }
  350. };
  351. pointerEventFactory.onPointerMove(onPointerMove, null, disposables);
  352. pointerEventFactory.onPointerUp(onPointerUp, null, disposables);
  353. disposables.add(pointerEventFactory);
  354. }
  355. onPointerDoublePress(e) {
  356. const orthogonalSash = this.getOrthogonalSash(e);
  357. if (orthogonalSash) {
  358. orthogonalSash._onDidReset.fire();
  359. }
  360. if (this.linkedSash) {
  361. this.linkedSash._onDidReset.fire();
  362. }
  363. this._onDidReset.fire();
  364. }
  365. static onMouseEnter(sash, fromLinkedSash = false) {
  366. if (sash.el.classList.contains('active')) {
  367. sash.hoverDelayer.cancel();
  368. sash.el.classList.add('hover');
  369. }
  370. else {
  371. sash.hoverDelayer.trigger(() => sash.el.classList.add('hover'), sash.hoverDelay).then(undefined, () => { });
  372. }
  373. if (!fromLinkedSash && sash.linkedSash) {
  374. Sash.onMouseEnter(sash.linkedSash, true);
  375. }
  376. }
  377. static onMouseLeave(sash, fromLinkedSash = false) {
  378. sash.hoverDelayer.cancel();
  379. sash.el.classList.remove('hover');
  380. if (!fromLinkedSash && sash.linkedSash) {
  381. Sash.onMouseLeave(sash.linkedSash, true);
  382. }
  383. }
  384. /**
  385. * Forcefully stop any user interactions with this sash.
  386. * Useful when hiding a parent component, while the user is still
  387. * interacting with the sash.
  388. */
  389. clearSashHoverState() {
  390. Sash.onMouseLeave(this);
  391. }
  392. /**
  393. * Layout the sash. The sash will size and position itself
  394. * based on its provided {@link ISashLayoutProvider layout provider}.
  395. */
  396. layout() {
  397. if (this.orientation === 0 /* Orientation.VERTICAL */) {
  398. const verticalProvider = this.layoutProvider;
  399. this.el.style.left = verticalProvider.getVerticalSashLeft(this) - (this.size / 2) + 'px';
  400. if (verticalProvider.getVerticalSashTop) {
  401. this.el.style.top = verticalProvider.getVerticalSashTop(this) + 'px';
  402. }
  403. if (verticalProvider.getVerticalSashHeight) {
  404. this.el.style.height = verticalProvider.getVerticalSashHeight(this) + 'px';
  405. }
  406. }
  407. else {
  408. const horizontalProvider = this.layoutProvider;
  409. this.el.style.top = horizontalProvider.getHorizontalSashTop(this) - (this.size / 2) + 'px';
  410. if (horizontalProvider.getHorizontalSashLeft) {
  411. this.el.style.left = horizontalProvider.getHorizontalSashLeft(this) + 'px';
  412. }
  413. if (horizontalProvider.getHorizontalSashWidth) {
  414. this.el.style.width = horizontalProvider.getHorizontalSashWidth(this) + 'px';
  415. }
  416. }
  417. }
  418. getOrthogonalSash(e) {
  419. if (!e.target || !(e.target instanceof HTMLElement)) {
  420. return undefined;
  421. }
  422. if (e.target.classList.contains('orthogonal-drag-handle')) {
  423. return e.target.classList.contains('start') ? this.orthogonalStartSash : this.orthogonalEndSash;
  424. }
  425. return undefined;
  426. }
  427. dispose() {
  428. super.dispose();
  429. this.el.remove();
  430. }
  431. }