//////////////////////////////////////////////////////////////////////////////// // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// package spark.components { import flash.events.Event; import flash.events.MouseEvent; import flash.geom.Point; import mx.core.EventPriority; import mx.core.IInvalidating; import mx.core.InteractionMode; import mx.core.mx_internal; import mx.events.FlexMouseEvent; import mx.events.PropertyChangeEvent; import mx.events.ResizeEvent; import spark.components.supportClasses.ScrollBarBase; import spark.core.IViewport; import spark.core.NavigationUnit; import spark.utils.MouseEventUtil; use namespace mx_internal; //-------------------------------------- // Events //-------------------------------------- /** * Dispatched when the horizontalScrollPosition is going * to change due to a mouseWheel event. * *

The default behavior is to scroll horizontally by the event * delta number of "steps". * The viewport's getHorizontalScrollPositionDelta method using * either LEFT or RIGHT, depending on the scroll * direction, determines the width of the step.

* *

Calling the preventDefault() method * on the event prevents the horizontal scroll position from changing. * Otherwise if you modify the delta property of the event, * that value will be used as the number horizontal of "steps".

* * @eventType mx.events.FlexMouseEvent.MOUSE_WHEEL_CHANGING * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ [Event(name="mouseWheelChanging", type="mx.events.FlexMouseEvent")] //-------------------------------------- // Other metadata //-------------------------------------- [IconFile("HScrollBar.png")] [DefaultTriggerEvent("change")] /** * The HScrollBar (horizontal scrollbar) control lets you control * the portion of data that is displayed when there is too much data * to fit horizontally in a display area. * *

Although you can use the HScrollBar control as a stand-alone control, * you usually combine it as part of another group of components to * provide scrolling functionality.

* *

The HScrollBar control has the following default characteristics:

* * * * * * * * * * * * * * * * * * * * * *
CharacteristicDescription
Default size85 pixels wide by 15 pixels high
Minimum size35 pixels wide and 35 pixels high
Maximum size10000 pixels wide and 10000 pixels high
Default skin classesspark.skins.spark.HScrollBarSkin *

spark.skins.spark.HScrollBarThumbSkin

*

spark.skins.spark.HScrollBarTrackSkin

* * @mxml *

The <s:HScrollBar> tag inherits all of the tag * attributes of its superclass and adds the following tag attributes:

* *
 *  <s:HScrollBar
 *
 *    Properties
 *    viewport=""
 *  />
 *  
* * @see spark.skins.spark.HScrollBarSkin * @see spark.skins.spark.HScrollBarThumbSkin * @see spark.skins.spark.HScrollBarTrackSkin * * @includeExample examples/HScrollBarExample.mxml * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public class HScrollBar extends ScrollBarBase { include "../core/Version.as"; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function HScrollBar() { super(); } //-------------------------------------------------------------------------- // // Overridden properties // //-------------------------------------------------------------------------- private function updateMaximumAndPageSize():void { var hsp:Number = viewport.horizontalScrollPosition; var viewportWidth:Number = isNaN(viewport.width) ? 0 : viewport.width; // Special case: if contentWidth is 0, assume that it hasn't been // updated yet. Making the maximum==hsp here avoids trouble later // when Range constrains value var cWidth:Number = viewport.contentWidth; if (getStyle("interactionMode") == InteractionMode.TOUCH) { // For mobile, we allow the min/max to extend a little beyond the ends so // we can support bounce/pull kinetic effects. minimum = -viewportWidth; maximum = (cWidth == 0) ? hsp + viewportWidth : cWidth; } else { minimum = 0; maximum = (cWidth == 0) ? hsp : cWidth - viewportWidth; } pageSize = viewportWidth; } /** * The viewport controlled by this scrollbar. * * @default null * * @see spark.core.IViewport * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 * */ override public function set viewport(newViewport:IViewport):void { const oldViewport:IViewport = super.viewport; if (oldViewport == newViewport) return; if (oldViewport) { oldViewport.removeEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler); removeEventListener(MouseEvent.MOUSE_WHEEL, hsb_mouseWheelHandler, true); } super.viewport = newViewport; if (newViewport) { updateMaximumAndPageSize() value = newViewport.horizontalScrollPosition; // The HSB viewport mouse wheel listener is added at a low priority so that // if a VSB installs a listener it will run first and cancel the event. newViewport.addEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler, false, EventPriority.DEFAULT_HANDLER); // The HSB mouse wheel listener stops propagation and redispatches its event, // so we listen during the capture phase. addEventListener(MouseEvent.MOUSE_WHEEL, hsb_mouseWheelHandler, true); } } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * @private */ override protected function pointToValue(x:Number, y:Number):Number { if (!thumb || !track) return 0; var r:Number = track.getLayoutBoundsWidth() - thumb.getLayoutBoundsWidth(); return minimum + ((r != 0) ? (x / r) * (maximum - minimum) : 0); } /** * @private */ override protected function updateSkinDisplayList():void { if (!thumb || !track) return; var trackSize:Number = track.getLayoutBoundsWidth(); var min:Number; var max:Number; if (getStyle("interactionMode") == InteractionMode.TOUCH && viewport) { // For calculating thumb position/size on mobile, we want to exclude // the extra margin we added to minimum and maximum for bounce/pull. var viewportWidth:Number = isNaN(viewport.width) ? 0 : viewport.width; // This code uses explicit values passed in from Scroller, since "item snapping" means // there may be visible padding at either end of the content, and bounce/pull occurs beyond // the padding min = !isNaN(contentMinimum) ? contentMinimum : 0; max = !isNaN(contentMaximum) ? contentMaximum : Math.max(0, maximum - viewportWidth); } else { min = minimum; max = maximum; } var range:Number = max - min; var fixedThumbSize:Boolean = !(getStyle("fixedThumbSize") === false); var thumbPos:Point; var thumbPosTrackX:Number = 0; var thumbPosParentX:Number = 0; var thumbSize:Number = trackSize; if (range > 0) { if (!fixedThumbSize) { thumbSize = Math.min((pageSize / (range + pageSize)) * trackSize, trackSize) thumbSize = Math.max(thumb.minWidth, thumbSize); } else { thumbSize = thumb ? thumb.width : 0; } // calculate new thumb position. thumbPosTrackX = (pendingValue - min) * ((trackSize - thumbSize) / range); } // Special thumb behavior for bounce/pull. When the component is positioned // beyond its min/max, we want the scroll thumb to shink in size. // Note: not checking interactionMode==TOUCH here because it is assumed that // value will never exceed min/max unless in touch mode. if (pendingValue < min) { if (!fixedThumbSize) { // The minimum size we'll shrink the thumb to is either thumb.minWidth or thumbSize: whichever is smaller. thumbSize = Math.max(Math.min(thumb.minWidth, thumbSize), thumbSize + (pendingValue - min)); } thumbPosTrackX = 0; // thumb is always at position zero when content is being "pulled" } if (pendingValue > max) { if (!fixedThumbSize) { // The minimum size we'll shrink the thumb to is either thumb.minWidth or thumbSize: whichever is smaller. thumbSize = Math.max(Math.min(thumb.minWidth, thumbSize), thumbSize - (pendingValue - max)); } thumbPosTrackX = trackSize - thumbSize; } if (!fixedThumbSize) thumb.setLayoutBoundsSize(thumbSize, NaN); if (getStyle("autoThumbVisibility") === true) thumb.visible = thumbSize < trackSize; // convert thumb position to parent's coordinates. thumbPos = track.localToGlobal(new Point(thumbPosTrackX, 0)); if (thumb.parent) thumbPosParentX = thumb.parent.globalToLocal(thumbPos).x; thumb.setLayoutBoundsPosition(Math.round(thumbPosParentX), thumb.getLayoutBoundsY()); } /** * Updates the value property and, if viewport is non-null, sets * its horizontalScrollPosition to value. * * @param value The new value of the value property. * @see #viewport * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ override protected function setValue(value:Number):void { super.setValue(value); if (viewport) viewport.horizontalScrollPosition = value; } /** * Increment value by a page if increase is true, * or decrement value by a page if increase is false. * Increasing the scrollbar's value scrolls the viewport to the right. * Decreasing the value scrolls to the viewport to the left. * *

If the viewport property is set, then its * getHorizontalScrollPositionDelta() method * is used to compute the size of the page increment. * If viewport is null, then the scrollbar's * pageSize property is used.

* * @param increase Whether to increment (true) or * decrement (false) value. * * @see spark.components.supportClasses.ScrollBarBase#changeValueByPage() * @see spark.components.supportClasses.Range#setValue() * @see spark.core.IViewport * @see spark.core.IViewport#horizontalScrollPosition * @see spark.core.IViewport#getHorizontalScrollPositionDelta() * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ override public function changeValueByPage(increase:Boolean = true):void { var oldPageSize:Number; if (viewport) { // Want to use ScrollBarBase's changeValueByPage() implementation to get the same // animated behavior for scrollbars with and without viewports. // For now, just change pageSize temporarily and call the superclass // implementation. oldPageSize = pageSize; pageSize = Math.abs(viewport.getHorizontalScrollPositionDelta( (increase) ? NavigationUnit.PAGE_RIGHT : NavigationUnit.PAGE_LEFT)); } super.changeValueByPage(increase); if (viewport) pageSize = oldPageSize; } /** * @private */ override protected function animatePaging(newValue:Number, pageSize:Number):void { if (viewport) { var vpPageSize:Number = Math.abs(viewport.getHorizontalScrollPositionDelta( (newValue > value) ? NavigationUnit.PAGE_RIGHT : NavigationUnit.PAGE_LEFT)); super.animatePaging(newValue, vpPageSize); return; } super.animatePaging(newValue, pageSize); } /** * If viewport is not null, * changes the horizontal scroll position for a line up * or line down operation by * scrolling the viewport. * This method calculates the amount to scroll by calling the * IViewport.getHorizontalScrollPositionDelta() method * with either flash.ui.Keyboard.RIGHT * or flash.ui.Keyboard.LEFT. * It then calls the setValue() method to * set the IViewport.horizontalScrollPosition property * to the appropriate value. * *

If viewport is null, * calling this method changes the scroll position for a line up * or line down operation by calling * the changeValueByStep() method.

* * @param increase Whether the line scoll is up (true) or * down (false). * * @see spark.components.supportClasses.Range#changeValueByStep() * @see spark.components.supportClasses.Range#setValue() * @see spark.core.IViewport * @see spark.core.IViewport#horizontalScrollPosition * @see spark.core.IViewport#getHorizontalScrollPositionDelta() * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ override public function changeValueByStep(increase:Boolean = true):void { var oldStepSize:Number; if (viewport) { // Want to use ScrollBarBase's changeValueByStep() implementation to get the same // animated behavior for scrollbars with and without viewports. // For now, just change stepSize temporarily and call the superclass // implementation. oldStepSize = stepSize; stepSize = Math.abs(viewport.getHorizontalScrollPositionDelta( (increase) ? NavigationUnit.RIGHT : NavigationUnit.LEFT)); } super.changeValueByStep(increase); if (viewport) stepSize = oldStepSize; } /** * @private */ override protected function partAdded(partName:String, instance:Object):void { if (instance == thumb) { thumb.setConstraintValue("left", undefined); thumb.setConstraintValue("right", undefined); thumb.setConstraintValue("horizontalCenter", undefined); } super.partAdded(partName, instance); } /** * @private * Set this scrollbar's value to the viewport's current horizontalScrollPosition. */ override mx_internal function viewportHorizontalScrollPositionChangeHandler(event:PropertyChangeEvent):void { if (viewport) value = viewport.horizontalScrollPosition; } /** * @private * Set this scrollbar's maximum to the viewport's contentWidth * less the viewport width and its pageSize to the viewport's width. */ override mx_internal function viewportResizeHandler(event:ResizeEvent):void { if (viewport) updateMaximumAndPageSize(); } /** * @private * Set this scrollbar's maximum to the viewport's contentWidth less the viewport width. */ override mx_internal function viewportContentWidthChangeHandler(event:PropertyChangeEvent):void { if (viewport) { if (getStyle("interactionMode") == InteractionMode.TOUCH) { updateMaximumAndPageSize(); } else { // SDK-28898: reverted previous behavior for desktop, resets // scroll position to zero when all content is removed. maximum = viewport.contentWidth - viewport.width; } } } /** * @private */ override public function styleChanged(styleName:String):void { super.styleChanged(styleName); var allStyles:Boolean = !styleName || styleName == "styleName"; if (allStyles || styleName == "interactionMode") { if (viewport) updateMaximumAndPageSize(); } } /** * @private * Scroll horizontally by event.delta "steps". This listener is added to the viewport * at a lower priority then the vertical scrollbar mouse wheel listener, so that vertical * scrolling is preferred when both scrollbars exist. */ mx_internal function mouseWheelHandler(event:MouseEvent):void { const vp:IViewport = viewport; if (event.isDefaultPrevented() || !vp || !vp.visible || !visible) return; // Dispatch the "mouseWheelChanging" event. If preventDefault() is called // on this event, the event will be cancelled. Otherwise if the delta // is modified the new value will be used. var changingEvent:FlexMouseEvent = MouseEventUtil.createMouseWheelChangingEvent(event); if (!dispatchEvent(changingEvent)) { event.preventDefault(); return; } const delta:int = changingEvent.delta; var nSteps:uint = Math.abs(delta); var navigationUnit:uint; var scrollPositionChanged:Boolean; // Scroll delta "steps". navigationUnit = (delta < 0) ? NavigationUnit.RIGHT : NavigationUnit.LEFT; for (var hStep:int = 0; hStep < nSteps; hStep++) { var hspDelta:Number = vp.getHorizontalScrollPositionDelta(navigationUnit); if (!isNaN(hspDelta)) { vp.horizontalScrollPosition += hspDelta; scrollPositionChanged = true; if (vp is IInvalidating) IInvalidating(vp).validateNow(); } } if (scrollPositionChanged) dispatchEvent(new Event(Event.CHANGE)); event.preventDefault(); } /** * @private * Redispatch HSB mouse wheel events to the viewport to give the VSB's listener, if any, * an opportunity to handle/cancel them. If no VSB exists, mouseWheelHandler (see above) * will process the event. */ private function hsb_mouseWheelHandler(event:MouseEvent):void { const vp:IViewport = viewport; if (event.isDefaultPrevented() || !vp || !vp.visible) return; event.stopImmediatePropagation(); vp.dispatchEvent(event); } } }