1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | import { ArrayExt } from '@lumino/algorithm';
|
11 |
|
12 | import { IDisposable } from '@lumino/disposable';
|
13 |
|
14 | import { Drag } from '@lumino/dragdrop';
|
15 |
|
16 | import { Message } from '@lumino/messaging';
|
17 |
|
18 | import { ISignal, Signal } from '@lumino/signaling';
|
19 |
|
20 | import { Panel } from './panel';
|
21 |
|
22 | import { SplitLayout } from './splitlayout';
|
23 |
|
24 | import { Widget } from './widget';
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 | export class SplitPanel extends Panel {
|
33 | |
34 |
|
35 |
|
36 |
|
37 |
|
38 | constructor(options: SplitPanel.IOptions = {}) {
|
39 | super({ layout: Private.createLayout(options) });
|
40 | this.addClass('lm-SplitPanel');
|
41 |
|
42 | this.addClass('p-SplitPanel');
|
43 |
|
44 | }
|
45 |
|
46 | |
47 |
|
48 |
|
49 | dispose(): void {
|
50 | this._releaseMouse();
|
51 | super.dispose();
|
52 | }
|
53 |
|
54 | |
55 |
|
56 |
|
57 | get orientation(): SplitPanel.Orientation {
|
58 | return (this.layout as SplitLayout).orientation;
|
59 | }
|
60 |
|
61 | |
62 |
|
63 |
|
64 | set orientation(value: SplitPanel.Orientation) {
|
65 | (this.layout as SplitLayout).orientation = value;
|
66 | }
|
67 |
|
68 | |
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 | get alignment(): SplitPanel.Alignment {
|
78 | return (this.layout as SplitLayout).alignment;
|
79 | }
|
80 |
|
81 | |
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 | set alignment(value: SplitPanel.Alignment) {
|
91 | (this.layout as SplitLayout).alignment = value;
|
92 | }
|
93 |
|
94 | |
95 |
|
96 |
|
97 | get spacing(): number {
|
98 | return (this.layout as SplitLayout).spacing;
|
99 | }
|
100 |
|
101 | |
102 |
|
103 |
|
104 | set spacing(value: number) {
|
105 | (this.layout as SplitLayout).spacing = value;
|
106 | }
|
107 |
|
108 | |
109 |
|
110 |
|
111 | get renderer(): SplitPanel.IRenderer {
|
112 | return (this.layout as SplitLayout).renderer;
|
113 | }
|
114 |
|
115 | |
116 |
|
117 |
|
118 | get handleMoved(): ISignal<this, void> {
|
119 | return this._handleMoved;
|
120 | }
|
121 |
|
122 | |
123 |
|
124 |
|
125 | get handles(): ReadonlyArray<HTMLDivElement> {
|
126 | return (this.layout as SplitLayout).handles;
|
127 | }
|
128 |
|
129 | |
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 | relativeSizes(): number[] {
|
141 | return (this.layout as SplitLayout).relativeSizes();
|
142 | }
|
143 |
|
144 | |
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 | setRelativeSizes(sizes: number[]): void {
|
155 | (this.layout as SplitLayout).setRelativeSizes(sizes);
|
156 | }
|
157 |
|
158 | |
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 | handleEvent(event: Event): void {
|
169 | switch (event.type) {
|
170 | case 'mousedown':
|
171 | this._evtMouseDown(event as MouseEvent);
|
172 | break;
|
173 | case 'mousemove':
|
174 | this._evtMouseMove(event as MouseEvent);
|
175 | break;
|
176 | case 'mouseup':
|
177 | this._evtMouseUp(event as MouseEvent);
|
178 | break;
|
179 | case 'pointerdown':
|
180 | this._evtMouseDown(event as MouseEvent);
|
181 | break;
|
182 | case 'pointermove':
|
183 | this._evtMouseMove(event as MouseEvent);
|
184 | break;
|
185 | case 'pointerup':
|
186 | this._evtMouseUp(event as MouseEvent);
|
187 | break;
|
188 | case 'keydown':
|
189 | this._evtKeyDown(event as KeyboardEvent);
|
190 | break;
|
191 | case 'contextmenu':
|
192 | event.preventDefault();
|
193 | event.stopPropagation();
|
194 | break;
|
195 | }
|
196 | }
|
197 |
|
198 | |
199 |
|
200 |
|
201 | protected onBeforeAttach(msg: Message): void {
|
202 | this.node.addEventListener('mousedown', this);
|
203 | this.node.addEventListener('pointerdown', this);
|
204 | }
|
205 |
|
206 | |
207 |
|
208 |
|
209 | protected onAfterDetach(msg: Message): void {
|
210 | this.node.removeEventListener('mousedown', this);
|
211 | this.node.removeEventListener('pointerdown', this);
|
212 | this._releaseMouse();
|
213 | }
|
214 |
|
215 | |
216 |
|
217 |
|
218 | protected onChildAdded(msg: Widget.ChildMessage): void {
|
219 | msg.child.addClass('lm-SplitPanel-child');
|
220 |
|
221 | msg.child.addClass('p-SplitPanel-child');
|
222 |
|
223 | this._releaseMouse();
|
224 | }
|
225 |
|
226 | |
227 |
|
228 |
|
229 | protected onChildRemoved(msg: Widget.ChildMessage): void {
|
230 | msg.child.removeClass('lm-SplitPanel-child');
|
231 |
|
232 | msg.child.removeClass('p-SplitPanel-child');
|
233 |
|
234 | this._releaseMouse();
|
235 | }
|
236 |
|
237 | |
238 |
|
239 |
|
240 | private _evtKeyDown(event: KeyboardEvent): void {
|
241 |
|
242 | if (this._pressData) {
|
243 | event.preventDefault();
|
244 | event.stopPropagation();
|
245 | }
|
246 |
|
247 |
|
248 | if (event.keyCode === 27) {
|
249 | this._releaseMouse();
|
250 | }
|
251 | }
|
252 |
|
253 | |
254 |
|
255 |
|
256 | private _evtMouseDown(event: MouseEvent): void {
|
257 |
|
258 | if (event.button !== 0) {
|
259 | return;
|
260 | }
|
261 |
|
262 |
|
263 | let layout = this.layout as SplitLayout;
|
264 | let index = ArrayExt.findFirstIndex(layout.handles, handle => {
|
265 | return handle.contains(event.target as HTMLElement);
|
266 | });
|
267 |
|
268 |
|
269 | if (index === -1) {
|
270 | return;
|
271 | }
|
272 |
|
273 |
|
274 | event.preventDefault();
|
275 | event.stopPropagation();
|
276 |
|
277 |
|
278 | document.addEventListener('mouseup', this, true);
|
279 | document.addEventListener('mousemove', this, true);
|
280 | document.addEventListener('pointerup', this, true);
|
281 | document.addEventListener('pointermove', this, true);
|
282 | document.addEventListener('keydown', this, true);
|
283 | document.addEventListener('contextmenu', this, true);
|
284 |
|
285 |
|
286 | let delta: number;
|
287 | let handle = layout.handles[index];
|
288 | let rect = handle.getBoundingClientRect();
|
289 | if (layout.orientation === 'horizontal') {
|
290 | delta = event.clientX - rect.left;
|
291 | } else {
|
292 | delta = event.clientY - rect.top;
|
293 | }
|
294 |
|
295 |
|
296 | let style = window.getComputedStyle(handle);
|
297 | let override = Drag.overrideCursor(style.cursor!);
|
298 | this._pressData = { index, delta, override };
|
299 | }
|
300 |
|
301 | |
302 |
|
303 |
|
304 | private _evtMouseMove(event: MouseEvent): void {
|
305 |
|
306 | event.preventDefault();
|
307 | event.stopPropagation();
|
308 |
|
309 |
|
310 | let pos: number;
|
311 | let layout = this.layout as SplitLayout;
|
312 | let rect = this.node.getBoundingClientRect();
|
313 | if (layout.orientation === 'horizontal') {
|
314 | pos = event.clientX - rect.left - this._pressData!.delta;
|
315 | } else {
|
316 | pos = event.clientY - rect.top - this._pressData!.delta;
|
317 | }
|
318 |
|
319 |
|
320 | layout.moveHandle(this._pressData!.index, pos);
|
321 | }
|
322 |
|
323 | |
324 |
|
325 |
|
326 | private _evtMouseUp(event: MouseEvent): void {
|
327 |
|
328 | if (event.button !== 0) {
|
329 | return;
|
330 | }
|
331 |
|
332 |
|
333 | event.preventDefault();
|
334 | event.stopPropagation();
|
335 |
|
336 |
|
337 | this._releaseMouse();
|
338 | }
|
339 |
|
340 | |
341 |
|
342 |
|
343 | private _releaseMouse(): void {
|
344 |
|
345 | if (!this._pressData) {
|
346 | return;
|
347 | }
|
348 |
|
349 |
|
350 | this._pressData.override.dispose();
|
351 | this._pressData = null;
|
352 |
|
353 |
|
354 | this._handleMoved.emit();
|
355 |
|
356 |
|
357 | document.removeEventListener('mouseup', this, true);
|
358 | document.removeEventListener('mousemove', this, true);
|
359 | document.removeEventListener('keydown', this, true);
|
360 | document.removeEventListener('pointerup', this, true);
|
361 | document.removeEventListener('pointermove', this, true);
|
362 | document.removeEventListener('contextmenu', this, true);
|
363 | }
|
364 |
|
365 | private _handleMoved = new Signal<any, void>(this);
|
366 | private _pressData: Private.IPressData | null = null;
|
367 | }
|
368 |
|
369 |
|
370 |
|
371 |
|
372 | export namespace SplitPanel {
|
373 | |
374 |
|
375 |
|
376 | export type Orientation = SplitLayout.Orientation;
|
377 |
|
378 | |
379 |
|
380 |
|
381 | export type Alignment = SplitLayout.Alignment;
|
382 |
|
383 | |
384 |
|
385 |
|
386 | export type IRenderer = SplitLayout.IRenderer;
|
387 |
|
388 | |
389 |
|
390 |
|
391 | export interface IOptions {
|
392 | |
393 |
|
394 |
|
395 |
|
396 |
|
397 | renderer?: IRenderer;
|
398 |
|
399 | |
400 |
|
401 |
|
402 |
|
403 |
|
404 | orientation?: Orientation;
|
405 |
|
406 | |
407 |
|
408 |
|
409 |
|
410 |
|
411 | alignment?: Alignment;
|
412 |
|
413 | |
414 |
|
415 |
|
416 |
|
417 |
|
418 | spacing?: number;
|
419 |
|
420 | |
421 |
|
422 |
|
423 |
|
424 |
|
425 |
|
426 |
|
427 | layout?: SplitLayout;
|
428 | }
|
429 |
|
430 | |
431 |
|
432 |
|
433 | export class Renderer implements IRenderer {
|
434 | |
435 |
|
436 |
|
437 |
|
438 |
|
439 | createHandle(): HTMLDivElement {
|
440 | let handle = document.createElement('div');
|
441 | handle.className = 'lm-SplitPanel-handle';
|
442 |
|
443 | handle.classList.add('p-SplitPanel-handle');
|
444 |
|
445 | return handle;
|
446 | }
|
447 | }
|
448 |
|
449 | |
450 |
|
451 |
|
452 | export const defaultRenderer = new Renderer();
|
453 |
|
454 | |
455 |
|
456 |
|
457 |
|
458 |
|
459 |
|
460 |
|
461 | export function getStretch(widget: Widget): number {
|
462 | return SplitLayout.getStretch(widget);
|
463 | }
|
464 |
|
465 | |
466 |
|
467 |
|
468 |
|
469 |
|
470 |
|
471 |
|
472 | export function setStretch(widget: Widget, value: number): void {
|
473 | SplitLayout.setStretch(widget, value);
|
474 | }
|
475 | }
|
476 |
|
477 |
|
478 |
|
479 |
|
480 | namespace Private {
|
481 | |
482 |
|
483 |
|
484 | export interface IPressData {
|
485 | |
486 |
|
487 |
|
488 | index: number;
|
489 |
|
490 | |
491 |
|
492 |
|
493 | delta: number;
|
494 |
|
495 | |
496 |
|
497 |
|
498 | override: IDisposable;
|
499 | }
|
500 |
|
501 | |
502 |
|
503 |
|
504 | export function createLayout(options: SplitPanel.IOptions): SplitLayout {
|
505 | return (
|
506 | options.layout ||
|
507 | new SplitLayout({
|
508 | renderer: options.renderer || SplitPanel.defaultRenderer,
|
509 | orientation: options.orientation,
|
510 | alignment: options.alignment,
|
511 | spacing: options.spacing
|
512 | })
|
513 | );
|
514 | }
|
515 | }
|