/* * OpenSeadragon - Button * * Copyright (C) 2009 CodePlex Foundation * Copyright (C) 2010-2024 OpenSeadragon contributors * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of CodePlex Foundation nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ (function( $ ){ /** * An enumeration of button states * @member ButtonState * @memberof OpenSeadragon * @static * @type {Object} * @property {Number} REST * @property {Number} GROUP * @property {Number} HOVER * @property {Number} DOWN */ $.ButtonState = { REST: 0, GROUP: 1, HOVER: 2, DOWN: 3 }; /** * @class Button * @classdesc Manages events, hover states for individual buttons, tool-tips, as well * as fading the buttons out when the user has not interacted with them * for a specified period. * * @memberof OpenSeadragon * @extends OpenSeadragon.EventSource * @param {Object} options * @param {Element} [options.element=null] Element to use as the button. If not specified, an HTML <div> element is created. * @param {String} [options.tooltip=null] Provides context help for the button when the * user hovers over it. * @param {String} [options.srcRest=null] URL of image to use in 'rest' state. * @param {String} [options.srcGroup=null] URL of image to use in 'up' state. * @param {String} [options.srcHover=null] URL of image to use in 'hover' state. * @param {String} [options.srcDown=null] URL of image to use in 'down' state. * @param {Number} [options.fadeDelay=0] How long to wait before fading. * @param {Number} [options.fadeLength=2000] How long should it take to fade the button. * @param {OpenSeadragon.EventHandler} [options.onPress=null] Event handler callback for {@link OpenSeadragon.Button.event:press}. * @param {OpenSeadragon.EventHandler} [options.onRelease=null] Event handler callback for {@link OpenSeadragon.Button.event:release}. * @param {OpenSeadragon.EventHandler} [options.onClick=null] Event handler callback for {@link OpenSeadragon.Button.event:click}. * @param {OpenSeadragon.EventHandler} [options.onEnter=null] Event handler callback for {@link OpenSeadragon.Button.event:enter}. * @param {OpenSeadragon.EventHandler} [options.onExit=null] Event handler callback for {@link OpenSeadragon.Button.event:exit}. * @param {OpenSeadragon.EventHandler} [options.onFocus=null] Event handler callback for {@link OpenSeadragon.Button.event:focus}. * @param {OpenSeadragon.EventHandler} [options.onBlur=null] Event handler callback for {@link OpenSeadragon.Button.event:blur}. * @param {Object} [options.userData=null] Arbitrary object to be passed unchanged to any attached handler methods. */ $.Button = function( options ) { var _this = this; $.EventSource.call( this ); $.extend( true, this, { tooltip: null, srcRest: null, srcGroup: null, srcHover: null, srcDown: null, clickTimeThreshold: $.DEFAULT_SETTINGS.clickTimeThreshold, clickDistThreshold: $.DEFAULT_SETTINGS.clickDistThreshold, /** * How long to wait before fading. * @member {Number} fadeDelay * @memberof OpenSeadragon.Button# */ fadeDelay: 0, /** * How long should it take to fade the button. * @member {Number} fadeLength * @memberof OpenSeadragon.Button# */ fadeLength: 2000, onPress: null, onRelease: null, onClick: null, onEnter: null, onExit: null, onFocus: null, onBlur: null, userData: null }, options ); /** * The button element. * @member {Element} element * @memberof OpenSeadragon.Button# */ this.element = options.element || $.makeNeutralElement("div"); //if the user has specified the element to bind the control to explicitly //then do not add the default control images if ( !options.element ) { this.imgRest = $.makeTransparentImage( this.srcRest ); this.imgGroup = $.makeTransparentImage( this.srcGroup ); this.imgHover = $.makeTransparentImage( this.srcHover ); this.imgDown = $.makeTransparentImage( this.srcDown ); this.imgRest.alt = this.imgGroup.alt = this.imgHover.alt = this.imgDown.alt = this.tooltip; // Allow pointer events to pass through the img elements so implicit // pointer capture works on touch devices $.setElementPointerEventsNone( this.imgRest ); $.setElementPointerEventsNone( this.imgGroup ); $.setElementPointerEventsNone( this.imgHover ); $.setElementPointerEventsNone( this.imgDown ); this.element.style.position = "relative"; $.setElementTouchActionNone( this.element ); this.imgGroup.style.position = this.imgHover.style.position = this.imgDown.style.position = "absolute"; this.imgGroup.style.top = this.imgHover.style.top = this.imgDown.style.top = "0px"; this.imgGroup.style.left = this.imgHover.style.left = this.imgDown.style.left = "0px"; this.imgHover.style.visibility = this.imgDown.style.visibility = "hidden"; if ($.Browser.vendor === $.BROWSERS.FIREFOX && $.Browser.version < 3) { this.imgGroup.style.top = this.imgHover.style.top = this.imgDown.style.top = ""; } this.element.appendChild( this.imgRest ); this.element.appendChild( this.imgGroup ); this.element.appendChild( this.imgHover ); this.element.appendChild( this.imgDown ); } this.addHandler("press", this.onPress); this.addHandler("release", this.onRelease); this.addHandler("click", this.onClick); this.addHandler("enter", this.onEnter); this.addHandler("exit", this.onExit); this.addHandler("focus", this.onFocus); this.addHandler("blur", this.onBlur); /** * The button's current state. * @member {OpenSeadragon.ButtonState} currentState * @memberof OpenSeadragon.Button# */ this.currentState = $.ButtonState.GROUP; // When the button last began to fade. this.fadeBeginTime = null; // Whether this button should fade after user stops interacting with the viewport. this.shouldFade = false; this.element.style.display = "inline-block"; this.element.style.position = "relative"; this.element.title = this.tooltip; /** * Tracks mouse/touch/key events on the button. * @member {OpenSeadragon.MouseTracker} tracker * @memberof OpenSeadragon.Button# */ this.tracker = new $.MouseTracker({ userData: 'Button.tracker', element: this.element, clickTimeThreshold: this.clickTimeThreshold, clickDistThreshold: this.clickDistThreshold, enterHandler: function( event ) { if ( event.insideElementPressed ) { inTo( _this, $.ButtonState.DOWN ); /** * Raised when the cursor enters the Button element. * * @event enter * @memberof OpenSeadragon.Button * @type {object} * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ _this.raiseEvent( "enter", { originalEvent: event.originalEvent } ); } else if ( !event.buttonDownAny ) { inTo( _this, $.ButtonState.HOVER ); } }, focusHandler: function ( event ) { _this.tracker.enterHandler( event ); /** * Raised when the Button element receives focus. * * @event focus * @memberof OpenSeadragon.Button * @type {object} * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ _this.raiseEvent( "focus", { originalEvent: event.originalEvent } ); }, leaveHandler: function( event ) { outTo( _this, $.ButtonState.GROUP ); if ( event.insideElementPressed ) { /** * Raised when the cursor leaves the Button element. * * @event exit * @memberof OpenSeadragon.Button * @type {object} * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ _this.raiseEvent( "exit", { originalEvent: event.originalEvent } ); } }, blurHandler: function ( event ) { _this.tracker.leaveHandler( event ); /** * Raised when the Button element loses focus. * * @event blur * @memberof OpenSeadragon.Button * @type {object} * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ _this.raiseEvent( "blur", { originalEvent: event.originalEvent } ); }, pressHandler: function ( event ) { inTo( _this, $.ButtonState.DOWN ); /** * Raised when a mouse button is pressed or touch occurs in the Button element. * * @event press * @memberof OpenSeadragon.Button * @type {object} * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ _this.raiseEvent( "press", { originalEvent: event.originalEvent } ); }, releaseHandler: function( event ) { if ( event.insideElementPressed && event.insideElementReleased ) { outTo( _this, $.ButtonState.HOVER ); /** * Raised when the mouse button is released or touch ends in the Button element. * * @event release * @memberof OpenSeadragon.Button * @type {object} * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ _this.raiseEvent( "release", { originalEvent: event.originalEvent } ); } else if ( event.insideElementPressed ) { outTo( _this, $.ButtonState.GROUP ); } else { inTo( _this, $.ButtonState.HOVER ); } }, clickHandler: function( event ) { if ( event.quick ) { /** * Raised when a mouse button is pressed and released or touch is initiated and ended in the Button element within the time and distance threshold. * * @event click * @memberof OpenSeadragon.Button * @type {object} * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ _this.raiseEvent("click", { originalEvent: event.originalEvent }); } }, keyHandler: function( event ){ //console.log( "%s : handling key %s!", _this.tooltip, event.keyCode); if( 13 === event.keyCode ){ /*** * Raised when a mouse button is pressed and released or touch is initiated and ended in the Button element within the time and distance threshold. * * @event click * @memberof OpenSeadragon.Button * @type {object} * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ _this.raiseEvent( "click", { originalEvent: event.originalEvent } ); /*** * Raised when the mouse button is released or touch ends in the Button element. * * @event release * @memberof OpenSeadragon.Button * @type {object} * @property {OpenSeadragon.Button} eventSource - A reference to the Button which raised the event. * @property {Object} originalEvent - The original DOM event. * @property {?Object} userData - Arbitrary subscriber-defined object. */ _this.raiseEvent( "release", { originalEvent: event.originalEvent } ); event.preventDefault = true; } else{ event.preventDefault = false; } } }); outTo( this, $.ButtonState.REST ); }; $.extend( $.Button.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.Button.prototype */{ /** * Used by a button container element (e.g. a ButtonGroup) to transition the button state * to ButtonState.GROUP. * @function */ notifyGroupEnter: function() { inTo( this, $.ButtonState.GROUP ); }, /** * Used by a button container element (e.g. a ButtonGroup) to transition the button state * to ButtonState.REST. * @function */ notifyGroupExit: function() { outTo( this, $.ButtonState.REST ); }, /** * @function */ disable: function(){ this.notifyGroupExit(); this.element.disabled = true; this.tracker.setTracking(false); $.setElementOpacity( this.element, 0.2, true ); }, /** * @function */ enable: function(){ this.element.disabled = false; this.tracker.setTracking(true); $.setElementOpacity( this.element, 1.0, true ); this.notifyGroupEnter(); }, destroy: function() { if (this.imgRest) { this.element.removeChild(this.imgRest); this.imgRest = null; } if (this.imgGroup) { this.element.removeChild(this.imgGroup); this.imgGroup = null; } if (this.imgHover) { this.element.removeChild(this.imgHover); this.imgHover = null; } if (this.imgDown) { this.element.removeChild(this.imgDown); this.imgDown = null; } this.removeAllHandlers(); this.tracker.destroy(); this.element = null; } }); function scheduleFade( button ) { $.requestAnimationFrame(function(){ updateFade( button ); }); } function updateFade( button ) { var currentTime, deltaTime, opacity; if ( button.shouldFade ) { currentTime = $.now(); deltaTime = currentTime - button.fadeBeginTime; opacity = 1.0 - deltaTime / button.fadeLength; opacity = Math.min( 1.0, opacity ); opacity = Math.max( 0.0, opacity ); if( button.imgGroup ){ $.setElementOpacity( button.imgGroup, opacity, true ); } if ( opacity > 0 ) { // fade again scheduleFade( button ); } } } function beginFading( button ) { button.shouldFade = true; button.fadeBeginTime = $.now() + button.fadeDelay; window.setTimeout( function(){ scheduleFade( button ); }, button.fadeDelay ); } function stopFading( button ) { button.shouldFade = false; if( button.imgGroup ){ $.setElementOpacity( button.imgGroup, 1.0, true ); } } function inTo( button, newState ) { if( button.element.disabled ){ return; } if ( newState >= $.ButtonState.GROUP && button.currentState === $.ButtonState.REST ) { stopFading( button ); button.currentState = $.ButtonState.GROUP; } if ( newState >= $.ButtonState.HOVER && button.currentState === $.ButtonState.GROUP ) { if( button.imgHover ){ button.imgHover.style.visibility = ""; } button.currentState = $.ButtonState.HOVER; } if ( newState >= $.ButtonState.DOWN && button.currentState === $.ButtonState.HOVER ) { if( button.imgDown ){ button.imgDown.style.visibility = ""; } button.currentState = $.ButtonState.DOWN; } } function outTo( button, newState ) { if( button.element.disabled ){ return; } if ( newState <= $.ButtonState.HOVER && button.currentState === $.ButtonState.DOWN ) { if( button.imgDown ){ button.imgDown.style.visibility = "hidden"; } button.currentState = $.ButtonState.HOVER; } if ( newState <= $.ButtonState.GROUP && button.currentState === $.ButtonState.HOVER ) { if( button.imgHover ){ button.imgHover.style.visibility = "hidden"; } button.currentState = $.ButtonState.GROUP; } if ( newState <= $.ButtonState.REST && button.currentState === $.ButtonState.GROUP ) { beginFading( button ); button.currentState = $.ButtonState.REST; } } }( OpenSeadragon ));