1 | /* eslint-disable @typescript-eslint/no-empty-function */
|
2 | // Copyright (c) Jupyter Development Team.
|
3 | // Distributed under the terms of the Modified BSD License.
|
4 | /*-----------------------------------------------------------------------------
|
5 | | Copyright (c) 2014-2017, PhosphorJS Contributors
|
6 | |
|
7 | | Distributed under the terms of the BSD 3-Clause License.
|
8 | |
|
9 | | The full license is in the file LICENSE, distributed with this software.
|
10 | |----------------------------------------------------------------------------*/
|
11 | import { each, IIterable, IIterator } from '@lumino/algorithm';
|
12 |
|
13 | import { IDisposable } from '@lumino/disposable';
|
14 |
|
15 | import { ElementExt } from '@lumino/domutils';
|
16 |
|
17 | import { Message, MessageLoop } from '@lumino/messaging';
|
18 |
|
19 | import { AttachedProperty } from '@lumino/properties';
|
20 |
|
21 | import { Signal } from '@lumino/signaling';
|
22 |
|
23 | import { Widget } from './widget';
|
24 |
|
25 | /**
|
26 | * An abstract base class for creating lumino layouts.
|
27 | *
|
28 | * #### Notes
|
29 | * A layout is used to add widgets to a parent and to arrange those
|
30 | * widgets within the parent's DOM node.
|
31 | *
|
32 | * This class implements the base functionality which is required of
|
33 | * nearly all layouts. It must be subclassed in order to be useful.
|
34 | *
|
35 | * Notably, this class does not define a uniform interface for adding
|
36 | * widgets to the layout. A subclass should define that API in a way
|
37 | * which is meaningful for its intended use.
|
38 | */
|
39 | export abstract class Layout implements IIterable<Widget>, IDisposable {
|
40 | /**
|
41 | * Construct a new layout.
|
42 | *
|
43 | * @param options - The options for initializing the layout.
|
44 | */
|
45 | constructor(options: Layout.IOptions = {}) {
|
46 | this._fitPolicy = options.fitPolicy || 'set-min-size';
|
47 | }
|
48 |
|
49 | /**
|
50 | * Dispose of the resources held by the layout.
|
51 | *
|
52 | * #### Notes
|
53 | * This should be reimplemented to clear and dispose of the widgets.
|
54 | *
|
55 | * All reimplementations should call the superclass method.
|
56 | *
|
57 | * This method is called automatically when the parent is disposed.
|
58 | */
|
59 | dispose(): void {
|
60 | this._parent = null;
|
61 | this._disposed = true;
|
62 | Signal.clearData(this);
|
63 | AttachedProperty.clearData(this);
|
64 | }
|
65 |
|
66 | /**
|
67 | * Test whether the layout is disposed.
|
68 | */
|
69 | get isDisposed(): boolean {
|
70 | return this._disposed;
|
71 | }
|
72 |
|
73 | /**
|
74 | * Get the parent widget of the layout.
|
75 | */
|
76 | get parent(): Widget | null {
|
77 | return this._parent;
|
78 | }
|
79 |
|
80 | /**
|
81 | * Set the parent widget of the layout.
|
82 | *
|
83 | * #### Notes
|
84 | * This is set automatically when installing the layout on the parent
|
85 | * widget. The parent widget should not be set directly by user code.
|
86 | */
|
87 | set parent(value: Widget | null) {
|
88 | if (this._parent === value) {
|
89 | return;
|
90 | }
|
91 | if (this._parent) {
|
92 | throw new Error('Cannot change parent widget.');
|
93 | }
|
94 | if (value!.layout !== this) {
|
95 | throw new Error('Invalid parent widget.');
|
96 | }
|
97 | this._parent = value;
|
98 | this.init();
|
99 | }
|
100 |
|
101 | /**
|
102 | * Get the fit policy for the layout.
|
103 | *
|
104 | * #### Notes
|
105 | * The fit policy controls the computed size constraints which are
|
106 | * applied to the parent widget by the layout.
|
107 | *
|
108 | * Some layout implementations may ignore the fit policy.
|
109 | */
|
110 | get fitPolicy(): Layout.FitPolicy {
|
111 | return this._fitPolicy;
|
112 | }
|
113 |
|
114 | /**
|
115 | * Set the fit policy for the layout.
|
116 | *
|
117 | * #### Notes
|
118 | * The fit policy controls the computed size constraints which are
|
119 | * applied to the parent widget by the layout.
|
120 | *
|
121 | * Some layout implementations may ignore the fit policy.
|
122 | *
|
123 | * Changing the fit policy will clear the current size constraint
|
124 | * for the parent widget and then re-fit the parent.
|
125 | */
|
126 | set fitPolicy(value: Layout.FitPolicy) {
|
127 | // Bail if the policy does not change
|
128 | if (this._fitPolicy === value) {
|
129 | return;
|
130 | }
|
131 |
|
132 | // Update the internal policy.
|
133 | this._fitPolicy = value;
|
134 |
|
135 | // Clear the size constraints and schedule a fit of the parent.
|
136 | if (this._parent) {
|
137 | let style = this._parent.node.style;
|
138 | style.minWidth = '';
|
139 | style.minHeight = '';
|
140 | style.maxWidth = '';
|
141 | style.maxHeight = '';
|
142 | this._parent.fit();
|
143 | }
|
144 | }
|
145 |
|
146 | /**
|
147 | * Create an iterator over the widgets in the layout.
|
148 | *
|
149 | * @returns A new iterator over the widgets in the layout.
|
150 | *
|
151 | * #### Notes
|
152 | * This abstract method must be implemented by a subclass.
|
153 | */
|
154 | abstract iter(): IIterator<Widget>;
|
155 |
|
156 | /**
|
157 | * Remove a widget from the layout.
|
158 | *
|
159 | * @param widget - The widget to remove from the layout.
|
160 | *
|
161 | * #### Notes
|
162 | * A widget is automatically removed from the layout when its `parent`
|
163 | * is set to `null`. This method should only be invoked directly when
|
164 | * removing a widget from a layout which has yet to be installed on a
|
165 | * parent widget.
|
166 | *
|
167 | * This method should *not* modify the widget's `parent`.
|
168 | */
|
169 | abstract removeWidget(widget: Widget): void;
|
170 |
|
171 | /**
|
172 | * Process a message sent to the parent widget.
|
173 | *
|
174 | * @param msg - The message sent to the parent widget.
|
175 | *
|
176 | * #### Notes
|
177 | * This method is called by the parent widget to process a message.
|
178 | *
|
179 | * Subclasses may reimplement this method as needed.
|
180 | */
|
181 | processParentMessage(msg: Message): void {
|
182 | switch (msg.type) {
|
183 | case 'resize':
|
184 | this.onResize(msg as Widget.ResizeMessage);
|
185 | break;
|
186 | case 'update-request':
|
187 | this.onUpdateRequest(msg);
|
188 | break;
|
189 | case 'fit-request':
|
190 | this.onFitRequest(msg);
|
191 | break;
|
192 | case 'before-show':
|
193 | this.onBeforeShow(msg);
|
194 | break;
|
195 | case 'after-show':
|
196 | this.onAfterShow(msg);
|
197 | break;
|
198 | case 'before-hide':
|
199 | this.onBeforeHide(msg);
|
200 | break;
|
201 | case 'after-hide':
|
202 | this.onAfterHide(msg);
|
203 | break;
|
204 | case 'before-attach':
|
205 | this.onBeforeAttach(msg);
|
206 | break;
|
207 | case 'after-attach':
|
208 | this.onAfterAttach(msg);
|
209 | break;
|
210 | case 'before-detach':
|
211 | this.onBeforeDetach(msg);
|
212 | break;
|
213 | case 'after-detach':
|
214 | this.onAfterDetach(msg);
|
215 | break;
|
216 | case 'child-removed':
|
217 | this.onChildRemoved(msg as Widget.ChildMessage);
|
218 | break;
|
219 | case 'child-shown':
|
220 | this.onChildShown(msg as Widget.ChildMessage);
|
221 | break;
|
222 | case 'child-hidden':
|
223 | this.onChildHidden(msg as Widget.ChildMessage);
|
224 | break;
|
225 | }
|
226 | }
|
227 |
|
228 | /**
|
229 | * Perform layout initialization which requires the parent widget.
|
230 | *
|
231 | * #### Notes
|
232 | * This method is invoked immediately after the layout is installed
|
233 | * on the parent widget.
|
234 | *
|
235 | * The default implementation reparents all of the widgets to the
|
236 | * layout parent widget.
|
237 | *
|
238 | * Subclasses should reimplement this method and attach the child
|
239 | * widget nodes to the parent widget's node.
|
240 | */
|
241 | protected init(): void {
|
242 | each(this, widget => {
|
243 | widget.parent = this.parent;
|
244 | });
|
245 | }
|
246 |
|
247 | /**
|
248 | * A message handler invoked on a `'resize'` message.
|
249 | *
|
250 | * #### Notes
|
251 | * The layout should ensure that its widgets are resized according
|
252 | * to the specified layout space, and that they are sent a `'resize'`
|
253 | * message if appropriate.
|
254 | *
|
255 | * The default implementation of this method sends an `UnknownSize`
|
256 | * resize message to all widgets.
|
257 | *
|
258 | * This may be reimplemented by subclasses as needed.
|
259 | */
|
260 | protected onResize(msg: Widget.ResizeMessage): void {
|
261 | each(this, widget => {
|
262 | MessageLoop.sendMessage(widget, Widget.ResizeMessage.UnknownSize);
|
263 | });
|
264 | }
|
265 |
|
266 | /**
|
267 | * A message handler invoked on an `'update-request'` message.
|
268 | *
|
269 | * #### Notes
|
270 | * The layout should ensure that its widgets are resized according
|
271 | * to the available layout space, and that they are sent a `'resize'`
|
272 | * message if appropriate.
|
273 | *
|
274 | * The default implementation of this method sends an `UnknownSize`
|
275 | * resize message to all widgets.
|
276 | *
|
277 | * This may be reimplemented by subclasses as needed.
|
278 | */
|
279 | protected onUpdateRequest(msg: Message): void {
|
280 | each(this, widget => {
|
281 | MessageLoop.sendMessage(widget, Widget.ResizeMessage.UnknownSize);
|
282 | });
|
283 | }
|
284 |
|
285 | /**
|
286 | * A message handler invoked on a `'before-attach'` message.
|
287 | *
|
288 | * #### Notes
|
289 | * The default implementation of this method forwards the message
|
290 | * to all widgets. It assumes all widget nodes are attached to the
|
291 | * parent widget node.
|
292 | *
|
293 | * This may be reimplemented by subclasses as needed.
|
294 | */
|
295 | protected onBeforeAttach(msg: Message): void {
|
296 | each(this, widget => {
|
297 | MessageLoop.sendMessage(widget, msg);
|
298 | });
|
299 | }
|
300 |
|
301 | /**
|
302 | * A message handler invoked on an `'after-attach'` message.
|
303 | *
|
304 | * #### Notes
|
305 | * The default implementation of this method forwards the message
|
306 | * to all widgets. It assumes all widget nodes are attached to the
|
307 | * parent widget node.
|
308 | *
|
309 | * This may be reimplemented by subclasses as needed.
|
310 | */
|
311 | protected onAfterAttach(msg: Message): void {
|
312 | each(this, widget => {
|
313 | MessageLoop.sendMessage(widget, msg);
|
314 | });
|
315 | }
|
316 |
|
317 | /**
|
318 | * A message handler invoked on a `'before-detach'` message.
|
319 | *
|
320 | * #### Notes
|
321 | * The default implementation of this method forwards the message
|
322 | * to all widgets. It assumes all widget nodes are attached to the
|
323 | * parent widget node.
|
324 | *
|
325 | * This may be reimplemented by subclasses as needed.
|
326 | */
|
327 | protected onBeforeDetach(msg: Message): void {
|
328 | each(this, widget => {
|
329 | MessageLoop.sendMessage(widget, msg);
|
330 | });
|
331 | }
|
332 |
|
333 | /**
|
334 | * A message handler invoked on an `'after-detach'` message.
|
335 | *
|
336 | * #### Notes
|
337 | * The default implementation of this method forwards the message
|
338 | * to all widgets. It assumes all widget nodes are attached to the
|
339 | * parent widget node.
|
340 | *
|
341 | * This may be reimplemented by subclasses as needed.
|
342 | */
|
343 | protected onAfterDetach(msg: Message): void {
|
344 | each(this, widget => {
|
345 | MessageLoop.sendMessage(widget, msg);
|
346 | });
|
347 | }
|
348 |
|
349 | /**
|
350 | * A message handler invoked on a `'before-show'` message.
|
351 | *
|
352 | * #### Notes
|
353 | * The default implementation of this method forwards the message to
|
354 | * all non-hidden widgets. It assumes all widget nodes are attached
|
355 | * to the parent widget node.
|
356 | *
|
357 | * This may be reimplemented by subclasses as needed.
|
358 | */
|
359 | protected onBeforeShow(msg: Message): void {
|
360 | each(this, widget => {
|
361 | if (!widget.isHidden) {
|
362 | MessageLoop.sendMessage(widget, msg);
|
363 | }
|
364 | });
|
365 | }
|
366 |
|
367 | /**
|
368 | * A message handler invoked on an `'after-show'` message.
|
369 | *
|
370 | * #### Notes
|
371 | * The default implementation of this method forwards the message to
|
372 | * all non-hidden widgets. It assumes all widget nodes are attached
|
373 | * to the parent widget node.
|
374 | *
|
375 | * This may be reimplemented by subclasses as needed.
|
376 | */
|
377 | protected onAfterShow(msg: Message): void {
|
378 | each(this, widget => {
|
379 | if (!widget.isHidden) {
|
380 | MessageLoop.sendMessage(widget, msg);
|
381 | }
|
382 | });
|
383 | }
|
384 |
|
385 | /**
|
386 | * A message handler invoked on a `'before-hide'` message.
|
387 | *
|
388 | * #### Notes
|
389 | * The default implementation of this method forwards the message to
|
390 | * all non-hidden widgets. It assumes all widget nodes are attached
|
391 | * to the parent widget node.
|
392 | *
|
393 | * This may be reimplemented by subclasses as needed.
|
394 | */
|
395 | protected onBeforeHide(msg: Message): void {
|
396 | each(this, widget => {
|
397 | if (!widget.isHidden) {
|
398 | MessageLoop.sendMessage(widget, msg);
|
399 | }
|
400 | });
|
401 | }
|
402 |
|
403 | /**
|
404 | * A message handler invoked on an `'after-hide'` message.
|
405 | *
|
406 | * #### Notes
|
407 | * The default implementation of this method forwards the message to
|
408 | * all non-hidden widgets. It assumes all widget nodes are attached
|
409 | * to the parent widget node.
|
410 | *
|
411 | * This may be reimplemented by subclasses as needed.
|
412 | */
|
413 | protected onAfterHide(msg: Message): void {
|
414 | each(this, widget => {
|
415 | if (!widget.isHidden) {
|
416 | MessageLoop.sendMessage(widget, msg);
|
417 | }
|
418 | });
|
419 | }
|
420 |
|
421 | /**
|
422 | * A message handler invoked on a `'child-removed'` message.
|
423 | *
|
424 | * #### Notes
|
425 | * This will remove the child widget from the layout.
|
426 | *
|
427 | * Subclasses should **not** typically reimplement this method.
|
428 | */
|
429 | protected onChildRemoved(msg: Widget.ChildMessage): void {
|
430 | this.removeWidget(msg.child);
|
431 | }
|
432 |
|
433 | /**
|
434 | * A message handler invoked on a `'fit-request'` message.
|
435 | *
|
436 | * #### Notes
|
437 | * The default implementation of this handler is a no-op.
|
438 | */
|
439 | protected onFitRequest(msg: Message): void {}
|
440 |
|
441 | /**
|
442 | * A message handler invoked on a `'child-shown'` message.
|
443 | *
|
444 | * #### Notes
|
445 | * The default implementation of this handler is a no-op.
|
446 | */
|
447 | protected onChildShown(msg: Widget.ChildMessage): void {}
|
448 |
|
449 | /**
|
450 | * A message handler invoked on a `'child-hidden'` message.
|
451 | *
|
452 | * #### Notes
|
453 | * The default implementation of this handler is a no-op.
|
454 | */
|
455 | protected onChildHidden(msg: Widget.ChildMessage): void {}
|
456 |
|
457 | private _disposed = false;
|
458 | private _fitPolicy: Layout.FitPolicy;
|
459 | private _parent: Widget | null = null;
|
460 | }
|
461 |
|
462 | /**
|
463 | * The namespace for the `Layout` class statics.
|
464 | */
|
465 | export namespace Layout {
|
466 | /**
|
467 | * A type alias for the layout fit policy.
|
468 | *
|
469 | * #### Notes
|
470 | * The fit policy controls the computed size constraints which are
|
471 | * applied to the parent widget by the layout.
|
472 | *
|
473 | * Some layout implementations may ignore the fit policy.
|
474 | */
|
475 | export type FitPolicy =
|
476 | | /**
|
477 | * No size constraint will be applied to the parent widget.
|
478 | */
|
479 | 'set-no-constraint'
|
480 |
|
481 | /**
|
482 | * The computed min size will be applied to the parent widget.
|
483 | */
|
484 | | 'set-min-size';
|
485 |
|
486 | /**
|
487 | * An options object for initializing a layout.
|
488 | */
|
489 | export interface IOptions {
|
490 | /**
|
491 | * The fit policy for the layout.
|
492 | *
|
493 | * The default is `'set-min-size'`.
|
494 | */
|
495 | fitPolicy?: FitPolicy;
|
496 | }
|
497 |
|
498 | /**
|
499 | * A type alias for the horizontal alignment of a widget.
|
500 | */
|
501 | export type HorizontalAlignment = 'left' | 'center' | 'right';
|
502 |
|
503 | /**
|
504 | * A type alias for the vertical alignment of a widget.
|
505 | */
|
506 | export type VerticalAlignment = 'top' | 'center' | 'bottom';
|
507 |
|
508 | /**
|
509 | * Get the horizontal alignment for a widget.
|
510 | *
|
511 | * @param widget - The widget of interest.
|
512 | *
|
513 | * @returns The horizontal alignment for the widget.
|
514 | *
|
515 | * #### Notes
|
516 | * If the layout width allocated to a widget is larger than its max
|
517 | * width, the horizontal alignment controls how the widget is placed
|
518 | * within the extra horizontal space.
|
519 | *
|
520 | * If the allocated width is less than the widget's max width, the
|
521 | * horizontal alignment has no effect.
|
522 | *
|
523 | * Some layout implementations may ignore horizontal alignment.
|
524 | */
|
525 | export function getHorizontalAlignment(widget: Widget): HorizontalAlignment {
|
526 | return Private.horizontalAlignmentProperty.get(widget);
|
527 | }
|
528 |
|
529 | /**
|
530 | * Set the horizontal alignment for a widget.
|
531 | *
|
532 | * @param widget - The widget of interest.
|
533 | *
|
534 | * @param value - The value for the horizontal alignment.
|
535 | *
|
536 | * #### Notes
|
537 | * If the layout width allocated to a widget is larger than its max
|
538 | * width, the horizontal alignment controls how the widget is placed
|
539 | * within the extra horizontal space.
|
540 | *
|
541 | * If the allocated width is less than the widget's max width, the
|
542 | * horizontal alignment has no effect.
|
543 | *
|
544 | * Some layout implementations may ignore horizontal alignment.
|
545 | *
|
546 | * Changing the horizontal alignment will post an `update-request`
|
547 | * message to widget's parent, provided the parent has a layout
|
548 | * installed.
|
549 | */
|
550 | export function setHorizontalAlignment(
|
551 | widget: Widget,
|
552 | value: HorizontalAlignment
|
553 | ): void {
|
554 | Private.horizontalAlignmentProperty.set(widget, value);
|
555 | }
|
556 |
|
557 | /**
|
558 | * Get the vertical alignment for a widget.
|
559 | *
|
560 | * @param widget - The widget of interest.
|
561 | *
|
562 | * @returns The vertical alignment for the widget.
|
563 | *
|
564 | * #### Notes
|
565 | * If the layout height allocated to a widget is larger than its max
|
566 | * height, the vertical alignment controls how the widget is placed
|
567 | * within the extra vertical space.
|
568 | *
|
569 | * If the allocated height is less than the widget's max height, the
|
570 | * vertical alignment has no effect.
|
571 | *
|
572 | * Some layout implementations may ignore vertical alignment.
|
573 | */
|
574 | export function getVerticalAlignment(widget: Widget): VerticalAlignment {
|
575 | return Private.verticalAlignmentProperty.get(widget);
|
576 | }
|
577 |
|
578 | /**
|
579 | * Set the vertical alignment for a widget.
|
580 | *
|
581 | * @param widget - The widget of interest.
|
582 | *
|
583 | * @param value - The value for the vertical alignment.
|
584 | *
|
585 | * #### Notes
|
586 | * If the layout height allocated to a widget is larger than its max
|
587 | * height, the vertical alignment controls how the widget is placed
|
588 | * within the extra vertical space.
|
589 | *
|
590 | * If the allocated height is less than the widget's max height, the
|
591 | * vertical alignment has no effect.
|
592 | *
|
593 | * Some layout implementations may ignore vertical alignment.
|
594 | *
|
595 | * Changing the horizontal alignment will post an `update-request`
|
596 | * message to widget's parent, provided the parent has a layout
|
597 | * installed.
|
598 | */
|
599 | export function setVerticalAlignment(
|
600 | widget: Widget,
|
601 | value: VerticalAlignment
|
602 | ): void {
|
603 | Private.verticalAlignmentProperty.set(widget, value);
|
604 | }
|
605 | }
|
606 |
|
607 | /**
|
608 | * An object which assists in the absolute layout of widgets.
|
609 | *
|
610 | * #### Notes
|
611 | * This class is useful when implementing a layout which arranges its
|
612 | * widgets using absolute positioning.
|
613 | *
|
614 | * This class is used by nearly all of the built-in lumino layouts.
|
615 | */
|
616 | export class LayoutItem implements IDisposable {
|
617 | /**
|
618 | * Construct a new layout item.
|
619 | *
|
620 | * @param widget - The widget to be managed by the item.
|
621 | *
|
622 | * #### Notes
|
623 | * The widget will be set to absolute positioning.
|
624 | */
|
625 | constructor(widget: Widget) {
|
626 | this.widget = widget;
|
627 | this.widget.node.style.position = 'absolute';
|
628 | }
|
629 |
|
630 | /**
|
631 | * Dispose of the the layout item.
|
632 | *
|
633 | * #### Notes
|
634 | * This will reset the positioning of the widget.
|
635 | */
|
636 | dispose(): void {
|
637 | // Do nothing if the item is already disposed.
|
638 | if (this._disposed) {
|
639 | return;
|
640 | }
|
641 |
|
642 | // Mark the item as disposed.
|
643 | this._disposed = true;
|
644 |
|
645 | // Reset the widget style.
|
646 | let style = this.widget.node.style;
|
647 | style.position = '';
|
648 | style.top = '';
|
649 | style.left = '';
|
650 | style.width = '';
|
651 | style.height = '';
|
652 | }
|
653 |
|
654 | /**
|
655 | * The widget managed by the layout item.
|
656 | */
|
657 | readonly widget: Widget;
|
658 |
|
659 | /**
|
660 | * The computed minimum width of the widget.
|
661 | *
|
662 | * #### Notes
|
663 | * This value can be updated by calling the `fit` method.
|
664 | */
|
665 | get minWidth(): number {
|
666 | return this._minWidth;
|
667 | }
|
668 |
|
669 | /**
|
670 | * The computed minimum height of the widget.
|
671 | *
|
672 | * #### Notes
|
673 | * This value can be updated by calling the `fit` method.
|
674 | */
|
675 | get minHeight(): number {
|
676 | return this._minHeight;
|
677 | }
|
678 |
|
679 | /**
|
680 | * The computed maximum width of the widget.
|
681 | *
|
682 | * #### Notes
|
683 | * This value can be updated by calling the `fit` method.
|
684 | */
|
685 | get maxWidth(): number {
|
686 | return this._maxWidth;
|
687 | }
|
688 |
|
689 | /**
|
690 | * The computed maximum height of the widget.
|
691 | *
|
692 | * #### Notes
|
693 | * This value can be updated by calling the `fit` method.
|
694 | */
|
695 | get maxHeight(): number {
|
696 | return this._maxHeight;
|
697 | }
|
698 |
|
699 | /**
|
700 | * Whether the layout item is disposed.
|
701 | */
|
702 | get isDisposed(): boolean {
|
703 | return this._disposed;
|
704 | }
|
705 |
|
706 | /**
|
707 | * Whether the managed widget is hidden.
|
708 | */
|
709 | get isHidden(): boolean {
|
710 | return this.widget.isHidden;
|
711 | }
|
712 |
|
713 | /**
|
714 | * Whether the managed widget is visible.
|
715 | */
|
716 | get isVisible(): boolean {
|
717 | return this.widget.isVisible;
|
718 | }
|
719 |
|
720 | /**
|
721 | * Whether the managed widget is attached.
|
722 | */
|
723 | get isAttached(): boolean {
|
724 | return this.widget.isAttached;
|
725 | }
|
726 |
|
727 | /**
|
728 | * Update the computed size limits of the managed widget.
|
729 | */
|
730 | fit(): void {
|
731 | let limits = ElementExt.sizeLimits(this.widget.node);
|
732 | this._minWidth = limits.minWidth;
|
733 | this._minHeight = limits.minHeight;
|
734 | this._maxWidth = limits.maxWidth;
|
735 | this._maxHeight = limits.maxHeight;
|
736 | }
|
737 |
|
738 | /**
|
739 | * Update the position and size of the managed widget.
|
740 | *
|
741 | * @param left - The left edge position of the layout box.
|
742 | *
|
743 | * @param top - The top edge position of the layout box.
|
744 | *
|
745 | * @param width - The width of the layout box.
|
746 | *
|
747 | * @param height - The height of the layout box.
|
748 | */
|
749 | update(left: number, top: number, width: number, height: number): void {
|
750 | // Clamp the size to the computed size limits.
|
751 | let clampW = Math.max(this._minWidth, Math.min(width, this._maxWidth));
|
752 | let clampH = Math.max(this._minHeight, Math.min(height, this._maxHeight));
|
753 |
|
754 | // Adjust the left edge for the horizontal alignment, if needed.
|
755 | if (clampW < width) {
|
756 | switch (Layout.getHorizontalAlignment(this.widget)) {
|
757 | case 'left':
|
758 | break;
|
759 | case 'center':
|
760 | left += (width - clampW) / 2;
|
761 | break;
|
762 | case 'right':
|
763 | left += width - clampW;
|
764 | break;
|
765 | default:
|
766 | throw 'unreachable';
|
767 | }
|
768 | }
|
769 |
|
770 | // Adjust the top edge for the vertical alignment, if needed.
|
771 | if (clampH < height) {
|
772 | switch (Layout.getVerticalAlignment(this.widget)) {
|
773 | case 'top':
|
774 | break;
|
775 | case 'center':
|
776 | top += (height - clampH) / 2;
|
777 | break;
|
778 | case 'bottom':
|
779 | top += height - clampH;
|
780 | break;
|
781 | default:
|
782 | throw 'unreachable';
|
783 | }
|
784 | }
|
785 |
|
786 | // Set up the resize variables.
|
787 | let resized = false;
|
788 | let style = this.widget.node.style;
|
789 |
|
790 | // Update the top edge of the widget if needed.
|
791 | if (this._top !== top) {
|
792 | this._top = top;
|
793 | style.top = `${top}px`;
|
794 | }
|
795 |
|
796 | // Update the left edge of the widget if needed.
|
797 | if (this._left !== left) {
|
798 | this._left = left;
|
799 | style.left = `${left}px`;
|
800 | }
|
801 |
|
802 | // Update the width of the widget if needed.
|
803 | if (this._width !== clampW) {
|
804 | resized = true;
|
805 | this._width = clampW;
|
806 | style.width = `${clampW}px`;
|
807 | }
|
808 |
|
809 | // Update the height of the widget if needed.
|
810 | if (this._height !== clampH) {
|
811 | resized = true;
|
812 | this._height = clampH;
|
813 | style.height = `${clampH}px`;
|
814 | }
|
815 |
|
816 | // Send a resize message to the widget if needed.
|
817 | if (resized) {
|
818 | let msg = new Widget.ResizeMessage(clampW, clampH);
|
819 | MessageLoop.sendMessage(this.widget, msg);
|
820 | }
|
821 | }
|
822 |
|
823 | private _top = NaN;
|
824 | private _left = NaN;
|
825 | private _width = NaN;
|
826 | private _height = NaN;
|
827 | private _minWidth = 0;
|
828 | private _minHeight = 0;
|
829 | private _maxWidth = Infinity;
|
830 | private _maxHeight = Infinity;
|
831 | private _disposed = false;
|
832 | }
|
833 |
|
834 | /**
|
835 | * The namespace for the module implementation details.
|
836 | */
|
837 | namespace Private {
|
838 | /**
|
839 | * The attached property for a widget horizontal alignment.
|
840 | */
|
841 | export const horizontalAlignmentProperty = new AttachedProperty<
|
842 | Widget,
|
843 | Layout.HorizontalAlignment
|
844 | >({
|
845 | name: 'horizontalAlignment',
|
846 | create: () => 'center',
|
847 | changed: onAlignmentChanged
|
848 | });
|
849 |
|
850 | /**
|
851 | * The attached property for a widget vertical alignment.
|
852 | */
|
853 | export const verticalAlignmentProperty = new AttachedProperty<
|
854 | Widget,
|
855 | Layout.VerticalAlignment
|
856 | >({
|
857 | name: 'verticalAlignment',
|
858 | create: () => 'top',
|
859 | changed: onAlignmentChanged
|
860 | });
|
861 |
|
862 | /**
|
863 | * The change handler for the attached alignment properties.
|
864 | */
|
865 | function onAlignmentChanged(child: Widget): void {
|
866 | if (child.parent && child.parent.layout) {
|
867 | child.parent.update();
|
868 | }
|
869 | }
|
870 | }
|