]>
Commit | Line | Data |
---|---|---|
7ff03bb4 | 1 | /** |
1d300911 | 2 | * @license MIT |
7ff03bb4 DI |
3 | */ |
4 | ||
e0d98711 | 5 | import { ITerminal } from './Interfaces'; |
74483fb2 | 6 | import { CharMeasure } from './utils/CharMeasure'; |
e0d98711 | 7 | |
7ff03bb4 DI |
8 | /** |
9 | * Represents the viewport of a terminal, the visible area within the larger buffer of output. | |
10 | * Logic for the virtual scroll bar is included in this object. | |
7ff03bb4 | 11 | */ |
e0d98711 | 12 | export class Viewport { |
e0d98711 DI |
13 | private currentRowHeight: number; |
14 | private lastRecordedBufferLength: number; | |
15 | private lastRecordedViewportHeight: number; | |
62233428 | 16 | private lastTouchY: number; |
7ff03bb4 | 17 | |
81dc48ef DI |
18 | /** |
19 | * Creates a new Viewport. | |
20 | * @param terminal The terminal this viewport belongs to. | |
21 | * @param viewportElement The DOM element acting as the viewport. | |
22 | * @param scrollArea The DOM element acting as the scroll area. | |
23 | * @param charMeasureElement A DOM element used to measure the character size of. the terminal. | |
24 | */ | |
e0d98711 DI |
25 | constructor( |
26 | private terminal: ITerminal, | |
27 | private viewportElement: HTMLElement, | |
28 | private scrollArea: HTMLElement, | |
74483fb2 | 29 | private charMeasure: CharMeasure |
e0d98711 DI |
30 | ) { |
31 | this.currentRowHeight = 0; | |
32 | this.lastRecordedBufferLength = 0; | |
33 | this.lastRecordedViewportHeight = 0; | |
7ff03bb4 | 34 | |
e0d98711 DI |
35 | this.terminal.on('scroll', this.syncScrollArea.bind(this)); |
36 | this.terminal.on('resize', this.syncScrollArea.bind(this)); | |
37 | this.viewportElement.addEventListener('scroll', this.onScroll.bind(this)); | |
7ff03bb4 | 38 | |
ee1de098 DI |
39 | // Perform this async to ensure the CharMeasure is ready. |
40 | setTimeout(() => this.syncScrollArea(), 0); | |
7ff03bb4 DI |
41 | } |
42 | ||
e0d98711 DI |
43 | /** |
44 | * Refreshes row height, setting line-height, viewport height and scroll area height if | |
45 | * necessary. | |
46 | * @param charSize A character size measurement bounding rect object, if it doesn't exist it will | |
47 | * be created. | |
48 | */ | |
74483fb2 DI |
49 | private refresh(): void { |
50 | if (this.charMeasure.height > 0) { | |
ada96a83 | 51 | const rowHeightChanged = this.charMeasure.height !== this.currentRowHeight; |
e0d98711 | 52 | if (rowHeightChanged) { |
74483fb2 DI |
53 | this.currentRowHeight = this.charMeasure.height; |
54 | this.viewportElement.style.lineHeight = this.charMeasure.height + 'px'; | |
55 | this.terminal.rowContainer.style.lineHeight = this.charMeasure.height + 'px'; | |
e0d98711 | 56 | } |
d2ef19df | 57 | const viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows; |
e0d98711 DI |
58 | if (rowHeightChanged || viewportHeightChanged) { |
59 | this.lastRecordedViewportHeight = this.terminal.rows; | |
74483fb2 | 60 | this.viewportElement.style.height = this.charMeasure.height * this.terminal.rows + 'px'; |
9c459aac | 61 | this.terminal.selectionContainer.style.height = this.viewportElement.style.height; |
e0d98711 | 62 | } |
74483fb2 | 63 | this.scrollArea.style.height = (this.charMeasure.height * this.lastRecordedBufferLength) + 'px'; |
e0d98711 | 64 | } |
7ff03bb4 | 65 | } |
7ff03bb4 | 66 | |
e0d98711 DI |
67 | /** |
68 | * Updates dimensions and synchronizes the scroll area if necessary. | |
69 | */ | |
70 | public syncScrollArea(): void { | |
8ede1fc9 | 71 | if (this.lastRecordedBufferLength !== this.terminal.buffer.lines.length) { |
e0d98711 | 72 | // If buffer height changed |
8ede1fc9 | 73 | this.lastRecordedBufferLength = this.terminal.buffer.lines.length; |
e0d98711 DI |
74 | this.refresh(); |
75 | } else if (this.lastRecordedViewportHeight !== this.terminal.rows) { | |
76 | // If viewport height changed | |
77 | this.refresh(); | |
78 | } else { | |
79 | // If size has changed, refresh viewport | |
74483fb2 | 80 | if (this.charMeasure.height !== this.currentRowHeight) { |
9385b930 | 81 | this.refresh(); |
e0d98711 DI |
82 | } |
83 | } | |
7ff03bb4 | 84 | |
e0d98711 | 85 | // Sync scrollTop |
d2ef19df | 86 | const scrollTop = this.terminal.ydisp * this.currentRowHeight; |
e0d98711 DI |
87 | if (this.viewportElement.scrollTop !== scrollTop) { |
88 | this.viewportElement.scrollTop = scrollTop; | |
89 | } | |
7ff03bb4 | 90 | } |
e0d98711 DI |
91 | |
92 | /** | |
93 | * Handles scroll events on the viewport, calculating the new viewport and requesting the | |
94 | * terminal to scroll to it. | |
95 | * @param ev The scroll event. | |
96 | */ | |
97 | private onScroll(ev: Event) { | |
d2ef19df DI |
98 | const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight); |
99 | const diff = newRow - this.terminal.ydisp; | |
e0d98711 | 100 | this.terminal.scrollDisp(diff, true); |
7ff03bb4 | 101 | } |
7ff03bb4 | 102 | |
e0d98711 DI |
103 | /** |
104 | * Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual | |
105 | * scrolling to `onScroll`, this event needs to be attached manually by the consumer of | |
106 | * `Viewport`. | |
107 | * @param ev The mouse wheel event. | |
108 | */ | |
109 | public onWheel(ev: WheelEvent) { | |
110 | if (ev.deltaY === 0) { | |
111 | // Do nothing if it's not a vertical scroll event | |
112 | return; | |
113 | } | |
114 | // Fallback to WheelEvent.DOM_DELTA_PIXEL | |
d2ef19df | 115 | let multiplier = 1; |
e0d98711 DI |
116 | if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { |
117 | multiplier = this.currentRowHeight; | |
118 | } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { | |
119 | multiplier = this.currentRowHeight * this.terminal.rows; | |
120 | } | |
121 | this.viewportElement.scrollTop += ev.deltaY * multiplier; | |
122 | // Prevent the page from scrolling when the terminal scrolls | |
123 | ev.preventDefault(); | |
124 | }; | |
62233428 | 125 | |
3e8ddd19 DI |
126 | /** |
127 | * Handles the touchstart event, recording the touch occurred. | |
128 | * @param ev The touch event. | |
129 | */ | |
62233428 AA |
130 | public onTouchStart(ev: TouchEvent) { |
131 | this.lastTouchY = ev.touches[0].pageY; | |
132 | }; | |
133 | ||
3e8ddd19 DI |
134 | /** |
135 | * Handles the touchmove event, scrolling the viewport if the position shifted. | |
136 | * @param ev The touch event. | |
137 | */ | |
62233428 AA |
138 | public onTouchMove(ev: TouchEvent) { |
139 | let deltaY = this.lastTouchY - ev.touches[0].pageY; | |
140 | this.lastTouchY = ev.touches[0].pageY; | |
141 | if (deltaY === 0) { | |
142 | return; | |
143 | } | |
144 | this.viewportElement.scrollTop += deltaY; | |
145 | ev.preventDefault(); | |
146 | }; | |
e0d98711 | 147 | } |