UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

615 lines (527 loc) 18 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2004-2008 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Sebastian Werner (wpbasti) * Fabian Jakobs (fjakobs) ************************************************************************ */ /** * The ScrollArea provides a container widget with on demand scroll bars * if the content size exceeds the size of the container. * * @childControl pane {qx.ui.core.scroll.ScrollPane} pane which holds the content to scroll * @childControl scrollbar-x {qx.ui.core.scroll.ScrollBar?qx.ui.core.scroll.NativeScrollBar} horizontal scrollbar * @childControl scrollbar-y {qx.ui.core.scroll.ScrollBar?qx.ui.core.scroll.NativeScrollBar} vertical scrollbar * @childControl corner {qx.ui.core.Widget} corner where no scrollbar is shown */ qx.Class.define("qx.ui.core.scroll.AbstractScrollArea", { extend: qx.ui.core.Widget, include: [ qx.ui.core.scroll.MScrollBarFactory, qx.ui.core.scroll.MRoll, qx.ui.core.MDragDropScrolling ], type: "abstract", /* ***************************************************************************** STATICS ***************************************************************************** */ statics: { /** * The default width which is used for the width of the scroll bar if * overlaid. */ DEFAULT_SCROLLBAR_WIDTH: 14 }, /* ***************************************************************************** CONSTRUCTOR ***************************************************************************** */ construct() { super(); if (qx.core.Environment.get("os.scrollBarOverlayed")) { // use a plain canvas to overlay the scroll bars this._setLayout(new qx.ui.layout.Canvas()); } else { // Create 'fixed' grid layout var grid = new qx.ui.layout.Grid(); grid.setColumnFlex(0, 1); grid.setRowFlex(0, 1); this._setLayout(grid); } // since the scroll container disregards the min size of the scrollbars // we have to set the min size of the scroll area to ensure that the // scrollbars always have an usable size. var size = qx.ui.core.scroll.AbstractScrollArea.DEFAULT_SCROLLBAR_WIDTH * 2 + 14; this.set({ minHeight: size, minWidth: size }); // Roll listener for scrolling this._addRollHandling(); }, events: { /** Fired as soon as the scroll animation in X direction ends. */ scrollAnimationXEnd: "qx.event.type.Event", /** Fired as soon as the scroll animation in Y direction ends. */ scrollAnimationYEnd: "qx.event.type.Event" }, /* ***************************************************************************** PROPERTIES ***************************************************************************** */ properties: { // overridden appearance: { refine: true, init: "scrollarea" }, // overridden width: { refine: true, init: 0 }, // overridden height: { refine: true, init: 0 }, /** * The policy, when the horizontal scrollbar should be shown. * <ul> * <li><b>auto</b>: Show scrollbar on demand</li> * <li><b>on</b>: Always show the scrollbar</li> * <li><b>off</b>: Never show the scrollbar</li> * </ul> */ scrollbarX: { check: ["auto", "on", "off"], init: "auto", themeable: true, apply: "_computeScrollbars" }, /** * The policy, when the horizontal scrollbar should be shown. * <ul> * <li><b>auto</b>: Show scrollbar on demand</li> * <li><b>on</b>: Always show the scrollbar</li> * <li><b>off</b>: Never show the scrollbar</li> * </ul> */ scrollbarY: { check: ["auto", "on", "off"], init: "auto", themeable: true, apply: "_computeScrollbars" }, /** * Group property, to set the overflow of both scroll bars. */ scrollbar: { group: ["scrollbarX", "scrollbarY"] } }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members: { /* --------------------------------------------------------------------------- CHILD CONTROL SUPPORT --------------------------------------------------------------------------- */ // overridden _createChildControlImpl(id, hash) { var control; switch (id) { case "pane": control = new qx.ui.core.scroll.ScrollPane(); control.addListener("update", this._computeScrollbars, this); control.addListener("scrollX", this._onScrollPaneX, this); control.addListener("scrollY", this._onScrollPaneY, this); if (qx.core.Environment.get("os.scrollBarOverlayed")) { this._add(control, { edge: 0 }); } else { this._add(control, { row: 0, column: 0 }); } break; case "scrollbar-x": control = this._createScrollBar("horizontal"); control.setMinWidth(0); control.exclude(); control.addListener("scroll", this._onScrollBarX, this); control.addListener( "changeVisibility", this._onChangeScrollbarXVisibility, this ); control.addListener( "scrollAnimationEnd", this._onScrollAnimationEnd.bind(this, "X") ); if (qx.core.Environment.get("os.scrollBarOverlayed")) { control.setMinHeight( qx.ui.core.scroll.AbstractScrollArea.DEFAULT_SCROLLBAR_WIDTH ); this._add(control, { bottom: 0, right: 0, left: 0 }); } else { this._add(control, { row: 1, column: 0 }); } break; case "scrollbar-y": control = this._createScrollBar("vertical"); control.setMinHeight(0); control.exclude(); control.addListener("scroll", this._onScrollBarY, this); control.addListener( "changeVisibility", this._onChangeScrollbarYVisibility, this ); control.addListener( "scrollAnimationEnd", this._onScrollAnimationEnd.bind(this, "Y") ); if (qx.core.Environment.get("os.scrollBarOverlayed")) { control.setMinWidth( qx.ui.core.scroll.AbstractScrollArea.DEFAULT_SCROLLBAR_WIDTH ); this._add(control, { right: 0, bottom: 0, top: 0 }); } else { this._add(control, { row: 0, column: 1 }); } break; case "corner": control = new qx.ui.core.Widget(); control.setWidth(0); control.setHeight(0); control.exclude(); if (!qx.core.Environment.get("os.scrollBarOverlayed")) { // only add for non overlayed scroll bars this._add(control, { row: 1, column: 1 }); } break; } return control || super._createChildControlImpl(id); }, /* --------------------------------------------------------------------------- PANE SIZE --------------------------------------------------------------------------- */ /** * Returns the dimensions of the pane. * * @return {Map|null} The pane dimension in pixel. Contains * the keys <code>width</code> and <code>height</code>. */ getPaneSize() { return this.getChildControl("pane").getInnerSize(); }, /* --------------------------------------------------------------------------- ITEM LOCATION SUPPORT --------------------------------------------------------------------------- */ /** * Returns the top offset of the given item in relation to the * inner height of this widget. * * @param item {qx.ui.core.Widget} Item to query * @return {Integer} Top offset */ getItemTop(item) { return this.getChildControl("pane").getItemTop(item); }, /** * Returns the top offset of the end of the given item in relation to the * inner height of this widget. * * @param item {qx.ui.core.Widget} Item to query * @return {Integer} Top offset */ getItemBottom(item) { return this.getChildControl("pane").getItemBottom(item); }, /** * Returns the left offset of the given item in relation to the * inner width of this widget. * * @param item {qx.ui.core.Widget} Item to query * @return {Integer} Top offset */ getItemLeft(item) { return this.getChildControl("pane").getItemLeft(item); }, /** * Returns the left offset of the end of the given item in relation to the * inner width of this widget. * * @param item {qx.ui.core.Widget} Item to query * @return {Integer} Right offset */ getItemRight(item) { return this.getChildControl("pane").getItemRight(item); }, /* --------------------------------------------------------------------------- SCROLL SUPPORT --------------------------------------------------------------------------- */ /** * Scrolls the element's content to the given left coordinate * * @param value {Integer} The vertical position to scroll to. * @param duration {Number?} The time in milliseconds the scroll to should take. */ scrollToX(value, duration) { // First flush queue before scroll qx.ui.core.queue.Manager.flush(); this.getChildControl("scrollbar-x").scrollTo(value, duration); }, /** * Scrolls the element's content by the given left offset * * @param value {Integer} The vertical position to scroll to. * @param duration {Number?} The time in milliseconds the scroll to should take. */ scrollByX(value, duration) { // First flush queue before scroll qx.ui.core.queue.Manager.flush(); this.getChildControl("scrollbar-x").scrollBy(value, duration); }, /** * Returns the scroll left position of the content * * @return {Integer} Horizontal scroll position */ getScrollX() { var scrollbar = this.getChildControl("scrollbar-x", true); return scrollbar ? scrollbar.getPosition() : 0; }, /** * Scrolls the element's content to the given top coordinate * * @param value {Integer} The horizontal position to scroll to. * @param duration {Number?} The time in milliseconds the scroll to should take. */ scrollToY(value, duration) { // First flush queue before scroll qx.ui.core.queue.Manager.flush(); this.getChildControl("scrollbar-y").scrollTo(value, duration); }, /** * Scrolls the element's content by the given top offset * * @param value {Integer} The horizontal position to scroll to. * @param duration {Number?} The time in milliseconds the scroll to should take. */ scrollByY(value, duration) { // First flush queue before scroll qx.ui.core.queue.Manager.flush(); this.getChildControl("scrollbar-y").scrollBy(value, duration); }, /** * Returns the scroll top position of the content * * @return {Integer} Vertical scroll position */ getScrollY() { var scrollbar = this.getChildControl("scrollbar-y", true); return scrollbar ? scrollbar.getPosition() : 0; }, /** * In case a scroll animation is currently running in X direction, * it will be stopped. If not, the method does nothing. */ stopScrollAnimationX() { var scrollbar = this.getChildControl("scrollbar-x", true); if (scrollbar) { scrollbar.stopScrollAnimation(); } }, /** * In case a scroll animation is currently running in X direction, * it will be stopped. If not, the method does nothing. */ stopScrollAnimationY() { var scrollbar = this.getChildControl("scrollbar-y", true); if (scrollbar) { scrollbar.stopScrollAnimation(); } }, /* --------------------------------------------------------------------------- EVENT LISTENERS --------------------------------------------------------------------------- */ /** * Event handler for the scroll animation end event for both scroll bars. * * @param direction {String} Either "X" or "Y". */ _onScrollAnimationEnd(direction) { this.fireEvent("scrollAnimation" + direction + "End"); }, /** * Event handler for the scroll event of the horizontal scrollbar * * @param e {qx.event.type.Data} The scroll event object */ _onScrollBarX(e) { this.getChildControl("pane").scrollToX(e.getData()); }, /** * Event handler for the scroll event of the vertical scrollbar * * @param e {qx.event.type.Data} The scroll event object */ _onScrollBarY(e) { this.getChildControl("pane").scrollToY(e.getData()); }, /** * Event handler for the horizontal scroll event of the pane * * @param e {qx.event.type.Data} The scroll event object */ _onScrollPaneX(e) { var scrollbar = this.getChildControl("scrollbar-x"); if (scrollbar) { scrollbar.updatePosition(e.getData()); } }, /** * Event handler for the vertical scroll event of the pane * * @param e {qx.event.type.Data} The scroll event object */ _onScrollPaneY(e) { var scrollbar = this.getChildControl("scrollbar-y"); if (scrollbar) { scrollbar.updatePosition(e.getData()); } }, /** * Event handler for visibility changes of horizontal scrollbar. * * @param e {qx.event.type.Event} Property change event */ _onChangeScrollbarXVisibility(e) { var showX = this._isChildControlVisible("scrollbar-x"); var showY = this._isChildControlVisible("scrollbar-y"); if (!showX) { this.scrollToX(0); } showX && showY ? this._showChildControl("corner") : this._excludeChildControl("corner"); }, /** * Event handler for visibility changes of horizontal scrollbar. * * @param e {qx.event.type.Event} Property change event */ _onChangeScrollbarYVisibility(e) { var showX = this._isChildControlVisible("scrollbar-x"); var showY = this._isChildControlVisible("scrollbar-y"); if (!showY) { this.scrollToY(0); } showX && showY ? this._showChildControl("corner") : this._excludeChildControl("corner"); }, /* --------------------------------------------------------------------------- HELPER METHODS --------------------------------------------------------------------------- */ /** * Computes the visibility state for scrollbars. * */ _computeScrollbars() { var pane = this.getChildControl("pane"); var content = pane.getChildren()[0]; if (!content) { this._excludeChildControl("scrollbar-x"); this._excludeChildControl("scrollbar-y"); return; } var innerSize = this.getInnerSize(); var paneSize = pane.getInnerSize(); var scrollSize = pane.getScrollSize(); // if the widget has not yet been rendered, return and try again in the // resize event if (!paneSize || !scrollSize) { return; } var scrollbarX = this.getScrollbarX(); var scrollbarY = this.getScrollbarY(); if (scrollbarX === "auto" && scrollbarY === "auto") { // Check if the container is big enough to show // the full content. var showX = scrollSize.width > innerSize.width; var showY = scrollSize.height > innerSize.height; // Dependency check // We need a special intelligence here when only one // of the autosized axis requires a scrollbar // This scrollbar may then influence the need // for the other one as well. if ((showX || showY) && !(showX && showY)) { if (showX) { showY = scrollSize.height > paneSize.height; } else if (showY) { showX = scrollSize.width > paneSize.width; } } } else { var showX = scrollbarX === "on"; var showY = scrollbarY === "on"; // Check auto values afterwards with already // corrected client dimensions if ( scrollSize.width > (showX ? paneSize.width : innerSize.width) && scrollbarX === "auto" ) { showX = true; } if ( scrollSize.height > (showX ? paneSize.height : innerSize.height) && scrollbarY === "auto" ) { showY = true; } } // Update scrollbars if (showX) { var barX = this.getChildControl("scrollbar-x"); barX.show(); barX.setMaximum(Math.max(0, scrollSize.width - paneSize.width)); barX.setKnobFactor( scrollSize.width === 0 ? 0 : paneSize.width / scrollSize.width ); } else { this._excludeChildControl("scrollbar-x"); } if (showY) { var barY = this.getChildControl("scrollbar-y"); barY.show(); barY.setMaximum(Math.max(0, scrollSize.height - paneSize.height)); barY.setKnobFactor( scrollSize.height === 0 ? 0 : paneSize.height / scrollSize.height ); } else { this._excludeChildControl("scrollbar-y"); } } } });